Compare commits
150 Commits
add-split-
...
views-post
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
809776f6bc | ||
|
|
21f4da6bf2 | ||
|
|
da44f637ed | ||
|
|
0897c8eebd | ||
|
|
7b9d51929d | ||
|
|
5424c8bfd5 | ||
|
|
3521b50405 | ||
|
|
d4264cbe4e | ||
|
|
97be0a930c | ||
|
|
3107ed847a | ||
|
|
944a1f8fb0 | ||
|
|
47a1ff7df9 | ||
|
|
b9d5eb17a3 | ||
|
|
adc7cfb0d3 | ||
|
|
a853a80634 | ||
|
|
2d41a119b3 | ||
|
|
0102ffbfca | ||
|
|
0edffd9248 | ||
|
|
e65a76f0ec | ||
|
|
6c93c4bd35 | ||
|
|
8bafc61ef5 | ||
|
|
3b882918f7 | ||
|
|
5e64d45194 | ||
|
|
3df7da236d | ||
|
|
5e81d780bd | ||
|
|
cbc2746d70 | ||
|
|
aaba98d8ec | ||
|
|
2cc2a61c77 | ||
|
|
3025e5620d | ||
|
|
c4083c3cf6 | ||
|
|
2187513026 | ||
|
|
5b7b5bfea5 | ||
|
|
a588a7dd4d | ||
|
|
dcca48482b | ||
|
|
c983c9b6df | ||
|
|
f98d636203 | ||
|
|
5333eff0e4 | ||
|
|
135bca262c | ||
|
|
5d85801d1f | ||
|
|
ebdabb907a | ||
|
|
689d43047d | ||
|
|
59f77d316f | ||
|
|
b7ced3943e | ||
|
|
39200ec9f7 | ||
|
|
6e443ac298 | ||
|
|
5941102aac | ||
|
|
8c8a5ad275 | ||
|
|
7cb97e57f9 | ||
|
|
634fe99fa5 | ||
|
|
c3d4fa4336 | ||
|
|
ba91adf48a | ||
|
|
e5fe811d7a | ||
|
|
9459394ea0 | ||
|
|
db99d4fef1 | ||
|
|
aee0f65b26 | ||
|
|
dbb5fad147 | ||
|
|
28f875f5f9 | ||
|
|
e338f34097 | ||
|
|
d97e780135 | ||
|
|
176f63e86e | ||
|
|
cbcaca4153 | ||
|
|
871b8525b4 | ||
|
|
a5826e22f5 | ||
|
|
7f66e366b4 | ||
|
|
3075e58729 | ||
|
|
911b4b5ae8 | ||
|
|
2e7f9c48bb | ||
|
|
a54eaaecee | ||
|
|
1d794dbb37 | ||
|
|
e756602610 | ||
|
|
e6ebe7974d | ||
|
|
7bfa584eb6 | ||
|
|
dfbcaf36fc | ||
|
|
c07355265f | ||
|
|
631f885900 | ||
|
|
30b9cef5ba | ||
|
|
e5c4c8522b | ||
|
|
2980f0508c | ||
|
|
6d4fe8098b | ||
|
|
6c7893db35 | ||
|
|
d18c0d9df0 | ||
|
|
a0582d02b9 | ||
|
|
fb9eb6a0fc | ||
|
|
7b8bd97652 | ||
|
|
4af542567c | ||
|
|
dc5f4c8719 | ||
|
|
cddc0fbf92 | ||
|
|
843916d585 | ||
|
|
561cd37c85 | ||
|
|
dd74643993 | ||
|
|
b865db455d | ||
|
|
372bc427bd | ||
|
|
0cb8b0e451 | ||
|
|
3736ce9d9d | ||
|
|
9f9bef4175 | ||
|
|
31e9526544 | ||
|
|
1ab49fdbe6 | ||
|
|
9cb5a84b8d | ||
|
|
5f4dd36a1a | ||
|
|
2c834c24a3 | ||
|
|
e69e6f5586 | ||
|
|
7656096814 | ||
|
|
2a2cf45e90 | ||
|
|
3728da1165 | ||
|
|
c008c78e87 | ||
|
|
1313402a6b | ||
|
|
b29f45ea68 | ||
|
|
5ab715aac9 | ||
|
|
941e838be9 | ||
|
|
849a1324f7 | ||
|
|
d60ef81ede | ||
|
|
d694017d05 | ||
|
|
d1a6003033 | ||
|
|
d2968866b2 | ||
|
|
a827d0dac6 | ||
|
|
843919dd14 | ||
|
|
8263da02bd | ||
|
|
49542757fd | ||
|
|
9ef830e9bb | ||
|
|
8fbc88b708 | ||
|
|
e22ffb6740 | ||
|
|
00a6ac6141 | ||
|
|
6cd306e4c9 | ||
|
|
5d0c144ce7 | ||
|
|
331b6e7e6e | ||
|
|
4ab1d8af62 | ||
|
|
7c93f6244d | ||
|
|
92911def96 | ||
|
|
80cdd60d4c | ||
|
|
6305761064 | ||
|
|
db68fc5130 | ||
|
|
7767062c09 | ||
|
|
444f918e51 | ||
|
|
52f7f2d770 | ||
|
|
c0c0abae56 | ||
|
|
8e523d812f | ||
|
|
1f82a2aca1 | ||
|
|
b213458803 | ||
|
|
4fc01163da | ||
|
|
42e605a766 | ||
|
|
027f055841 | ||
|
|
e8bf06fc42 | ||
|
|
5dec9f7163 | ||
|
|
f7b6bfefe6 | ||
|
|
e38489196d | ||
|
|
3493e71ad4 | ||
|
|
3eb0a2c3c7 | ||
|
|
ff76b2ec4d | ||
|
|
5b9f15e075 | ||
|
|
f369e9a48b |
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Prevent GitHub from displaying comments within JSON files as errors.
|
||||
*.json linguist-language=JSON-with-Comments
|
||||
8
.github/actions/check_style/action.yml
vendored
@@ -14,7 +14,7 @@ runs:
|
||||
# so specify those here, and disable the rest until Zed's workspace
|
||||
# will have more fixes & suppression for the standard lint set
|
||||
run: |
|
||||
cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo
|
||||
cargo clippy --release --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo
|
||||
cargo clippy -p gpui
|
||||
|
||||
- name: Find modified migrations
|
||||
@@ -22,3 +22,9 @@ runs:
|
||||
run: |
|
||||
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
|
||||
. ./script/squawk
|
||||
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/rpc/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,subdir=crates/rpc/proto/"
|
||||
|
||||
1
.github/workflows/ci.yml
vendored
@@ -81,6 +81,7 @@ jobs:
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
35
.github/workflows/danger.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Danger
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "script/danger/pnpm-lock.yaml"
|
||||
|
||||
- run: pnpm install --dir script/danger
|
||||
|
||||
- name: Run Danger
|
||||
run: pnpm run --dir script/danger danger ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
107
.github/workflows/deploy_collab.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Publish Collab Server Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- collab-production
|
||||
- collab-staging
|
||||
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check formatting and Clippy lints
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: style
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
publish:
|
||||
name: Publish collab server image
|
||||
needs:
|
||||
- style
|
||||
- tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Sign into DigitalOcean docker registry
|
||||
run: doctl registry login
|
||||
|
||||
- name: Prune Docker system
|
||||
run: docker system prune --filter 'until=720h' -f
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Build docker image
|
||||
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
||||
|
||||
- name: Publish docker image
|
||||
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
||||
|
||||
deploy:
|
||||
name: Deploy new server image
|
||||
needs:
|
||||
- publish
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
|
||||
steps:
|
||||
- name: Sign into Kubernetes
|
||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }}
|
||||
|
||||
- name: Determine namespace
|
||||
run: |
|
||||
set -eu
|
||||
if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
|
||||
echo "Deploying collab:$GITHUB_SHA to production"
|
||||
echo "KUBE_NAMESPACE=production" >> $GITHUB_ENV
|
||||
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
|
||||
echo "Deploying collab:$GITHUB_SHA to staging"
|
||||
echo "KUBE_NAMESPACE=staging" >> $GITHUB_ENV
|
||||
else
|
||||
echo "cowardly refusing to deploy from an unknown branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Start rollout
|
||||
run: kubectl -n "$KUBE_NAMESPACE" set image deployment/collab collab=registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
||||
|
||||
- name: Wait for rollout to finish
|
||||
run: kubectl -n "$KUBE_NAMESPACE" rollout status deployment/collab
|
||||
49
.github/workflows/publish_collab_image.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Publish Collab Server Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- collab-v*
|
||||
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish collab server image
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Sign into DigitalOcean docker registry
|
||||
run: doctl registry login
|
||||
|
||||
- name: Prune Docker system
|
||||
run: docker system prune
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Determine version
|
||||
run: |
|
||||
set -eu
|
||||
version=$(script/get-crate-version collab)
|
||||
if [[ $GITHUB_REF_NAME != "collab-v${version}" ]]; then
|
||||
echo "release tag ${GITHUB_REF_NAME} does not match version ${version}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Publishing collab version: ${version}"
|
||||
echo "COLLAB_VERSION=${version}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build docker image
|
||||
run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
|
||||
|
||||
- name: Publish docker image
|
||||
run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/update_top_ranking_issues/requirements.txt
|
||||
- run: python script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --prod
|
||||
- run: python script/update_top_ranking_issues/main.py 5393 --github-token ${{ secrets.GITHUB_TOKEN }} --prod
|
||||
17
.github/workflows/update_weekly_top_ranking_issues.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 15 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/update_top_ranking_issues/requirements.txt
|
||||
- run: python script/update_top_ranking_issues/main.py 6952 --github-token ${{ secrets.GITHUB_TOKEN }} --prod --query-day-interval 7
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"formatter": "auto"
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"formatter": "auto"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/d
|
||||
|
||||
If you're looking for ideas about what to work on, check out:
|
||||
|
||||
- Our public roadmap (link coming soon!) contains a rough outline of our near-term priorities for Zed.
|
||||
- Our [public roadmap](https://zed.dev/roadmap) contains a rough outline of our near-term priorities for Zed.
|
||||
- Our [top-ranking issues](https://github.com/zed-industries/zed/issues/5393) based on votes by the community.
|
||||
|
||||
Outside of a handful of extremely popular languages and themes, we are generally not looking to extend Zed's language or theme support by directly building them into Zed. We really want to build a plugin system to handle making the editor extensible going forward. If you are passionate about shipping new languages or themes we suggest contributing to the extension system to help us get there faster.
|
||||
@@ -19,7 +19,7 @@ Outside of a handful of extremely popular languages and themes, we are generally
|
||||
|
||||
The best way to propose a change is to [start a discussion on our GitHub repository](https://github.com/zed-industries/zed/discussions).
|
||||
|
||||
First, write a short **problem statement**, which *clearly* and *briefly* describes the problem you want to solve independently from any specific solution. It doesn't need to be long or formal, but it's difficult to consider a solution in absence of a clear understanding of the problem.
|
||||
First, write a short **problem statement**, which _clearly_ and _briefly_ describes the problem you want to solve independently from any specific solution. It doesn't need to be long or formal, but it's difficult to consider a solution in absence of a clear understanding of the problem.
|
||||
|
||||
Next, write a short **solution proposal**. How can the problem (or set of problems) you have stated above be addressed? What are the pros and cons of your approach? Again, keep it brief and informal. This isn't a specification, but rather a starting point for a conversation.
|
||||
|
||||
@@ -43,14 +43,14 @@ We plan to set aside time each week to pair program with contributors on promisi
|
||||
|
||||
Zed is made up of several smaller crates - let's go over those you're most likely to interact with:
|
||||
|
||||
- [gpui](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
|
||||
- [editor](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
|
||||
- [project](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
|
||||
- [workspace](/crates/workspace) handles local state serialization and groups projects together.
|
||||
- [vim](/crates/vim) is a thin implementation of Vim workflow over `editor`.
|
||||
- [lsp](/crates/lsp) handles communication with external LSP server.
|
||||
- [language](/crates/language) drives `editor`'s understanding of language - from providing a list of symbols to the syntax map.
|
||||
- [collab](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing.
|
||||
- [rpc](/crates/rpc) defines messages to be exchanged with collaboration server.
|
||||
- [theme](/crates/theme) defines the theme system and provides a default theme.
|
||||
- [ui](/crates/ui) is a collection of UI components and common patterns used throughout Zed.
|
||||
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
|
||||
- [`editor`](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
|
||||
- [`project`](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
|
||||
- [`workspace`](/crates/workspace) handles local state serialization and groups projects together.
|
||||
- [`vim`](/crates/vim) is a thin implementation of Vim workflow over `editor`.
|
||||
- [`lsp`](/crates/lsp) handles communication with external LSP server.
|
||||
- [`language`](/crates/language) drives `editor`'s understanding of language - from providing a list of symbols to the syntax map.
|
||||
- [`collab`](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing.
|
||||
- [`rpc`](/crates/rpc) defines messages to be exchanged with collaboration server.
|
||||
- [`theme`](/crates/theme) defines the theme system and provides a default theme.
|
||||
- [`ui`](/crates/ui) is a collection of UI components and common patterns used throughout Zed.
|
||||
|
||||
773
Cargo.lock
generated
114
Cargo.toml
@@ -1,8 +1,8 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/assets",
|
||||
"crates/activity_indicator",
|
||||
"crates/ai",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
@@ -19,8 +19,6 @@ members = [
|
||||
"crates/copilot",
|
||||
"crates/copilot_ui",
|
||||
"crates/db",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/feature_flags",
|
||||
@@ -32,9 +30,9 @@ members = [
|
||||
"crates/git",
|
||||
"crates/go_to_line",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui_macros",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/journal",
|
||||
@@ -44,6 +42,7 @@ members = [
|
||||
"crates/live_kit_client",
|
||||
"crates/live_kit_server",
|
||||
"crates/lsp",
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/multi_buffer",
|
||||
@@ -59,6 +58,10 @@ members = [
|
||||
"crates/project_symbols",
|
||||
"crates/quick_action_bar",
|
||||
"crates/recent_projects",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/release_channel",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/search",
|
||||
@@ -67,7 +70,7 @@ members = [
|
||||
"crates/snippet",
|
||||
"crates/sqlez",
|
||||
"crates/sqlez_macros",
|
||||
"crates/rich_text",
|
||||
"crates/story",
|
||||
"crates/storybook",
|
||||
"crates/sum_tree",
|
||||
"crates/terminal",
|
||||
@@ -78,11 +81,10 @@ members = [
|
||||
"crates/theme_selector",
|
||||
"crates/ui",
|
||||
"crates/util",
|
||||
"crates/story",
|
||||
"crates/vim",
|
||||
"crates/vcs_menu",
|
||||
"crates/workspace",
|
||||
"crates/vim",
|
||||
"crates/welcome",
|
||||
"crates/workspace",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
]
|
||||
@@ -90,84 +92,94 @@ default-members = ["crates/zed"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.57" }
|
||||
async-trait = { version = "0.1" }
|
||||
anyhow = "1.0.57"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-trait = "0.1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
ctor = "0.2.6"
|
||||
derive_more = { version = "0.99.17" }
|
||||
env_logger = { version = "0.9" }
|
||||
futures = { version = "0.3" }
|
||||
globset = { version = "0.4" }
|
||||
derive_more = "0.99.17"
|
||||
env_logger = "0.9"
|
||||
futures = "0.3"
|
||||
git2 = { version = "0.15", default-features = false}
|
||||
globset = "0.4"
|
||||
indoc = "1"
|
||||
# We explicitly disable a http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
|
||||
lazy_static = { version = "1.4.0" }
|
||||
lazy_static = "1.4.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = { version = "2.1.1" }
|
||||
parking_lot = { version = "0.11.1" }
|
||||
ordered-float = "2.1.1"
|
||||
parking_lot = "0.11.1"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
prost = { version = "0.8" }
|
||||
rand = { version = "0.8.5" }
|
||||
pretty_assertions = "1.3.0"
|
||||
prost = "0.8"
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
rand = "0.8.5"
|
||||
refineable = { path = "./crates/refineable" }
|
||||
regex = { version = "1.5" }
|
||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||
regex = "1.5"
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
schemars = { version = "0.8" }
|
||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] }
|
||||
serde_repr = "0.1"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = { version = "1.2" }
|
||||
smol = "1.2"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
sysinfo = "0.29.10"
|
||||
tempfile = { version = "3.9.0" }
|
||||
thiserror = { version = "1.0.29" }
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = { version = "0.5" }
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.7"
|
||||
tree-sitter = { version = "0.20" }
|
||||
unindent = { version = "0.1.7" }
|
||||
pretty_assertions = "1.3.0"
|
||||
git2 = { version = "0.15", default-features = false}
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = "0.5"
|
||||
tree-sitter = { version = "0.20", features = ["wasm"] }
|
||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
|
||||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
|
||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
|
||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
|
||||
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
|
||||
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||
tree-sitter-erlang = "0.4.0"
|
||||
tree-sitter-gitcommit = { git = "https://github.com/gbprod/tree-sitter-gitcommit" }
|
||||
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
|
||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-php = "0.21.1"
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-ruby = "0.20.0"
|
||||
tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" }
|
||||
tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "cf98de23e4285b8e6bcb57b050ef2326e2cc284b" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||
tree-sitter-html = "0.19.0"
|
||||
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" }
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca" }
|
||||
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
tree-sitter-lua = "0.0.14"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa" }
|
||||
tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
|
||||
tree-sitter-php = "0.21.1"
|
||||
tree-sitter-proto = {git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
|
||||
tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }
|
||||
tree-sitter-ruby = "0.20.0"
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" }
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca" }
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-uiua = { git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2" }
|
||||
tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
|
||||
unindent = "0.1.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmtime = "16"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" }
|
||||
# wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d8975319c2d5de1bf710e7e21db25b0eee4bc66" }
|
||||
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
@@ -6,6 +6,9 @@ COPY . .
|
||||
|
||||
# Compile collab server
|
||||
ARG CARGO_PROFILE_RELEASE_PANIC=abort
|
||||
ARG GITHUB_SHA
|
||||
|
||||
ENV GITHUB_SHA=$GITHUB_SHA
|
||||
RUN --mount=type=cache,target=./script/node_modules \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=./target \
|
||||
|
||||
@@ -14,6 +14,11 @@ Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
|
||||
- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
|
||||
- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
|
||||
|
||||
For macOS users, you can also install Zed from Homebrew:
|
||||
```sh
|
||||
brew install zed
|
||||
```
|
||||
|
||||
## Developing Zed
|
||||
|
||||
- [Building Zed](./docs/src/developing_zed__building_zed.md)
|
||||
|
||||
4
assets/icons/file_icons/css.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.235 6.519l-16.47-0.004 0.266 3.277 12.653 0.002-0.319 3.394h-8.298l0.3 3.215h7.725l-0.457 4.403-3.636 1.005-3.694-1.012-0.235-2.637h-3.262l0.362 4.817 6.829 2.128 6.714-1.912 1.521-16.675zM2.879 1.004h26.242l-2.387 26.946-10.763 3.045-10.703-3.047z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 482 B |
1
assets/icons/file_icons/erlang.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"suffixes": {
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
"app.src": "erlang",
|
||||
"avif": "image",
|
||||
"bak": "backup",
|
||||
"bash": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
@@ -13,7 +16,7 @@
|
||||
"cc": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"css": "code",
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
@@ -22,6 +25,8 @@
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eex": "elixir",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
@@ -36,12 +41,16 @@
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"go": "code",
|
||||
"go": "go",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"heex": "elixir",
|
||||
"heif": "image",
|
||||
"hrl": "erlang",
|
||||
"hs": "haskell",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"ib": "storage",
|
||||
@@ -52,6 +61,7 @@
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"jsonc": "storage",
|
||||
"ldf": "storage",
|
||||
"lock": "lock",
|
||||
"log": "log",
|
||||
@@ -69,7 +79,7 @@
|
||||
"ogg": "video",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "code",
|
||||
"php": "php",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
@@ -79,7 +89,8 @@
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "python",
|
||||
"rb": "code",
|
||||
"rb": "ruby",
|
||||
"rebar.config": "erlang",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"rtf": "document",
|
||||
@@ -97,13 +108,17 @@
|
||||
"tsv": "storage",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"webp": "image",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"xrl": "erlang",
|
||||
"yaml": "yaml",
|
||||
"yml": "yaml",
|
||||
"yrl": "erlang",
|
||||
"zlogin": "terminal",
|
||||
"zsh": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
@@ -125,6 +140,9 @@
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
@@ -134,6 +152,9 @@
|
||||
"elixir": {
|
||||
"icon": "icons/file_icons/elixir.svg"
|
||||
},
|
||||
"erlang": {
|
||||
"icon": "icons/file_icons/erlang.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
@@ -143,6 +164,12 @@
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
@@ -155,12 +182,21 @@
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
"php": {
|
||||
"icon": "icons/file_icons/php.svg"
|
||||
},
|
||||
"yaml": {
|
||||
"icon": "icons/file_icons/yaml.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"python": {
|
||||
"icon": "icons/file_icons/python.svg"
|
||||
},
|
||||
"ruby": {
|
||||
"icon": "icons/file_icons/ruby.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
@@ -187,6 +223,9 @@
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
},
|
||||
"vue": {
|
||||
"icon": "icons/file_icons/vue.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
assets/icons/file_icons/go.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="14" viewBox="0 0 207 78" width="14" xmlns="http://www.w3.org/2000/svg"><g fill="#000000"><path d="m16.2 24.1c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h35.7c.4 0 .5.3.3.6l-1.7 2.6c-.2.3-.7.6-1 .6z"/><path d="m1.1 33.3c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h45.6c.4 0 .6.3.5.6l-.8 2.4c-.1.4-.5.6-.9.6z"/><path d="m25.3 42.5c-.4 0-.5-.3-.3-.6l1.4-2.5c.2-.3.6-.6 1-.6h20c.4 0 .6.3.6.7l-.2 2.4c0 .4-.4.7-.7.7z"/><g transform="translate(55)"><path d="m74.1 22.3c-6.3 1.6-10.6 2.8-16.8 4.4-1.5.4-1.6.5-2.9-1-1.5-1.7-2.6-2.8-4.7-3.8-6.3-3.1-12.4-2.2-18.1 1.5-6.8 4.4-10.3 10.9-10.2 19 .1 8 5.6 14.6 13.5 15.7 6.8.9 12.5-1.5 17-6.6.9-1.1 1.7-2.3 2.7-3.7-3.6 0-8.1 0-19.3 0-2.1 0-2.6-1.3-1.9-3 1.3-3.1 3.7-8.3 5.1-10.9.3-.6 1-1.6 2.5-1.6h36.4c-.2 2.7-.2 5.4-.6 8.1-1.1 7.2-3.8 13.8-8.2 19.6-7.2 9.5-16.6 15.4-28.5 17-9.8 1.3-18.9-.6-26.9-6.6-7.4-5.6-11.6-13-12.7-22.2-1.3-10.9 1.9-20.7 8.5-29.3 7.1-9.3 16.5-15.2 28-17.3 9.4-1.7 18.4-.6 26.5 4.9 5.3 3.5 9.1 8.3 11.6 14.1.6.9.2 1.4-1 1.7z"/><path d="m107.2 77.6c-9.1-.2-17.4-2.8-24.4-8.8-5.9-5.1-9.6-11.6-10.8-19.3-1.8-11.3 1.3-21.3 8.1-30.2 7.3-9.6 16.1-14.6 28-16.7 10.2-1.8 19.8-.8 28.5 5.1 7.9 5.4 12.8 12.7 14.1 22.3 1.7 13.5-2.2 24.5-11.5 33.9-6.6 6.7-14.7 10.9-24 12.8-2.7.5-5.4.6-8 .9zm23.8-40.4c-.1-1.3-.1-2.3-.3-3.3-1.8-9.9-10.9-15.5-20.4-13.3-9.3 2.1-15.3 8-17.5 17.4-1.8 7.8 2 15.7 9.2 18.9 5.5 2.4 11 2.1 16.3-.6 7.9-4.1 12.2-10.5 12.7-19.1z" fill-rule="nonzero"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
13
assets/icons/file_icons/haskell.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg977"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs981" />
|
||||
<path
|
||||
id="path973"
|
||||
d="M 10.699219 8.9003906 L 10.699219 9 C 12.199219 11.3 13.800781 13.600391 15.300781 15.900391 L 15.300781 16 C 13.800781 18.3 12.199219 20.600391 10.699219 22.900391 L 10.699219 23 L 14.199219 23 L 14.300781 22.900391 C 15.200781 21.500391 16.199609 20.099219 17.099609 18.699219 C 17.199609 18.599219 17.099219 18.599219 17.199219 18.699219 C 18.099219 20.099219 19.1 21.500391 20 22.900391 L 20.099609 23 L 23.599609 23 C 21.699609 20 19.699219 17.099609 17.699219 14.099609 C 16.499219 12.399609 15.399219 10.600391 14.199219 8.9003906 L 10.699219 8.9003906 z M 6 9 C 7.6 11.3 9.0996094 13.6 10.599609 16 L 10.599609 16.099609 C 9.4996094 17.799609 8.4007813 19.399609 7.3007812 21.099609 C 6.8007813 21.699609 6.4 22.4 6 23 L 6 23.099609 L 9.5 23.099609 C 11.1 20.699609 12.699219 18.399609 14.199219 16.099609 L 14.199219 16 C 13.499219 14.8 12.700391 13.7 11.900391 12.5 C 11.100391 11.4 10.399609 10.199609 9.5996094 9.0996094 L 9.5 9 L 6 9 z M 18.199219 13 L 18.199219 13.099609 C 18.699219 13.899609 19.199219 14.600391 19.699219 15.400391 L 26 15.400391 L 26 13 L 18.199219 13 z M 20.5 16.599609 L 20.5 16.699219 C 21 17.499219 21.5 18.2 22 19 L 26 19 L 26 16.599609 L 20.5 16.599609 z " />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
assets/icons/file_icons/php.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M170.322 349.808c-2.4-15.66-9-28.38-25.020-34.531-6.27-2.4-11.7-6.78-17.88-9.54-7.020-3.15-14.16-6.15-21.57-8.1-5.61-1.5-10.83 1.020-14.16 5.94-3.15 4.62-0.87 8.97 1.77 12.84 2.97 4.35 6.27 8.49 9.6 12.57 5.52 6.78 11.37 13.29 16.74 20.161 5.13 6.57 9.51 13.86 8.76 22.56-1.65 19.080-10.29 34.891-24.21 47.76-1.53 1.38-4.23 2.37-6.21 2.19-8.88-0.96-16.95-4.32-23.46-10.53-7.47-7.11-6.33-15.48 2.61-20.67 2.13-1.23 4.35-2.37 6.3-3.87 5.46-4.11 7.29-11.13 4.32-17.22-1.41-2.94-3-6.12-5.34-8.25-11.43-10.41-22.651-21.151-34.891-30.63-29.671-23.041-44.91-53.52-47.251-90.421-2.64-40.981 6.87-79.231 28.5-114.242 8.19-13.29 17.73-25.951 32.37-32.52 9.96-4.47 20.88-6.99 31.531-9.78 29.311-7.71 58.89-13.5 89.401-8.34 26.28 4.41 45.511 17.94 54.331 43.77 5.79 16.89 7.17 34.35 5.37 52.231-3.54 35.131-29.49 66.541-63.331 75.841-14.67 4.020-22.68 1.77-31.5-10.44-6.33-8.79-11.58-18.36-17.25-27.631-0.84-1.38-1.44-2.97-2.16-4.44-0.69-1.47-1.44-2.88-2.16-4.35 2.13 15.24 5.67 29.911 13.98 42.99 4.5 7.11 10.5 12.36 19.29 13.14 32.34 2.91 59.641-7.71 79.021-33.721 21.69-29.101 26.461-62.581 20.19-97.831-1.23-6.96-3.3-13.77-4.77-20.7-0.99-4.47 0.78-7.77 5.19-9.33 2.040-0.69 4.14-1.26 6.18-1.68 26.461-5.7 53.221-7.59 80.191-4.86 30.601 3.060 59.551 11.46 85.441 28.471 40.531 26.67 65.641 64.621 79.291 110.522 1.98 6.66 2.28 13.95 2.46 20.971 0.12 4.68-2.88 5.91-6.45 2.97-3.93-3.21-7.53-6.87-10.92-10.65-3.15-3.57-5.67-7.65-8.73-11.4-2.37-2.94-4.44-2.49-5.58 1.17-0.72 2.22-1.35 4.41-1.98 6.63-7.080 25.26-18.24 48.3-36.33 67.711-2.52 2.73-4.77 6.78-5.070 10.38-0.78 9.96-1.35 20.13-0.39 30.060 1.98 21.331 5.070 42.57 7.47 63.871 1.35 12.030-2.52 19.11-13.83 23.281-7.95 2.91-16.47 5.040-24.87 5.64-13.38 0.93-26.88 0.27-40.32 0.27-0.36-15 0.93-29.731-13.17-37.771 2.73-11.13 5.88-21.69 7.77-32.49 1.56-8.97 0.24-17.79-6.060-25.14-5.91-6.93-13.32-8.82-20.101-4.86-20.43 11.91-41.671 11.97-63.301 4.17-9.93-3.6-16.86-1.56-22.351 7.5-5.91 9.75-8.4 20.7-7.74 31.771 0.84 13.95 3.27 27.75 5.13 41.64 1.020 7.77 0.15 9.78-7.56 11.76-17.13 4.35-34.56 4.83-52.081 3.42-0.93-0.090-1.86-0.48-2.46-0.63-0.87-14.55 0.66-29.671-16.68-37.411 7.68-16.29 6.63-33.18 3.99-50.070l-0.060-0.15zM66.761 292.718c2.55-2.4 4.59-6.15 5.31-9.6 1.8-8.64-4.68-20.22-12.18-23.43-3.99-1.74-7.47-1.11-10.29 2.070-6.87 7.77-13.65 15.63-20.401 23.521-1.14 1.35-2.16 2.94-2.97 4.53-2.7 5.19-1.11 8.97 4.65 10.38 3.48 0.87 7.080 1.050 10.65 1.56 9.3-0.9 18.3-2.46 25.23-9v-0.030zM67.541 206.347c-0.030-6.18-5.19-11.34-11.28-11.37-6.27-0.030-11.67 5.58-11.46 11.76 0.27 6.21 5.43 11.19 11.61 11.070 6.24-0.090 11.22-5.19 11.16-11.43l-0.030-0.030z"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
7
assets/icons/file_icons/ruby.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 7.9 4.8 12C6.1 12 8.5 10.5 9.5 9.5Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M12 4.8 7.9 5 9.5 9.5 12 12Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M12 4.8V12H4.8" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.3 7.2 2 10C2 11.8 3.6 12 4.8 12 6.5 12 8.5 10.5 9.5 9.5 10.5 8.5 12 6.5 12 4.8 12 3.6 11.8 2 10 2L7.2 2.3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<ellipse rx="1.9" ry="3.8" cx="7.5" cy="0" transform="rotate(45)" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 674 B |
4
assets/icons/file_icons/vue.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
|
||||
<path d="M7 8.135 3.578 2.212l-.18-.312H1l6 10.392L13 1.9h-2.4L7 8.135Z"/>
|
||||
<path d="M7 3.675 5.972 1.9H1l.18.312h4.615L7 4.3l1.205-2.088h4.615L13 1.9H8.028L7 3.675Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 240 B |
1
assets/icons/file_icons/yaml.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="457px" height="512px"><polygon points="342.0159302,0 457,0 114.9831009,512 0,512 171.0082092,256 0,0 114.9831009,0 228.4997559,169.9342041 "/></svg>
|
||||
|
After Width: | Height: | Size: 209 B |
@@ -31,6 +31,7 @@
|
||||
"up": "vim::Up",
|
||||
"l": "vim::Right",
|
||||
"right": "vim::Right",
|
||||
"space": "vim::Space",
|
||||
"$": "vim::EndOfLine",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"_": "vim::StartOfLineDownward",
|
||||
@@ -95,6 +96,8 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"ctrl-i": "pane::GoForward",
|
||||
"ctrl-]": "editor::GoToDefinition",
|
||||
@@ -206,6 +209,9 @@
|
||||
"displayLines": true
|
||||
}
|
||||
],
|
||||
"shift-h": "vim::WindowTop",
|
||||
"shift-m": "vim::WindowMiddle",
|
||||
"shift-l": "vim::WindowBottom",
|
||||
// z commands
|
||||
"z t": "editor::ScrollCursorTop",
|
||||
"z z": "editor::ScrollCursorCenter",
|
||||
@@ -329,13 +335,6 @@
|
||||
],
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
";": "vim::RepeatFind",
|
||||
",": [
|
||||
"vim::RepeatFind",
|
||||
{
|
||||
"backwards": true
|
||||
}
|
||||
],
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
@@ -400,7 +399,8 @@
|
||||
{
|
||||
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
|
||||
"bindings": {
|
||||
"u": "editor::Undo",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
"U": "vim::ConvertToUpperCase",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
@@ -502,5 +502,18 @@
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
"bindings": {
|
||||
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
// Whether to display inline and alongside documentation for items in the
|
||||
// completions menu
|
||||
"show_completion_documentation": true,
|
||||
// The debounce delay before re-querying the language server for completion
|
||||
// documentation when not included in original completion list.
|
||||
"completion_documentation_secondary_query_debounce": 300,
|
||||
// Whether to show wrap guides in the editor. Setting this to true will
|
||||
// show a guide at the 'preferred_line_length' value if softwrap is set to
|
||||
// 'preferred_line_length', and will show any additional guides as specified
|
||||
@@ -69,6 +72,17 @@
|
||||
"show_wrap_guides": true,
|
||||
// Character counts at which to show wrap guides in the editor.
|
||||
"wrap_guides": [],
|
||||
// Hide the values of in variables from visual display in private files
|
||||
"redact_private_values": false,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
"**/*.key",
|
||||
"**/*.cert",
|
||||
"**/*.crt",
|
||||
"**/secrets.yml"
|
||||
],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
@@ -111,7 +125,9 @@
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true,
|
||||
// Whether to show selections in the scrollbar.
|
||||
"selections": true
|
||||
"selections": true,
|
||||
// Whether to show symbols selections in the scrollbar.
|
||||
"symbols_selections": true
|
||||
},
|
||||
"relative_line_numbers": false,
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
@@ -503,5 +519,28 @@
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
// The server to connect to. If the environment variable
|
||||
// ZED_SERVER_URL is set, it will override this setting.
|
||||
"server_url": "https://zed.dev",
|
||||
// Settings overrides to use when using Zed Preview.
|
||||
// Mostly useful for developers who are managing multiple instances of Zed.
|
||||
"preview": {
|
||||
// "theme": "Andromeda"
|
||||
},
|
||||
// Settings overrides to use when using Zed Nightly.
|
||||
// Mostly useful for developers who are managing multiple instances of Zed.
|
||||
"nightly": {
|
||||
// "theme": "Andromeda"
|
||||
},
|
||||
// Settings overrides to use when using Zed Stable.
|
||||
// Mostly useful for developers who are managing multiple instances of Zed.
|
||||
"stable": {
|
||||
// "theme": "Andromeda"
|
||||
},
|
||||
// Settings overrides to use when using Zed Stable.
|
||||
// Mostly useful for developers who are managing multiple instances of Zed.
|
||||
"dev": {
|
||||
// "theme": "Andromeda"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings
|
||||
{}
|
||||
|
||||
@@ -5,26 +5,24 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/activity_indicator.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
auto_update = { path = "../auto_update" }
|
||||
editor = { path = "../editor" }
|
||||
language = { path = "../language" }
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
smallvec.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace", package = "workspace" }
|
||||
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/ai.rs"
|
||||
doctest = false
|
||||
@@ -14,27 +13,27 @@ doctest = false
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
language = { path = "../language" }
|
||||
async-trait.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
bincode = "1.3.3"
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
isahc.workspace = true
|
||||
language = { path = "../language" }
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
matrixmultiply = "0.3.7"
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
isahc.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
parse_duration = "2.1.1"
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
log.workspace = true
|
||||
parse_duration = "2.1.1"
|
||||
tiktoken-rs.workspace = true
|
||||
matrixmultiply = "0.3.7"
|
||||
regex.workspace = true
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
bincode = "1.3.3"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tiktoken-rs.workspace = true
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
|
||||
9
crates/ai/src/providers/open_ai.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod completion;
|
||||
pub mod embedding;
|
||||
pub mod model;
|
||||
|
||||
pub use completion::*;
|
||||
pub use embedding::*;
|
||||
pub use model::OpenAiLanguageModel;
|
||||
|
||||
pub const OPEN_AI_API_URL: &'static str = "https://api.openai.com/v1";
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
models::LanguageModel,
|
||||
};
|
||||
|
||||
use crate::providers::open_ai::{OpenAILanguageModel, OPENAI_API_URL};
|
||||
use crate::providers::open_ai::{OpenAiLanguageModel, OPEN_AI_API_URL};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -58,7 +58,7 @@ pub struct RequestMessage {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct OpenAIRequest {
|
||||
pub struct OpenAiRequest {
|
||||
pub model: String,
|
||||
pub messages: Vec<RequestMessage>,
|
||||
pub stream: bool,
|
||||
@@ -66,7 +66,7 @@ pub struct OpenAIRequest {
|
||||
pub temperature: f32,
|
||||
}
|
||||
|
||||
impl CompletionRequest for OpenAIRequest {
|
||||
impl CompletionRequest for OpenAiRequest {
|
||||
fn data(&self) -> serde_json::Result<String> {
|
||||
serde_json::to_string(self)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ pub struct ResponseMessage {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct OpenAIUsage {
|
||||
pub struct OpenAiUsage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
@@ -93,20 +93,20 @@ pub struct ChatChoiceDelta {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct OpenAIResponseStreamEvent {
|
||||
pub struct OpenAiResponseStreamEvent {
|
||||
pub id: Option<String>,
|
||||
pub object: String,
|
||||
pub created: u32,
|
||||
pub model: String,
|
||||
pub choices: Vec<ChatChoiceDelta>,
|
||||
pub usage: Option<OpenAIUsage>,
|
||||
pub usage: Option<OpenAiUsage>,
|
||||
}
|
||||
|
||||
pub async fn stream_completion(
|
||||
credential: ProviderCredential,
|
||||
executor: BackgroundExecutor,
|
||||
request: Box<dyn CompletionRequest>,
|
||||
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
|
||||
) -> Result<impl Stream<Item = Result<OpenAiResponseStreamEvent>>> {
|
||||
let api_key = match credential {
|
||||
ProviderCredential::Credentials { api_key } => api_key,
|
||||
_ => {
|
||||
@@ -114,10 +114,10 @@ pub async fn stream_completion(
|
||||
}
|
||||
};
|
||||
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAiResponseStreamEvent>>();
|
||||
|
||||
let json_data = request.data()?;
|
||||
let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
|
||||
let mut response = Request::post(format!("{OPEN_AI_API_URL}/chat/completions"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.body(json_data)?
|
||||
@@ -132,7 +132,7 @@ pub async fn stream_completion(
|
||||
|
||||
fn parse_line(
|
||||
line: Result<String, io::Error>,
|
||||
) -> Result<Option<OpenAIResponseStreamEvent>> {
|
||||
) -> Result<Option<OpenAiResponseStreamEvent>> {
|
||||
if let Some(data) = line?.strip_prefix("data: ") {
|
||||
let event = serde_json::from_str(data)?;
|
||||
Ok(Some(event))
|
||||
@@ -169,16 +169,16 @@ pub async fn stream_completion(
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OpenAIResponse {
|
||||
error: OpenAIError,
|
||||
struct OpenAiResponse {
|
||||
error: OpenAiError,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OpenAIError {
|
||||
struct OpenAiError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
match serde_json::from_str::<OpenAIResponse>(&body) {
|
||||
match serde_json::from_str::<OpenAiResponse>(&body) {
|
||||
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
|
||||
"Failed to connect to OpenAI API: {}",
|
||||
response.error.message,
|
||||
@@ -194,16 +194,16 @@ pub async fn stream_completion(
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAICompletionProvider {
|
||||
model: OpenAILanguageModel,
|
||||
pub struct OpenAiCompletionProvider {
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl OpenAICompletionProvider {
|
||||
impl OpenAiCompletionProvider {
|
||||
pub async fn new(model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
let model = executor
|
||||
.spawn(async move { OpenAILanguageModel::load(&model_name) })
|
||||
.spawn(async move { OpenAiLanguageModel::load(&model_name) })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
@@ -214,7 +214,7 @@ impl OpenAICompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl CredentialProvider for OpenAICompletionProvider {
|
||||
impl CredentialProvider for OpenAiCompletionProvider {
|
||||
fn has_credentials(&self) -> bool {
|
||||
match *self.credential.read() {
|
||||
ProviderCredential::Credentials { .. } => true,
|
||||
@@ -232,7 +232,7 @@ impl CredentialProvider for OpenAICompletionProvider {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
async move { ProviderCredential::Credentials { api_key } }.boxed()
|
||||
} else {
|
||||
let credentials = cx.read_credentials(OPENAI_API_URL);
|
||||
let credentials = cx.read_credentials(OPEN_AI_API_URL);
|
||||
async move {
|
||||
if let Some(Some((_, api_key))) = credentials.await.log_err() {
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
@@ -266,7 +266,7 @@ impl CredentialProvider for OpenAICompletionProvider {
|
||||
let credential = credential.clone();
|
||||
let write_credentials = match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
|
||||
Some(cx.write_credentials(OPEN_AI_API_URL, "Bearer", api_key.as_bytes()))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
@@ -281,7 +281,7 @@ impl CredentialProvider for OpenAICompletionProvider {
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
|
||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
||||
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
|
||||
let delete_credentials = cx.delete_credentials(OPEN_AI_API_URL);
|
||||
async move {
|
||||
delete_credentials.await.log_err();
|
||||
}
|
||||
@@ -289,7 +289,7 @@ impl CredentialProvider for OpenAICompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletionProvider for OpenAICompletionProvider {
|
||||
impl CompletionProvider for OpenAiCompletionProvider {
|
||||
fn base_model(&self) -> Box<dyn LanguageModel> {
|
||||
let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
|
||||
model
|
||||
|
||||
@@ -25,17 +25,17 @@ use util::ResultExt;
|
||||
use crate::auth::{CredentialProvider, ProviderCredential};
|
||||
use crate::embedding::{Embedding, EmbeddingProvider};
|
||||
use crate::models::LanguageModel;
|
||||
use crate::providers::open_ai::OpenAILanguageModel;
|
||||
use crate::providers::open_ai::OpenAiLanguageModel;
|
||||
|
||||
use crate::providers::open_ai::OPENAI_API_URL;
|
||||
use crate::providers::open_ai::OPEN_AI_API_URL;
|
||||
|
||||
lazy_static! {
|
||||
static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
|
||||
pub(crate) static ref OPEN_AI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAIEmbeddingProvider {
|
||||
model: OpenAILanguageModel,
|
||||
pub struct OpenAiEmbeddingProvider {
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
pub client: Arc<dyn HttpClient>,
|
||||
pub executor: BackgroundExecutor,
|
||||
@@ -44,42 +44,42 @@ pub struct OpenAIEmbeddingProvider {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OpenAIEmbeddingRequest<'a> {
|
||||
struct OpenAiEmbeddingRequest<'a> {
|
||||
model: &'static str,
|
||||
input: Vec<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OpenAIEmbeddingResponse {
|
||||
data: Vec<OpenAIEmbedding>,
|
||||
usage: OpenAIEmbeddingUsage,
|
||||
struct OpenAiEmbeddingResponse {
|
||||
data: Vec<OpenAiEmbedding>,
|
||||
usage: OpenAiEmbeddingUsage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct OpenAIEmbedding {
|
||||
struct OpenAiEmbedding {
|
||||
embedding: Vec<f32>,
|
||||
index: usize,
|
||||
object: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OpenAIEmbeddingUsage {
|
||||
struct OpenAiEmbeddingUsage {
|
||||
prompt_tokens: usize,
|
||||
total_tokens: usize,
|
||||
}
|
||||
|
||||
impl OpenAIEmbeddingProvider {
|
||||
impl OpenAiEmbeddingProvider {
|
||||
pub async fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
|
||||
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
|
||||
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
|
||||
|
||||
// Loading the model is expensive, so ensure this runs off the main thread.
|
||||
let model = executor
|
||||
.spawn(async move { OpenAILanguageModel::load("text-embedding-ada-002") })
|
||||
.spawn(async move { OpenAiLanguageModel::load("text-embedding-ada-002") })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
|
||||
OpenAIEmbeddingProvider {
|
||||
OpenAiEmbeddingProvider {
|
||||
model,
|
||||
credential,
|
||||
client,
|
||||
@@ -140,7 +140,7 @@ impl OpenAIEmbeddingProvider {
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.body(
|
||||
serde_json::to_string(&OpenAIEmbeddingRequest {
|
||||
serde_json::to_string(&OpenAiEmbeddingRequest {
|
||||
input: spans.clone(),
|
||||
model: "text-embedding-ada-002",
|
||||
})
|
||||
@@ -152,7 +152,7 @@ impl OpenAIEmbeddingProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
impl CredentialProvider for OpenAiEmbeddingProvider {
|
||||
fn has_credentials(&self) -> bool {
|
||||
match *self.credential.read() {
|
||||
ProviderCredential::Credentials { .. } => true,
|
||||
@@ -170,7 +170,7 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
async move { ProviderCredential::Credentials { api_key } }.boxed()
|
||||
} else {
|
||||
let credentials = cx.read_credentials(OPENAI_API_URL);
|
||||
let credentials = cx.read_credentials(OPEN_AI_API_URL);
|
||||
async move {
|
||||
if let Some(Some((_, api_key))) = credentials.await.log_err() {
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
@@ -204,7 +204,7 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
let credential = credential.clone();
|
||||
let write_credentials = match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
|
||||
Some(cx.write_credentials(OPEN_AI_API_URL, "Bearer", api_key.as_bytes()))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
@@ -219,7 +219,7 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
|
||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
||||
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
|
||||
let delete_credentials = cx.delete_credentials(OPEN_AI_API_URL);
|
||||
async move {
|
||||
delete_credentials.await.log_err();
|
||||
}
|
||||
@@ -228,7 +228,7 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EmbeddingProvider for OpenAIEmbeddingProvider {
|
||||
impl EmbeddingProvider for OpenAiEmbeddingProvider {
|
||||
fn base_model(&self) -> Box<dyn LanguageModel> {
|
||||
let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
|
||||
model
|
||||
@@ -270,7 +270,7 @@ impl EmbeddingProvider for OpenAIEmbeddingProvider {
|
||||
StatusCode::OK => {
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?;
|
||||
let response: OpenAiEmbeddingResponse = serde_json::from_str(&body)?;
|
||||
|
||||
log::trace!(
|
||||
"openai embedding completed. tokens: {:?}",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
pub mod completion;
|
||||
pub mod embedding;
|
||||
pub mod model;
|
||||
|
||||
pub use completion::*;
|
||||
pub use embedding::*;
|
||||
pub use model::OpenAILanguageModel;
|
||||
|
||||
pub const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
|
||||
@@ -1,26 +1,28 @@
|
||||
use anyhow::anyhow;
|
||||
use tiktoken_rs::CoreBPE;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::models::{LanguageModel, TruncationDirection};
|
||||
|
||||
use super::OPEN_AI_BPE_TOKENIZER;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAILanguageModel {
|
||||
pub struct OpenAiLanguageModel {
|
||||
name: String,
|
||||
bpe: Option<CoreBPE>,
|
||||
}
|
||||
|
||||
impl OpenAILanguageModel {
|
||||
impl OpenAiLanguageModel {
|
||||
pub fn load(model_name: &str) -> Self {
|
||||
let bpe = tiktoken_rs::get_bpe_from_model(model_name).log_err();
|
||||
OpenAILanguageModel {
|
||||
let bpe =
|
||||
tiktoken_rs::get_bpe_from_model(model_name).unwrap_or(OPEN_AI_BPE_TOKENIZER.to_owned());
|
||||
OpenAiLanguageModel {
|
||||
name: model_name.to_string(),
|
||||
bpe,
|
||||
bpe: Some(bpe),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageModel for OpenAILanguageModel {
|
||||
impl LanguageModel for OpenAiLanguageModel {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
pub trait LanguageModel {
|
||||
fn name(&self) -> String;
|
||||
fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
|
||||
fn truncate(
|
||||
&self,
|
||||
content: &str,
|
||||
length: usize,
|
||||
direction: TruncationDirection,
|
||||
) -> anyhow::Result<String>;
|
||||
fn capacity(&self) -> anyhow::Result<usize>;
|
||||
}
|
||||
@@ -5,10 +5,7 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
gpui = {path = "../gpui"}
|
||||
rust-embed.workspace = true
|
||||
anyhow.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
rust-embed.workspace = true
|
||||
|
||||
@@ -16,7 +16,7 @@ use rust_embed::RustEmbed;
|
||||
pub struct Assets;
|
||||
|
||||
impl AssetSource for Assets {
|
||||
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
||||
fn load(&self, path: &str) -> Result<std::borrow::Cow<'static, [u8]>> {
|
||||
Self::get(path)
|
||||
.map(|f| f.data)
|
||||
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||
|
||||
@@ -5,52 +5,49 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ai = { path = "../ai" }
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections"}
|
||||
editor = { path = "../editor" }
|
||||
fs = { path = "../fs" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
multi_buffer = { path = "../multi_buffer" }
|
||||
project = { path = "../project" }
|
||||
search = { path = "../search" }
|
||||
semantic_index = { path = "../semantic_index" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
uuid.workspace = true
|
||||
log.workspace = true
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
fs = { path = "../fs" }
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
indoc.workspace = true
|
||||
isahc.workspace = true
|
||||
language = { path = "../language" }
|
||||
log.workspace = true
|
||||
menu = { path = "../menu" }
|
||||
multi_buffer = { path = "../multi_buffer" }
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project = { path = "../project" }
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
search = { path = "../search" }
|
||||
semantic_index = { path = "../semantic_index" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smol.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
tiktoken-rs.workspace = true
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
uuid.workspace = true
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
[dev-dependencies]
|
||||
ai = { path = "../ai", features = ["test-support"]}
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
|
||||
ai = { path = "../ai", features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
|
||||
@@ -7,7 +7,7 @@ mod streaming_diff;
|
||||
use ai::providers::open_ai::Role;
|
||||
use anyhow::Result;
|
||||
pub use assistant_panel::AssistantPanel;
|
||||
use assistant_settings::OpenAIModel;
|
||||
use assistant_settings::OpenAiModel;
|
||||
use chrono::{DateTime, Local};
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
@@ -68,7 +68,7 @@ struct SavedConversation {
|
||||
messages: Vec<SavedMessage>,
|
||||
message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: String,
|
||||
model: OpenAIModel,
|
||||
model: OpenAiModel,
|
||||
}
|
||||
|
||||
impl SavedConversation {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
|
||||
assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAiModel},
|
||||
codegen::{self, Codegen, CodegenKind},
|
||||
prompts::generate_content_prompt,
|
||||
Assist, CycleMessageRole, InlineAssist, MessageId, MessageMetadata, MessageStatus,
|
||||
@@ -10,7 +10,7 @@ use ai::prompts::repository_context::PromptCodeSnippet;
|
||||
use ai::{
|
||||
auth::ProviderCredential,
|
||||
completion::{CompletionProvider, CompletionRequest},
|
||||
providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
|
||||
providers::open_ai::{OpenAiCompletionProvider, OpenAiRequest, RequestMessage},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Local};
|
||||
@@ -35,7 +35,7 @@ use gpui::{
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
|
||||
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
|
||||
use language::{language_settings::SoftWrap, Buffer, BufferId, LanguageRegistry, ToOffset as _};
|
||||
use project::Project;
|
||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||
use semantic_index::{SemanticIndex, SemanticIndexStatus};
|
||||
@@ -123,7 +123,7 @@ impl AssistantPanel {
|
||||
.unwrap_or_default();
|
||||
// Defaulting currently to GPT4, allow for this to be set via config.
|
||||
let completion_provider =
|
||||
OpenAICompletionProvider::new("gpt-4".into(), cx.background_executor().clone())
|
||||
OpenAiCompletionProvider::new("gpt-4".into(), cx.background_executor().clone())
|
||||
.await;
|
||||
|
||||
// TODO: deserialize state.
|
||||
@@ -199,9 +199,13 @@ impl AssistantPanel {
|
||||
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
|
||||
cx.notify();
|
||||
if self.focus_handle.is_focused(cx) {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
if self.has_credentials() {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
cx.focus_view(api_key_editor);
|
||||
}
|
||||
}
|
||||
@@ -717,7 +721,7 @@ impl AssistantPanel {
|
||||
content: prompt,
|
||||
});
|
||||
|
||||
let request = Box::new(OpenAIRequest {
|
||||
let request = Box::new(OpenAiRequest {
|
||||
model: model.full_name().into(),
|
||||
messages,
|
||||
stream: true,
|
||||
@@ -777,6 +781,10 @@ impl AssistantPanel {
|
||||
});
|
||||
}
|
||||
|
||||
fn build_api_key_editor(&mut self, cx: &mut WindowContext<'_>) {
|
||||
self.api_key_editor = Some(build_api_key_editor(cx));
|
||||
}
|
||||
|
||||
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
|
||||
let editor = cx.new_view(|cx| {
|
||||
ConversationEditor::new(
|
||||
@@ -870,7 +878,7 @@ impl AssistantPanel {
|
||||
cx.update(|cx| completion_provider.delete_credentials(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.api_key_editor = Some(build_api_key_editor(cx));
|
||||
this.build_api_key_editor(cx);
|
||||
this.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -1096,6 +1104,7 @@ impl AssistantPanel {
|
||||
let conversation =
|
||||
Conversation::deserialize(saved_conversation, path.clone(), languages, &mut cx)
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
// If, by the time we've loaded the conversation, the user has already opened
|
||||
// the same conversation, we don't want to open it again.
|
||||
@@ -1135,7 +1144,7 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
||||
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
|
||||
cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
|
||||
@@ -1146,9 +1155,10 @@ fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
if let Some(api_key_editor) = self.api_key_editor.clone() {
|
||||
const INSTRUCTIONS: [&'static str; 5] = [
|
||||
const INSTRUCTIONS: [&'static str; 6] = [
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
|
||||
" - You can create an API key at: platform.openai.com/api-keys",
|
||||
" - Make sure your OpenAI account has credits",
|
||||
" - Having a subscription for another service like GitHub Copilot won't work.",
|
||||
" ",
|
||||
"Paste your OpenAI API key and press Enter to use the assistant:"
|
||||
@@ -1341,7 +1351,9 @@ impl Panel for AssistantPanel {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
load_credentials.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.editors.is_empty() {
|
||||
if !this.has_credentials() {
|
||||
this.build_api_key_editor(cx);
|
||||
} else if this.editors.is_empty() {
|
||||
this.new_conversation(cx);
|
||||
}
|
||||
})
|
||||
@@ -1393,7 +1405,7 @@ struct Conversation {
|
||||
pending_summary: Task<Option<()>>,
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
model: OpenAIModel,
|
||||
model: OpenAiModel,
|
||||
token_count: Option<usize>,
|
||||
max_token_count: usize,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
@@ -1413,7 +1425,7 @@ impl Conversation {
|
||||
) -> Self {
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "");
|
||||
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "");
|
||||
buffer.set_language_registry(language_registry);
|
||||
cx.spawn(|buffer, mut cx| async move {
|
||||
let markdown = markdown.await?;
|
||||
@@ -1501,18 +1513,24 @@ impl Conversation {
|
||||
};
|
||||
let model = saved_conversation.model;
|
||||
let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
|
||||
OpenAICompletionProvider::new(
|
||||
OpenAiCompletionProvider::new(
|
||||
model.full_name().into(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
cx.update(|cx| completion_provider.retrieve_credentials(cx))?;
|
||||
cx.update(|cx| completion_provider.retrieve_credentials(cx))?
|
||||
.await;
|
||||
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let mut message_anchors = Vec::new();
|
||||
let mut next_message_id = MessageId(0);
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), saved_conversation.text);
|
||||
let mut buffer = Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
saved_conversation.text,
|
||||
);
|
||||
for message in saved_conversation.messages {
|
||||
message_anchors.push(MessageAnchor {
|
||||
id: message.id,
|
||||
@@ -1626,7 +1644,7 @@ impl Conversation {
|
||||
Some(self.max_token_count as isize - self.token_count? as isize)
|
||||
}
|
||||
|
||||
fn set_model(&mut self, model: OpenAIModel, cx: &mut ModelContext<Self>) {
|
||||
fn set_model(&mut self, model: OpenAiModel, cx: &mut ModelContext<Self>) {
|
||||
self.model = model;
|
||||
self.count_remaining_tokens(cx);
|
||||
cx.notify();
|
||||
@@ -1676,10 +1694,11 @@ impl Conversation {
|
||||
|
||||
if should_assist {
|
||||
if !self.completion_provider.has_credentials() {
|
||||
log::info!("completion provider has no credentials");
|
||||
return Default::default();
|
||||
}
|
||||
|
||||
let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
|
||||
let request: Box<dyn CompletionRequest> = Box::new(OpenAiRequest {
|
||||
model: self.model.full_name().to_string(),
|
||||
messages: self
|
||||
.messages(cx)
|
||||
@@ -1962,7 +1981,7 @@ impl Conversation {
|
||||
content: "Summarize the conversation into a short title without punctuation"
|
||||
.into(),
|
||||
}));
|
||||
let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
|
||||
let request: Box<dyn CompletionRequest> = Box::new(OpenAiRequest {
|
||||
model: self.model.full_name().to_string(),
|
||||
messages: messages.collect(),
|
||||
stream: true,
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub enum OpenAIModel {
|
||||
pub enum OpenAiModel {
|
||||
#[serde(rename = "gpt-3.5-turbo-0613")]
|
||||
ThreePointFiveTurbo,
|
||||
#[serde(rename = "gpt-4-0613")]
|
||||
@@ -14,28 +14,28 @@ pub enum OpenAIModel {
|
||||
FourTurbo,
|
||||
}
|
||||
|
||||
impl OpenAIModel {
|
||||
impl OpenAiModel {
|
||||
pub fn full_name(&self) -> &'static str {
|
||||
match self {
|
||||
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
|
||||
OpenAIModel::Four => "gpt-4-0613",
|
||||
OpenAIModel::FourTurbo => "gpt-4-1106-preview",
|
||||
OpenAiModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
|
||||
OpenAiModel::Four => "gpt-4-0613",
|
||||
OpenAiModel::FourTurbo => "gpt-4-1106-preview",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_name(&self) -> &'static str {
|
||||
match self {
|
||||
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
|
||||
OpenAIModel::Four => "gpt-4",
|
||||
OpenAIModel::FourTurbo => "gpt-4-turbo",
|
||||
OpenAiModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
|
||||
OpenAiModel::Four => "gpt-4",
|
||||
OpenAiModel::FourTurbo => "gpt-4-turbo",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle(&self) -> Self {
|
||||
match self {
|
||||
OpenAIModel::ThreePointFiveTurbo => OpenAIModel::Four,
|
||||
OpenAIModel::Four => OpenAIModel::FourTurbo,
|
||||
OpenAIModel::FourTurbo => OpenAIModel::ThreePointFiveTurbo,
|
||||
OpenAiModel::ThreePointFiveTurbo => OpenAiModel::Four,
|
||||
OpenAiModel::Four => OpenAiModel::FourTurbo,
|
||||
OpenAiModel::FourTurbo => OpenAiModel::ThreePointFiveTurbo,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ pub struct AssistantSettings {
|
||||
pub dock: AssistantDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_open_ai_model: OpenAIModel,
|
||||
pub default_open_ai_model: OpenAiModel,
|
||||
}
|
||||
|
||||
/// Assistant panel settings
|
||||
@@ -79,7 +79,7 @@ pub struct AssistantSettingsContent {
|
||||
/// The default OpenAI model to use when starting new conversations.
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAIModel>,
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
}
|
||||
|
||||
impl Settings for AssistantSettings {
|
||||
|
||||
@@ -365,7 +365,9 @@ mod tests {
|
||||
use futures::stream::{self};
|
||||
use gpui::{Context, TestAppContext};
|
||||
use indoc::indoc;
|
||||
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
|
||||
use language::{
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
use settings::SettingsStore;
|
||||
@@ -394,8 +396,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
"};
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let range = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
@@ -460,8 +463,9 @@ mod tests {
|
||||
le
|
||||
}
|
||||
"};
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let position = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
@@ -525,8 +529,9 @@ mod tests {
|
||||
" \n",
|
||||
"}\n" //
|
||||
);
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let position = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
@@ -4,7 +4,7 @@ use ai::prompts::file_context::FileContext;
|
||||
use ai::prompts::generate::GenerateInlineContent;
|
||||
use ai::prompts::preamble::EngineerPreamble;
|
||||
use ai::prompts::repository_context::{PromptCodeSnippet, RepositoryContext};
|
||||
use ai::providers::open_ai::OpenAILanguageModel;
|
||||
use ai::providers::open_ai::OpenAiLanguageModel;
|
||||
use language::{BufferSnapshot, OffsetRangeExt, ToOffset};
|
||||
use std::cmp::{self, Reverse};
|
||||
use std::ops::Range;
|
||||
@@ -131,7 +131,7 @@ pub fn generate_content_prompt(
|
||||
project_name: Option<String>,
|
||||
) -> anyhow::Result<String> {
|
||||
// Using new Prompt Templates
|
||||
let openai_model: Arc<dyn LanguageModel> = Arc::new(OpenAILanguageModel::load(model));
|
||||
let openai_model: Arc<dyn LanguageModel> = Arc::new(OpenAiLanguageModel::load(model));
|
||||
let lang_name = if let Some(language_name) = language_name {
|
||||
Some(language_name.to_string())
|
||||
} else {
|
||||
@@ -178,7 +178,9 @@ pub(crate) mod tests {
|
||||
|
||||
use gpui::{AppContext, Context};
|
||||
use indoc::indoc;
|
||||
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
|
||||
use language::{
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
|
||||
};
|
||||
use settings::SettingsStore;
|
||||
|
||||
pub(crate) fn rust_lang() -> Language {
|
||||
@@ -253,8 +255,9 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
"};
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -5,20 +5,17 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/audio.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
collections = { path = "../collections" }
|
||||
util = { path = "../util" }
|
||||
|
||||
|
||||
rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
|
||||
|
||||
log.workspace = true
|
||||
futures.workspace = true
|
||||
anyhow.workspace = true
|
||||
collections = { path = "../collections" }
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
|
||||
util = { path = "../util" }
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, AssetSource};
|
||||
use gpui::{AppContext, AssetSource, Global};
|
||||
use rodio::{
|
||||
source::{Buffered, SamplesConverter},
|
||||
Decoder, Source,
|
||||
@@ -15,6 +15,10 @@ pub struct SoundRegistry {
|
||||
assets: Box<dyn AssetSource>,
|
||||
}
|
||||
|
||||
struct GlobalSoundRegistry(Arc<SoundRegistry>);
|
||||
|
||||
impl Global for GlobalSoundRegistry {}
|
||||
|
||||
impl SoundRegistry {
|
||||
pub fn new(source: impl AssetSource) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
@@ -24,7 +28,11 @@ impl SoundRegistry {
|
||||
}
|
||||
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
cx.global::<Arc<Self>>().clone()
|
||||
cx.global::<GlobalSoundRegistry>().0.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn set_global(source: impl AssetSource, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source)));
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use assets::SoundRegistry;
|
||||
use gpui::{AppContext, AssetSource};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{AppContext, AssetSource, Global};
|
||||
use rodio::{OutputStream, OutputStreamHandle};
|
||||
use util::ResultExt;
|
||||
|
||||
mod assets;
|
||||
|
||||
pub fn init(source: impl AssetSource, cx: &mut AppContext) {
|
||||
cx.set_global(SoundRegistry::new(source));
|
||||
cx.set_global(Audio::new());
|
||||
SoundRegistry::set_global(source, cx);
|
||||
cx.set_global(GlobalAudio(Audio::new()));
|
||||
}
|
||||
|
||||
pub enum Sound {
|
||||
@@ -37,6 +38,11 @@ pub struct Audio {
|
||||
output_handle: Option<OutputStreamHandle>,
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct GlobalAudio(Audio);
|
||||
|
||||
impl Global for GlobalAudio {}
|
||||
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -56,11 +62,11 @@ impl Audio {
|
||||
}
|
||||
|
||||
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
|
||||
if !cx.has_global::<Self>() {
|
||||
if !cx.has_global::<GlobalAudio>() {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update_global::<Self, _>(|this, cx| {
|
||||
cx.update_global::<GlobalAudio, _>(|this, cx| {
|
||||
let output_handle = this.ensure_output_exists()?;
|
||||
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
|
||||
output_handle.play_raw(source).log_err()?;
|
||||
@@ -69,11 +75,11 @@ impl Audio {
|
||||
}
|
||||
|
||||
pub fn end_call(cx: &mut AppContext) {
|
||||
if !cx.has_global::<Self>() {
|
||||
if !cx.has_global::<GlobalAudio>() {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update_global::<Self, _>(|this, _| {
|
||||
cx.update_global::<GlobalAudio, _>(|this, _| {
|
||||
this._output_stream.take();
|
||||
this.output_handle.take();
|
||||
});
|
||||
|
||||
@@ -5,28 +5,28 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/auto_update.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
db = { path = "../db" }
|
||||
client = { path = "../client" }
|
||||
gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
util = { path = "../util" }
|
||||
anyhow.workspace = true
|
||||
client = { path = "../client" }
|
||||
db = { path = "../db" }
|
||||
gpui = { path = "../gpui" }
|
||||
isahc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
menu = { path = "../menu" }
|
||||
project = { path = "../project" }
|
||||
release_channel = { path = "../release_channel" }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
mod update_notification;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use db::RELEASE_CHANNEL;
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, Task, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use isahc::AsyncBody;
|
||||
|
||||
@@ -18,10 +18,15 @@ use smol::io::AsyncReadExt;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use smol::{fs::File, process::Command};
|
||||
|
||||
use std::{ffi::OsString, sync::Arc, time::Duration};
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use std::{
|
||||
env::consts::{ARCH, OS},
|
||||
ffi::OsString,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use update_notification::UpdateNotification;
|
||||
use util::channel::{AppCommitSha, ReleaseChannel};
|
||||
use util::http::HttpClient;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use workspace::Workspace;
|
||||
|
||||
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
|
||||
@@ -49,9 +54,8 @@ pub enum AutoUpdateStatus {
|
||||
pub struct AutoUpdater {
|
||||
status: AutoUpdateStatus,
|
||||
current_version: SemanticVersion,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
pending_poll: Option<Task<Option<()>>>,
|
||||
server_url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -87,7 +91,12 @@ impl Settings for AutoUpdateSetting {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
|
||||
#[derive(Default)]
|
||||
struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
|
||||
|
||||
impl Global for GlobalAutoUpdate {}
|
||||
|
||||
pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
|
||||
AutoUpdateSetting::register(cx);
|
||||
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
@@ -99,29 +108,28 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
|
||||
let auto_updater = cx.new_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, http_client, server_url);
|
||||
let version = release_channel::AppVersion::global(cx);
|
||||
let auto_updater = cx.new_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, http_client);
|
||||
|
||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||
.0
|
||||
.then(|| updater.start_polling(cx));
|
||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||
.0
|
||||
.then(|| updater.start_polling(cx));
|
||||
|
||||
cx.observe_global::<SettingsStore>(move |updater, cx| {
|
||||
if AutoUpdateSetting::get_global(cx).0 {
|
||||
if update_subscription.is_none() {
|
||||
update_subscription = Some(updater.start_polling(cx))
|
||||
}
|
||||
} else {
|
||||
update_subscription.take();
|
||||
cx.observe_global::<SettingsStore>(move |updater, cx| {
|
||||
if AutoUpdateSetting::get_global(cx).0 {
|
||||
if update_subscription.is_none() {
|
||||
update_subscription = Some(updater.start_polling(cx))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
update_subscription.take();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
updater
|
||||
});
|
||||
cx.set_global(Some(auto_updater));
|
||||
}
|
||||
updater
|
||||
});
|
||||
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
|
||||
}
|
||||
|
||||
pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
@@ -139,17 +147,18 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
|
||||
pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<()> {
|
||||
let auto_updater = AutoUpdater::get(cx)?;
|
||||
let release_channel = cx.try_global::<ReleaseChannel>()?;
|
||||
let release_channel = ReleaseChannel::try_global(cx)?;
|
||||
|
||||
if matches!(
|
||||
release_channel,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview
|
||||
) {
|
||||
let auto_updater = auto_updater.read(cx);
|
||||
let server_url = &auto_updater.server_url;
|
||||
let release_channel = release_channel.dev_name();
|
||||
let current_version = auto_updater.current_version;
|
||||
let url = format!("{server_url}/releases/{release_channel}/{current_version}");
|
||||
let url = &auto_updater
|
||||
.http_client
|
||||
.zed_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
cx.open_url(&url);
|
||||
}
|
||||
|
||||
@@ -183,19 +192,14 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||
|
||||
impl AutoUpdater {
|
||||
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
|
||||
cx.default_global::<Option<Model<Self>>>().clone()
|
||||
cx.default_global::<GlobalAutoUpdate>().0.clone()
|
||||
}
|
||||
|
||||
fn new(
|
||||
current_version: SemanticVersion,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
server_url: String,
|
||||
) -> Self {
|
||||
fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
|
||||
Self {
|
||||
status: AutoUpdateStatus::Idle,
|
||||
current_version,
|
||||
http_client,
|
||||
server_url,
|
||||
pending_poll: None,
|
||||
}
|
||||
}
|
||||
@@ -241,18 +245,16 @@ impl AutoUpdater {
|
||||
}
|
||||
|
||||
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
|
||||
let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
|
||||
(
|
||||
this.http_client.clone(),
|
||||
this.server_url.clone(),
|
||||
this.current_version,
|
||||
)
|
||||
let (client, current_version) = this.read_with(&cx, |this, _| {
|
||||
(this.http_client.clone(), this.current_version)
|
||||
})?;
|
||||
|
||||
let mut url_string = format!("{server_url}/api/releases/latest?asset=Zed.dmg");
|
||||
let mut url_string = client.zed_url(&format!(
|
||||
"/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
|
||||
OS, ARCH
|
||||
));
|
||||
cx.update(|cx| {
|
||||
if let Some(param) = cx
|
||||
.try_global::<ReleaseChannel>()
|
||||
if let Some(param) = ReleaseChannel::try_global(cx)
|
||||
.map(|release_channel| release_channel.release_query_param())
|
||||
.flatten()
|
||||
{
|
||||
@@ -274,7 +276,9 @@ impl AutoUpdater {
|
||||
|
||||
let should_download = match *RELEASE_CHANNEL {
|
||||
ReleaseChannel::Nightly => cx
|
||||
.try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
|
||||
.update(|cx| AppCommitSha::try_global(cx).map(|sha| release.version != sha.0))
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true),
|
||||
_ => release.version.parse::<SemanticVersion>()? > current_version,
|
||||
};
|
||||
@@ -309,9 +313,8 @@ impl AutoUpdater {
|
||||
let mut dmg_file = File::create(&dmg_path).await?;
|
||||
|
||||
let (installation_id, release_channel, telemetry) = cx.update(|cx| {
|
||||
let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
|
||||
let release_channel = cx
|
||||
.try_global::<ReleaseChannel>()
|
||||
let installation_id = Client::global(cx).telemetry().installation_id();
|
||||
let release_channel = ReleaseChannel::try_global(cx)
|
||||
.map(|release_channel| release_channel.display_name());
|
||||
let telemetry = TelemetrySettings::get_global(cx).metrics;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use gpui::{
|
||||
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use util::channel::ReleaseChannel;
|
||||
use release_channel::ReleaseChannel;
|
||||
use workspace::ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt};
|
||||
|
||||
pub struct UpdateNotification {
|
||||
@@ -14,7 +14,7 @@ impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||
|
||||
impl Render for UpdateNotification {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||
let app_name = ReleaseChannel::global(cx).display_name();
|
||||
|
||||
v_flex()
|
||||
.on_action(cx.listener(UpdateNotification::dismiss))
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/breadcrumbs.rs"
|
||||
doctest = false
|
||||
@@ -14,15 +13,15 @@ doctest = false
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
ui = { path = "../ui" }
|
||||
itertools = "0.10"
|
||||
language = { path = "../language" }
|
||||
outline = { path = "../outline" }
|
||||
project = { path = "../project" }
|
||||
search = { path = "../search" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
workspace = { path = "../workspace" }
|
||||
outline = { path = "../outline" }
|
||||
itertools = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/call.rs"
|
||||
doctest = false
|
||||
@@ -21,36 +20,35 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
audio = { path = "../audio" }
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
log.workspace = true
|
||||
live_kit_client = { path = "../live_kit_client" }
|
||||
fs = { path = "../fs" }
|
||||
language = { path = "../language" }
|
||||
media = { path = "../media" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
image = "0.23"
|
||||
language = { path = "../language" }
|
||||
live_kit_client = { path = "../live_kit_client" }
|
||||
log.workspace = true
|
||||
media = { path = "../media" }
|
||||
postage.workspace = true
|
||||
project = { path = "../project" }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smallvec.workspace = true
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
||||
@@ -9,8 +9,8 @@ use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
|
||||
use collections::HashSet;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
||||
WeakModel,
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use postage::watch;
|
||||
use project::Project;
|
||||
@@ -21,11 +21,15 @@ use std::sync::Arc;
|
||||
pub use participant::ParticipantLocation;
|
||||
pub use room::Room;
|
||||
|
||||
struct GlobalActiveCall(Model<ActiveCall>);
|
||||
|
||||
impl Global for GlobalActiveCall {}
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
CallSettings::register(cx);
|
||||
|
||||
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(active_call);
|
||||
cx.set_global(GlobalActiveCall(active_call));
|
||||
}
|
||||
|
||||
pub struct OneAtATime {
|
||||
@@ -154,7 +158,12 @@ impl ActiveCall {
|
||||
}
|
||||
|
||||
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||
cx.global::<Model<Self>>().clone()
|
||||
cx.global::<GlobalActiveCall>().0.clone()
|
||||
}
|
||||
|
||||
pub fn try_global(cx: &AppContext) -> Option<Model<Self>> {
|
||||
cx.try_global::<GlobalActiveCall>()
|
||||
.map(|call| call.0.clone())
|
||||
}
|
||||
|
||||
pub fn invite(
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/channel.rs"
|
||||
doctest = false
|
||||
@@ -14,38 +13,38 @@ doctest = false
|
||||
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client = { path = "../client" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
db = { path = "../db" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
rpc = { path = "../rpc" }
|
||||
text = { path = "../text" }
|
||||
language = { path = "../language" }
|
||||
settings = { path = "../settings" }
|
||||
feature_flags = { path = "../feature_flags" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
clock = { path = "../clock" }
|
||||
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
image = "0.23"
|
||||
language = { path = "../language" }
|
||||
lazy_static.workspace = true
|
||||
smallvec.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel = { path = "../release_channel" }
|
||||
rpc = { path = "../rpc" }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
tempfile.workspace = true
|
||||
text = { path = "../text" }
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
url.workspace = true
|
||||
util = { path = "../util" }
|
||||
uuid.workspace = true
|
||||
url = "2.2"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
tempfile = "3"
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
|
||||
@@ -9,6 +9,7 @@ use rpc::{
|
||||
TypedEnvelope,
|
||||
};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use text::BufferId;
|
||||
use util::ResultExt;
|
||||
|
||||
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
|
||||
@@ -53,7 +54,7 @@ impl ChannelBuffer {
|
||||
channel_id: channel.id,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let buffer_id = BufferId::new(response.buffer_id)?;
|
||||
let base_text = response.base_text;
|
||||
let operations = response
|
||||
.operations
|
||||
@@ -63,12 +64,7 @@ impl ChannelBuffer {
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let capability = channel_store.read(cx).channel_capability(channel.id);
|
||||
language::Buffer::remote(
|
||||
response.buffer_id,
|
||||
response.replica_id as u16,
|
||||
capability,
|
||||
base_text,
|
||||
)
|
||||
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
|
||||
})?;
|
||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
|
||||
|
||||
@@ -107,7 +103,7 @@ impl ChannelBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remote_id(&self, cx: &AppContext) -> u64 {
|
||||
pub fn remote_id(&self, cx: &AppContext) -> BufferId {
|
||||
self.buffer.read(cx).remote_id()
|
||||
}
|
||||
|
||||
@@ -210,7 +206,7 @@ impl ChannelBuffer {
|
||||
pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let version = buffer.version();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let buffer_id = buffer.remote_id().into();
|
||||
let client = self.client.clone();
|
||||
let epoch = self.epoch();
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ use anyhow::{anyhow, Result};
|
||||
use channel_index::ChannelIndex;
|
||||
use client::{Client, Subscription, User, UserId, UserStore};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use db::RELEASE_CHANNEL;
|
||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
|
||||
WeakModel,
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, SharedString,
|
||||
Task, WeakModel,
|
||||
};
|
||||
use language::Capability;
|
||||
use release_channel::RELEASE_CHANNEL;
|
||||
use rpc::{
|
||||
proto::{self, ChannelRole, ChannelVisibility},
|
||||
TypedEnvelope,
|
||||
@@ -22,7 +22,7 @@ use util::{async_maybe, maybe, ResultExt};
|
||||
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
let channel_store =
|
||||
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
|
||||
cx.set_global(channel_store);
|
||||
cx.set_global(GlobalChannelStore(channel_store));
|
||||
}
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
@@ -143,9 +143,13 @@ enum OpenedModelHandle<E> {
|
||||
Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
|
||||
}
|
||||
|
||||
struct GlobalChannelStore(Model<ChannelStore>);
|
||||
|
||||
impl Global for GlobalChannelStore {}
|
||||
|
||||
impl ChannelStore {
|
||||
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||
cx.global::<Model<Self>>().clone()
|
||||
cx.global::<GlobalChannelStore>().0.clone()
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
|
||||
@@ -329,6 +329,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let client = Client::new(http.clone(), cx);
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/cli.rs"
|
||||
doctest = false
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/client.rs"
|
||||
doctest = false
|
||||
@@ -19,6 +18,7 @@ collections = { path = "../collections" }
|
||||
db = { path = "../db" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
release_channel = { path = "../release_channel" }
|
||||
rpc = { path = "../rpc" }
|
||||
text = { path = "../text" }
|
||||
settings = { path = "../settings" }
|
||||
@@ -32,6 +32,7 @@ futures.workspace = true
|
||||
image = "0.23"
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
once_cell = "1.19.0"
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
@@ -39,14 +40,15 @@ schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2 = "0.10"
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
tempfile = "3"
|
||||
tempfile.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
uuid.workspace = true
|
||||
url = "2.2"
|
||||
url.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
|
||||
@@ -15,18 +15,18 @@ use futures::{
|
||||
TryFutureExt as _, TryStreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task,
|
||||
WeakModel,
|
||||
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use settings::Settings;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
@@ -41,8 +41,7 @@ use std::{
|
||||
use telemetry::Telemetry;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use util::channel::ReleaseChannel;
|
||||
use util::http::HttpClient;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub use rpc::*;
|
||||
@@ -50,18 +49,14 @@ pub use telemetry::Event;
|
||||
pub use user::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ZED_SERVER_URL: String =
|
||||
std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string());
|
||||
pub static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
|
||||
static ref ZED_SERVER_URL: Option<String> = std::env::var("ZED_SERVER_URL").ok();
|
||||
static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
|
||||
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
|
||||
.ok()
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
|
||||
.ok()
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok());
|
||||
pub static ref ZED_APP_PATH: Option<PathBuf> =
|
||||
std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
|
||||
pub static ref ZED_ALWAYS_ACTIVE: bool =
|
||||
@@ -73,13 +68,45 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
actions!(client, [SignIn, SignOut, Reconnect]);
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ClientSettingsContent {
|
||||
server_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ClientSettings {
|
||||
pub server_url: String,
|
||||
}
|
||||
|
||||
impl Settings for ClientSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = ClientSettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &mut AppContext,
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut result = Self::load_via_json_merge(default_value, user_values)?;
|
||||
if let Some(server_url) = &*ZED_SERVER_URL {
|
||||
result.server_url = server_url.clone()
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_settings(cx: &mut AppContext) {
|
||||
TelemetrySettings::register(cx);
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.register_setting::<ClientSettings>(cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
|
||||
let client = Arc::downgrade(client);
|
||||
cx.on_action({
|
||||
let client = client.clone();
|
||||
@@ -118,10 +145,14 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
struct GlobalClient(Arc<Client>);
|
||||
|
||||
impl Global for GlobalClient {}
|
||||
|
||||
pub struct Client {
|
||||
id: AtomicU64,
|
||||
peer: Arc<Peer>,
|
||||
http: Arc<dyn HttpClient>,
|
||||
http: Arc<ZedHttpClient>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
state: RwLock<ClientState>,
|
||||
|
||||
@@ -390,8 +421,8 @@ impl settings::Settings for TelemetrySettings {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
let client = Arc::new(Self {
|
||||
id: AtomicU64::new(0),
|
||||
peer: Peer::new(0),
|
||||
telemetry: Telemetry::new(http.clone(), cx),
|
||||
@@ -402,14 +433,16 @@ impl Client {
|
||||
authenticate: Default::default(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
establish_connection: Default::default(),
|
||||
})
|
||||
});
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn http_client(&self) -> Arc<dyn HttpClient> {
|
||||
pub fn http_client(&self) -> Arc<ZedHttpClient> {
|
||||
self.http.clone()
|
||||
}
|
||||
|
||||
@@ -450,6 +483,13 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
cx.global::<GlobalClient>().0.clone()
|
||||
}
|
||||
pub fn set_global(client: Arc<Client>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalClient(client))
|
||||
}
|
||||
|
||||
pub fn user_id(&self) -> Option<u64> {
|
||||
self.state
|
||||
.read()
|
||||
@@ -925,14 +965,14 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn get_rpc_url(
|
||||
http: Arc<dyn HttpClient>,
|
||||
http: Arc<ZedHttpClient>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> Result<Url> {
|
||||
if let Some(url) = &*ZED_RPC_URL {
|
||||
return Url::parse(url).context("invalid rpc url");
|
||||
}
|
||||
|
||||
let mut url = format!("{}/rpc", *ZED_SERVER_URL);
|
||||
let mut url = http.zed_url("/rpc");
|
||||
if let Some(preview_param) =
|
||||
release_channel.and_then(|channel| channel.release_query_param())
|
||||
{
|
||||
@@ -963,14 +1003,26 @@ impl Client {
|
||||
credentials: &Credentials,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Task<Result<Connection, EstablishConnectionError>> {
|
||||
let release_channel = cx.try_read_global(|channel: &ReleaseChannel, _| *channel);
|
||||
let release_channel = cx
|
||||
.update(|cx| ReleaseChannel::try_global(cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
let app_version = cx
|
||||
.update(|cx| AppVersion::global(cx).to_string())
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
let request = Request::builder()
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("{} {}", credentials.user_id, credentials.access_token),
|
||||
)
|
||||
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
|
||||
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION)
|
||||
.header("x-zed-app-version", app_version)
|
||||
.header(
|
||||
"x-zed-release-channel",
|
||||
release_channel.map(|r| r.dev_name()).unwrap_or("unknown"),
|
||||
);
|
||||
|
||||
let http = self.http.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -1053,10 +1105,10 @@ impl Client {
|
||||
|
||||
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
|
||||
// that the user is signing in from a Zed app running on the same device.
|
||||
let mut url = format!(
|
||||
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
|
||||
*ZED_SERVER_URL, port, public_key_string
|
||||
);
|
||||
let mut url = http.zed_url(&format!(
|
||||
"/native_app_signin?native_app_port={}&native_app_public_key={}",
|
||||
port, public_key_string
|
||||
));
|
||||
|
||||
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
|
||||
log::info!("impersonating user @{}", impersonate_login);
|
||||
@@ -1088,7 +1140,7 @@ impl Client {
|
||||
}
|
||||
|
||||
let post_auth_url =
|
||||
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
|
||||
http.zed_url("/native_app_signin_succeeded");
|
||||
req.respond(
|
||||
tiny_http::Response::empty(302).with_header(
|
||||
tiny_http::Header::from_bytes(
|
||||
@@ -1130,7 +1182,7 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn authenticate_as_admin(
|
||||
http: Arc<dyn HttpClient>,
|
||||
http: Arc<ZedHttpClient>,
|
||||
login: String,
|
||||
mut api_token: String,
|
||||
) -> Result<Credentials> {
|
||||
@@ -1351,7 +1403,7 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti
|
||||
}
|
||||
|
||||
let (user_id, access_token) = cx
|
||||
.update(|cx| cx.read_credentials(&ZED_SERVER_URL))
|
||||
.update(|cx| cx.read_credentials(&ClientSettings::get_global(cx).server_url))
|
||||
.log_err()?
|
||||
.await
|
||||
.log_err()??;
|
||||
@@ -1368,7 +1420,7 @@ async fn write_credentials_to_keychain(
|
||||
) -> Result<()> {
|
||||
cx.update(move |cx| {
|
||||
cx.write_credentials(
|
||||
&ZED_SERVER_URL,
|
||||
&ClientSettings::get_global(cx).server_url,
|
||||
&credentials.user_id.to_string(),
|
||||
credentials.access_token.as_bytes(),
|
||||
)
|
||||
@@ -1377,7 +1429,7 @@ async fn write_credentials_to_keychain(
|
||||
}
|
||||
|
||||
async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
|
||||
cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
|
||||
cx.update(move |cx| cx.delete_credentials(&ClientSettings::get_global(cx).server_url))?
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1684,6 +1736,7 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
init_settings(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
mod event_coalescer;
|
||||
|
||||
use crate::{TelemetrySettings, ZED_SERVER_URL};
|
||||
use crate::TelemetrySettings;
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::Future;
|
||||
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use release_channel::ReleaseChannel;
|
||||
use serde::Serialize;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Write;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{
|
||||
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::http::HttpClient;
|
||||
use util::http::{self, HttpClient, Method, ZedHttpClient};
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::{channel::ReleaseChannel, TryFutureExt};
|
||||
use util::TryFutureExt;
|
||||
|
||||
use self::event_coalescer::EventCoalescer;
|
||||
|
||||
pub struct Telemetry {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
executor: BackgroundExecutor,
|
||||
state: Arc<Mutex<TelemetryState>>,
|
||||
}
|
||||
@@ -43,12 +46,6 @@ struct TelemetryState {
|
||||
max_queue_size: usize,
|
||||
}
|
||||
|
||||
const EVENTS_URL_PATH: &'static str = "/api/events";
|
||||
|
||||
lazy_static! {
|
||||
static ref EVENTS_URL: String = format!("{}{}", *ZED_SERVER_URL, EVENTS_URL_PATH);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct EventRequestBody {
|
||||
installation_id: Option<Arc<str>>,
|
||||
@@ -148,11 +145,17 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
|
||||
#[cfg(not(debug_assertions))]
|
||||
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Vec<u8>> = Lazy::new(|| {
|
||||
option_env!("ZED_CLIENT_CHECKSUM_SEED")
|
||||
.unwrap_or("development-checksum-seed")
|
||||
.as_bytes()
|
||||
.into()
|
||||
});
|
||||
|
||||
impl Telemetry {
|
||||
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
let release_channel = cx
|
||||
.try_global::<ReleaseChannel>()
|
||||
.map(|release_channel| release_channel.display_name());
|
||||
pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
let release_channel =
|
||||
ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name());
|
||||
|
||||
TelemetrySettings::register(cx);
|
||||
|
||||
@@ -547,9 +550,27 @@ impl Telemetry {
|
||||
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
||||
}
|
||||
|
||||
this.http_client
|
||||
.post_json(EVENTS_URL.as_str(), json_bytes.into())
|
||||
.await?;
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(&*ZED_CLIENT_CHECKSUM_SEED);
|
||||
summer.update(&json_bytes);
|
||||
summer.update(&*ZED_CLIENT_CHECKSUM_SEED);
|
||||
let mut checksum = String::new();
|
||||
for byte in summer.finalize().as_slice() {
|
||||
use std::fmt::Write;
|
||||
write!(&mut checksum, "{:02x}", byte).unwrap();
|
||||
}
|
||||
|
||||
let request = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(&this.http_client.zed_url("/api/events"))
|
||||
.header("Content-Type", "text/plain")
|
||||
.header("x-zed-checksum", checksum)
|
||||
.body(json_bytes.into());
|
||||
|
||||
let response = this.http_client.send(request?).await?;
|
||||
if response.status() != 200 {
|
||||
log::error!("Failed to send events: HTTP {:?}", response.status());
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use futures::{channel::mpsc, Future, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUrl, Task,
|
||||
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUri, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use postage::{sink::Sink, watch};
|
||||
@@ -22,7 +22,7 @@ pub struct ParticipantIndex(pub u32);
|
||||
pub struct User {
|
||||
pub id: UserId,
|
||||
pub github_login: String,
|
||||
pub avatar_uri: SharedUrl,
|
||||
pub avatar_uri: SharedUri,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/clock.rs"
|
||||
doctest = false
|
||||
|
||||
@@ -3,11 +3,10 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.42.0"
|
||||
version = "0.44.0"
|
||||
publish = false
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "collab"
|
||||
|
||||
@@ -16,26 +15,22 @@ name = "seed"
|
||||
required-features = ["seed-support"]
|
||||
|
||||
[dependencies]
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
live_kit_server = { path = "../live_kit_server" }
|
||||
text = { path = "../text" }
|
||||
rpc = { path = "../rpc" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
async-tungstenite = "0.16"
|
||||
axum = { version = "0.5", features = ["json", "headers", "ws"] }
|
||||
axum-extra = { version = "0.3", features = ["erased-json"] }
|
||||
base64 = "0.13"
|
||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||
chrono.workspace = true
|
||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
dashmap = "5.4"
|
||||
envy = "0.4.2"
|
||||
futures.workspace = true
|
||||
hyper = "0.14"
|
||||
lazy_static.workspace = true
|
||||
lipsum = { version = "0.8", optional = true }
|
||||
live_kit_server = { path = "../live_kit_server" }
|
||||
log.workspace = true
|
||||
nanoid = "0.4"
|
||||
parking_lot.workspace = true
|
||||
@@ -43,62 +38,63 @@ prometheus = "0.13"
|
||||
prost.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||
rpc = { path = "../rpc" }
|
||||
scrypt = "0.7"
|
||||
smallvec.workspace = true
|
||||
sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha-1 = "0.9"
|
||||
smallvec.workspace = true
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
|
||||
text = { path = "../text" }
|
||||
time.workspace = true
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-tungstenite = "0.17"
|
||||
toml.workspace = true
|
||||
tonic = "0.6"
|
||||
tower = "0.4"
|
||||
toml.workspace = true
|
||||
tracing = "0.1.34"
|
||||
tracing-log = "0.1.3"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
|
||||
util = { path = "../util" }
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
release_channel = { path = "../release_channel" }
|
||||
async-trait.workspace = true
|
||||
audio = { path = "../audio" }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
channel = { path = "../channel" }
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collab_ui = { path = "../collab_ui", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
file_finder = { path = "../file_finder" }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
git = { path = "../git", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lazy_static.workspace = true
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
menu = { path = "../menu" }
|
||||
node_runtime = { path = "../node_runtime" }
|
||||
notifications = { path = "../notifications", features = ["test-support"] }
|
||||
file_finder = { path = "../file_finder"}
|
||||
menu = { path = "../menu"}
|
||||
|
||||
pretty_assertions.workspace = true
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
collab_ui = { path = "../collab_ui", features = ["test-support"] }
|
||||
|
||||
async-trait.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
indoc.workspace = true
|
||||
util = { path = "../util" }
|
||||
lazy_static.workspace = true
|
||||
sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] }
|
||||
serde_json.workspace = true
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
sqlx = { version = "0.7", features = ["sqlite"] }
|
||||
theme = { path = "../theme" }
|
||||
unindent.workspace = true
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
[features]
|
||||
seed-support = ["clap", "lipsum", "reqwest"]
|
||||
|
||||
@@ -3,3 +3,35 @@
|
||||
This crate is what we run at https://collab.zed.dev.
|
||||
|
||||
It contains our back-end logic for collaboration, to which we connect from the Zed client via a websocket after authenticating via https://zed.dev, which is a separate repo running on Vercel.
|
||||
|
||||
# Local Development
|
||||
|
||||
Detailed instructions on getting started are [here](https://zed.dev/docs/local-collaboration).
|
||||
|
||||
# Deployment
|
||||
|
||||
We run two instances of collab:
|
||||
|
||||
* Staging (https://staging-collab.zed.dev)
|
||||
* Production (https://collab.zed.dev)
|
||||
|
||||
Both of these run on the Kubernetes cluster hosted in Digital Ocean.
|
||||
|
||||
Deployment is triggered by pushing to the `collab-staging` (or `collab-production`) tag in Github. The best way to do this is:
|
||||
|
||||
* `./script/deploy-collab staging`
|
||||
* `./script/deploy-collab production`
|
||||
|
||||
You can tell what is currently deployed with `./script/what-is-deployed`.
|
||||
|
||||
# Database Migrations
|
||||
|
||||
To create a new migration:
|
||||
|
||||
```
|
||||
./script/sqlx migrate add <name>
|
||||
```
|
||||
|
||||
Migrations are run automatically on service start, so run `foreman start` again. The service will crash if the migrations fail.
|
||||
|
||||
When you create a new migration, you also need to update the [SQLite schema](./migrations.sqlite/20221109000000_test_schema.sql) that is used for testing.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
ZED_ENVIRONMENT=nightly
|
||||
RUST_LOG=info
|
||||
INVITE_LINK_PREFIX=https://zed.dev/invites/
|
||||
DATABASE_MAX_CONNECTIONS=10
|
||||
@@ -1,4 +0,0 @@
|
||||
ZED_ENVIRONMENT=preview
|
||||
RUST_LOG=info
|
||||
INVITE_LINK_PREFIX=https://zed.dev/invites/
|
||||
DATABASE_MAX_CONNECTIONS=10
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add migration script here
|
||||
|
||||
DROP INDEX index_channels_on_parent_path;
|
||||
CREATE INDEX index_channels_on_parent_path ON channels (parent_path text_pattern_ops);
|
||||
@@ -693,7 +693,7 @@ impl Database {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut text_buffer = text::Buffer::new(0, 0, base_text);
|
||||
let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
|
||||
text_buffer
|
||||
.apply_ops(operations.into_iter().filter_map(operation_from_wire))
|
||||
.unwrap();
|
||||
|
||||
@@ -173,7 +173,7 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the visibiltity of the given channel.
|
||||
/// Sets the visibility of the given channel.
|
||||
pub async fn set_channel_visibility(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
@@ -197,12 +197,10 @@ impl Database {
|
||||
}
|
||||
} else if visibility == ChannelVisibility::Members {
|
||||
if self
|
||||
.get_channel_descendants_including_self(vec![channel_id], &*tx)
|
||||
.get_channel_descendants_excluding_self([&channel], &*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.any(|channel| {
|
||||
channel.id != channel_id && channel.visibility == ChannelVisibility::Public
|
||||
})
|
||||
.any(|channel| channel.visibility == ChannelVisibility::Public)
|
||||
{
|
||||
Err(ErrorCode::BadPublicNesting
|
||||
.with_tag("direction", "children")
|
||||
@@ -261,10 +259,11 @@ impl Database {
|
||||
.await?;
|
||||
|
||||
let channels_to_remove = self
|
||||
.get_channel_descendants_including_self(vec![channel.id], &*tx)
|
||||
.get_channel_descendants_excluding_self([&channel], &*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.chain(Some(channel_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
channel::Entity::delete_many()
|
||||
@@ -445,16 +444,12 @@ impl Database {
|
||||
) -> Result<MembershipUpdated> {
|
||||
let new_channels = self.get_user_channels(user_id, Some(channel), &*tx).await?;
|
||||
let removed_channels = self
|
||||
.get_channel_descendants_including_self(vec![channel.id], &*tx)
|
||||
.get_channel_descendants_excluding_self([channel], &*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|channel| {
|
||||
if !new_channels.channels.iter().any(|c| c.id == channel.id) {
|
||||
Some(channel.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|channel| channel.id)
|
||||
.chain([channel.id])
|
||||
.filter(|channel_id| !new_channels.channels.iter().any(|c| c.id == *channel_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(MembershipUpdated {
|
||||
@@ -545,26 +540,6 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_channel_memberships(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<(Vec<channel_member::Model>, Vec<channel::Model>)> {
|
||||
self.transaction(|tx| async move {
|
||||
let memberships = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::UserId.eq(user_id))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
let channels = self
|
||||
.get_channel_descendants_including_self(
|
||||
memberships.iter().map(|m| m.channel_id),
|
||||
&*tx,
|
||||
)
|
||||
.await?;
|
||||
Ok((memberships, channels))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns all channels for the user with the given ID.
|
||||
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
|
||||
self.transaction(|tx| async move {
|
||||
@@ -596,13 +571,21 @@ impl Database {
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let descendants = self
|
||||
.get_channel_descendants_including_self(
|
||||
channel_memberships.iter().map(|m| m.channel_id),
|
||||
&*tx,
|
||||
)
|
||||
let channels = channel::Entity::find()
|
||||
.filter(channel::Column::Id.is_in(channel_memberships.iter().map(|m| m.channel_id)))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut descendants = self
|
||||
.get_channel_descendants_excluding_self(channels.iter(), &*tx)
|
||||
.await?;
|
||||
|
||||
for channel in channels {
|
||||
if let Err(ix) = descendants.binary_search_by_key(&channel.path(), |c| c.path()) {
|
||||
descendants.insert(ix, channel);
|
||||
}
|
||||
}
|
||||
|
||||
let roles_by_channel_id = channel_memberships
|
||||
.iter()
|
||||
.map(|membership| (membership.channel_id, membership.role))
|
||||
@@ -880,46 +863,23 @@ impl Database {
|
||||
|
||||
// Get the descendants of the given set if channels, ordered by their
|
||||
// path.
|
||||
async fn get_channel_descendants_including_self(
|
||||
pub(crate) async fn get_channel_descendants_excluding_self(
|
||||
&self,
|
||||
channel_ids: impl IntoIterator<Item = ChannelId>,
|
||||
channels: impl IntoIterator<Item = &channel::Model>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<channel::Model>> {
|
||||
let mut values = String::new();
|
||||
for id in channel_ids {
|
||||
if !values.is_empty() {
|
||||
values.push_str(", ");
|
||||
}
|
||||
write!(&mut values, "({})", id).unwrap();
|
||||
let mut filter = Condition::any();
|
||||
for channel in channels.into_iter() {
|
||||
filter = filter.add(channel::Column::ParentPath.like(channel.descendant_path_filter()));
|
||||
}
|
||||
|
||||
if values.is_empty() {
|
||||
if filter.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let sql = format!(
|
||||
r#"
|
||||
SELECT DISTINCT
|
||||
descendant_channels.*,
|
||||
descendant_channels.parent_path || descendant_channels.id as full_path
|
||||
FROM
|
||||
channels parent_channels, channels descendant_channels
|
||||
WHERE
|
||||
descendant_channels.id IN ({values}) OR
|
||||
(
|
||||
parent_channels.id IN ({values}) AND
|
||||
descendant_channels.parent_path LIKE (parent_channels.parent_path || parent_channels.id || '/%')
|
||||
)
|
||||
ORDER BY
|
||||
full_path ASC
|
||||
"#
|
||||
);
|
||||
|
||||
Ok(channel::Entity::find()
|
||||
.from_raw_sql(Statement::from_string(
|
||||
self.pool.get_database_backend(),
|
||||
sql,
|
||||
))
|
||||
.filter(filter)
|
||||
.order_by_asc(Expr::cust("parent_path || id || '/'"))
|
||||
.all(tx)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ impl Model {
|
||||
pub fn path(&self) -> String {
|
||||
format!("{}{}/", self.parent_path, self.id)
|
||||
}
|
||||
|
||||
pub fn descendant_path_filter(&self) -> String {
|
||||
format!("{}{}/%", self.parent_path, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -67,7 +67,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut buffer_a = Buffer::new(0, 0, "".to_string());
|
||||
let mut buffer_a = Buffer::new(0, text::BufferId::new(1).unwrap(), "".to_string());
|
||||
let mut operations = Vec::new();
|
||||
operations.push(buffer_a.edit([(0..0, "hello world")]));
|
||||
operations.push(buffer_a.edit([(5..5, ", cruel")]));
|
||||
@@ -90,7 +90,11 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut buffer_b = Buffer::new(0, 0, buffer_response_b.base_text);
|
||||
let mut buffer_b = Buffer::new(
|
||||
0,
|
||||
text::BufferId::new(1).unwrap(),
|
||||
buffer_response_b.base_text,
|
||||
);
|
||||
buffer_b
|
||||
.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
|
||||
let operation = proto::deserialize_operation(operation).unwrap();
|
||||
@@ -223,7 +227,11 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
text_buffers.push(Buffer::new(0, 0, "".to_string()));
|
||||
text_buffers.push(Buffer::new(
|
||||
0,
|
||||
text::BufferId::new(1).unwrap(),
|
||||
"".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let operations = db
|
||||
@@ -270,7 +278,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
|
||||
.await
|
||||
.unwrap();
|
||||
text_buffers[1] = Buffer::new(1, 0, "def".to_string());
|
||||
text_buffers[1] = Buffer::new(1, text::BufferId::new(1).unwrap(), "def".to_string());
|
||||
update_buffer(
|
||||
buffers[1].channel_id,
|
||||
user_id,
|
||||
|
||||
@@ -14,6 +14,7 @@ use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
|
||||
use util::ResultExt;
|
||||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
const REVISION: Option<&'static str> = option_env!("GITHUB_SHA");
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
@@ -26,51 +27,16 @@ async fn main() -> Result<()> {
|
||||
|
||||
match args().skip(1).next().as_deref() {
|
||||
Some("version") => {
|
||||
println!("collab v{VERSION}");
|
||||
println!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"));
|
||||
}
|
||||
Some("migrate") => {
|
||||
let config = envy::from_env::<MigrateConfig>().expect("error loading config");
|
||||
let mut db_options = db::ConnectOptions::new(config.database_url.clone());
|
||||
db_options.max_connections(5);
|
||||
let db = Database::new(db_options, Executor::Production).await?;
|
||||
|
||||
let migrations_path = config
|
||||
.migrations_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")));
|
||||
|
||||
let migrations = db.migrate(&migrations_path, false).await?;
|
||||
for (migration, duration) in migrations {
|
||||
println!(
|
||||
"Ran {} {} {:?}",
|
||||
migration.version, migration.description, duration
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
run_migrations().await?;
|
||||
}
|
||||
Some("serve") => {
|
||||
let config = envy::from_env::<Config>().expect("error loading config");
|
||||
init_tracing(&config);
|
||||
|
||||
if config.is_development() {
|
||||
// sanity check database url so even if we deploy a busted ZED_ENVIRONMENT to production
|
||||
// we do not run
|
||||
if config.database_url != "postgres://postgres@localhost/zed" {
|
||||
panic!("about to run development migrations on a non-development database?")
|
||||
}
|
||||
let migrations_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations"));
|
||||
let db_options = db::ConnectOptions::new(config.database_url.clone());
|
||||
let db = Database::new(db_options, Executor::Production).await?;
|
||||
|
||||
let migrations = db.migrate(&migrations_path, false).await?;
|
||||
for (migration, duration) in migrations {
|
||||
println!(
|
||||
"Ran {} {} {:?}",
|
||||
migration.version, migration.description, duration
|
||||
);
|
||||
}
|
||||
}
|
||||
run_migrations().await?;
|
||||
|
||||
let state = AppState::new(config).await?;
|
||||
|
||||
@@ -116,8 +82,31 @@ async fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_migrations() -> Result<()> {
|
||||
let config = envy::from_env::<MigrateConfig>().expect("error loading config");
|
||||
let db_options = db::ConnectOptions::new(config.database_url.clone());
|
||||
let db = Database::new(db_options, Executor::Production).await?;
|
||||
|
||||
let migrations_path = config
|
||||
.migrations_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")));
|
||||
|
||||
let migrations = db.migrate(&migrations_path, false).await?;
|
||||
for (migration, duration) in migrations {
|
||||
log::info!(
|
||||
"Migrated {} {} {:?}",
|
||||
migration.version,
|
||||
migration.description,
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn handle_root() -> String {
|
||||
format!("collab v{VERSION}")
|
||||
format!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"))
|
||||
}
|
||||
|
||||
async fn handle_liveness_probe(Extension(state): Extension<Arc<AppState>>) -> Result<String> {
|
||||
|
||||
@@ -64,6 +64,7 @@ use time::OffsetDateTime;
|
||||
use tokio::sync::{watch, Semaphore};
|
||||
use tower::ServiceBuilder;
|
||||
use tracing::{field, info_span, instrument, Instrument};
|
||||
use util::SemanticVersion;
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
@@ -795,6 +796,7 @@ fn broadcast<F>(
|
||||
|
||||
lazy_static! {
|
||||
static ref ZED_PROTOCOL_VERSION: HeaderName = HeaderName::from_static("x-zed-protocol-version");
|
||||
static ref ZED_APP_VERSION: HeaderName = HeaderName::from_static("x-zed-app-version");
|
||||
}
|
||||
|
||||
pub struct ProtocolVersion(u32);
|
||||
@@ -824,6 +826,32 @@ impl Header for ProtocolVersion {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppVersionHeader(SemanticVersion);
|
||||
impl Header for AppVersionHeader {
|
||||
fn name() -> &'static HeaderName {
|
||||
&ZED_APP_VERSION
|
||||
}
|
||||
|
||||
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
I: Iterator<Item = &'i axum::http::HeaderValue>,
|
||||
{
|
||||
let version = values
|
||||
.next()
|
||||
.ok_or_else(axum::headers::Error::invalid)?
|
||||
.to_str()
|
||||
.map_err(|_| axum::headers::Error::invalid())?
|
||||
.parse()
|
||||
.map_err(|_| axum::headers::Error::invalid())?;
|
||||
Ok(Self(version))
|
||||
}
|
||||
|
||||
fn encode<E: Extend<axum::http::HeaderValue>>(&self, values: &mut E) {
|
||||
values.extend([self.0.to_string().parse().unwrap()]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes(server: Arc<Server>) -> Router<Body> {
|
||||
Router::new()
|
||||
.route("/rpc", get(handle_websocket_request))
|
||||
@@ -838,6 +866,7 @@ pub fn routes(server: Arc<Server>) -> Router<Body> {
|
||||
|
||||
pub async fn handle_websocket_request(
|
||||
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
|
||||
_app_version_header: Option<TypedHeader<AppVersionHeader>>,
|
||||
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
|
||||
Extension(server): Extension<Arc<Server>>,
|
||||
Extension(user): Extension<User>,
|
||||
@@ -851,6 +880,7 @@ pub async fn handle_websocket_request(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let socket_address = socket_address.to_string();
|
||||
ws.on_upgrade(move |socket| {
|
||||
use util::ResultExt;
|
||||
@@ -1263,7 +1293,7 @@ async fn rejoin_room(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// leave room disonnects from the room.
|
||||
/// leave room disconnects from the room.
|
||||
async fn leave_room(
|
||||
_: proto::LeaveRoom,
|
||||
response: Response<proto::LeaveRoom>,
|
||||
|
||||
@@ -153,6 +153,8 @@ impl TestServer {
|
||||
}
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
});
|
||||
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/collab_ui.rs"
|
||||
doctest = false
|
||||
@@ -26,50 +25,47 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
auto_update = { path = "../auto_update" }
|
||||
db = { path = "../db" }
|
||||
call = { path = "../call" }
|
||||
client = { path = "../client" }
|
||||
channel = { path = "../channel" }
|
||||
client = { path = "../client" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
# context_menu = { path = "../context_menu" }
|
||||
# drag_and_drop = { path = "../drag_and_drop" }
|
||||
db = { path = "../db" }
|
||||
editor = { path = "../editor" }
|
||||
feature_flags = { path = "../feature_flags" }
|
||||
feedback = { path = "../feedback" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
futures.workspace = true
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
notifications = { path = "../notifications" }
|
||||
rich_text = { path = "../rich_text" }
|
||||
picker = { path = "../picker" }
|
||||
project = { path = "../project" }
|
||||
recent_projects = { path = "../recent_projects" }
|
||||
rpc = { path = "../rpc" }
|
||||
settings = { path = "../settings" }
|
||||
story = { path = "../story", optional = true }
|
||||
feature_flags = { path = "../feature_flags"}
|
||||
theme = { path = "../theme" }
|
||||
theme_selector = { path = "../theme_selector" }
|
||||
vcs_menu = { path = "../vcs_menu" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
zed_actions = { path = "../zed_actions"}
|
||||
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
menu = { path = "../menu" }
|
||||
notifications = { path = "../notifications" }
|
||||
parking_lot.workspace = true
|
||||
schemars.workspace = true
|
||||
picker = { path = "../picker" }
|
||||
postage.workspace = true
|
||||
project = { path = "../project" }
|
||||
recent_projects = { path = "../recent_projects" }
|
||||
rich_text = { path = "../rich_text" }
|
||||
rpc = { path = "../rpc" }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
time.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smallvec.workspace = true
|
||||
story = { path = "../story", optional = true }
|
||||
theme = { path = "../theme" }
|
||||
theme_selector = { path = "../theme_selector" }
|
||||
time.workspace = true
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
vcs_menu = { path = "../vcs_menu" }
|
||||
workspace = { path = "../workspace" }
|
||||
zed_actions = { path = "../zed_actions" }
|
||||
|
||||
[dev-dependencies]
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
@@ -78,11 +74,10 @@ collections = { path = "../collections", features = ["test-support"] }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
notifications = { path = "../notifications", features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
tree-sitter-markdown.workspace = true
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
pretty_assertions.workspace = true
|
||||
tree-sitter-markdown.workspace = true
|
||||
|
||||
@@ -8,8 +8,9 @@ use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, DismissEvent,
|
||||
ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, ListOffset, ListScrollEvent,
|
||||
ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
||||
ElementId, EventEmitter, Fill, FocusHandle, FocusableView, FontWeight, ListOffset,
|
||||
ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use menu::Confirm;
|
||||
@@ -335,67 +336,78 @@ impl ChatPanel {
|
||||
};
|
||||
let this = cx.view().clone();
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.when(!is_continuation_from_previous, |this| {
|
||||
this.pt_3().child(
|
||||
h_flex()
|
||||
.text_ui_sm()
|
||||
.child(
|
||||
div().absolute().child(
|
||||
let mentioning_you = message
|
||||
.mentions
|
||||
.iter()
|
||||
.any(|m| Some(m.1) == self.client.user_id());
|
||||
|
||||
v_flex().w_full().relative().child(
|
||||
div()
|
||||
.bg(if mentioning_you {
|
||||
Fill::from(cx.theme().colors().background)
|
||||
} else {
|
||||
Fill::default()
|
||||
})
|
||||
.rounded_md()
|
||||
.overflow_hidden()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.when(!is_continuation_from_previous, |this| {
|
||||
this.mt_1().child(
|
||||
h_flex()
|
||||
.text_ui_sm()
|
||||
.child(div().absolute().child(
|
||||
Avatar::new(message.sender.avatar_uri.clone()).size(rems(1.)),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.pl(cx.rem_size() + px(6.0))
|
||||
.pr(px(8.0))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.child(Label::new(message.sender.github_login.clone())),
|
||||
)
|
||||
.child(
|
||||
Label::new(format_timestamp(
|
||||
OffsetDateTime::now_utc(),
|
||||
message.timestamp,
|
||||
self.local_timezone,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
)
|
||||
})
|
||||
.when(mentioning_you, |this| this.mt_1())
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.text_ui_sm()
|
||||
.id(element_id)
|
||||
.group("")
|
||||
.child(text.element("body".into(), cx))
|
||||
.child(
|
||||
div()
|
||||
.pl(cx.rem_size() + px(6.0))
|
||||
.pr(px(8.0))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.child(Label::new(message.sender.github_login.clone())),
|
||||
)
|
||||
.child(
|
||||
Label::new(format_timestamp(
|
||||
OffsetDateTime::now_utc(),
|
||||
message.timestamp,
|
||||
self.local_timezone,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.right_0()
|
||||
.w_6()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.when(!self.has_open_menu(message_id_to_remove), |el| {
|
||||
el.visible_on_hover("")
|
||||
})
|
||||
.children(message_id_to_remove.map(|message_id| {
|
||||
popover_menu(("menu", message_id))
|
||||
.trigger(IconButton::new(
|
||||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
))
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_message_menu(&this, message_id, cx))
|
||||
})
|
||||
})),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(is_continuation_from_previous, |this| this.pt_1())
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.text_ui_sm()
|
||||
.id(element_id)
|
||||
.group("")
|
||||
.child(text.element("body".into(), cx))
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.right_0()
|
||||
.w_6()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.when(!self.has_open_menu(message_id_to_remove), |el| {
|
||||
el.visible_on_hover("")
|
||||
})
|
||||
.children(message_id_to_remove.map(|message_id| {
|
||||
popover_menu(("menu", message_id))
|
||||
.trigger(IconButton::new(
|
||||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
))
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_message_menu(&this, message_id, cx))
|
||||
})
|
||||
})),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn has_open_menu(&self, message_id: Option<u64>) -> bool {
|
||||
@@ -441,7 +453,7 @@ impl ChatPanel {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
|
||||
rich_text::render_rich_text(message.body.clone(), &mentions, language_registry, None)
|
||||
}
|
||||
|
||||
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -2495,9 +2495,8 @@ impl CollabPanel {
|
||||
h_flex()
|
||||
.absolute()
|
||||
.right(rems(0.))
|
||||
.z_index(1)
|
||||
.h_full()
|
||||
// HACK: Without this the channel name clips on top of the icons, but I'm not sure why.
|
||||
.z_index(10)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
|
||||
@@ -14,13 +14,13 @@ pub use collab_panel::CollabPanel;
|
||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||
use gpui::{
|
||||
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
|
||||
WindowKind, WindowOptions,
|
||||
WindowContext, WindowKind, WindowOptions,
|
||||
};
|
||||
pub use panel_settings::{
|
||||
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::AppState;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||
|
||||
actions!(
|
||||
collab,
|
||||
@@ -41,7 +41,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
notifications::init(&app_state, cx);
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
@@ -64,7 +64,7 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||
room.share_screen(cx)
|
||||
}
|
||||
});
|
||||
toggle_screen_sharing.detach_and_log_err(cx);
|
||||
toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", cx, |e, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use gpui::{img, prelude::*, AnyElement, SharedUrl};
|
||||
use gpui::{img, prelude::*, AnyElement, SharedUri};
|
||||
use smallvec::SmallVec;
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct CollabNotification {
|
||||
avatar_uri: SharedUrl,
|
||||
avatar_uri: SharedUri,
|
||||
accept_button: Button,
|
||||
dismiss_button: Button,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
@@ -12,7 +12,7 @@ pub struct CollabNotification {
|
||||
|
||||
impl CollabNotification {
|
||||
pub fn new(
|
||||
avatar_uri: impl Into<SharedUrl>,
|
||||
avatar_uri: impl Into<SharedUri>,
|
||||
accept_button: Button,
|
||||
dismiss_button: Button,
|
||||
) -> Self {
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/collections.rs"
|
||||
doctest = false
|
||||
|
||||
@@ -11,13 +11,4 @@ pub type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||
pub type HashSet<T> = std::collections::HashSet<T>;
|
||||
|
||||
pub use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::any::TypeId;
|
||||
pub use std::collections::*;
|
||||
|
||||
// NEW TYPES
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteFilter {
|
||||
pub hidden_namespaces: HashSet<&'static str>,
|
||||
pub hidden_action_types: HashSet<TypeId>,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,6 @@ path = "src/color.rs"
|
||||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
story = { path = "../story", optional = true }
|
||||
itertools = { version = "0.11.0", optional = true }
|
||||
palette = "0.7.3"
|
||||
story = { path = "../story", optional = true }
|
||||
|
||||
@@ -5,36 +5,38 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/command_palette.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
# HACK: We're only depending on `copilot` here for `CommandPaletteFilter`. See the attached comment on that type.
|
||||
copilot = { path = "../copilot" }
|
||||
editor = { path = "../editor" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
picker = { path = "../picker" }
|
||||
project = { path = "../project" }
|
||||
release_channel = { path = "../release_channel" }
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
zed_actions = { path = "../zed_actions" }
|
||||
anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
menu = { path = "../menu" }
|
||||
env_logger.workspace = true
|
||||
go_to_line = { path = "../go_to_line" }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
menu = { path = "../menu" }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
@@ -4,21 +4,20 @@ use std::{
|
||||
};
|
||||
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{CommandPaletteFilter, HashMap};
|
||||
use collections::HashMap;
|
||||
use copilot::CommandPaletteFilter;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
|
||||
actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
|
||||
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
||||
use release_channel::{parse_zed_link, ReleaseChannel};
|
||||
use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
|
||||
use util::{
|
||||
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
||||
ResultExt,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
use zed_actions::OpenZedURL;
|
||||
use zed_actions::OpenZedUrl;
|
||||
|
||||
actions!(command_palette, [Toggle]);
|
||||
|
||||
@@ -100,8 +99,11 @@ impl Render for CommandPalette {
|
||||
}
|
||||
}
|
||||
|
||||
pub type CommandPaletteInterceptor =
|
||||
Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>;
|
||||
pub struct CommandPaletteInterceptor(
|
||||
pub Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
|
||||
);
|
||||
|
||||
impl Global for CommandPaletteInterceptor {}
|
||||
|
||||
pub struct CommandInterceptResult {
|
||||
pub action: Box<dyn Action>,
|
||||
@@ -135,10 +137,12 @@ impl Clone for Command {
|
||||
|
||||
/// Hit count for each command in the palette.
|
||||
/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
|
||||
/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
|
||||
/// if a user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
|
||||
#[derive(Default)]
|
||||
struct HitCounts(HashMap<String, usize>);
|
||||
|
||||
impl Global for HitCounts {}
|
||||
|
||||
impl CommandPaletteDelegate {
|
||||
fn new(
|
||||
command_palette: WeakView<CommandPalette>,
|
||||
@@ -229,14 +233,17 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
|
||||
let mut intercept_result = cx
|
||||
.try_read_global(|interceptor: &CommandPaletteInterceptor, cx| {
|
||||
(interceptor)(&query, cx)
|
||||
(interceptor.0)(&query, cx)
|
||||
})
|
||||
.flatten();
|
||||
|
||||
if *RELEASE_CHANNEL == ReleaseChannel::Dev {
|
||||
let release_channel = cx
|
||||
.update(|cx| ReleaseChannel::try_global(cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
if release_channel == Some(ReleaseChannel::Dev) {
|
||||
if parse_zed_link(&query).is_some() {
|
||||
intercept_result = Some(CommandInterceptResult {
|
||||
action: OpenZedURL { url: query.clone() }.boxed_clone(),
|
||||
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
|
||||
string: query.clone(),
|
||||
positions: vec![],
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/copilot.rs"
|
||||
doctest = false
|
||||
@@ -21,24 +20,23 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
# context_menu = { path = "../context_menu" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
lsp = { path = "../lsp" }
|
||||
node_runtime = { path = "../node_runtime"}
|
||||
util = { path = "../util" }
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar = "0.4.2"
|
||||
anyhow.workspace = true
|
||||
collections = { path = "../collections" }
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
log.workspace = true
|
||||
lsp = { path = "../lsp" }
|
||||
node_runtime = { path = "../node_runtime" }
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smol.workspace = true
|
||||
futures.workspace = true
|
||||
parking_lot.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { path = "../clock" }
|
||||
|
||||
@@ -5,7 +5,7 @@ use async_tar::Archive;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model,
|
||||
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
|
||||
ModelContext, Task, WeakModel,
|
||||
};
|
||||
use language::{
|
||||
@@ -32,6 +32,17 @@ use util::{
|
||||
ResultExt,
|
||||
};
|
||||
|
||||
// HACK: This type is only defined in `copilot` since it is the earliest ancestor
|
||||
// of the crates that use it.
|
||||
//
|
||||
// This is not great. Let's find a better place for it to live.
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteFilter {
|
||||
pub hidden_namespaces: HashSet<&'static str>,
|
||||
pub hidden_action_types: HashSet<TypeId>,
|
||||
}
|
||||
|
||||
impl Global for CommandPaletteFilter {}
|
||||
actions!(
|
||||
copilot,
|
||||
[
|
||||
@@ -54,7 +65,7 @@ pub fn init(
|
||||
let node_runtime = node_runtime.clone();
|
||||
move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
|
||||
});
|
||||
cx.set_global(copilot.clone());
|
||||
Copilot::set_global(copilot.clone(), cx);
|
||||
cx.observe(&copilot, |handle, cx| {
|
||||
let copilot_action_types = [
|
||||
TypeId::of::<Suggest>(),
|
||||
@@ -65,7 +76,7 @@ pub fn init(
|
||||
let copilot_auth_action_types = [TypeId::of::<SignOut>()];
|
||||
let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
|
||||
let status = handle.read(cx).status();
|
||||
let filter = cx.default_global::<collections::CommandPaletteFilter>();
|
||||
let filter = cx.default_global::<CommandPaletteFilter>();
|
||||
|
||||
match status {
|
||||
Status::Disabled => {
|
||||
@@ -307,9 +318,18 @@ pub enum Event {
|
||||
|
||||
impl EventEmitter<Event> for Copilot {}
|
||||
|
||||
struct GlobalCopilot(Model<Copilot>);
|
||||
|
||||
impl Global for GlobalCopilot {}
|
||||
|
||||
impl Copilot {
|
||||
pub fn global(cx: &AppContext) -> Option<Model<Self>> {
|
||||
cx.try_global::<Model<Self>>().map(|model| model.clone())
|
||||
cx.try_global::<GlobalCopilot>()
|
||||
.map(|model| model.0.clone())
|
||||
}
|
||||
|
||||
pub fn set_global(copilot: Model<Self>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalCopilot(copilot));
|
||||
}
|
||||
|
||||
fn start(
|
||||
@@ -425,7 +445,7 @@ impl Copilot {
|
||||
)
|
||||
.detach();
|
||||
|
||||
let server = server.initialize(Default::default()).await?;
|
||||
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
|
||||
|
||||
let status = server
|
||||
.request::<request::CheckStatus>(request::CheckStatusParams {
|
||||
@@ -964,7 +984,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
let server_path = version_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
// Copilot LSP looks for this dist dir specifcially, so lets add it in.
|
||||
// Copilot LSP looks for this dist dir specifically, so lets add it in.
|
||||
let dist_dir = version_dir.join("dist");
|
||||
fs::create_dir_all(dist_dir.as_path()).await?;
|
||||
|
||||
@@ -1023,12 +1043,15 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use language::BufferId;
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_buffer_management(cx: &mut TestAppContext) {
|
||||
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Hello"));
|
||||
let buffer_1 = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "Hello")
|
||||
});
|
||||
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
@@ -1046,7 +1069,13 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
|
||||
let buffer_2 = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
"Goodbye",
|
||||
)
|
||||
});
|
||||
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
@@ -1222,6 +1251,10 @@ mod tests {
|
||||
fn worktree_id(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn is_private(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl language::LocalFile for File {
|
||||
@@ -1235,7 +1268,7 @@ mod tests {
|
||||
|
||||
fn buffer_reloaded(
|
||||
&self,
|
||||
_: u64,
|
||||
_: BufferId,
|
||||
_: &clock::Global,
|
||||
_: language::RopeFingerprint,
|
||||
_: language::LineEnding,
|
||||
|
||||
@@ -5,26 +5,25 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/copilot_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
copilot = { path = "../copilot" }
|
||||
editor = { path = "../editor" }
|
||||
fs = { path = "../fs" }
|
||||
zed_actions = { path = "../zed_actions"}
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
settings = { path = "../settings" }
|
||||
smol.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = {path = "../workspace" }
|
||||
anyhow.workspace = true
|
||||
smol.workspace = true
|
||||
futures.workspace = true
|
||||
workspace = { path = "../workspace" }
|
||||
zed_actions = { path = "../zed_actions" }
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -225,8 +225,9 @@ impl CopilotButton {
|
||||
let file = snapshot.file_at(suggestion_anchor).cloned();
|
||||
|
||||
self.editor_enabled = Some(
|
||||
all_language_settings(self.file.as_ref(), cx)
|
||||
.copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())),
|
||||
file.as_ref().map(|file| !file.is_private()).unwrap_or(true)
|
||||
&& all_language_settings(self.file.as_ref(), cx)
|
||||
.copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())),
|
||||
);
|
||||
self.language = language.cloned();
|
||||
self.file = file;
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/db.rs"
|
||||
doctest = false
|
||||
@@ -14,22 +13,23 @@ doctest = false
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
sqlez_macros = { path = "../sqlez_macros" }
|
||||
util = { path = "../util" }
|
||||
anyhow.workspace = true
|
||||
indoc.workspace = true
|
||||
async-trait.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
release_channel = { path = "../release_channel" }
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
smol.workspace = true
|
||||
sqlez = { path = "../sqlez" }
|
||||
sqlez_macros = { path = "../sqlez_macros" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
tempfile.workspace = true
|
||||
|
||||
@@ -10,16 +10,16 @@ pub use lazy_static;
|
||||
pub use smol;
|
||||
pub use sqlez;
|
||||
pub use sqlez_macros;
|
||||
pub use util::channel::{RELEASE_CHANNEL, RELEASE_CHANNEL_NAME};
|
||||
pub use util::paths::DB_DIR;
|
||||
|
||||
use release_channel::ReleaseChannel;
|
||||
pub use release_channel::RELEASE_CHANNEL;
|
||||
use sqlez::domain::Migrator;
|
||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
use sqlez_macros::sql;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use util::channel::ReleaseChannel;
|
||||
use util::{async_maybe, ResultExt};
|
||||
|
||||
const CONNECTION_INITIALIZE_QUERY: &'static str = sql!(
|
||||
@@ -223,7 +223,7 @@ mod tests {
|
||||
.prefix("DbTests")
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
let _bad_db = open_db::<BadDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
let _bad_db = open_db::<BadDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
}
|
||||
|
||||
/// Test that DB exists but corrupted (causing recreate)
|
||||
@@ -261,11 +261,12 @@ mod tests {
|
||||
.unwrap();
|
||||
{
|
||||
let corrupt_db =
|
||||
open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
assert!(corrupt_db.persistent());
|
||||
}
|
||||
|
||||
let good_db = open_db::<GoodDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
let good_db =
|
||||
open_db::<GoodDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
assert!(
|
||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
.unwrap()
|
||||
@@ -309,7 +310,7 @@ mod tests {
|
||||
{
|
||||
// Setup the bad database
|
||||
let corrupt_db =
|
||||
open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
|
||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
assert!(corrupt_db.persistent());
|
||||
}
|
||||
|
||||
@@ -320,7 +321,7 @@ mod tests {
|
||||
let guard = thread::spawn(move || {
|
||||
let good_db = smol::block_on(open_db::<GoodDB>(
|
||||
tmp_path.as_path(),
|
||||
&util::channel::ReleaseChannel::Dev,
|
||||
&release_channel::ReleaseChannel::Dev,
|
||||
));
|
||||
assert!(
|
||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
|
||||
@@ -5,41 +5,38 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/diagnostics.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
ui = { path = "../ui" }
|
||||
language = { path = "../language" }
|
||||
lsp = { path = "../lsp" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = {path = "../workspace" }
|
||||
|
||||
log.workspace = true
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
log.workspace = true
|
||||
lsp = { path = "../lsp" }
|
||||
postage.workspace = true
|
||||
project = { path = "../project" }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smallvec.workspace = true
|
||||
postage.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
workspace = {path = "../workspace", features = ["test-support"] }
|
||||
theme = { path = "../theme", features = ["test-support"] }
|
||||
|
||||
serde_json.workspace = true
|
||||
theme = { path = "../theme", features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/editor.rs"
|
||||
doctest = false
|
||||
@@ -25,71 +24,69 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
aho-corasick = "1.1"
|
||||
anyhow.workspace = true
|
||||
client = { path = "../client" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
convert_case = "0.6.0"
|
||||
copilot = { path = "../copilot" }
|
||||
db = { path = "../db" }
|
||||
collections = { path = "../collections" }
|
||||
# context_menu = { path = "../context_menu" }
|
||||
futures.workspace = true
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
git = { path = "../git" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
lsp = { path = "../lsp" }
|
||||
multi_buffer = { path = "../multi_buffer" }
|
||||
project = { path = "../project" }
|
||||
rpc = { path = "../rpc" }
|
||||
rich_text = { path = "../rich_text" }
|
||||
settings = { path = "../settings" }
|
||||
snippet = { path = "../snippet" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
text = { path = "../text" }
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
aho-corasick = "1.1"
|
||||
anyhow.workspace = true
|
||||
convert_case = "0.6.0"
|
||||
futures.workspace = true
|
||||
indoc = "1.0.4"
|
||||
itertools = "0.10"
|
||||
language = { path = "../language" }
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
lsp = { path = "../lsp" }
|
||||
multi_buffer = { path = "../multi_buffer" }
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
project = { path = "../project" }
|
||||
rand.workspace = true
|
||||
rich_text = { path = "../rich_text" }
|
||||
rpc = { path = "../rpc" }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
snippet = { path = "../snippet" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
text = { path = "../text" }
|
||||
theme = { path = "../theme" }
|
||||
tree-sitter-html = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
ui = { path = "../ui" }
|
||||
url.workspace = true
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
[dev-dependencies]
|
||||
copilot = { path = "../copilot", features = ["test-support"] }
|
||||
text = { path = "../text", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
|
||||
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
release_channel = { path = "../release_channel" }
|
||||
rand.workspace = true
|
||||
unindent.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
text = { path = "../text", features = ["test-support"] }
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
unindent.workspace = true
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
@@ -110,6 +110,7 @@ gpui::actions!(
|
||||
Copy,
|
||||
CopyHighlightJson,
|
||||
CopyPath,
|
||||
CopyPermalinkToLine,
|
||||
CopyRelativePath,
|
||||
Cut,
|
||||
CutToEndOfLine,
|
||||
|
||||
49
crates/editor/src/debounced_delay.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::{channel::oneshot, FutureExt};
|
||||
use gpui::{Task, ViewContext};
|
||||
|
||||
use crate::Editor;
|
||||
|
||||
pub struct DebouncedDelay {
|
||||
task: Option<Task<()>>,
|
||||
cancel_channel: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl DebouncedDelay {
|
||||
pub fn new() -> DebouncedDelay {
|
||||
DebouncedDelay {
|
||||
task: None,
|
||||
cancel_channel: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Editor>, func: F)
|
||||
where
|
||||
F: 'static + Send + FnOnce(&mut Editor, &mut ViewContext<Editor>) -> Task<()>,
|
||||
{
|
||||
if let Some(channel) = self.cancel_channel.take() {
|
||||
_ = channel.send(());
|
||||
}
|
||||
|
||||
let (sender, mut receiver) = oneshot::channel::<()>();
|
||||
self.cancel_channel = Some(sender);
|
||||
|
||||
let previous_task = self.task.take();
|
||||
self.task = Some(cx.spawn(move |model, mut cx| async move {
|
||||
let mut timer = cx.background_executor().timer(delay).fuse();
|
||||
if let Some(previous_task) = previous_task {
|
||||
previous_task.await;
|
||||
}
|
||||
|
||||
futures::select_biased! {
|
||||
_ = receiver => return,
|
||||
_ = timer => {}
|
||||
}
|
||||
|
||||
if let Ok(task) = model.update(&mut cx, |project, cx| (func)(project, cx)) {
|
||||
task.await;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -586,6 +586,8 @@ impl DisplaySnapshot {
|
||||
text_system,
|
||||
editor_style,
|
||||
rem_size,
|
||||
anchor: _,
|
||||
visible_rows: _,
|
||||
}: &TextLayoutDetails,
|
||||
) -> Arc<LineLayout> {
|
||||
let mut runs = Vec::new();
|
||||
@@ -1007,6 +1009,7 @@ pub mod tests {
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{env, sync::Arc};
|
||||
use text::BufferId;
|
||||
use theme::{LoadThemes, SyntaxTheme};
|
||||
use util::test::{marked_text_ranges, sample_text};
|
||||
use Bias::*;
|
||||
@@ -1467,7 +1470,8 @@ pub mod tests {
|
||||
cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
@@ -1553,7 +1557,8 @@ pub mod tests {
|
||||
cx.update(|cx| init_test(cx, |_| {}));
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
@@ -1620,7 +1625,8 @@ pub mod tests {
|
||||
let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ mod editor_settings;
|
||||
mod element;
|
||||
mod inlay_hint_cache;
|
||||
|
||||
mod debounced_delay;
|
||||
mod git;
|
||||
mod highlight_matching_bracket;
|
||||
mod hover_popover;
|
||||
@@ -45,6 +46,7 @@ use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||
use convert_case::{Case, Casing};
|
||||
use copilot::Copilot;
|
||||
use debounced_delay::DebouncedDelay;
|
||||
pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use editor_settings::EditorSettings;
|
||||
@@ -54,9 +56,9 @@ use futures::FutureExt;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use git::diff_hunk_to_display;
|
||||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, px, red, relative, rems, size, uniform_list, Action,
|
||||
AnyElement, AnyView, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem,
|
||||
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
|
||||
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
||||
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||
DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
|
||||
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton,
|
||||
ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakView,
|
||||
@@ -72,7 +74,7 @@ use language::{
|
||||
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
|
||||
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
|
||||
Language, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||
Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
|
||||
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
|
||||
@@ -85,7 +87,7 @@ pub use multi_buffer::{
|
||||
ToPoint,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use parking_lot::RwLock;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::*;
|
||||
@@ -104,12 +106,11 @@ use std::{
|
||||
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
sync::Weak,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use sum_tree::Bias;
|
||||
use sum_tree::TreeMap;
|
||||
use text::{OffsetUtf16, Rope};
|
||||
use text::{BufferId, OffsetUtf16, Rope};
|
||||
use theme::{
|
||||
observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
|
||||
ThemeColors, ThemeSettings,
|
||||
@@ -118,7 +119,7 @@ use ui::{
|
||||
h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover,
|
||||
Tooltip,
|
||||
};
|
||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
@@ -241,7 +242,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
.detach();
|
||||
|
||||
cx.on_action(move |_: &workspace::NewFile, cx| {
|
||||
let app_state = cx.global::<Weak<workspace::AppState>>();
|
||||
let app_state = workspace::AppState::global(cx);
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
workspace::open_new(&app_state, cx, |workspace, cx| {
|
||||
Editor::new_file(workspace, &Default::default(), cx)
|
||||
@@ -250,7 +251,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
});
|
||||
cx.on_action(move |_: &workspace::NewWindow, cx| {
|
||||
let app_state = cx.global::<Weak<workspace::AppState>>();
|
||||
let app_state = workspace::AppState::global(cx);
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
workspace::open_new(&app_state, cx, |workspace, cx| {
|
||||
Editor::new_file(workspace, &Default::default(), cx)
|
||||
@@ -384,6 +385,7 @@ pub struct Editor {
|
||||
mouse_context_menu: Option<MouseContextMenu>,
|
||||
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
|
||||
next_completion_id: CompletionId,
|
||||
completion_documentation_pre_resolve_debounce: DebouncedDelay,
|
||||
available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
|
||||
code_actions_task: Option<Task<()>>,
|
||||
document_highlights_task: Option<Task<()>>,
|
||||
@@ -411,7 +413,6 @@ pub struct Editor {
|
||||
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
|
||||
show_copilot_suggestions: bool,
|
||||
use_autoclose: bool,
|
||||
split_view: Option<AnyView>,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
@@ -703,6 +704,7 @@ struct CompletionsMenu {
|
||||
matches: Arc<[StringMatch]>,
|
||||
selected_item: usize,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_completion_documentation_resolve_debounce: Arc<Mutex<DebouncedDelay>>,
|
||||
}
|
||||
|
||||
impl CompletionsMenu {
|
||||
@@ -743,30 +745,31 @@ impl CompletionsMenu {
|
||||
}
|
||||
|
||||
fn pre_resolve_completion_documentation(
|
||||
&self,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
matches: Arc<[StringMatch]>,
|
||||
editor: &Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<Task<()>> {
|
||||
) -> Task<()> {
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
if !settings.show_completion_documentation {
|
||||
return None;
|
||||
return Task::ready(());
|
||||
}
|
||||
|
||||
let Some(provider) = editor.completion_provider.as_ref() else {
|
||||
return None;
|
||||
return Task::ready(());
|
||||
};
|
||||
|
||||
let resolve_task = provider.resolve_completions(
|
||||
self.matches.iter().map(|m| m.candidate_id).collect(),
|
||||
self.completions.clone(),
|
||||
matches.iter().map(|m| m.candidate_id).collect(),
|
||||
completions.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
return Some(cx.spawn(move |this, mut cx| async move {
|
||||
return cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
fn attempt_resolve_selected_completion_documentation(
|
||||
@@ -787,12 +790,20 @@ impl CompletionsMenu {
|
||||
let resolve_task = project.update(cx, |project, cx| {
|
||||
project.resolve_completions(vec![completion_index], self.completions.clone(), cx)
|
||||
});
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let delay_ms =
|
||||
EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
|
||||
let delay = Duration::from_millis(delay_ms);
|
||||
|
||||
self.selected_completion_documentation_resolve_debounce
|
||||
.lock()
|
||||
.fire_new(delay, cx, |_, cx| {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn visible(&self) -> bool {
|
||||
@@ -1290,19 +1301,37 @@ impl InlayHintRefreshReason {
|
||||
|
||||
impl Editor {
|
||||
pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
String::new(),
|
||||
)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
Self::new(EditorMode::SingleLine, buffer, None, cx)
|
||||
}
|
||||
|
||||
pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
String::new(),
|
||||
)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
Self::new(EditorMode::Full, buffer, None, cx)
|
||||
}
|
||||
|
||||
pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
String::new(),
|
||||
)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
|
||||
}
|
||||
@@ -1418,6 +1447,7 @@ impl Editor {
|
||||
mouse_context_menu: None,
|
||||
completion_tasks: Default::default(),
|
||||
next_completion_id: 0,
|
||||
completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
|
||||
next_inlay_id: 0,
|
||||
available_code_actions: Default::default(),
|
||||
code_actions_task: Default::default(),
|
||||
@@ -1465,19 +1495,6 @@ impl Editor {
|
||||
});
|
||||
}),
|
||||
],
|
||||
split_view: Some(
|
||||
cx.new_view(|_cx| {
|
||||
struct TestView;
|
||||
impl Render for TestView {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div().size_full().bg(red())
|
||||
}
|
||||
}
|
||||
|
||||
TestView {}
|
||||
})
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
|
||||
this._subscriptions.extend(project_subscriptions);
|
||||
@@ -2293,7 +2310,7 @@ impl Editor {
|
||||
let mut bracket_pair = None;
|
||||
let mut is_bracket_pair_start = false;
|
||||
if !text.is_empty() {
|
||||
// `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified)
|
||||
// `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
|
||||
// and they are removing the character that triggered IME popup.
|
||||
for (pair, enabled) in scope.brackets() {
|
||||
if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
|
||||
@@ -3048,6 +3065,8 @@ impl Editor {
|
||||
text_system: cx.text_system().clone(),
|
||||
editor_style: self.style.clone().unwrap(),
|
||||
rem_size: cx.rem_size(),
|
||||
anchor: self.scroll_manager.anchor().anchor,
|
||||
visible_rows: self.visible_line_count(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3138,7 +3157,7 @@ impl Editor {
|
||||
let task = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let completions = completions.await.log_err();
|
||||
let (menu, pre_resolve_task) = if let Some(completions) = completions {
|
||||
let menu = if let Some(completions) = completions {
|
||||
let mut menu = CompletionsMenu {
|
||||
id,
|
||||
initial_position: position,
|
||||
@@ -3158,23 +3177,40 @@ impl Editor {
|
||||
matches: Vec::new().into(),
|
||||
selected_item: 0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new(
|
||||
DebouncedDelay::new(),
|
||||
)),
|
||||
};
|
||||
menu.filter(query.as_deref(), cx.background_executor().clone())
|
||||
.await;
|
||||
|
||||
if menu.matches.is_empty() {
|
||||
(None, None)
|
||||
None
|
||||
} else {
|
||||
let pre_resolve_task = this
|
||||
.update(&mut cx, |editor, cx| {
|
||||
menu.pre_resolve_completion_documentation(editor, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
(Some(menu), pre_resolve_task)
|
||||
this.update(&mut cx, |editor, cx| {
|
||||
let completions = menu.completions.clone();
|
||||
let matches = menu.matches.clone();
|
||||
|
||||
let delay_ms = EditorSettings::get_global(cx)
|
||||
.completion_documentation_secondary_query_debounce;
|
||||
let delay = Duration::from_millis(delay_ms);
|
||||
|
||||
editor
|
||||
.completion_documentation_pre_resolve_debounce
|
||||
.fire_new(delay, cx, |editor, cx| {
|
||||
CompletionsMenu::pre_resolve_completion_documentation(
|
||||
completions,
|
||||
matches,
|
||||
editor,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
Some(menu)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
None
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
@@ -3210,10 +3246,6 @@ impl Editor {
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(pre_resolve_task) = pre_resolve_task {
|
||||
pre_resolve_task.await;
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
.log_err()
|
||||
@@ -7257,9 +7289,7 @@ impl Editor {
|
||||
editor.buffer.read(cx).as_singleton().and_then(|buffer| {
|
||||
project
|
||||
.language_server_for_buffer(buffer.read(cx), server_id, cx)
|
||||
.map(|(_, lsp_adapter)| {
|
||||
LanguageServerName(Arc::from(lsp_adapter.name()))
|
||||
})
|
||||
.map(|(lsp_adapter, _)| lsp_adapter.name.clone())
|
||||
});
|
||||
language_server_name.map(|language_server_name| {
|
||||
project.open_local_buffer_via_lsp(
|
||||
@@ -8210,6 +8240,43 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_permalink_to_line(&mut self, _: &CopyPermalinkToLine, cx: &mut ViewContext<Self>) {
|
||||
use git::permalink::{build_permalink, BuildPermalinkParams};
|
||||
|
||||
let permalink = maybe!({
|
||||
let project = self.project.clone()?;
|
||||
let project = project.read(cx);
|
||||
|
||||
let worktree = project.visible_worktrees(cx).next()?;
|
||||
|
||||
let mut cwd = worktree.read(cx).abs_path().to_path_buf();
|
||||
cwd.push(".git");
|
||||
|
||||
let repo = project.fs().open_repo(&cwd)?;
|
||||
let origin_url = repo.lock().remote_url("origin")?;
|
||||
let sha = repo.lock().head_sha()?;
|
||||
|
||||
let buffer = self.buffer().read(cx).as_singleton()?;
|
||||
let file = buffer.read(cx).file().and_then(|f| f.as_local())?;
|
||||
let path = file.path().to_str().map(|path| path.to_string())?;
|
||||
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
let selection = selections.iter().peekable().next();
|
||||
|
||||
build_permalink(BuildPermalinkParams {
|
||||
remote_url: &origin_url,
|
||||
sha: &sha,
|
||||
path: &path,
|
||||
selection: selection.map(|selection| selection.range()),
|
||||
})
|
||||
.log_err()
|
||||
});
|
||||
|
||||
if let Some(permalink) = permalink {
|
||||
cx.write_to_clipboard(ClipboardItem::new(permalink.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
|
||||
self.highlighted_rows = rows;
|
||||
}
|
||||
@@ -8424,6 +8491,31 @@ impl Editor {
|
||||
results
|
||||
}
|
||||
|
||||
/// Get the text ranges corresponding to the redaction query
|
||||
pub fn redacted_ranges(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Vec<Range<DisplayPoint>> {
|
||||
display_snapshot
|
||||
.buffer_snapshot
|
||||
.redacted_ranges(search_range, |file| {
|
||||
if let Some(file) = file {
|
||||
file.is_private()
|
||||
&& EditorSettings::get(Some((file.worktree_id(), file.path())), cx)
|
||||
.redact_private_values
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|range| {
|
||||
range.start.to_display_point(display_snapshot)
|
||||
..range.end.to_display_point(display_snapshot)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn highlight_text<T: 'static>(
|
||||
&mut self,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
@@ -9284,7 +9376,7 @@ impl Render for Editor {
|
||||
EditorMode::Full => cx.theme().colors().editor_background,
|
||||
};
|
||||
|
||||
let editor_element = EditorElement::new(
|
||||
EditorElement::new(
|
||||
cx.view(),
|
||||
EditorStyle {
|
||||
background,
|
||||
@@ -9303,23 +9395,7 @@ impl Render for Editor {
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(view) = &self.split_view {
|
||||
div()
|
||||
.size_full()
|
||||
.child(div().w(relative(0.5)).h_full().child(editor_element))
|
||||
.child(
|
||||
div()
|
||||
.w(relative(0.5))
|
||||
.h_full()
|
||||
.debug_below()
|
||||
.child(view.clone()),
|
||||
)
|
||||
.into_any()
|
||||
} else {
|
||||
editor_element.into_any()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ pub struct EditorSettings {
|
||||
pub hover_popover_enabled: bool,
|
||||
pub show_completions_on_input: bool,
|
||||
pub show_completion_documentation: bool,
|
||||
pub completion_documentation_secondary_query_debounce: u64,
|
||||
pub use_on_type_format: bool,
|
||||
pub scrollbar: Scrollbar,
|
||||
pub relative_line_numbers: bool,
|
||||
pub seed_search_query_from_cursor: SeedQuerySetting,
|
||||
pub redact_private_values: bool,
|
||||
}
|
||||
|
||||
/// When to populate a new search's query based on the text under the cursor.
|
||||
@@ -31,6 +33,7 @@ pub struct Scrollbar {
|
||||
pub show: ShowScrollbar,
|
||||
pub git_diff: bool,
|
||||
pub selections: bool,
|
||||
pub symbols_selections: bool,
|
||||
}
|
||||
|
||||
/// When to show the scrollbar in the editor.
|
||||
@@ -71,6 +74,11 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub show_completion_documentation: Option<bool>,
|
||||
/// The debounce delay before re-querying the language server for completion
|
||||
/// documentation when not included in original completion list.
|
||||
///
|
||||
/// Default: 300 ms
|
||||
pub completion_documentation_secondary_query_debounce: Option<u64>,
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
///
|
||||
@@ -86,6 +94,13 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: always
|
||||
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
|
||||
|
||||
/// Hide the values of variables in `private` files, as defined by the
|
||||
/// private_files setting. This only changes the visual representation,
|
||||
/// the values are still present in the file and can be selected / copied / pasted
|
||||
///
|
||||
/// Default: false
|
||||
pub redact_private_values: Option<bool>,
|
||||
}
|
||||
|
||||
/// Scrollbar related settings
|
||||
@@ -103,6 +118,10 @@ pub struct ScrollbarContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub selections: Option<bool>,
|
||||
/// Whether to show symbols highlighted markers in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub symbols_selections: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for EditorSettings {
|
||||
|
||||
@@ -39,7 +39,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
|
||||
let mut buffer =
|
||||
language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456");
|
||||
buffer.set_group_interval(Duration::from_secs(1));
|
||||
buffer
|
||||
});
|
||||
@@ -154,7 +155,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut now = Instant::now();
|
||||
let buffer = cx.new_model(|cx| language::Buffer::new(0, cx.entity_id().as_u64(), "123456"));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456")
|
||||
});
|
||||
let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
|
||||
@@ -225,7 +228,8 @@ fn test_ime_composition(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
|
||||
let mut buffer =
|
||||
language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcde");
|
||||
// Ensure automatic grouping doesn't occur.
|
||||
buffer.set_group_interval(Duration::ZERO);
|
||||
buffer
|
||||
@@ -629,7 +633,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
|
||||
|
||||
// Ensure we don't panic when navigation data contains invalid anchors *and* points.
|
||||
let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
|
||||
invalid_anchor.text_anchor.buffer_id = Some(999);
|
||||
invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
|
||||
let invalid_point = Point::new(9999, 0);
|
||||
editor.navigate(
|
||||
Box::new(NavigationData {
|
||||
@@ -2342,11 +2346,20 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
|
||||
));
|
||||
|
||||
let toml_buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n").with_language(toml_language, cx)
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
"a = 1\nb = 2\n",
|
||||
)
|
||||
.with_language(toml_language, cx)
|
||||
});
|
||||
let rust_buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, cx.entity_id().as_u64(), "const c: usize = 3;\n")
|
||||
.with_language(rust_language, cx)
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
"const c: usize = 3;\n",
|
||||
)
|
||||
.with_language(rust_language, cx)
|
||||
});
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
@@ -3984,8 +3997,10 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let buffer = cx
|
||||
.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
|
||||
|
||||
@@ -4149,8 +4164,10 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
let text = "fn a() {}";
|
||||
|
||||
let buffer = cx
|
||||
.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
|
||||
editor
|
||||
@@ -4713,8 +4730,10 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let buffer = cx
|
||||
.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
|
||||
view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
|
||||
@@ -4862,8 +4881,10 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let buffer = cx
|
||||
.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
|
||||
editor
|
||||
@@ -6095,7 +6116,13 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
sample_text(3, 4, 'a'),
|
||||
)
|
||||
});
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
@@ -6179,7 +6206,13 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
|
||||
primary: None,
|
||||
}
|
||||
});
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
initial_text,
|
||||
)
|
||||
});
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
|
||||
@@ -6237,7 +6270,13 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
|
||||
fn test_refresh_selections(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
sample_text(3, 4, 'a'),
|
||||
)
|
||||
});
|
||||
let mut excerpt1_id = None;
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
@@ -6322,7 +6361,13 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
|
||||
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
sample_text(3, 4, 'a'),
|
||||
)
|
||||
});
|
||||
let mut excerpt1_id = None;
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
@@ -6417,8 +6462,10 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
||||
"{{} }\n", //
|
||||
);
|
||||
|
||||
let buffer = cx
|
||||
.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx));
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
|
||||
.with_language(language, cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
|
||||
view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
|
||||
@@ -7179,7 +7226,7 @@ async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContex
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
_ = cx.update(|cx| cx.set_global(copilot));
|
||||
_ = cx.update(|cx| Copilot::set_global(copilot, cx));
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
@@ -7432,7 +7479,7 @@ async fn test_copilot_completion_invalidation(
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
_ = cx.update(|cx| cx.set_global(copilot));
|
||||
_ = cx.update(|cx| Copilot::set_global(copilot, cx));
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
@@ -7496,10 +7543,22 @@ async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::T
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
_ = cx.update(|cx| cx.set_global(copilot));
|
||||
_ = cx.update(|cx| Copilot::set_global(copilot, cx));
|
||||
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
|
||||
let buffer_1 = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
"a = 1\nb = 2\n",
|
||||
)
|
||||
});
|
||||
let buffer_2 = cx.new_model(|cx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
BufferId::new(cx.entity_id().as_u64()).unwrap(),
|
||||
"c = 3\nd = 4\n",
|
||||
)
|
||||
});
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
@@ -7601,7 +7660,7 @@ async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui
|
||||
});
|
||||
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
_ = cx.update(|cx| cx.set_global(copilot));
|
||||
_ = cx.update(|cx| Copilot::set_global(copilot, cx));
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
@@ -8333,6 +8392,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
|
||||
@@ -16,9 +16,10 @@ use crate::{
|
||||
},
|
||||
mouse_context_menu,
|
||||
scroll::scroll_amount::ScrollAmount,
|
||||
CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
|
||||
HalfPageDown, HalfPageUp, HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp,
|
||||
Point, SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, HalfPageDown, HalfPageUp, HoveredCursor, LineDown,
|
||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
@@ -276,6 +277,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::copy_path);
|
||||
register_action(view, cx, Editor::copy_relative_path);
|
||||
register_action(view, cx, Editor::copy_highlight_json);
|
||||
register_action(view, cx, Editor::copy_permalink_to_line);
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
if let Some(task) = editor.format(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
@@ -1151,7 +1153,9 @@ impl EditorElement {
|
||||
)
|
||||
}
|
||||
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.with_z_index(0, |cx| self.paint_redactions(text_bounds, &layout, cx));
|
||||
|
||||
cx.with_z_index(1, |cx| {
|
||||
for cursor in cursors {
|
||||
cursor.paint(content_origin, cx);
|
||||
}
|
||||
@@ -1160,6 +1164,32 @@ impl EditorElement {
|
||||
)
|
||||
}
|
||||
|
||||
fn paint_redactions(
|
||||
&mut self,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
layout: &LayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
let line_end_overshoot = layout.line_end_overshoot();
|
||||
|
||||
// A softer than perfect black
|
||||
let redaction_color = gpui::rgb(0x0e1111);
|
||||
|
||||
for range in layout.redacted_ranges.iter() {
|
||||
self.paint_highlighted_range(
|
||||
range.clone(),
|
||||
redaction_color.into(),
|
||||
Pixels::ZERO,
|
||||
line_end_overshoot,
|
||||
layout,
|
||||
content_origin,
|
||||
text_bounds,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_overlays(
|
||||
&mut self,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
@@ -1366,6 +1396,44 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
if layout.is_singleton && scrollbar_settings.symbols_selections {
|
||||
let selection_ranges = self.editor.read(cx).background_highlights_in_range(
|
||||
Anchor::min()..Anchor::max(),
|
||||
&layout.position_map.snapshot,
|
||||
cx.theme().colors(),
|
||||
);
|
||||
for hunk in selection_ranges {
|
||||
let start_display = Point::new(hunk.0.start.row(), 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let end_display = Point::new(hunk.0.end.row(), 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let start_y = y_for_row(start_display.row() as f32);
|
||||
let mut end_y = if hunk.0.start == hunk.0.end {
|
||||
y_for_row((end_display.row() + 1) as f32)
|
||||
} else {
|
||||
y_for_row((end_display.row()) as f32)
|
||||
};
|
||||
|
||||
if end_y - start_y < px(1.) {
|
||||
end_y = start_y + px(1.);
|
||||
}
|
||||
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
|
||||
|
||||
cx.paint_quad(quad(
|
||||
bounds,
|
||||
Corners::default(),
|
||||
cx.theme().status().info,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: px(1.),
|
||||
bottom: Pixels::ZERO,
|
||||
left: px(1.),
|
||||
},
|
||||
cx.theme().colors().scrollbar_thumb_border,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if layout.is_singleton && scrollbar_settings.git_diff {
|
||||
for hunk in layout
|
||||
.position_map
|
||||
@@ -1917,6 +1985,8 @@ impl EditorElement {
|
||||
cx.theme().colors(),
|
||||
);
|
||||
|
||||
let redacted_ranges = editor.redacted_ranges(start_anchor..end_anchor, &snapshot.display_snapshot, cx);
|
||||
|
||||
let mut newest_selection_head = None;
|
||||
|
||||
if editor.show_local_selections {
|
||||
@@ -2032,8 +2102,12 @@ impl EditorElement {
|
||||
||
|
||||
// Selections
|
||||
(is_singleton && scrollbar_settings.selections && editor.has_background_highlights::<BufferSearchHighlights>())
|
||||
||
|
||||
// Symbols Selections
|
||||
(is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
|
||||
||
|
||||
// Scrollmanager
|
||||
|| editor.scroll_manager.scrollbars_visible()
|
||||
editor.scroll_manager.scrollbars_visible()
|
||||
}
|
||||
ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
|
||||
ShowScrollbar::Always => true,
|
||||
@@ -2254,6 +2328,7 @@ impl EditorElement {
|
||||
active_rows,
|
||||
highlighted_rows,
|
||||
highlighted_ranges,
|
||||
redacted_ranges,
|
||||
line_numbers,
|
||||
display_hunks,
|
||||
blocks,
|
||||
@@ -3038,6 +3113,7 @@ pub struct LayoutState {
|
||||
display_hunks: Vec<DisplayDiffHunk>,
|
||||
blocks: Vec<BlockLayout>,
|
||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
redacted_ranges: Vec<Range<DisplayPoint>>,
|
||||
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
||||
scrollbar_row_range: Range<f32>,
|
||||
show_scrollbars: bool,
|
||||
@@ -3051,6 +3127,12 @@ pub struct LayoutState {
|
||||
space_invisible: ShapedLine,
|
||||
}
|
||||
|
||||
impl LayoutState {
|
||||
fn line_end_overshoot(&self) -> Pixels {
|
||||
0.15 * self.position_map.line_height
|
||||
}
|
||||
}
|
||||
|
||||
struct CodeActionsIndicator {
|
||||
row: u32,
|
||||
button: IconButton,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod permalink;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use git::diff::{DiffHunk, DiffHunkStatus};
|
||||
|
||||
390
crates/editor/src/git/permalink.rs
Normal file
@@ -0,0 +1,390 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use language::Point;
|
||||
use url::Url;
|
||||
|
||||
enum GitHostingProvider {
|
||||
Github,
|
||||
Gitlab,
|
||||
Gitee,
|
||||
}
|
||||
|
||||
impl GitHostingProvider {
|
||||
fn base_url(&self) -> Url {
|
||||
let base_url = match self {
|
||||
Self::Github => "https://github.com",
|
||||
Self::Gitlab => "https://gitlab.com",
|
||||
Self::Gitee => "https://gitee.com",
|
||||
};
|
||||
|
||||
Url::parse(&base_url).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the fragment portion of the URL for the selected lines in
|
||||
/// the representation the [`GitHostingProvider`] expects.
|
||||
fn line_fragment(&self, selection: &Range<Point>) -> String {
|
||||
if selection.start.row == selection.end.row {
|
||||
let line = selection.start.row + 1;
|
||||
|
||||
match self {
|
||||
Self::Github | Self::Gitlab | Self::Gitee => format!("L{}", line),
|
||||
}
|
||||
} else {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
|
||||
match self {
|
||||
Self::Github => format!("L{}-L{}", start_line, end_line),
|
||||
Self::Gitlab => format!("L{}-{}", start_line, end_line),
|
||||
Self::Gitee => format!("L{}-{}", start_line, end_line),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuildPermalinkParams<'a> {
|
||||
pub remote_url: &'a str,
|
||||
pub sha: &'a str,
|
||||
pub path: &'a str,
|
||||
pub selection: Option<Range<Point>>,
|
||||
}
|
||||
|
||||
pub fn build_permalink(params: BuildPermalinkParams) -> Result<Url> {
|
||||
let BuildPermalinkParams {
|
||||
remote_url,
|
||||
sha,
|
||||
path,
|
||||
selection,
|
||||
} = params;
|
||||
|
||||
let ParsedGitRemote {
|
||||
provider,
|
||||
owner,
|
||||
repo,
|
||||
} = parse_git_remote_url(remote_url)
|
||||
.ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
|
||||
|
||||
let path = match provider {
|
||||
GitHostingProvider::Github => format!("{owner}/{repo}/blob/{sha}/{path}"),
|
||||
GitHostingProvider::Gitlab => format!("{owner}/{repo}/-/blob/{sha}/{path}"),
|
||||
GitHostingProvider::Gitee => format!("{owner}/{repo}/blob/{sha}/{path}"),
|
||||
};
|
||||
let line_fragment = selection.map(|selection| provider.line_fragment(&selection));
|
||||
|
||||
let mut permalink = provider.base_url().join(&path).unwrap();
|
||||
permalink.set_fragment(line_fragment.as_deref());
|
||||
|
||||
Ok(permalink)
|
||||
}
|
||||
|
||||
struct ParsedGitRemote<'a> {
|
||||
pub provider: GitHostingProvider,
|
||||
pub owner: &'a str,
|
||||
pub repo: &'a str,
|
||||
}
|
||||
|
||||
fn parse_git_remote_url(url: &str) -> Option<ParsedGitRemote> {
|
||||
if url.starts_with("git@github.com:") || url.starts_with("https://github.com/") {
|
||||
let repo_with_owner = url
|
||||
.trim_start_matches("git@github.com:")
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_end_matches(".git");
|
||||
|
||||
let (owner, repo) = repo_with_owner.split_once("/")?;
|
||||
|
||||
return Some(ParsedGitRemote {
|
||||
provider: GitHostingProvider::Github,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
}
|
||||
|
||||
if url.starts_with("git@gitlab.com:") || url.starts_with("https://gitlab.com/") {
|
||||
let repo_with_owner = url
|
||||
.trim_start_matches("git@gitlab.com:")
|
||||
.trim_start_matches("https://gitlab.com/")
|
||||
.trim_end_matches(".git");
|
||||
|
||||
let (owner, repo) = repo_with_owner.split_once("/")?;
|
||||
|
||||
return Some(ParsedGitRemote {
|
||||
provider: GitHostingProvider::Gitlab,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
}
|
||||
|
||||
if url.starts_with("git@gitee.com:") || url.starts_with("https://gitee.com/") {
|
||||
let repo_with_owner = url
|
||||
.trim_start_matches("git@gitee.com:")
|
||||
.trim_start_matches("https://gitee.com/")
|
||||
.trim_end_matches(".git");
|
||||
|
||||
let (owner, repo) = repo_with_owner.split_once("/")?;
|
||||
|
||||
return Some(ParsedGitRemote {
|
||||
provider: GitHostingProvider::Gitee,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_build_github_permalink_from_ssh_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@github.com:zed-industries/zed.git",
|
||||
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://github.com/zed-industries/zed/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_github_permalink_from_ssh_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@github.com:zed-industries/zed.git",
|
||||
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://github.com/zed-industries/zed/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_github_permalink_from_ssh_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@github.com:zed-industries/zed.git",
|
||||
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://github.com/zed-industries/zed/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs#L24-L48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_github_permalink_from_https_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://github.com/zed-industries/zed.git",
|
||||
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://github.com/zed-industries/zed/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_github_permalink_from_https_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://github.com/zed-industries/zed.git",
|
||||
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://github.com/zed-industries/zed/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_github_permalink_from_https_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://github.com/zed-industries/zed.git",
|
||||
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://github.com/zed-industries/zed/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs#L24-L48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitlab_permalink_from_ssh_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@gitlab.com:zed-industries/zed.git",
|
||||
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitlab_permalink_from_ssh_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@gitlab.com:zed-industries/zed.git",
|
||||
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitlab_permalink_from_ssh_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@gitlab.com:zed-industries/zed.git",
|
||||
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitlab_permalink_from_https_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://gitlab.com/zed-industries/zed.git",
|
||||
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitlab_permalink_from_https_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://gitlab.com/zed-industries/zed.git",
|
||||
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitlab_permalink_from_https_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://gitlab.com/zed-industries/zed.git",
|
||||
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitee_permalink_from_ssh_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@gitee.com:libkitten/zed.git",
|
||||
sha: "e5fe811d7ad0fc26934edd76f891d20bdc3bb194",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitee_permalink_from_ssh_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@gitee.com:libkitten/zed.git",
|
||||
sha: "e5fe811d7ad0fc26934edd76f891d20bdc3bb194",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/editor/src/git/permalink.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitee_permalink_from_ssh_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@gitee.com:libkitten/zed.git",
|
||||
sha: "e5fe811d7ad0fc26934edd76f891d20bdc3bb194",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/editor/src/git/permalink.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitee_permalink_from_https_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://gitee.com/libkitten/zed.git",
|
||||
sha: "e5fe811d7ad0fc26934edd76f891d20bdc3bb194",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/zed/src/main.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitee_permalink_from_https_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://gitee.com/libkitten/zed.git",
|
||||
sha: "e5fe811d7ad0fc26934edd76f891d20bdc3bb194",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/zed/src/main.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_gitee_permalink_from_https_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://gitee.com/libkitten/zed.git",
|
||||
sha: "e5fe811d7ad0fc26934edd76f891d20bdc3bb194",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/zed/src/main.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
}
|
||||