Compare commits
266 Commits
stream-liv
...
single-cra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12c9198fcf | ||
|
|
10784e3f03 | ||
|
|
4dc8bef6de | ||
|
|
69da8bdea7 | ||
|
|
eaf02ef7bd | ||
|
|
b0efc40cb3 | ||
|
|
f6e642a891 | ||
|
|
7b22d555e6 | ||
|
|
923b2e1464 | ||
|
|
a7e8f4face | ||
|
|
74ae682563 | ||
|
|
e5fcee943c | ||
|
|
72c6f82b1a | ||
|
|
3256458494 | ||
|
|
9508f33d71 | ||
|
|
20a5165f5b | ||
|
|
fd7a487c3d | ||
|
|
f393499076 | ||
|
|
94c77be93e | ||
|
|
5e96ecc5bf | ||
|
|
226f945a11 | ||
|
|
228a82ce41 | ||
|
|
ede4a23fc9 | ||
|
|
8bea01faf5 | ||
|
|
f275145a4c | ||
|
|
99fddd8280 | ||
|
|
28921fd632 | ||
|
|
3da964de05 | ||
|
|
a619bc6f95 | ||
|
|
7b4800407e | ||
|
|
35e2831c6d | ||
|
|
8abacd5a50 | ||
|
|
4db0e59fb7 | ||
|
|
d76240d941 | ||
|
|
35aefc6580 | ||
|
|
149e5fde36 | ||
|
|
97b542b22a | ||
|
|
6152230152 | ||
|
|
a47759fd03 | ||
|
|
b5da1198f5 | ||
|
|
ba743a1bd9 | ||
|
|
9e7afe870a | ||
|
|
94d8ead270 | ||
|
|
93ab6ad922 | ||
|
|
45bbfe077a | ||
|
|
be8cc1146a | ||
|
|
a97ab5eb3d | ||
|
|
f4024cc602 | ||
|
|
1460249a70 | ||
|
|
da7670cd6f | ||
|
|
ebdc255b73 | ||
|
|
4d49a851ef | ||
|
|
0dbda71423 | ||
|
|
f3320998a8 | ||
|
|
2b7ee1e872 | ||
|
|
767a82527a | ||
|
|
ba8f027c18 | ||
|
|
0a28800049 | ||
|
|
31a6ee0229 | ||
|
|
1f974d074e | ||
|
|
72125949d9 | ||
|
|
d605d192af | ||
|
|
f92e6e9a95 | ||
|
|
ff4f67993b | ||
|
|
07821083df | ||
|
|
09c599385a | ||
|
|
01503511ad | ||
|
|
8bc5bcf0a6 | ||
|
|
983bb5c5fc | ||
|
|
653b2dc676 | ||
|
|
7142d3777f | ||
|
|
01e12c0d3c | ||
|
|
706c385c24 | ||
|
|
edb89d8d11 | ||
|
|
09675d43b3 | ||
|
|
187356ab9b | ||
|
|
435708b615 | ||
|
|
2fe9cd8faa | ||
|
|
8cc3ce1f17 | ||
|
|
6ad8c4a0f4 | ||
|
|
30a94fa59b | ||
|
|
36fe364c05 | ||
|
|
f6d4a73c34 | ||
|
|
7e7f25df6c | ||
|
|
176314bfd2 | ||
|
|
6606e6e37f | ||
|
|
c350321318 | ||
|
|
9da040da3f | ||
|
|
f6385221c5 | ||
|
|
999853fee0 | ||
|
|
f924c3ef00 | ||
|
|
2c4984091c | ||
|
|
453c41205b | ||
|
|
e85ab077be | ||
|
|
daa35e98f1 | ||
|
|
de70852497 | ||
|
|
454c9dc06d | ||
|
|
71aeb6a636 | ||
|
|
cdd2128311 | ||
|
|
20b60e8dd2 | ||
|
|
37366ac907 | ||
|
|
083f06322d | ||
|
|
16cbff9118 | ||
|
|
e62d60c84c | ||
|
|
4f62ebe4be | ||
|
|
b33ae888c0 | ||
|
|
029d08350e | ||
|
|
6d0aa72226 | ||
|
|
12afd1264e | ||
|
|
b526d69387 | ||
|
|
4f06f5b8fe | ||
|
|
555a219f11 | ||
|
|
74540231e5 | ||
|
|
5c0ecc09fb | ||
|
|
5b59ef3456 | ||
|
|
fda3e4c69a | ||
|
|
028c2a8249 | ||
|
|
a86c4deb78 | ||
|
|
e645aa9d20 | ||
|
|
216ea4ddc4 | ||
|
|
29c5ea0a50 | ||
|
|
b129e18396 | ||
|
|
f6fbf662b4 | ||
|
|
a409123342 | ||
|
|
b0b29d91f9 | ||
|
|
290c9113b7 | ||
|
|
36427e0a87 | ||
|
|
e16d5c3a68 | ||
|
|
608addf641 | ||
|
|
f22e56ff42 | ||
|
|
449e20de3d | ||
|
|
1aac35cc1c | ||
|
|
815385cc5f | ||
|
|
eca3424cb5 | ||
|
|
71b3633c1b | ||
|
|
21f778c6db | ||
|
|
1eb6fb0b5d | ||
|
|
484e5df2ee | ||
|
|
fef7df667c | ||
|
|
1c84fd1fef | ||
|
|
b6adab84a0 | ||
|
|
d3a49f6d8f | ||
|
|
c2cf4c45c0 | ||
|
|
bd03dea296 | ||
|
|
3f777f0c68 | ||
|
|
846aec701f | ||
|
|
cfce6a8fbf | ||
|
|
803e5d4c9f | ||
|
|
c10c35ffda | ||
|
|
38b1940251 | ||
|
|
50069a2153 | ||
|
|
b23835b5a5 | ||
|
|
e47b305ca7 | ||
|
|
7931342455 | ||
|
|
282f6241b7 | ||
|
|
73bbdd4456 | ||
|
|
236498408f | ||
|
|
86ff6e2c8e | ||
|
|
4bf6fb217e | ||
|
|
c527f2e212 | ||
|
|
47defa2849 | ||
|
|
6dfff1b46d | ||
|
|
66e06616db | ||
|
|
765626a007 | ||
|
|
27cdc6cb93 | ||
|
|
87ba5fd7bc | ||
|
|
7c72929f0b | ||
|
|
9e49894ed6 | ||
|
|
2364804f17 | ||
|
|
db11a3b554 | ||
|
|
d0ca49f0e1 | ||
|
|
d6fcd9853a | ||
|
|
bc3550d991 | ||
|
|
b8501199c2 | ||
|
|
7fb9549098 | ||
|
|
4097118070 | ||
|
|
a26c0a8537 | ||
|
|
f8bd6c66f4 | ||
|
|
02b1e3a3c1 | ||
|
|
83ad28dfe7 | ||
|
|
17b9d1976f | ||
|
|
3856599853 | ||
|
|
81dd4ca1c9 | ||
|
|
2965119201 | ||
|
|
258cf6c746 | ||
|
|
369de400be | ||
|
|
dc02894db4 | ||
|
|
966b18e142 | ||
|
|
2c7e71028d | ||
|
|
24dba07a9b | ||
|
|
16e9b4ceeb | ||
|
|
bc4bd2e168 | ||
|
|
4d3a18cbdc | ||
|
|
cfcbfc1d82 | ||
|
|
c9ec235b12 | ||
|
|
8196db6022 | ||
|
|
dc5fad52a3 | ||
|
|
77de20c23a | ||
|
|
e1cb8a66f0 | ||
|
|
7025d3f29d | ||
|
|
4bbde40267 | ||
|
|
15e7b67559 | ||
|
|
f0aeab7d00 | ||
|
|
4bbddcad31 | ||
|
|
1e944a51ff | ||
|
|
d90770c673 | ||
|
|
6316151a83 | ||
|
|
49a0a11c4f | ||
|
|
376a45528d | ||
|
|
20eeb78251 | ||
|
|
67be6ec3b5 | ||
|
|
070e5914c9 | ||
|
|
c41a8e33a0 | ||
|
|
25443a91ca | ||
|
|
8e00caf23b | ||
|
|
9a869f0c5f | ||
|
|
de2483e132 | ||
|
|
95259bf9fe | ||
|
|
3b76ba6d5a | ||
|
|
773a3b335e | ||
|
|
b5c38e9a09 | ||
|
|
273173ec8a | ||
|
|
4084ba36f9 | ||
|
|
770886880f | ||
|
|
ea44c510a3 | ||
|
|
c8f1969916 | ||
|
|
a960344301 | ||
|
|
af9e7f1f96 | ||
|
|
c04c439d23 | ||
|
|
d3cd8f8f14 | ||
|
|
cd8d776fe1 | ||
|
|
6de2330253 | ||
|
|
0e264b5a68 | ||
|
|
1af5304074 | ||
|
|
dde692eb88 | ||
|
|
cec72b837e | ||
|
|
95842c7987 | ||
|
|
183e3664cc | ||
|
|
08b124c8d4 | ||
|
|
ea08026cd0 | ||
|
|
daa9939c03 | ||
|
|
f757e5a6c3 | ||
|
|
ecb874db62 | ||
|
|
75f1862268 | ||
|
|
f8ab86f930 | ||
|
|
155854d9a9 | ||
|
|
5b6578247f | ||
|
|
b87c4a1e13 | ||
|
|
a0988508f0 | ||
|
|
a347c4def7 | ||
|
|
9c77bcc827 | ||
|
|
8d1f377bf0 | ||
|
|
f766f6ceae | ||
|
|
9dad897d49 | ||
|
|
5b6401519b | ||
|
|
293e080f03 | ||
|
|
633b665379 | ||
|
|
7fd334fddb | ||
|
|
10226a3992 | ||
|
|
383e868af0 | ||
|
|
40802d91d4 | ||
|
|
6d5784daa6 | ||
|
|
f80eb264fb | ||
|
|
3d956ca68b | ||
|
|
7ce131aaf8 | ||
|
|
60be47d115 |
7
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
7
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -15,6 +15,13 @@ body:
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -2,7 +2,7 @@ name: Bug Report
|
||||
description: |
|
||||
Use this template for **non-crash-related** bug reports.
|
||||
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
labels: ["admin read", "triage", "bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -21,7 +21,7 @@ body:
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -38,9 +38,12 @@ body:
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary><pre>
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
13
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Crash Report
|
||||
description: |
|
||||
Use this template for crash reports.
|
||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||
labels: ["admin read", "triage", "bug", "panic / crash"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -20,7 +20,7 @@ body:
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -31,9 +31,12 @@ body:
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary><pre>
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
|
||||
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.github/workflows/bump_patch_version.yml
vendored
2
.github/workflows/bump_patch_version.yml
vendored
@@ -43,6 +43,8 @@ jobs:
|
||||
esac
|
||||
which cargo-set-version > /dev/null || cargo install cargo-edit
|
||||
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
||||
export GIT_COMMITTER_NAME="Zed Bot"
|
||||
export GIT_COMMITTER_EMAIL="hi@zed.dev"
|
||||
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
||||
git tag v${output}${tag_suffix}
|
||||
git push origin HEAD v${output}${tag_suffix}
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "docs/**/*"
|
||||
- ".github/workflows/community_*"
|
||||
|
||||
concurrency:
|
||||
@@ -232,7 +232,7 @@ jobs:
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
@@ -260,7 +260,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p target/
|
||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
||||
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
|
||||
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
|
||||
@@ -24,7 +25,7 @@ jobs:
|
||||
# issues, preventing 365 days from working until then.
|
||||
days-before-stale: 180
|
||||
days-before-close: 7
|
||||
any-of-issue-labels: "defect,panic / crash"
|
||||
any-of-issue-labels: "bug,panic / crash"
|
||||
operations-per-run: 1000
|
||||
ascending: true
|
||||
enable-statistics: true
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
|
||||
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
|
||||
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
|
||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
|
||||
8
.github/workflows/deploy_cloudflare.yml
vendored
8
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,28 +37,28 @@ jobs:
|
||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||
|
||||
- name: Deploy Docs
|
||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy target/deploy --project-name=docs
|
||||
|
||||
- name: Deploy Install
|
||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||
|
||||
- name: Deploy Docs Workers
|
||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||
|
||||
- name: Deploy Install Workers
|
||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
2
.github/workflows/randomized_tests.yml
vendored
2
.github/workflows/randomized_tests.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.mailmap
2
.mailmap
@@ -60,6 +60,8 @@ Max Brunsfeld <maxbrunsfeld@gmail.com>
|
||||
Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
|
||||
Max Linke <maxlinke88@gmail.com>
|
||||
Max Linke <maxlinke88@gmail.com> <kain88-de@users.noreply.github.com>
|
||||
Michael Sloan <michael@zed.dev>
|
||||
Michael Sloan <michael@zed.dev> <mgsloan@google.com>
|
||||
Mikayla Maki <mikayla@zed.dev>
|
||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
||||
|
||||
1849
Cargo.lock
generated
1849
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
72
Cargo.toml
72
Cargo.toml
@@ -1,24 +1,17 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/anthropic",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_tool",
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
"crates/breadcrumbs",
|
||||
"crates/call",
|
||||
"crates/channel",
|
||||
"crates/cli",
|
||||
"crates/client",
|
||||
"crates/clock",
|
||||
"crates/collab",
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/context_servers",
|
||||
"crates/copilot",
|
||||
@@ -30,10 +23,9 @@ members = [
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
"crates/extension_cli",
|
||||
"crates/extensions_ui",
|
||||
"crates/extension_host",
|
||||
"crates/feature_flags",
|
||||
"crates/feedback",
|
||||
"crates/file_finder",
|
||||
"crates/file_icons",
|
||||
"crates/fs",
|
||||
"crates/fsevent",
|
||||
@@ -48,13 +40,9 @@ members = [
|
||||
"crates/http_client",
|
||||
"crates/image_viewer",
|
||||
"crates/indexed_docs",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_model",
|
||||
"crates/language_selector",
|
||||
"crates/language_tools",
|
||||
"crates/languages",
|
||||
"crates/live_kit_client",
|
||||
"crates/live_kit_server",
|
||||
@@ -69,35 +57,26 @@ members = [
|
||||
"crates/ollama",
|
||||
"crates/open_ai",
|
||||
"crates/outline",
|
||||
"crates/outline_panel",
|
||||
"crates/paths",
|
||||
"crates/picker",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/proto",
|
||||
"crates/quick_action_bar",
|
||||
"crates/recent_projects",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/release_channel",
|
||||
"crates/remote",
|
||||
"crates/remote_server",
|
||||
"crates/repl",
|
||||
"crates/reqwest_client",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/search",
|
||||
"crates/semantic_index",
|
||||
"crates/semantic_version",
|
||||
"crates/session",
|
||||
"crates/settings",
|
||||
"crates/settings_ui",
|
||||
"crates/snippet",
|
||||
"crates/snippet_provider",
|
||||
"crates/snippets_ui",
|
||||
"crates/sqlez",
|
||||
"crates/sqlez_macros",
|
||||
"crates/story",
|
||||
@@ -105,31 +84,24 @@ members = [
|
||||
"crates/sum_tree",
|
||||
"crates/supermaven",
|
||||
"crates/supermaven_api",
|
||||
"crates/tab_switcher",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/telemetry_events",
|
||||
"crates/terminal",
|
||||
"crates/terminal_view",
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/time_format",
|
||||
"crates/title_bar",
|
||||
"crates/toolchain_selector",
|
||||
"crates/ui",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
"crates/reqwest_client",
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
"crates/welcome",
|
||||
"crates/workspace",
|
||||
"crates/worktree",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
"crates/zed_common",
|
||||
|
||||
#
|
||||
# Extensions
|
||||
@@ -176,25 +148,18 @@ default-members = ["crates/zed"]
|
||||
# Workspace member crates
|
||||
#
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
ai = { path = "crates/ai" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_tool = { path = "crates/assistant_tool" }
|
||||
audio = { path = "crates/audio" }
|
||||
auto_update = { path = "crates/auto_update" }
|
||||
breadcrumbs = { path = "crates/breadcrumbs" }
|
||||
call = { path = "crates/call" }
|
||||
channel = { path = "crates/channel" }
|
||||
cli = { path = "crates/cli" }
|
||||
client = { path = "crates/client" }
|
||||
clock = { path = "crates/clock" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
context_servers = { path = "crates/context_servers" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
@@ -202,10 +167,9 @@ db = { path = "crates/db" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
extensions_ui = { path = "crates/extensions_ui" }
|
||||
extension_host = { path = "crates/extension_host" }
|
||||
feature_flags = { path = "crates/feature_flags" }
|
||||
feedback = { path = "crates/feedback" }
|
||||
file_finder = { path = "crates/file_finder" }
|
||||
file_icons = { path = "crates/file_icons" }
|
||||
fs = { path = "crates/fs" }
|
||||
fsevent = { path = "crates/fsevent" }
|
||||
@@ -220,13 +184,9 @@ html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http_client = { path = "crates/http_client" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
indexed_docs = { path = "crates/indexed_docs" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
language_tools = { path = "crates/language_tools" }
|
||||
languages = { path = "crates/languages" }
|
||||
live_kit_client = { path = "crates/live_kit_client" }
|
||||
live_kit_server = { path = "crates/live_kit_server" }
|
||||
@@ -241,36 +201,27 @@ notifications = { path = "crates/notifications" }
|
||||
ollama = { path = "crates/ollama" }
|
||||
open_ai = { path = "crates/open_ai" }
|
||||
outline = { path = "crates/outline" }
|
||||
outline_panel = { path = "crates/outline_panel" }
|
||||
paths = { path = "crates/paths" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
proto = { path = "crates/proto" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
refineable = { path = "crates/refineable" }
|
||||
release_channel = { path = "crates/release_channel" }
|
||||
remote = { path = "crates/remote" }
|
||||
remote_server = { path = "crates/remote_server" }
|
||||
repl = { path = "crates/repl" }
|
||||
reqwest_client = { path = "crates/reqwest_client" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
search = { path = "crates/search" }
|
||||
semantic_index = { path = "crates/semantic_index" }
|
||||
semantic_version = { path = "crates/semantic_version" }
|
||||
session = { path = "crates/session" }
|
||||
settings = { path = "crates/settings" }
|
||||
settings_ui = { path = "crates/settings_ui" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
snippet_provider = { path = "crates/snippet_provider" }
|
||||
snippets_ui = { path = "crates/snippets_ui" }
|
||||
sqlez = { path = "crates/sqlez" }
|
||||
sqlez_macros = { path = "crates/sqlez_macros" }
|
||||
story = { path = "crates/story" }
|
||||
@@ -278,30 +229,23 @@ storybook = { path = "crates/storybook" }
|
||||
sum_tree = { path = "crates/sum_tree" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
tab_switcher = { path = "crates/tab_switcher" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
title_bar = { path = "crates/title_bar" }
|
||||
toolchain_selector = { path = "crates/toolchain_selector" }
|
||||
ui = { path = "crates/ui" }
|
||||
ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
workspace = { path = "crates/workspace" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
zed = { path = "crates/zed" }
|
||||
zed_actions = { path = "crates/zed_actions" }
|
||||
zed_common = { path = "crates/zed_common" }
|
||||
|
||||
#
|
||||
# External crates
|
||||
@@ -337,6 +281,7 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
clickhouse = "0.11.6"
|
||||
cocoa = "0.26"
|
||||
cocoa-foundation = "0.2.0"
|
||||
convert_case = "0.6.0"
|
||||
core-foundation = "0.9.3"
|
||||
core-foundation-sys = "0.8.6"
|
||||
@@ -348,6 +293,7 @@ ec4rs = "1.1"
|
||||
emojis = "0.6.1"
|
||||
env_logger = "0.11"
|
||||
exec = "0.3.1"
|
||||
fancy-regex = "0.14.0"
|
||||
fork = "0.2.0"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
@@ -370,7 +316,7 @@ linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nbformat = "0.3.1"
|
||||
nbformat = "0.3.2"
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -379,6 +325,7 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
@@ -403,7 +350,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.16.0", default-features = false, features = [
|
||||
runtimelib = { version = "0.16.1", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
@@ -473,6 +420,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
|
||||
unicase = "2.6"
|
||||
unindent = "0.1.7"
|
||||
unicode-segmentation = "1.10"
|
||||
unicode-script = "0.5.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||
wasmparser = "0.215"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path fill-rule="evenodd" fill="black" d="M 3.828125 14.601562 C 3.894531 15.726562 5.183594 16.375 6.132812 15.785156 L 6.136719 15.785156 L 8.988281 13.824219 C 8.996094 13.816406 9.007812 13.8125 9.015625 13.804688 C 9.203125 13.675781 9.4375 13.636719 9.65625 13.691406 L 12.988281 14.550781 C 14.105469 14.839844 15.140625 13.769531 14.8125 12.667969 L 13.832031 9.386719 C 13.769531 9.167969 13.800781 8.9375 13.921875 8.75 C 13.921875 8.746094 13.925781 8.746094 13.925781 8.746094 L 15.777344 5.863281 L 15.777344 5.859375 C 15.78125 5.851562 15.785156 5.84375 15.789062 5.835938 L 15.792969 5.835938 C 16.382812 4.871094 15.6875 3.582031 14.542969 3.554688 L 11.109375 3.472656 C 10.878906 3.464844 10.664062 3.359375 10.519531 3.183594 L 8.339844 0.542969 C 8.019531 0.152344 7.550781 -0.015625 7.105469 0.0078125 L 7.101562 0.0078125 C 7.039062 0.0117188 6.976562 0.0195312 6.914062 0.0273438 C 6.414062 0.117188 5.945312 0.453125 5.75 1 L 4.609375 4.222656 C 4.535156 4.4375 4.367188 4.613281 4.152344 4.695312 L 0.957031 5.945312 C -0.121094 6.363281 -0.328125 7.835938 0.589844 8.535156 L 3.316406 10.609375 C 3.5 10.75 3.609375 10.960938 3.625 11.191406 Z M 7.515625 1.847656 C 7.421875 1.730469 7.296875 1.695312 7.183594 1.714844 C 7.066406 1.734375 6.960938 1.8125 6.914062 1.953125 L 5.867188 4.902344 C 5.699219 5.382812 5.328125 5.765625 4.851562 5.949219 L 1.925781 7.09375 C 1.785156 7.148438 1.710938 7.253906 1.695312 7.371094 C 1.679688 7.484375 1.71875 7.605469 1.839844 7.695312 L 4.335938 9.597656 C 4.742188 9.90625 4.992188 10.375 5.023438 10.882812 L 5.207031 14.003906 C 5.214844 14.152344 5.296875 14.253906 5.398438 14.304688 C 5.503906 14.355469 5.632812 14.355469 5.757812 14.269531 L 8.347656 12.492188 C 8.765625 12.207031 9.292969 12.113281 9.785156 12.242188 L 12.824219 13.027344 C 12.972656 13.066406 13.09375 13.023438 13.175781 12.9375 C 13.257812 12.855469 13.296875 12.734375 13.253906 12.589844 L 12.355469 9.589844 C 12.210938 9.105469 12.285156 8.578125 12.558594 8.148438 L 14.253906 5.511719 C 14.335938 5.386719 14.332031 5.257812 14.277344 5.15625 C 14.222656 5.054688 14.117188 4.980469 13.964844 4.976562 L 10.824219 4.902344 C 10.316406 4.886719 9.835938 4.65625 9.511719 4.261719 Z M 7.515625 1.847656 "/>
|
||||
<path fill="black" d="M 5.71875 7.257812 C 5.671875 7.25 5.628906 7.246094 5.582031 7.246094 C 5.09375 7.246094 4.695312 7.644531 4.695312 8.128906 C 4.695312 8.613281 5.09375 9.011719 5.582031 9.011719 C 6.070312 9.011719 6.46875 8.613281 6.46875 8.128906 C 6.46875 7.6875 6.140625 7.320312 5.71875 7.257812 Z M 5.71875 7.257812 "/>
|
||||
<path fill="black" d="M 11.019531 7.953125 C 10.976562 7.957031 10.929688 7.960938 10.886719 7.960938 C 10.398438 7.960938 10 7.5625 10 7.078125 C 10 6.59375 10.398438 6.195312 10.886719 6.195312 C 11.371094 6.195312 11.773438 6.59375 11.773438 7.078125 C 11.773438 7.519531 11.445312 7.886719 11.019531 7.953125 Z M 11.019531 7.953125 "/>
|
||||
<path fill="black" d="M 7.269531 9.089844 C 7.53125 8.988281 7.828125 9.113281 7.933594 9.375 C 8.125 9.859375 8.503906 9.996094 8.796875 9.949219 C 9.082031 9.898438 9.378906 9.664062 9.378906 9.136719 C 9.378906 8.855469 9.605469 8.628906 9.886719 8.628906 C 10.167969 8.628906 10.398438 8.855469 10.398438 9.136719 C 10.398438 10.140625 9.757812 10.816406 8.96875 10.949219 C 8.1875 11.078125 7.351562 10.664062 6.988281 9.75 C 6.882812 9.488281 7.011719 9.195312 7.269531 9.089844 Z M 7.269531 9.089844 "/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.3848 9.30444C7.3848 9.30444 7.53254 10.2646 8.53248 10.0882C9.53242 9.91193 9.36378 8.95549 9.36378 8.95549" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="#FF7676" stroke-opacity="0.52" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="6.25098" cy="7.75" r="0.75" fill="black"/>
|
||||
<circle cx="10.1035" cy="7.25" r="0.75" fill="black"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.6 KiB |
1
assets/icons/file_search.svg
Normal file
1
assets/icons/file_search.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-search"><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3"/><path d="m9 18-1.5-1.5"/><circle cx="5" cy="14" r="3"/></svg>
|
||||
|
After Width: | Height: | Size: 393 B |
5
assets/icons/refresh_title.svg
Normal file
5
assets/icons/refresh_title.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 21V12M7 12H3M7 12H11" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M21 19L16 19L16 14" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.99987 5.07027L7.49915 4.20467L7.99987 5.07027ZM6.04652 5.25026C5.63245 5.61573 5.59305 6.24766 5.95851 6.66173C6.32398 7.0758 6.95592 7.1152 7.36999 6.74974L6.04652 5.25026ZM11.9999 5C15.8659 5 18.9999 8.13401 18.9999 12H20.9999C20.9999 7.02944 16.9705 3 11.9999 3V5ZM18.9999 12C18.9999 14.2101 17.9768 16.1806 16.3744 17.4651L17.6254 19.0256C19.6809 17.3779 20.9999 14.8426 20.9999 12H18.9999ZM8.5006 5.93588C9.5292 5.34086 10.7232 5 11.9999 5V3C10.3623 3 8.82395 3.4383 7.49915 4.20467L8.5006 5.93588ZM7.36999 6.74974C7.71803 6.44255 8.09667 6.16954 8.5006 5.93588L7.49915 4.20467C6.9797 4.50515 6.49329 4.85593 6.04652 5.25026L7.36999 6.74974Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 979 B |
1
assets/icons/wand.svg
Normal file
1
assets/icons/wand.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wand"><path d="M15 4V2"/><path d="M15 16v-2"/><path d="M8 9h2"/><path d="M20 9h2"/><path d="M17.8 11.8 19 13"/><path d="M15 9h.01"/><path d="M17.8 6.2 19 5"/><path d="m3 21 9-9"/><path d="M12.2 6.2 11 5"/></svg>
|
||||
|
After Width: | Height: | Size: 414 B |
@@ -56,7 +56,8 @@
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
// "ctrl-t": "editor::Transpose",
|
||||
"alt-q": "editor::Rewrap",
|
||||
"ctrl-k ctrl-q": "editor::Rewrap",
|
||||
"ctrl-k q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"shift-delete": "editor::Cut",
|
||||
@@ -126,7 +127,8 @@
|
||||
"shift-enter": "editor::Newline",
|
||||
"ctrl-enter": "editor::NewlineAbove",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow",
|
||||
"alt-z": "editor::ToggleSoftWrap",
|
||||
"ctrl-k ctrl-z": "editor::ToggleSoftWrap",
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
@@ -140,7 +142,7 @@
|
||||
"bindings": {
|
||||
"alt-]": "editor::NextInlineCompletion",
|
||||
"alt-[": "editor::PreviousInlineCompletion",
|
||||
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
||||
"alt-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -169,7 +171,7 @@
|
||||
"ctrl-k c": "assistant::CopyCode",
|
||||
"ctrl-g": "search::SelectNextMatch",
|
||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
"ctrl-shift-m": "assistant::ToggleModelSelector",
|
||||
"ctrl-k h": "assistant::DeployHistory",
|
||||
"ctrl-k l": "assistant::DeployPromptLibrary",
|
||||
"ctrl-n": "assistant::NewContext"
|
||||
@@ -251,10 +253,10 @@
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"alt-ctrl-t": "pane::CloseInactiveItems",
|
||||
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-k u": "pane::CloseCleanItems",
|
||||
"ctrl-k w": "pane::CloseAllItems",
|
||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||
"ctrl-alt-g": "search::SelectNextMatch",
|
||||
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
||||
@@ -326,7 +328,6 @@
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"alt-ctrl-r": "editor::RevealInFileManager",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
@@ -417,6 +418,8 @@
|
||||
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-shift-x": "zed::Extensions",
|
||||
"ctrl-shift-r": "task::Rerun",
|
||||
"ctrl-alt-r": "task::Rerun",
|
||||
"alt-t": "task::Rerun",
|
||||
"alt-shift-t": "task::Spawn"
|
||||
}
|
||||
@@ -564,9 +567,11 @@
|
||||
"ctrl-alt-c": "outline_panel::CopyPath",
|
||||
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"space": "outline_panel::Open",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"ctrl-k enter": "editor::OpenExcerptsSplit"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -587,7 +592,7 @@
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"escape": "menu::Cancel"
|
||||
|
||||
@@ -51,14 +51,13 @@
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-t": "editor::Transpose",
|
||||
"alt-q": "editor::Rewrap",
|
||||
"cmd-k q": "editor::Rewrap",
|
||||
"cmd-k cmd-q": "editor::Rewrap",
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"alt-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-h": "editor::DeleteToPreviousWordStart",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"cmd-x": "editor::Cut",
|
||||
"cmd-c": "editor::Copy",
|
||||
"cmd-v": "editor::Paste",
|
||||
@@ -86,9 +85,7 @@
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-l": "editor::ScrollCursorCenter",
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-b": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
"alt-f": "editor::MoveToNextWordEnd",
|
||||
"cmd-left": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"cmd-right": "editor::MoveToEndOfLine",
|
||||
@@ -104,9 +101,7 @@
|
||||
"shift-right": "editor::SelectRight",
|
||||
"ctrl-shift-f": "editor::SelectRight",
|
||||
"alt-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
|
||||
"alt-shift-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
||||
"alt-shift-f": "editor::SelectToNextWordEnd",
|
||||
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||
"cmd-shift-up": "editor::SelectToBeginning",
|
||||
@@ -121,7 +116,7 @@
|
||||
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"ctrl-v": ["editor::MovePageDown", { "center_cursor": true }],
|
||||
"alt-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||
"ctrl-shift-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||
"ctrl-cmd-space": "editor::ShowCharacterPalette",
|
||||
"cmd-;": "editor::ToggleLineNumbers",
|
||||
"cmd-alt-z": "editor::RevertSelectedHunks",
|
||||
@@ -140,7 +135,7 @@
|
||||
"shift-enter": "editor::Newline",
|
||||
"cmd-enter": "editor::NewlineBelow",
|
||||
"cmd-shift-enter": "editor::NewlineAbove",
|
||||
"alt-z": "editor::ToggleSoftWrap",
|
||||
"cmd-k z": "editor::ToggleSoftWrap",
|
||||
"cmd-f": "buffer_search::Deploy",
|
||||
"cmd-alt-f": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
|
||||
@@ -155,7 +150,7 @@
|
||||
"bindings": {
|
||||
"alt-]": "editor::NextInlineCompletion",
|
||||
"alt-[": "editor::PreviousInlineCompletion",
|
||||
"cmd-right": "editor::AcceptPartialInlineCompletion"
|
||||
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -191,7 +186,7 @@
|
||||
"cmd-k c": "assistant::CopyCode",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
"cmd-shift-m": "assistant::ToggleModelSelector",
|
||||
"cmd-k h": "assistant::DeployHistory",
|
||||
"cmd-k l": "assistant::DeployPromptLibrary",
|
||||
"cmd-n": "assistant::NewContext"
|
||||
@@ -291,10 +286,10 @@
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
"alt-cmd-t": "pane::CloseInactiveItems",
|
||||
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"cmd-k u": "pane::CloseCleanItems",
|
||||
"cmd-k cmd-w": "pane::CloseAllItems",
|
||||
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
@@ -364,7 +359,6 @@
|
||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"cmd-.": "editor::ToggleCodeActions",
|
||||
"alt-cmd-r": "editor::RevealInFileManager",
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
"cmd-k p": "editor::CopyPath",
|
||||
"cmd-\\": "pane::SplitRight",
|
||||
@@ -457,7 +451,9 @@
|
||||
{
|
||||
"context": "Workspace && !Terminal",
|
||||
"bindings": {
|
||||
"alt-t": "task::Rerun",
|
||||
"cmd-shift-r": "task::Spawn",
|
||||
"cmd-alt-r": "task::Rerun",
|
||||
"alt-t": "task::Spawn",
|
||||
"alt-shift-t": "task::Spawn"
|
||||
}
|
||||
},
|
||||
@@ -577,9 +573,11 @@
|
||||
"cmd-alt-c": "outline_panel::CopyPath",
|
||||
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-cmd-r": "outline_panel::RevealInFileManager",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"space": "outline_panel::Open",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"cmd-k enter": "editor::OpenExcerptsSplit"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -604,7 +602,7 @@
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"cmd-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"escape": "menu::Cancel"
|
||||
|
||||
63
assets/keymaps/linux/emacs.json
Executable file
63
assets/keymaps/linux/emacs.json
Executable file
@@ -0,0 +1,63 @@
|
||||
// documentation: https://zed.dev/docs/key-bindings
|
||||
//
|
||||
// To see the default key bindings run `zed: open default keymap`
|
||||
// from the command palette.
|
||||
[
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-shift-g": "go_to_line::Toggle",
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-x u": "editor::Undo",
|
||||
"ctrl-x ctrl-u": "editor::Redo",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"alt-f": "editor::MoveToNextSubwordEnd",
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-w": "editor::Cut",
|
||||
"alt-w": "editor::Copy",
|
||||
"ctrl-y": "editor::Paste",
|
||||
"ctrl-_": "editor::Undo",
|
||||
"ctrl-v": "editor::MovePageDown",
|
||||
"alt-v": "editor::MovePageUp",
|
||||
"ctrl-x ]": "editor::MoveToEnd",
|
||||
"ctrl-x [": "editor::MoveToBeginning",
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
|
||||
"ctrl-s": "buffer_search::Deploy",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-shift-r": "editor::Rename"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"ctrl-x k": "pane::CloseActiveItem",
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow",
|
||||
"ctrl-x o": "workspace::ActivateNextPane",
|
||||
"ctrl-x b": "tab_switcher::Toggle",
|
||||
"ctrl-x 0": "pane::CloseActiveItem",
|
||||
"ctrl-x 1": "pane::CloseInactiveItems",
|
||||
"ctrl-x 2": "pane::SplitVertical",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-x ctrl-s": "workspace::Save",
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs",
|
||||
"ctrl-x s": "workspace::SaveAll",
|
||||
"shift shift": "file_finder::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-alt-left": "pane::GoBack",
|
||||
"ctrl-alt-right": "pane::GoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-alt-s": "zed::OpenSettings",
|
||||
"ctrl-shift-[": "pane::ActivatePrevItem",
|
||||
"ctrl-shift-]": "pane::ActivateNextItem"
|
||||
}
|
||||
@@ -25,8 +26,8 @@
|
||||
"alt-j": ["editor::SelectNext", { "replace_newest": false }],
|
||||
"alt-shift-j": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"ctrl-w": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-shift-w": "editor::SelectSmallerSyntaxNode",
|
||||
"shift-alt-up": "editor::MoveLineUp",
|
||||
"shift-alt-down": "editor::MoveLineDown",
|
||||
"ctrl-alt-l": "editor::Format",
|
||||
@@ -43,6 +44,7 @@
|
||||
"shift-f2": "editor::GoToPrevDiagnostic",
|
||||
"ctrl-alt-shift-down": "editor::GoToHunk",
|
||||
"ctrl-alt-shift-up": "editor::GoToPrevHunk",
|
||||
"ctrl-alt-z": "editor::RevertSelectedHunks",
|
||||
"ctrl-home": "editor::MoveToBeginning",
|
||||
"ctrl-end": "editor::MoveToEnd",
|
||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||
|
||||
63
assets/keymaps/macos/emacs.json
Executable file
63
assets/keymaps/macos/emacs.json
Executable file
@@ -0,0 +1,63 @@
|
||||
// documentation: https://zed.dev/docs/key-bindings
|
||||
//
|
||||
// To see the default key bindings run `zed: open default keymap`
|
||||
// from the command palette.
|
||||
[
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-shift-g": "go_to_line::Toggle",
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-x u": "editor::Undo",
|
||||
"ctrl-x ctrl-u": "editor::Redo",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"alt-f": "editor::MoveToNextSubwordEnd",
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-w": "editor::Cut",
|
||||
"alt-w": "editor::Copy",
|
||||
"ctrl-y": "editor::Paste",
|
||||
"ctrl-_": "editor::Undo",
|
||||
"ctrl-v": "editor::MovePageDown",
|
||||
"alt-v": "editor::MovePageUp",
|
||||
"ctrl-x ]": "editor::MoveToEnd",
|
||||
"ctrl-x [": "editor::MoveToBeginning",
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
|
||||
"ctrl-s": "buffer_search::Deploy",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-shift-r": "editor::Rename"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"ctrl-x k": "pane::CloseActiveItem",
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow",
|
||||
"ctrl-x o": "workspace::ActivateNextPane",
|
||||
"ctrl-x b": "tab_switcher::Toggle",
|
||||
"ctrl-x 0": "pane::CloseActiveItem",
|
||||
"ctrl-x 1": "pane::CloseInactiveItems",
|
||||
"ctrl-x 2": "pane::SplitVertical",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-x ctrl-s": "workspace::Save",
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs",
|
||||
"ctrl-x s": "workspace::SaveAll",
|
||||
"shift shift": "file_finder::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-alt-left": "pane::GoBack",
|
||||
"ctrl-alt-right": "pane::GoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -24,8 +24,8 @@
|
||||
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
|
||||
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"cmd-up": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-down": "editor::SelectSmallerSyntaxNode",
|
||||
"shift-alt-up": "editor::MoveLineUp",
|
||||
"shift-alt-down": "editor::MoveLineDown",
|
||||
"cmd-alt-l": "editor::Format",
|
||||
@@ -58,6 +58,12 @@
|
||||
"alt-enter": "editor::ToggleCodeActions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > Editor",
|
||||
"bindings": {
|
||||
"shift-enter": "search::SelectPrevMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[
|
||||
{
|
||||
"context": "VimControl && !menu",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
@@ -127,6 +128,9 @@
|
||||
"shift-h": "vim::WindowTop",
|
||||
"shift-m": "vim::WindowMiddle",
|
||||
"shift-l": "vim::WindowBottom",
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
// z commands
|
||||
"z enter": ["workspace::SendKeystrokes", "z t ^"],
|
||||
"z -": ["workspace::SendKeystrokes", "z b ^"],
|
||||
@@ -137,14 +141,14 @@
|
||||
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
||||
"z b": "editor::ScrollCursorBottom",
|
||||
"z a": "editor::ToggleFold",
|
||||
"z A": "editor::ToggleFoldRecursive",
|
||||
"z shift-a": "editor::ToggleFoldRecursive",
|
||||
"z c": "editor::Fold",
|
||||
"z C": "editor::FoldRecursive",
|
||||
"z shift-c": "editor::FoldRecursive",
|
||||
"z o": "editor::UnfoldLines",
|
||||
"z O": "editor::UnfoldRecursive",
|
||||
"z shift-o": "editor::UnfoldRecursive",
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"z M": "editor::FoldAll",
|
||||
"z R": "editor::UnfoldAll",
|
||||
"z shift-m": "editor::FoldAll",
|
||||
"z shift-r": "editor::UnfoldAll",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||
// Count support
|
||||
@@ -157,6 +161,374 @@
|
||||
"7": ["vim::Number", 7],
|
||||
"8": ["vim::Number", 8],
|
||||
"9": ["vim::Number", 9],
|
||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-6": "pane::AlternateFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": ["vim::PushOperator", "Change"],
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"x": "vim::DeleteRight",
|
||||
"shift-x": "vim::DeleteLeft",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"~": "vim::ChangeCase",
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"g q": ["vim::PushOperator", "Rewrap"],
|
||||
"g w": ["vim::PushOperator", "Rewrap"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
"] d": "editor::GoToDiagnostic",
|
||||
"[ d": "editor::GoToPrevDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPrevHunk",
|
||||
"g c": ["vim::PushOperator", "ToggleComments"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "VimControl && VimCount",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"0": ["vim::Number", 0],
|
||||
":": "vim::CountCommand"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == visual",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "vim::VisualCommand",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
"shift-u": "vim::ConvertToUpperCase",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
"shift-d": "vim::VisualDeleteLine",
|
||||
"shift-x": "vim::VisualDeleteLine",
|
||||
"y": "vim::VisualYank",
|
||||
"shift-y": "vim::VisualYank",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"shift-r": "vim::SubstituteLine",
|
||||
"c": "vim::Substitute",
|
||||
"~": "vim::ChangeCase",
|
||||
"*": ["vim::MoveToNext", { "partialWord": true }],
|
||||
"#": ["vim::MoveToPrev", { "partialWord": true }],
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"g ctrl-a": ["vim::Increment", { "step": true }],
|
||||
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
||||
"shift-i": "vim::InsertBefore",
|
||||
"shift-a": "vim::InsertAfter",
|
||||
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"ctrl-x": null,
|
||||
"ctrl-x ctrl-o": "editor::ShowCompletions",
|
||||
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
|
||||
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
|
||||
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
|
||||
"ctrl-x ctrl-z": "editor::Cancel",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||
"insert": "vim::ToggleReplace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"backspace": "vim::UndoReplace",
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"insert": "vim::InsertBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == waiting",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == operator",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"p": "vim::Paragraph",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
"\"": "vim::DoubleQuotes",
|
||||
"|": "vim::VerticalBars",
|
||||
"(": "vim::Parentheses",
|
||||
")": "vim::Parentheses",
|
||||
"b": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"r": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::AngleBrackets",
|
||||
"g": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == c",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
"o": "editor::ToggleHunkDiff", // "d o"
|
||||
"p": "editor::RevertSelectedHunks" // "d p"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gu",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g u": "vim::CurrentLine",
|
||||
"u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gU",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g shift-u": "vim::CurrentLine",
|
||||
"shift-u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == g~",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g ~": "vim::CurrentLine",
|
||||
"~": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gq",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g q": "vim::CurrentLine",
|
||||
"q": "vim::CurrentLine",
|
||||
"g w": "vim::CurrentLine",
|
||||
"w": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == y",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == ys",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"s": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == >",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
">": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == <",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"<": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == literal",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
|
||||
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
|
||||
"ctrl-b": ["vim::Literal", ["ctrl-b", "\u0002"]],
|
||||
"ctrl-c": ["vim::Literal", ["ctrl-c", "\u0003"]],
|
||||
"ctrl-d": ["vim::Literal", ["ctrl-d", "\u0004"]],
|
||||
"ctrl-e": ["vim::Literal", ["ctrl-e", "\u0005"]],
|
||||
"ctrl-f": ["vim::Literal", ["ctrl-f", "\u0006"]],
|
||||
"ctrl-g": ["vim::Literal", ["ctrl-g", "\u0007"]],
|
||||
"ctrl-h": ["vim::Literal", ["ctrl-h", "\u0008"]],
|
||||
"ctrl-i": ["vim::Literal", ["ctrl-i", "\u0009"]],
|
||||
"ctrl-j": ["vim::Literal", ["ctrl-j", "\u000A"]],
|
||||
"ctrl-k": ["vim::Literal", ["ctrl-k", "\u000B"]],
|
||||
"ctrl-l": ["vim::Literal", ["ctrl-l", "\u000C"]],
|
||||
"ctrl-m": ["vim::Literal", ["ctrl-m", "\u000D"]],
|
||||
"ctrl-n": ["vim::Literal", ["ctrl-n", "\u000E"]],
|
||||
"ctrl-o": ["vim::Literal", ["ctrl-o", "\u000F"]],
|
||||
"ctrl-p": ["vim::Literal", ["ctrl-p", "\u0010"]],
|
||||
"ctrl-q": ["vim::Literal", ["ctrl-q", "\u0011"]],
|
||||
"ctrl-r": ["vim::Literal", ["ctrl-r", "\u0012"]],
|
||||
"ctrl-s": ["vim::Literal", ["ctrl-s", "\u0013"]],
|
||||
"ctrl-t": ["vim::Literal", ["ctrl-t", "\u0014"]],
|
||||
"ctrl-u": ["vim::Literal", ["ctrl-u", "\u0015"]],
|
||||
"ctrl-v": ["vim::Literal", ["ctrl-v", "\u0016"]],
|
||||
"ctrl-w": ["vim::Literal", ["ctrl-w", "\u0017"]],
|
||||
"ctrl-x": ["vim::Literal", ["ctrl-x", "\u0018"]],
|
||||
"ctrl-y": ["vim::Literal", ["ctrl-y", "\u0019"]],
|
||||
"ctrl-z": ["vim::Literal", ["ctrl-z", "\u001A"]],
|
||||
"ctrl-[": ["vim::Literal", ["ctrl-[", "\u001B"]],
|
||||
"ctrl-\\": ["vim::Literal", ["ctrl-\\", "\u001C"]],
|
||||
"ctrl-]": ["vim::Literal", ["ctrl-]", "\u001D"]],
|
||||
"ctrl-^": ["vim::Literal", ["ctrl-^", "\u001E"]],
|
||||
"ctrl-_": ["vim::Literal", ["ctrl-_", "\u001F"]],
|
||||
"escape": ["vim::Literal", ["escape", "\u001B"]],
|
||||
"enter": ["vim::Literal", ["enter", "\u000D"]],
|
||||
"tab": ["vim::Literal", ["tab", "\u0009"]],
|
||||
// zed extensions:
|
||||
"backspace": ["vim::Literal", ["backspace", "\u0008"]],
|
||||
"delete": ["vim::Literal", ["delete", "\u007F"]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
@@ -201,299 +573,12 @@
|
||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-6": "pane::AlternateFile"
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": ["vim::PushOperator", "Change"],
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"x": "vim::DeleteRight",
|
||||
"shift-x": "vim::DeleteLeft",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"~": "vim::ChangeCase",
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"g q": ["vim::PushOperator", "Rewrap"],
|
||||
"g w": ["vim::PushOperator", "Rewrap"],
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
"] d": "editor::GoToDiagnostic",
|
||||
"[ d": "editor::GoToPrevDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPrevHunk",
|
||||
"g c": ["vim::PushOperator", "ToggleComments"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "VimControl && VimCount",
|
||||
"bindings": {
|
||||
"0": ["vim::Number", 0],
|
||||
":": "vim::CountCommand"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == visual",
|
||||
"bindings": {
|
||||
":": "vim::VisualCommand",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
"U": "vim::ConvertToUpperCase",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
"shift-d": "vim::VisualDeleteLine",
|
||||
"shift-x": "vim::VisualDeleteLine",
|
||||
"y": "vim::VisualYank",
|
||||
"shift-y": "vim::VisualYank",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"shift-r": "vim::SubstituteLine",
|
||||
"c": "vim::Substitute",
|
||||
"~": "vim::ChangeCase",
|
||||
"*": ["vim::MoveToNext", { "partialWord": true }],
|
||||
"#": ["vim::MoveToPrev", { "partialWord": true }],
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"g ctrl-a": ["vim::Increment", { "step": true }],
|
||||
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
||||
"shift-i": "vim::InsertBefore",
|
||||
"shift-a": "vim::InsertAfter",
|
||||
"g I": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g A": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"ctrl-x": null,
|
||||
"ctrl-x ctrl-o": "editor::ShowCompletions",
|
||||
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
|
||||
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
|
||||
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
|
||||
"ctrl-x ctrl-z": "editor::Cancel",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||
"insert": "vim::ToggleReplace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"backspace": "vim::UndoReplace",
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"insert": "vim::InsertBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == waiting",
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == operator",
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"p": "vim::Paragraph",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
"\"": "vim::DoubleQuotes",
|
||||
"|": "vim::VerticalBars",
|
||||
"(": "vim::Parentheses",
|
||||
")": "vim::Parentheses",
|
||||
"b": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == c",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
"o": "editor::ToggleHunkDiff", // "d o"
|
||||
"p": "editor::RevertSelectedHunks" // "d p"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gu",
|
||||
"bindings": {
|
||||
"g u": "vim::CurrentLine",
|
||||
"u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gU",
|
||||
"bindings": {
|
||||
"g shift-u": "vim::CurrentLine",
|
||||
"shift-u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == g~",
|
||||
"bindings": {
|
||||
"g ~": "vim::CurrentLine",
|
||||
"~": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gq",
|
||||
"bindings": {
|
||||
"g q": "vim::CurrentLine",
|
||||
"q": "vim::CurrentLine",
|
||||
"g w": "vim::CurrentLine",
|
||||
"w": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == y",
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == ys",
|
||||
"bindings": {
|
||||
"s": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == >",
|
||||
"bindings": {
|
||||
">": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == <",
|
||||
"bindings": {
|
||||
"<": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen",
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
@@ -502,6 +587,7 @@
|
||||
{
|
||||
// netrw compatibility
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"%": "project_panel::NewFile",
|
||||
@@ -529,6 +615,7 @@
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel && not_editing",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"j": "menu::SelectNext",
|
||||
"k": "menu::SelectPrev",
|
||||
|
||||
@@ -68,9 +68,17 @@
|
||||
"ui_font_size": 16,
|
||||
// How much to fade out unused code.
|
||||
"unnecessary_code_fade": 0.3,
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"active_pane_magnification": 1.0,
|
||||
// Active pane styling settings.
|
||||
"active_pane_modifiers": {
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"magnification": 1.0,
|
||||
// Inset border size of the active pane, in pixels.
|
||||
"border_size": 0.0,
|
||||
// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
|
||||
// Values are clamped to the [0.0, 1.0] range.
|
||||
"inactive_opacity": 1.0
|
||||
},
|
||||
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||
"pane_split_direction_horizontal": "up",
|
||||
// The direction that you want to split panes horizontally. Defaults to "left"
|
||||
@@ -152,7 +160,7 @@
|
||||
"show_signature_help_after_edits": true,
|
||||
// Whether to show wrap guides (vertical rulers) 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
|
||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||
// additional guides as specified by the 'wrap_guides' setting.
|
||||
"show_wrap_guides": true,
|
||||
// Character counts at which to show wrap guides in the editor.
|
||||
@@ -174,6 +182,8 @@
|
||||
// bracket, brace, single or double quote characters.
|
||||
// For example, when you select text and type (, Zed will surround the text with ().
|
||||
"use_auto_surround": true,
|
||||
// Whether indentation of pasted content should be adjusted based on the context.
|
||||
"auto_indent_on_paste": true,
|
||||
// Controls how the editor handles the autoclosed characters.
|
||||
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
||||
// happen only for auto-inserted characters.
|
||||
@@ -183,6 +193,9 @@
|
||||
// Controls whether inline completions are shown immediately (true)
|
||||
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||
"show_inline_completions": true,
|
||||
// Controls whether inline completions are shown in a given language scope.
|
||||
// Example: ["string", "comment"]
|
||||
"inline_completions_disabled_in": [],
|
||||
// Whether to show tabs and spaces in the editor.
|
||||
// This setting can take three values:
|
||||
//
|
||||
@@ -475,7 +488,7 @@
|
||||
"default_width": 640,
|
||||
// Default height when the assistant is docked to the bottom.
|
||||
"default_height": 320,
|
||||
// The default model to use when creating new contexts.
|
||||
// The default model to use when creating new chats.
|
||||
"default_model": {
|
||||
// The provider to use.
|
||||
"provider": "zed.dev",
|
||||
@@ -652,6 +665,12 @@
|
||||
// Sets a delay after which the inline blame information is shown.
|
||||
// Delay is restarted with every cursor movement.
|
||||
// "delay_ms": 600
|
||||
//
|
||||
// Whether or not do display the git commit summary on the same line.
|
||||
// "show_commit_summary": false
|
||||
//
|
||||
// The minimum column number to show the inline blame information at
|
||||
// "min_column": 0
|
||||
}
|
||||
},
|
||||
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
||||
@@ -814,7 +833,6 @@
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
"toolchain": { "name": "default", "path": "default" },
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
// use those languages.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"allow_concurrent_runs": false,
|
||||
// What to do with the terminal pane and tab, after the command was started:
|
||||
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
||||
// * `no_focus` — always show the terminal pane, add/reuse the task's tab there, but don't focus it
|
||||
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||
"reveal": "always",
|
||||
// What to do with the terminal pane and tab, after the command had finished:
|
||||
|
||||
@@ -27,15 +27,15 @@
|
||||
"ghost_element.active": "#454a56ff",
|
||||
"ghost_element.selected": "#454a56ff",
|
||||
"ghost_element.disabled": "#2e343eff",
|
||||
"text": "#c8ccd4ff",
|
||||
"text.muted": "#838994ff",
|
||||
"text.placeholder": "#696B77ff",
|
||||
"text.disabled": "#696B77ff",
|
||||
"text": "#dce0e5ff",
|
||||
"text.muted": "#a9afbcff",
|
||||
"text.placeholder": "#878a98ff",
|
||||
"text.disabled": "#878a98ff",
|
||||
"text.accent": "#74ade8ff",
|
||||
"icon": "#c8ccd4ff",
|
||||
"icon.muted": "#838994ff",
|
||||
"icon.disabled": "#696B77ff",
|
||||
"icon.placeholder": "#838994ff",
|
||||
"icon": "#dce0e5ff",
|
||||
"icon.muted": "#a9afbcff",
|
||||
"icon.disabled": "#878a98ff",
|
||||
"icon.placeholder": "#a9afbcff",
|
||||
"icon.accent": "#74ade8ff",
|
||||
"status_bar.background": "#3b414dff",
|
||||
"title_bar.background": "#3b414dff",
|
||||
@@ -60,19 +60,19 @@
|
||||
"editor.active_line.background": "#2f343ebf",
|
||||
"editor.highlighted_line.background": "#2f343eff",
|
||||
"editor.line_number": "#c8ccd459",
|
||||
"editor.active_line_number": "#c8ccd4ff",
|
||||
"editor.invisible": "#696B77ff",
|
||||
"editor.active_line_number": "#dce0e5ff",
|
||||
"editor.invisible": "#878a98ff",
|
||||
"editor.wrap_guide": "#c8ccd40d",
|
||||
"editor.active_wrap_guide": "#c8ccd41a",
|
||||
"editor.document_highlight.read_background": "#74ade81a",
|
||||
"editor.document_highlight.write_background": "#555a6366",
|
||||
"terminal.background": "#282c33ff",
|
||||
"terminal.foreground": "#c8ccd4ff",
|
||||
"terminal.bright_foreground": "#c8ccd4ff",
|
||||
"terminal.foreground": "#dce0e5ff",
|
||||
"terminal.bright_foreground": "#dce0e5ff",
|
||||
"terminal.dim_foreground": "#282c33ff",
|
||||
"terminal.ansi.black": "#282c33ff",
|
||||
"terminal.ansi.bright_black": "#525561ff",
|
||||
"terminal.ansi.dim_black": "#c8ccd4ff",
|
||||
"terminal.ansi.dim_black": "#dce0e5ff",
|
||||
"terminal.ansi.red": "#d07277ff",
|
||||
"terminal.ansi.bright_red": "#673a3cff",
|
||||
"terminal.ansi.dim_red": "#eab7b9ff",
|
||||
@@ -91,8 +91,8 @@
|
||||
"terminal.ansi.cyan": "#6eb4bfff",
|
||||
"terminal.ansi.bright_cyan": "#3a565bff",
|
||||
"terminal.ansi.dim_cyan": "#b9d9dfff",
|
||||
"terminal.ansi.white": "#c8ccd4ff",
|
||||
"terminal.ansi.bright_white": "#c8ccd4ff",
|
||||
"terminal.ansi.white": "#dce0e5ff",
|
||||
"terminal.ansi.bright_white": "#dce0e5ff",
|
||||
"terminal.ansi.dim_white": "#575d65ff",
|
||||
"link_text.hover": "#74ade8ff",
|
||||
"conflict": "#dec184ff",
|
||||
@@ -107,14 +107,14 @@
|
||||
"error": "#d07277ff",
|
||||
"error.background": "#d072771a",
|
||||
"error.border": "#4c2b2cff",
|
||||
"hidden": "#696B77ff",
|
||||
"hidden.background": "#696B771a",
|
||||
"hidden": "#878a98ff",
|
||||
"hidden.background": "#696b771a",
|
||||
"hidden.border": "#414754ff",
|
||||
"hint": "#5a6f89ff",
|
||||
"hint": "#788ca6ff",
|
||||
"hint.background": "#5a6f891a",
|
||||
"hint.border": "#293b5bff",
|
||||
"ignored": "#696B77ff",
|
||||
"ignored.background": "#696B771a",
|
||||
"ignored": "#878a98ff",
|
||||
"ignored.background": "#696b771a",
|
||||
"ignored.border": "#464b57ff",
|
||||
"info": "#74ade8ff",
|
||||
"info.background": "#74ade81a",
|
||||
@@ -131,7 +131,7 @@
|
||||
"success": "#a1c181ff",
|
||||
"success.background": "#a1c1811a",
|
||||
"success.border": "#38482fff",
|
||||
"unreachable": "#838994ff",
|
||||
"unreachable": "#a9afbcff",
|
||||
"unreachable.background": "#8389941a",
|
||||
"unreachable.border": "#464b57ff",
|
||||
"warning": "#dec184ff",
|
||||
@@ -211,7 +211,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#c8ccd4ff",
|
||||
"color": "#dce0e5ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -236,7 +236,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"hint": {
|
||||
"color": "#5a6f89ff",
|
||||
"color": "#788ca6ff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
},
|
||||
@@ -276,7 +276,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"preproc": {
|
||||
"color": "#c8ccd4ff",
|
||||
"color": "#dce0e5ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -361,7 +361,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#c8ccd4ff",
|
||||
"color": "#dce0e5ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -402,15 +402,15 @@
|
||||
"ghost_element.active": "#cacacaff",
|
||||
"ghost_element.selected": "#cacacaff",
|
||||
"ghost_element.disabled": "#ebebecff",
|
||||
"text": "#383a41ff",
|
||||
"text.muted": "#7e8087ff",
|
||||
"text.placeholder": "#a1a1a3ff",
|
||||
"text.disabled": "#a1a1a3ff",
|
||||
"text": "#242529ff",
|
||||
"text.muted": "#58585aff",
|
||||
"text.placeholder": "#7e8086ff",
|
||||
"text.disabled": "#7e8086ff",
|
||||
"text.accent": "#5c78e2ff",
|
||||
"icon": "#383a41ff",
|
||||
"icon.muted": "#7e8087ff",
|
||||
"icon.disabled": "#a1a1a3ff",
|
||||
"icon.placeholder": "#7e8087ff",
|
||||
"icon": "#242529ff",
|
||||
"icon.muted": "#58585aff",
|
||||
"icon.disabled": "#7e8086ff",
|
||||
"icon.placeholder": "#58585aff",
|
||||
"icon.accent": "#5c78e2ff",
|
||||
"status_bar.background": "#dcdcddff",
|
||||
"title_bar.background": "#dcdcddff",
|
||||
@@ -428,26 +428,26 @@
|
||||
"scrollbar.thumb.border": "#dfdfe0ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eeeeeeff",
|
||||
"editor.foreground": "#383a41ff",
|
||||
"editor.foreground": "#242529ff",
|
||||
"editor.background": "#fafafaff",
|
||||
"editor.gutter.background": "#fafafaff",
|
||||
"editor.subheader.background": "#ebebecff",
|
||||
"editor.active_line.background": "#ebebecbf",
|
||||
"editor.highlighted_line.background": "#ebebecff",
|
||||
"editor.line_number": "#383a4159",
|
||||
"editor.active_line_number": "#383a41ff",
|
||||
"editor.active_line_number": "#242529ff",
|
||||
"editor.invisible": "#a3a3a4ff",
|
||||
"editor.wrap_guide": "#383a410d",
|
||||
"editor.active_wrap_guide": "#383a411a",
|
||||
"editor.document_highlight.read_background": "#5c78e21a",
|
||||
"editor.document_highlight.write_background": "#a3a3a466",
|
||||
"terminal.background": "#fafafaff",
|
||||
"terminal.foreground": "#383a41ff",
|
||||
"terminal.bright_foreground": "#383a41ff",
|
||||
"terminal.foreground": "#242529ff",
|
||||
"terminal.bright_foreground": "#242529ff",
|
||||
"terminal.dim_foreground": "#fafafaff",
|
||||
"terminal.ansi.black": "#fafafaff",
|
||||
"terminal.ansi.bright_black": "#aaaaaaff",
|
||||
"terminal.ansi.dim_black": "#383a41ff",
|
||||
"terminal.ansi.dim_black": "#242529ff",
|
||||
"terminal.ansi.red": "#d36151ff",
|
||||
"terminal.ansi.bright_red": "#f0b0a4ff",
|
||||
"terminal.ansi.dim_red": "#6f312aff",
|
||||
@@ -466,11 +466,11 @@
|
||||
"terminal.ansi.cyan": "#3a82b7ff",
|
||||
"terminal.ansi.bright_cyan": "#a3bedaff",
|
||||
"terminal.ansi.dim_cyan": "#254058ff",
|
||||
"terminal.ansi.white": "#383a41ff",
|
||||
"terminal.ansi.bright_white": "#383a41ff",
|
||||
"terminal.ansi.white": "#242529ff",
|
||||
"terminal.ansi.bright_white": "#242529ff",
|
||||
"terminal.ansi.dim_white": "#97979aff",
|
||||
"link_text.hover": "#5c78e2ff",
|
||||
"conflict": "#dec184ff",
|
||||
"conflict": "#a48819ff",
|
||||
"conflict.background": "#faf2e6ff",
|
||||
"conflict.border": "#f4e7d1ff",
|
||||
"created": "#669f59ff",
|
||||
@@ -482,19 +482,19 @@
|
||||
"error": "#d36151ff",
|
||||
"error.background": "#fbdfd9ff",
|
||||
"error.border": "#f6c6bdff",
|
||||
"hidden": "#a1a1a3ff",
|
||||
"hidden": "#7e8086ff",
|
||||
"hidden.background": "#dcdcddff",
|
||||
"hidden.border": "#d3d3d4ff",
|
||||
"hint": "#9294beff",
|
||||
"hint": "#7274a7ff",
|
||||
"hint.background": "#e2e2faff",
|
||||
"hint.border": "#cbcdf6ff",
|
||||
"ignored": "#a1a1a3ff",
|
||||
"ignored": "#7e8086ff",
|
||||
"ignored.background": "#dcdcddff",
|
||||
"ignored.border": "#c9c9caff",
|
||||
"info": "#5c78e2ff",
|
||||
"info.background": "#e2e2faff",
|
||||
"info.border": "#cbcdf6ff",
|
||||
"modified": "#a47a23ff",
|
||||
"modified": "#a48819ff",
|
||||
"modified.background": "#faf2e6ff",
|
||||
"modified.border": "#f4e7d1ff",
|
||||
"predictive": "#9b9ec6ff",
|
||||
@@ -506,10 +506,10 @@
|
||||
"success": "#669f59ff",
|
||||
"success.background": "#dfeadbff",
|
||||
"success.border": "#c8dcc1ff",
|
||||
"unreachable": "#7e8087ff",
|
||||
"unreachable": "#58585aff",
|
||||
"unreachable.background": "#dcdcddff",
|
||||
"unreachable.border": "#c9c9caff",
|
||||
"warning": "#dec184ff",
|
||||
"warning": "#a48819ff",
|
||||
"warning.background": "#faf2e6ff",
|
||||
"warning.border": "#f4e7d1ff",
|
||||
"players": [
|
||||
@@ -544,7 +544,7 @@
|
||||
"selection": "#d361513d"
|
||||
},
|
||||
{
|
||||
"cursor": "#dec184ff",
|
||||
"cursor": "#a48819ff",
|
||||
"background": "#dec184ff",
|
||||
"selection": "#dec1843d"
|
||||
},
|
||||
@@ -586,7 +586,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#383a41ff",
|
||||
"color": "#242529ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -611,7 +611,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"hint": {
|
||||
"color": "#9294beff",
|
||||
"color": "#7274a7ff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
},
|
||||
@@ -651,12 +651,12 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"preproc": {
|
||||
"color": "#383a41ff",
|
||||
"color": "#242529ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"primary": {
|
||||
"color": "#383a41ff",
|
||||
"color": "#242529ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -666,7 +666,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation": {
|
||||
"color": "#383a41ff",
|
||||
"color": "#242529ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -736,7 +736,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#383a41ff",
|
||||
"color": "#242529ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -519,8 +519,8 @@
|
||||
"selection": "#d337813d"
|
||||
},
|
||||
{
|
||||
"cursor": "#cb4b17ff",
|
||||
"background": "#cb4b17ff",
|
||||
"cursor": "#cb4b16ff",
|
||||
"background": "#cb4b16ff",
|
||||
"selection": "#cb4b173d"
|
||||
},
|
||||
{
|
||||
@@ -596,7 +596,7 @@
|
||||
"font_weight": 700
|
||||
},
|
||||
"enum": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -621,7 +621,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -636,7 +636,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"operator": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -686,7 +686,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -696,17 +696,17 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -716,7 +716,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#cb4b17ff",
|
||||
"color": "#cb4b16ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "activity_indicator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/activity_indicator.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
smallvec.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
@@ -1,106 +0,0 @@
|
||||
[package]
|
||||
name = "assistant"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"editor/test-support",
|
||||
"language/test-support",
|
||||
"project/test-support",
|
||||
"text/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
assets.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
cargo_toml.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
context_servers.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
globset.workspace = true
|
||||
gpui.workspace = true
|
||||
handlebars.workspace = true
|
||||
heed.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
regex.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
semantic_index.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
similar.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
strum.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
log.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,29 +0,0 @@
|
||||
[package]
|
||||
name = "assistant_slash_command"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant_slash_command.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
name = "auto_update"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/auto_update.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
log.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
util.workspace = true
|
||||
which.workspace = true
|
||||
workspace.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "breadcrumbs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/breadcrumbs.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
outline.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -13,7 +13,6 @@ path = "src/call.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
no-webrtc = ["live_kit_client/no-webrtc"]
|
||||
test-support = [
|
||||
"client/test-support",
|
||||
"collections/test-support",
|
||||
|
||||
@@ -29,7 +29,7 @@ serde.workspace = true
|
||||
util.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
exec.workspace = true
|
||||
fork.workspace = true
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#![cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
|
||||
#![cfg_attr(
|
||||
any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
|
||||
allow(dead_code)
|
||||
)]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
@@ -88,7 +91,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Exit flatpak sandbox if needed
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
{
|
||||
flatpak::try_restart_to_host();
|
||||
flatpak::ld_extra_libs();
|
||||
@@ -106,7 +109,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
let args = Args::parse();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
let args = flatpak::set_bin_if_no_escape(args);
|
||||
|
||||
let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?;
|
||||
@@ -220,7 +223,7 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
mod linux {
|
||||
use std::{
|
||||
env,
|
||||
@@ -344,7 +347,7 @@ mod linux {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
mod flatpak {
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -44,7 +44,6 @@ sha2.workspace = true
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
@@ -889,7 +889,7 @@ impl Client {
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let executor = cx.background_executor();
|
||||
log::info!("add connection to peer");
|
||||
log::debug!("add connection to peer");
|
||||
let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
|
||||
let executor = executor.clone();
|
||||
move |duration| executor.timer(duration)
|
||||
@@ -897,12 +897,12 @@ impl Client {
|
||||
let handle_io = executor.spawn(handle_io);
|
||||
|
||||
let peer_id = async {
|
||||
log::info!("waiting for server hello");
|
||||
log::debug!("waiting for server hello");
|
||||
let message = incoming
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("no hello message received"))?;
|
||||
log::info!("got server hello");
|
||||
log::debug!("got server hello");
|
||||
let hello_message_type_name = message.payload_type_name().to_string();
|
||||
let hello = message
|
||||
.into_any()
|
||||
@@ -928,7 +928,7 @@ impl Client {
|
||||
}
|
||||
};
|
||||
|
||||
log::info!(
|
||||
log::debug!(
|
||||
"set status to connected (connection id: {:?}, peer id: {:?})",
|
||||
connection_id,
|
||||
peer_id
|
||||
|
||||
@@ -13,6 +13,7 @@ use parking_lot::Mutex;
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
@@ -21,10 +22,7 @@ use telemetry_events::{
|
||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
|
||||
SettingEvent,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::TryFutureExt;
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
|
||||
use self::event_coalescer::EventCoalescer;
|
||||
@@ -46,7 +44,7 @@ struct TelemetryState {
|
||||
architecture: &'static str,
|
||||
events_queue: Vec<EventWrapper>,
|
||||
flush_events_task: Option<Task<()>>,
|
||||
log_file: Option<NamedTempFile>,
|
||||
log_file: Option<File>,
|
||||
is_staff: Option<bool>,
|
||||
first_event_date_time: Option<DateTime<Utc>>,
|
||||
event_coalescer: EventCoalescer,
|
||||
@@ -102,7 +100,7 @@ pub fn os_name() -> String {
|
||||
{
|
||||
"macOS".to_string()
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
{
|
||||
format!("Linux {}", gpui::guess_compositor())
|
||||
}
|
||||
@@ -131,7 +129,7 @@ pub fn os_version() -> String {
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
{
|
||||
use std::path::Path;
|
||||
|
||||
@@ -223,15 +221,13 @@ impl Telemetry {
|
||||
os_name: os_name(),
|
||||
app_version: release_channel::AppVersion::global(cx).to_string(),
|
||||
}));
|
||||
Self::log_file_path();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
if let Some(tempfile) =
|
||||
NamedTempFile::new_in(paths::logs_dir().as_path()).log_err()
|
||||
{
|
||||
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
|
||||
state.lock().log_file = Some(tempfile);
|
||||
}
|
||||
}
|
||||
@@ -280,8 +276,8 @@ impl Telemetry {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
pub fn log_file_path(&self) -> Option<PathBuf> {
|
||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||
pub fn log_file_path() -> PathBuf {
|
||||
paths::logs_dir().join("telemetry.log")
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
@@ -341,6 +337,13 @@ impl Telemetry {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn metrics_enabled(self: &Arc<Self>) -> bool {
|
||||
let state = self.state.lock();
|
||||
let enabled = state.settings.metrics;
|
||||
drop(state);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
pub fn set_authenticated_user_info(
|
||||
self: &Arc<Self>,
|
||||
metrics_id: Option<String>,
|
||||
@@ -638,7 +641,6 @@ impl Telemetry {
|
||||
let mut json_bytes = Vec::new();
|
||||
|
||||
if let Some(file) = &mut this.state.lock().log_file {
|
||||
let file = file.as_file_mut();
|
||||
for event in &mut events {
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, event)?;
|
||||
|
||||
@@ -77,18 +77,15 @@ util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant = { workspace = true, features = ["test-support"] }
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
channel.workspace = true
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
collab_ui = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
git = { workspace = true, features = ["test-support"] }
|
||||
git_hosting_providers.workspace = true
|
||||
@@ -96,7 +93,6 @@ gpui = { workspace = true, features = ["test-support"] }
|
||||
hyper.workspace = true
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
live_kit_client = { workspace = true, features = ["test-support"] }
|
||||
lsp = { workspace = true, features = ["test-support"] }
|
||||
menu.workspace = true
|
||||
@@ -105,7 +101,6 @@ node_runtime.workspace = true
|
||||
notifications = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
recent_projects = { workspace = true }
|
||||
release_channel.workspace = true
|
||||
remote = { workspace = true, features = ["test-support"] }
|
||||
remote_server.workspace = true
|
||||
@@ -120,6 +115,7 @@ unindent.workspace = true
|
||||
util.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
worktree = { workspace = true, features = ["test-support"] }
|
||||
zed_common = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["async-stripe"]
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::{
|
||||
use call::ActiveCall;
|
||||
use channel::ACKNOWLEDGE_DEBOUNCE_INTERVAL;
|
||||
use client::{Collaborator, ParticipantIndex, UserId};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::HashMap;
|
||||
use editor::{Anchor, Editor, ToOffset};
|
||||
use futures::future;
|
||||
@@ -13,6 +12,7 @@ use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
|
||||
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
|
||||
use serde_json::json;
|
||||
use std::ops::Range;
|
||||
use zed_common::collab_ui::channel_view::ChannelView;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_core_channel_buffers(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
use channel::{ChannelChat, ChannelMessageId, MessageParams};
|
||||
use collab_ui::chat_panel::ChatPanel;
|
||||
use gpui::{BackgroundExecutor, Model, TestAppContext};
|
||||
use rpc::Notification;
|
||||
use workspace::dock::Panel;
|
||||
use zed_common::collab_ui::chat_panel::ChatPanel;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_basic_channel_messages(
|
||||
@@ -351,7 +351,7 @@ async fn test_channel_message_changes(
|
||||
|
||||
// Opening the chat should clear the changed flag.
|
||||
cx_b.update(|cx| {
|
||||
collab_ui::init(&client_b.app_state, cx);
|
||||
zed_common::collab_ui::init(&client_b.app_state, cx);
|
||||
});
|
||||
let project_b = client_b.build_empty_local_project(cx_b);
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
|
||||
@@ -25,7 +25,6 @@ use project::{
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
};
|
||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@@ -39,6 +38,7 @@ use std::{
|
||||
};
|
||||
use text::Point;
|
||||
use workspace::{CloseIntent, Workspace};
|
||||
use zed_common::recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_host_disconnect(
|
||||
@@ -55,7 +55,7 @@ async fn test_host_disconnect(
|
||||
.await;
|
||||
|
||||
cx_b.update(editor::init);
|
||||
cx_b.update(recent_projects::init);
|
||||
cx_b.update(zed_common::recent_projects::init);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use client::ChannelId;
|
||||
use collab_ui::{
|
||||
channel_view::ChannelView,
|
||||
notifications::project_shared_notification::ProjectSharedNotification,
|
||||
};
|
||||
use editor::{Editor, ExcerptRange, MultiBuffer};
|
||||
use gpui::{
|
||||
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
|
||||
@@ -23,6 +19,10 @@ use workspace::{
|
||||
shared_screen::SharedScreen,
|
||||
SplitDirection, Workspace,
|
||||
};
|
||||
use zed_common::collab_ui::{
|
||||
channel_view::ChannelView,
|
||||
notifications::project_shared_notification::ProjectSharedNotification,
|
||||
};
|
||||
|
||||
use super::TestClient;
|
||||
|
||||
@@ -1861,9 +1861,9 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
cx_c.update(editor::init);
|
||||
cx_a.update(collab_ui::channel_view::init);
|
||||
cx_b.update(collab_ui::channel_view::init);
|
||||
cx_c.update(collab_ui::channel_view::init);
|
||||
cx_a.update(zed_common::collab_ui::channel_view::init);
|
||||
cx_b.update(zed_common::collab_ui::channel_view::init);
|
||||
cx_c.update(zed_common::collab_ui::channel_view::init);
|
||||
|
||||
let channel_1_id = server
|
||||
.make_channel(
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant::{ContextStore, PromptBuilder};
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -48,6 +47,7 @@ use std::{
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use workspace::Pane;
|
||||
use zed_common::assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet, ToolWorkingSet};
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
@@ -5130,11 +5130,10 @@ async fn test_lsp_hover(
|
||||
});
|
||||
let new_server_name = new_server.server.name();
|
||||
assert!(
|
||||
!servers_with_hover_requests.contains_key(new_server_name),
|
||||
!servers_with_hover_requests.contains_key(&new_server_name),
|
||||
"Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
|
||||
);
|
||||
let new_server_name = new_server_name.to_string();
|
||||
match new_server_name.as_str() {
|
||||
match new_server_name.as_ref() {
|
||||
"CrabLang-ls" => {
|
||||
servers_with_hover_requests.insert(
|
||||
new_server_name.clone(),
|
||||
@@ -6489,15 +6488,31 @@ async fn test_context_collaboration_with_reconnect(
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context_store_a = cx_a
|
||||
.update(|cx| ContextStore::new(project_a.clone(), prompt_builder.clone(), cx))
|
||||
.update(|cx| {
|
||||
ContextStore::new(
|
||||
project_a.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let context_store_b = cx_b
|
||||
.update(|cx| ContextStore::new(project_b.clone(), prompt_builder.clone(), cx))
|
||||
.update(|cx| {
|
||||
ContextStore::new(
|
||||
project_b.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A creates a new context.
|
||||
// Client A creates a new chats.
|
||||
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use call::ActiveCall;
|
||||
use collections::HashSet;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{BackgroundExecutor, Context as _, TestAppContext, UpdateGlobal as _};
|
||||
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _};
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{
|
||||
language_settings::{
|
||||
@@ -31,6 +31,12 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
server_cx: &mut TestAppContext,
|
||||
) {
|
||||
let executor = cx_a.executor();
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -199,6 +205,13 @@ async fn test_ssh_collaboration_git_branches(
|
||||
cx_b.set_name("b");
|
||||
server_cx.set_name("server");
|
||||
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -329,6 +342,13 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
cx_b.set_name("b");
|
||||
server_cx.set_name("server");
|
||||
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
|
||||
@@ -12,7 +12,6 @@ use client::{
|
||||
UserStore,
|
||||
};
|
||||
use clock::FakeSystemClock;
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{channel::oneshot, StreamExt as _};
|
||||
@@ -44,6 +43,7 @@ use std::{
|
||||
},
|
||||
};
|
||||
use workspace::{Workspace, WorkspaceStore};
|
||||
use zed_common::collab_ui::channel_view::ChannelView;
|
||||
|
||||
pub struct TestServer {
|
||||
pub app_state: Arc<AppState>,
|
||||
@@ -293,12 +293,12 @@ impl TestServer {
|
||||
call::init(client.clone(), user_store.clone(), cx);
|
||||
channel::init(&client, user_store.clone(), cx);
|
||||
notifications::init(client.clone(), user_store, cx);
|
||||
collab_ui::init(&app_state, cx);
|
||||
file_finder::init(cx);
|
||||
zed_common::collab_ui::init(&app_state, cx);
|
||||
zed_common::file_finder::init(cx);
|
||||
menu::init();
|
||||
settings::KeymapFile::load_asset(os_keymap, cx).unwrap();
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
assistant::context_store::init(&client.clone().into());
|
||||
zed_common::language_model::LanguageModelRegistry::test(cx);
|
||||
zed_common::assistant::context_store::init(&client.clone().into());
|
||||
});
|
||||
|
||||
client
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
[package]
|
||||
name = "collab_ui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/collab_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
stories = ["dep:story"]
|
||||
test-support = [
|
||||
"call/test-support",
|
||||
"client/test-support",
|
||||
"collections/test-support",
|
||||
"editor/test-support",
|
||||
"gpui/test-support",
|
||||
"project/test-support",
|
||||
"settings/test-support",
|
||||
"util/test-support",
|
||||
"workspace/test-support",
|
||||
"http_client/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
call.workspace = true
|
||||
channel.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
emojis.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
menu.workspace = true
|
||||
notifications.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
rich_text.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
time_format.workspace = true
|
||||
time.workspace = true
|
||||
title_bar.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vcs_menu.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
notifications = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-md.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,41 +0,0 @@
|
||||
[package]
|
||||
name = "command_palette"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/command_palette.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
go_to_line.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
menu.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -13,6 +13,7 @@ path = "src/context_servers.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -27,4 +28,3 @@ settings.workspace = true
|
||||
smol.workspace = true
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -55,11 +55,20 @@ pub struct Client {
|
||||
#[repr(transparent)]
|
||||
pub struct ContextServerId(pub String);
|
||||
|
||||
fn is_null_value<T: Serialize>(value: &T) -> bool {
|
||||
if let Ok(Value::Null) = serde_json::to_value(value) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Request<'a, T> {
|
||||
jsonrpc: &'static str,
|
||||
id: RequestId,
|
||||
method: &'a str,
|
||||
#[serde(skip_serializing_if = "is_null_value")]
|
||||
params: T,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,27 @@
|
||||
use gpui::{actions, AppContext, Context, ViewContext};
|
||||
use manager::ContextServerManager;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub mod client;
|
||||
pub mod manager;
|
||||
pub mod protocol;
|
||||
mod registry;
|
||||
pub mod types;
|
||||
|
||||
pub use registry::*;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use gpui::{actions, AppContext};
|
||||
use settings::Settings;
|
||||
|
||||
pub use crate::manager::ContextServer;
|
||||
use crate::manager::ContextServerSettings;
|
||||
pub use crate::registry::ContextServerFactoryRegistry;
|
||||
|
||||
actions!(context_servers, [Restart]);
|
||||
|
||||
/// The namespace for the context servers actions.
|
||||
const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
|
||||
pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
log::info!("initializing context server client");
|
||||
manager::init(cx);
|
||||
ContextServerRegistry::register(cx);
|
||||
ContextServerSettings::register(cx);
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(restart_servers);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn restart_servers(_workspace: &mut Workspace, _action: &Restart, cx: &mut ViewContext<Workspace>) {
|
||||
let model = ContextServerManager::global(cx);
|
||||
cx.update_model(&model, |manager, cx| {
|
||||
for server in manager.servers() {
|
||||
manager
|
||||
.restart_server(&server.id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
//! The module also includes initialization logic to set up the context server system
|
||||
//! and react to changes in settings.
|
||||
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::{HashMap, HashSet};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
|
||||
use futures::{Future, FutureExt};
|
||||
use gpui::{AsyncAppContext, EventEmitter, ModelContext, Task};
|
||||
use log;
|
||||
use parking_lot::RwLock;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
use crate::CONTEXT_SERVERS_NAMESPACE;
|
||||
use crate::{
|
||||
client::{self, Client},
|
||||
types,
|
||||
@@ -57,51 +60,84 @@ impl Settings for ContextServerSettings {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextServer {
|
||||
pub id: String,
|
||||
pub config: ServerConfig,
|
||||
#[async_trait(?Send)]
|
||||
pub trait ContextServer: Send + Sync + 'static {
|
||||
fn id(&self) -> Arc<str>;
|
||||
fn config(&self) -> Arc<ServerConfig>;
|
||||
fn client(&self) -> Option<Arc<crate::protocol::InitializedContextServerProtocol>>;
|
||||
fn start<'a>(
|
||||
self: Arc<Self>,
|
||||
cx: &'a AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<()>>>>;
|
||||
fn stop(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct NativeContextServer {
|
||||
pub id: Arc<str>,
|
||||
pub config: Arc<ServerConfig>,
|
||||
pub client: RwLock<Option<Arc<crate::protocol::InitializedContextServerProtocol>>>,
|
||||
}
|
||||
|
||||
impl ContextServer {
|
||||
fn new(config: ServerConfig) -> Self {
|
||||
impl NativeContextServer {
|
||||
pub fn new(config: Arc<ServerConfig>) -> Self {
|
||||
Self {
|
||||
id: config.id.clone(),
|
||||
id: config.id.clone().into(),
|
||||
config,
|
||||
client: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start(&self, cx: &AsyncAppContext) -> anyhow::Result<()> {
|
||||
log::info!("starting context server {}", self.config.id,);
|
||||
let client = Client::new(
|
||||
client::ContextServerId(self.config.id.clone()),
|
||||
client::ModelContextServerBinary {
|
||||
executable: Path::new(&self.config.executable).to_path_buf(),
|
||||
args: self.config.args.clone(),
|
||||
env: self.config.env.clone(),
|
||||
},
|
||||
cx.clone(),
|
||||
)?;
|
||||
|
||||
let protocol = crate::protocol::ModelContextProtocol::new(client);
|
||||
let client_info = types::Implementation {
|
||||
name: "Zed".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
let initialized_protocol = protocol.initialize(client_info).await?;
|
||||
|
||||
log::debug!(
|
||||
"context server {} initialized: {:?}",
|
||||
self.config.id,
|
||||
initialized_protocol.initialize,
|
||||
);
|
||||
|
||||
*self.client.write() = Some(Arc::new(initialized_protocol));
|
||||
Ok(())
|
||||
#[async_trait(?Send)]
|
||||
impl ContextServer for NativeContextServer {
|
||||
fn id(&self) -> Arc<str> {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
async fn stop(&self) -> anyhow::Result<()> {
|
||||
fn config(&self) -> Arc<ServerConfig> {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
fn client(&self) -> Option<Arc<crate::protocol::InitializedContextServerProtocol>> {
|
||||
self.client.read().clone()
|
||||
}
|
||||
|
||||
fn start<'a>(
|
||||
self: Arc<Self>,
|
||||
cx: &'a AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<()>>>> {
|
||||
async move {
|
||||
log::info!("starting context server {}", self.config.id,);
|
||||
let client = Client::new(
|
||||
client::ContextServerId(self.config.id.clone()),
|
||||
client::ModelContextServerBinary {
|
||||
executable: Path::new(&self.config.executable).to_path_buf(),
|
||||
args: self.config.args.clone(),
|
||||
env: self.config.env.clone(),
|
||||
},
|
||||
cx.clone(),
|
||||
)?;
|
||||
|
||||
let protocol = crate::protocol::ModelContextProtocol::new(client);
|
||||
let client_info = types::Implementation {
|
||||
name: "Zed".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
let initialized_protocol = protocol.initialize(client_info).await?;
|
||||
|
||||
log::debug!(
|
||||
"context server {} initialized: {:?}",
|
||||
self.config.id,
|
||||
initialized_protocol.initialize,
|
||||
);
|
||||
|
||||
*self.client.write() = Some(Arc::new(initialized_protocol));
|
||||
Ok(())
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<()> {
|
||||
let mut client = self.client.write();
|
||||
if let Some(protocol) = client.take() {
|
||||
drop(protocol);
|
||||
@@ -115,16 +151,15 @@ impl ContextServer {
|
||||
/// must go through the `GlobalContextServerManager` which holds
|
||||
/// a model to the ContextServerManager.
|
||||
pub struct ContextServerManager {
|
||||
servers: HashMap<String, Arc<ContextServer>>,
|
||||
pending_servers: HashSet<String>,
|
||||
servers: HashMap<Arc<str>, Arc<dyn ContextServer>>,
|
||||
pending_servers: HashSet<Arc<str>>,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
ServerStarted { server_id: String },
|
||||
ServerStopped { server_id: String },
|
||||
ServerStarted { server_id: Arc<str> },
|
||||
ServerStopped { server_id: Arc<str> },
|
||||
}
|
||||
|
||||
impl Global for ContextServerManager {}
|
||||
impl EventEmitter<Event> for ContextServerManager {}
|
||||
|
||||
impl Default for ContextServerManager {
|
||||
@@ -140,16 +175,13 @@ impl ContextServerManager {
|
||||
pending_servers: HashSet::default(),
|
||||
}
|
||||
}
|
||||
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||
cx.global::<GlobalContextServerManager>().0.clone()
|
||||
}
|
||||
|
||||
pub fn add_server(
|
||||
&mut self,
|
||||
config: ServerConfig,
|
||||
cx: &mut ModelContext<Self>,
|
||||
server: Arc<dyn ContextServer>,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let server_id = config.id.clone();
|
||||
let server_id = server.id();
|
||||
|
||||
if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) {
|
||||
return Task::ready(Ok(()));
|
||||
@@ -158,8 +190,7 @@ impl ContextServerManager {
|
||||
let task = {
|
||||
let server_id = server_id.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let server = Arc::new(ContextServer::new(config));
|
||||
server.start(&cx).await?;
|
||||
server.clone().start(&cx).await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.servers.insert(server_id.clone(), server);
|
||||
this.pending_servers.remove(&server_id);
|
||||
@@ -175,22 +206,24 @@ impl ContextServerManager {
|
||||
task
|
||||
}
|
||||
|
||||
pub fn get_server(&self, id: &str) -> Option<Arc<ContextServer>> {
|
||||
pub fn get_server(&self, id: &str) -> Option<Arc<dyn ContextServer>> {
|
||||
self.servers.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn remove_server(
|
||||
&mut self,
|
||||
id: &str,
|
||||
cx: &mut ModelContext<Self>,
|
||||
id: &Arc<str>,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let id = id.to_string();
|
||||
let id = id.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? {
|
||||
server.stop().await?;
|
||||
if let Some(server) =
|
||||
this.update(&mut cx, |this, _cx| this.servers.remove(id.as_ref()))?
|
||||
{
|
||||
server.stop()?;
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_servers.remove(&id);
|
||||
this.pending_servers.remove(id.as_ref());
|
||||
cx.emit(Event::ServerStopped {
|
||||
server_id: id.clone(),
|
||||
})
|
||||
@@ -201,16 +234,16 @@ impl ContextServerManager {
|
||||
|
||||
pub fn restart_server(
|
||||
&mut self,
|
||||
id: &str,
|
||||
id: &Arc<str>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let id = id.to_string();
|
||||
let id = id.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? {
|
||||
server.stop().await?;
|
||||
let config = server.config.clone();
|
||||
let new_server = Arc::new(ContextServer::new(config));
|
||||
new_server.start(&cx).await?;
|
||||
server.stop()?;
|
||||
let config = server.config();
|
||||
let new_server = Arc::new(NativeContextServer::new(config));
|
||||
new_server.clone().start(&cx).await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.servers.insert(id.clone(), new_server);
|
||||
cx.emit(Event::ServerStopped {
|
||||
@@ -225,79 +258,43 @@ impl ContextServerManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn servers(&self) -> Vec<Arc<ContextServer>> {
|
||||
pub fn servers(&self) -> Vec<Arc<dyn ContextServer>> {
|
||||
self.servers.values().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn model(cx: &mut AppContext) -> Model<Self> {
|
||||
cx.new_model(|_cx| ContextServerManager::new())
|
||||
pub fn maintain_servers(&mut self, settings: &ContextServerSettings, cx: &ModelContext<Self>) {
|
||||
let current_servers = self
|
||||
.servers()
|
||||
.into_iter()
|
||||
.map(|server| (server.id(), server.config()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let new_servers = settings
|
||||
.servers
|
||||
.iter()
|
||||
.map(|config| (config.id.clone(), config.clone()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let servers_to_add = new_servers
|
||||
.values()
|
||||
.filter(|config| !current_servers.contains_key(config.id.as_str()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let servers_to_remove = current_servers
|
||||
.keys()
|
||||
.filter(|id| !new_servers.contains_key(id.as_ref()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
log::trace!("servers_to_add={:?}", servers_to_add);
|
||||
for config in servers_to_add {
|
||||
let server = Arc::new(NativeContextServer::new(Arc::new(config)));
|
||||
self.add_server(server, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
for id in servers_to_remove {
|
||||
self.remove_server(&id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalContextServerManager(Model<ContextServerManager>);
|
||||
impl Global for GlobalContextServerManager {}
|
||||
|
||||
impl GlobalContextServerManager {
|
||||
fn register(cx: &mut AppContext) {
|
||||
let model = ContextServerManager::model(cx);
|
||||
cx.set_global(Self(model));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
ContextServerSettings::register(cx);
|
||||
GlobalContextServerManager::register(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
});
|
||||
|
||||
cx.observe_global::<SettingsStore>(|cx| {
|
||||
let manager = ContextServerManager::global(cx);
|
||||
cx.update_model(&manager, |manager, cx| {
|
||||
let settings = ContextServerSettings::get_global(cx);
|
||||
let current_servers = manager
|
||||
.servers()
|
||||
.into_iter()
|
||||
.map(|server| (server.id.clone(), server.config.clone()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let new_servers = settings
|
||||
.servers
|
||||
.iter()
|
||||
.map(|config| (config.id.clone(), config.clone()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let servers_to_add = new_servers
|
||||
.values()
|
||||
.filter(|config| !current_servers.contains_key(&config.id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let servers_to_remove = current_servers
|
||||
.keys()
|
||||
.filter(|id| !new_servers.contains_key(*id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
log::trace!("servers_to_add={:?}", servers_to_add);
|
||||
for config in servers_to_add {
|
||||
manager.add_server(config, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
for id in servers_to_remove {
|
||||
manager.remove_server(&id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
let has_any_context_servers = !manager.servers().is_empty();
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
if has_any_context_servers {
|
||||
filter.show_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
} else {
|
||||
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -113,7 +113,10 @@ impl InitializedContextServerProtocol {
|
||||
|
||||
let response: types::PromptsListResponse = self
|
||||
.inner
|
||||
.request(types::RequestType::PromptsList.as_str(), ())
|
||||
.request(
|
||||
types::RequestType::PromptsList.as_str(),
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(response.prompts)
|
||||
@@ -125,7 +128,10 @@ impl InitializedContextServerProtocol {
|
||||
|
||||
let response: types::ResourcesListResponse = self
|
||||
.inner
|
||||
.request(types::RequestType::ResourcesList.as_str(), ())
|
||||
.request(
|
||||
types::RequestType::ResourcesList.as_str(),
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
|
||||
@@ -1,69 +1,72 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, Global, ReadGlobal};
|
||||
use gpui::{AppContext, AsyncAppContext, ReadGlobal};
|
||||
use gpui::{Global, Task};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
struct GlobalContextServerRegistry(Arc<ContextServerRegistry>);
|
||||
use crate::ContextServer;
|
||||
|
||||
impl Global for GlobalContextServerRegistry {}
|
||||
pub type ContextServerFactory =
|
||||
Arc<dyn Fn(&AsyncAppContext) -> Task<Result<Arc<dyn ContextServer>>> + Send + Sync + 'static>;
|
||||
|
||||
pub struct ContextServerRegistry {
|
||||
command_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
tool_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
#[derive(Default)]
|
||||
struct GlobalContextServerFactoryRegistry(Arc<ContextServerFactoryRegistry>);
|
||||
|
||||
impl Global for GlobalContextServerFactoryRegistry {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContextServerFactoryRegistryState {
|
||||
context_servers: HashMap<Arc<str>, ContextServerFactory>,
|
||||
}
|
||||
|
||||
impl ContextServerRegistry {
|
||||
#[derive(Default)]
|
||||
pub struct ContextServerFactoryRegistry {
|
||||
state: RwLock<ContextServerFactoryRegistryState>,
|
||||
}
|
||||
|
||||
impl ContextServerFactoryRegistry {
|
||||
/// Returns the global [`ContextServerFactoryRegistry`].
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
GlobalContextServerRegistry::global(cx).0.clone()
|
||||
GlobalContextServerFactoryRegistry::global(cx).0.clone()
|
||||
}
|
||||
|
||||
pub fn register(cx: &mut AppContext) {
|
||||
cx.set_global(GlobalContextServerRegistry(Arc::new(
|
||||
ContextServerRegistry {
|
||||
command_registry: RwLock::new(HashMap::default()),
|
||||
tool_registry: RwLock::new(HashMap::default()),
|
||||
},
|
||||
)))
|
||||
/// Returns the global [`ContextServerFactoryRegistry`].
|
||||
///
|
||||
/// Inserts a default [`ContextServerFactoryRegistry`] if one does not yet exist.
|
||||
pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
|
||||
cx.default_global::<GlobalContextServerFactoryRegistry>()
|
||||
.0
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn register_command(&self, server_id: String, command_name: &str) {
|
||||
let mut registry = self.command_registry.write();
|
||||
registry
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
.push(command_name.into());
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
state: RwLock::new(ContextServerFactoryRegistryState {
|
||||
context_servers: HashMap::default(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unregister_command(&self, server_id: &str, command_name: &str) {
|
||||
let mut registry = self.command_registry.write();
|
||||
if let Some(commands) = registry.get_mut(server_id) {
|
||||
commands.retain(|name| name.as_ref() != command_name);
|
||||
}
|
||||
pub fn context_server_factories(&self) -> Vec<(Arc<str>, ContextServerFactory)> {
|
||||
self.state
|
||||
.read()
|
||||
.context_servers
|
||||
.iter()
|
||||
.map(|(id, factory)| (id.clone(), factory.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_commands(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
|
||||
let registry = self.command_registry.read();
|
||||
registry.get(server_id).cloned()
|
||||
/// Registers the provided [`ContextServerFactory`].
|
||||
pub fn register_server_factory(&self, id: Arc<str>, factory: ContextServerFactory) {
|
||||
let mut state = self.state.write();
|
||||
state.context_servers.insert(id, factory);
|
||||
}
|
||||
|
||||
pub fn register_tool(&self, server_id: String, tool_name: &str) {
|
||||
let mut registry = self.tool_registry.write();
|
||||
registry
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
.push(tool_name.into());
|
||||
}
|
||||
|
||||
pub fn unregister_tool(&self, server_id: &str, tool_name: &str) {
|
||||
let mut registry = self.tool_registry.write();
|
||||
if let Some(tools) = registry.get_mut(server_id) {
|
||||
tools.retain(|name| name.as_ref() != tool_name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tools(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
|
||||
let registry = self.tool_registry.read();
|
||||
registry.get(server_id).cloned()
|
||||
/// Unregisters the [`ContextServerFactory`] for the server with the given ID.
|
||||
pub fn unregister_server_factory_by_id(&self, server_id: &str) {
|
||||
let mut state = self.state.write();
|
||||
state.context_servers.remove(server_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use language::{
|
||||
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||
ToPointUtf16,
|
||||
};
|
||||
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
||||
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use request::StatusNotification;
|
||||
@@ -446,9 +446,11 @@ impl Copilot {
|
||||
Path::new("/")
|
||||
};
|
||||
|
||||
let server_name = LanguageServerName("copilot".into());
|
||||
let server = LanguageServer::new(
|
||||
Arc::new(Mutex::new(None)),
|
||||
new_server_id,
|
||||
server_name,
|
||||
binary,
|
||||
root_path,
|
||||
None,
|
||||
@@ -1272,5 +1274,9 @@ mod tests {
|
||||
fn load(&self, _: &AppContext) -> Task<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn load_bytes(&self, _cx: &AppContext) -> Task<Result<Vec<u8>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ pub enum Model {
|
||||
Gpt4,
|
||||
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
|
||||
Gpt3_5Turbo,
|
||||
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
|
||||
O1Preview,
|
||||
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
|
||||
O1Mini,
|
||||
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
|
||||
Claude3_5Sonnet,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn uses_streaming(&self) -> bool {
|
||||
match self {
|
||||
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
|
||||
Self::O1Mini | Self::O1Preview => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_id(id: &str) -> Result<Self> {
|
||||
match id {
|
||||
"gpt-4o" => Ok(Self::Gpt4o),
|
||||
"gpt-4" => Ok(Self::Gpt4),
|
||||
"gpt-3.5-turbo" => Ok(Self::Gpt3_5Turbo),
|
||||
"o1-preview" => Ok(Self::O1Preview),
|
||||
"o1-mini" => Ok(Self::O1Mini),
|
||||
"claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet),
|
||||
_ => Err(anyhow!("Invalid model id: {}", id)),
|
||||
}
|
||||
}
|
||||
@@ -52,6 +68,9 @@ impl Model {
|
||||
Self::Gpt3_5Turbo => "gpt-3.5-turbo",
|
||||
Self::Gpt4 => "gpt-4",
|
||||
Self::Gpt4o => "gpt-4o",
|
||||
Self::O1Mini => "o1-mini",
|
||||
Self::O1Preview => "o1-preview",
|
||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,14 +79,20 @@ impl Model {
|
||||
Self::Gpt3_5Turbo => "GPT-3.5",
|
||||
Self::Gpt4 => "GPT-4",
|
||||
Self::Gpt4o => "GPT-4o",
|
||||
Self::O1Mini => "o1-mini",
|
||||
Self::O1Preview => "o1-preview",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Gpt4o => 128000,
|
||||
Self::Gpt4 => 8192,
|
||||
Self::Gpt3_5Turbo => 16385,
|
||||
Self::Gpt4o => 64000,
|
||||
Self::Gpt4 => 32768,
|
||||
Self::Gpt3_5Turbo => 12288,
|
||||
Self::O1Mini => 20000,
|
||||
Self::O1Preview => 20000,
|
||||
Self::Claude3_5Sonnet => 200_000,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +112,7 @@ impl Request {
|
||||
Self {
|
||||
intent: true,
|
||||
n: 1,
|
||||
stream: true,
|
||||
stream: model.uses_streaming(),
|
||||
temperature: 0.1,
|
||||
model,
|
||||
messages,
|
||||
@@ -113,7 +138,8 @@ pub struct ResponseEvent {
|
||||
pub struct ResponseChoice {
|
||||
pub index: usize,
|
||||
pub finish_reason: Option<String>,
|
||||
pub delta: ResponseDelta,
|
||||
pub delta: Option<ResponseDelta>,
|
||||
pub message: Option<ResponseDelta>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -333,9 +359,23 @@ async fn stream_completion(
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
let is_streaming = request.stream;
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let mut response = client.send(request).await?;
|
||||
if response.status().is_success() {
|
||||
|
||||
if !response.status().is_success() {
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
let body_str = std::str::from_utf8(&body)?;
|
||||
return Err(anyhow!(
|
||||
"Failed to connect to API: {} {}",
|
||||
response.status(),
|
||||
body_str
|
||||
));
|
||||
}
|
||||
|
||||
if is_streaming {
|
||||
let reader = BufReader::new(response.into_body());
|
||||
Ok(reader
|
||||
.lines()
|
||||
@@ -367,19 +407,9 @@ async fn stream_completion(
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
|
||||
let body_str = std::str::from_utf8(&body)?;
|
||||
let response: ResponseEvent = serde_json::from_str(body_str)?;
|
||||
|
||||
match serde_json::from_str::<ResponseEvent>(body_str) {
|
||||
Ok(_) => Err(anyhow!(
|
||||
"Unexpected success response while expecting an error: {}",
|
||||
body_str,
|
||||
)),
|
||||
Err(_) => Err(anyhow!(
|
||||
"Failed to connect to API: {} {}",
|
||||
response.status(),
|
||||
body_str,
|
||||
)),
|
||||
}
|
||||
Ok(futures::stream::once(async move { Ok(response) }).boxed())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
editor.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -14,10 +14,6 @@ use editor::{
|
||||
scroll::Autoscroll,
|
||||
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||
};
|
||||
use futures::{
|
||||
channel::mpsc::{self, UnboundedSender},
|
||||
StreamExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
||||
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
|
||||
@@ -62,11 +58,10 @@ struct ProjectDiagnosticsEditor {
|
||||
summary: DiagnosticSummary,
|
||||
excerpts: Model<MultiBuffer>,
|
||||
path_states: Vec<PathState>,
|
||||
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
|
||||
paths_to_update: BTreeSet<(ProjectPath, Option<LanguageServerId>)>,
|
||||
include_warnings: bool,
|
||||
context: u32,
|
||||
update_paths_tx: UnboundedSender<(ProjectPath, Option<LanguageServerId>)>,
|
||||
_update_excerpts_task: Task<Result<()>>,
|
||||
update_excerpts_task: Option<Task<Result<()>>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -129,14 +124,14 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
|
||||
log::debug!("disk based diagnostics finished for server {language_server_id}");
|
||||
this.enqueue_update_stale_excerpts(Some(*language_server_id));
|
||||
this.update_stale_excerpts(cx);
|
||||
}
|
||||
project::Event::DiagnosticsUpdated {
|
||||
language_server_id,
|
||||
path,
|
||||
} => {
|
||||
this.paths_to_update
|
||||
.insert((path.clone(), *language_server_id));
|
||||
.insert((path.clone(), Some(*language_server_id)));
|
||||
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
|
||||
@@ -144,7 +139,7 @@ impl ProjectDiagnosticsEditor {
|
||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
||||
} else {
|
||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
||||
this.enqueue_update_stale_excerpts(Some(*language_server_id));
|
||||
this.update_stale_excerpts(cx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -171,14 +166,12 @@ impl ProjectDiagnosticsEditor {
|
||||
cx.focus(&this.focus_handle);
|
||||
}
|
||||
}
|
||||
EditorEvent::Blurred => this.enqueue_update_stale_excerpts(None),
|
||||
EditorEvent::Blurred => this.update_stale_excerpts(cx),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let (update_excerpts_tx, mut update_excerpts_rx) = mpsc::unbounded();
|
||||
|
||||
let project = project_handle.read(cx);
|
||||
let mut this = Self {
|
||||
project: project_handle.clone(),
|
||||
@@ -191,27 +184,45 @@ impl ProjectDiagnosticsEditor {
|
||||
path_states: Default::default(),
|
||||
paths_to_update: Default::default(),
|
||||
include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
update_paths_tx: update_excerpts_tx,
|
||||
_update_excerpts_task: cx.spawn(move |this, mut cx| async move {
|
||||
while let Some((path, language_server_id)) = update_excerpts_rx.next().await {
|
||||
if let Some(buffer) = project_handle
|
||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_excerpts(path, language_server_id, buffer, cx);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}),
|
||||
update_excerpts_task: None,
|
||||
_subscription: project_event_subscription,
|
||||
};
|
||||
this.enqueue_update_all_excerpts(cx);
|
||||
this.update_all_excerpts(cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn update_stale_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self.update_excerpts_task.is_some() {
|
||||
return;
|
||||
}
|
||||
let project_handle = self.project.clone();
|
||||
self.update_excerpts_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
loop {
|
||||
let Some((path, language_server_id)) = this.update(&mut cx, |this, _| {
|
||||
let Some((path, language_server_id)) = this.paths_to_update.pop_first() else {
|
||||
this.update_excerpts_task.take();
|
||||
return None;
|
||||
};
|
||||
Some((path, language_server_id))
|
||||
})?
|
||||
else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Some(buffer) = project_handle
|
||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_excerpts(path, language_server_id, buffer, cx);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
fn new(
|
||||
project_handle: Model<Project>,
|
||||
workspace: WeakView<Workspace>,
|
||||
@@ -239,7 +250,7 @@ impl ProjectDiagnosticsEditor {
|
||||
|
||||
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
||||
self.include_warnings = !self.include_warnings;
|
||||
self.enqueue_update_all_excerpts(cx);
|
||||
self.update_all_excerpts(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -251,37 +262,28 @@ impl ProjectDiagnosticsEditor {
|
||||
|
||||
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
|
||||
self.enqueue_update_stale_excerpts(None);
|
||||
self.update_stale_excerpts(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue an update of all excerpts. Updates all paths that either
|
||||
/// currently have diagnostics or are currently present in this view.
|
||||
fn enqueue_update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.project.update(cx, |project, cx| {
|
||||
let mut paths = project
|
||||
.diagnostic_summaries(false, cx)
|
||||
.map(|(path, _, _)| path)
|
||||
.map(|(path, _, _)| (path, None))
|
||||
.collect::<BTreeSet<_>>();
|
||||
paths.extend(self.path_states.iter().map(|state| state.path.clone()));
|
||||
for path in paths {
|
||||
self.update_paths_tx.unbounded_send((path, None)).unwrap();
|
||||
}
|
||||
paths.extend(
|
||||
self.path_states
|
||||
.iter()
|
||||
.map(|state| (state.path.clone(), None)),
|
||||
);
|
||||
let paths_to_update = std::mem::take(&mut self.paths_to_update);
|
||||
paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None)));
|
||||
self.paths_to_update = paths;
|
||||
});
|
||||
}
|
||||
|
||||
/// Enqueue an update of the excerpts for any path whose diagnostics are known
|
||||
/// to have changed. If a language server id is passed, then only the excerpts for
|
||||
/// that language server's diagnostics will be updated. Otherwise, all stale excerpts
|
||||
/// will be refreshed.
|
||||
fn enqueue_update_stale_excerpts(&mut self, language_server_id: Option<LanguageServerId>) {
|
||||
for (path, server_id) in &self.paths_to_update {
|
||||
if language_server_id.map_or(true, |id| id == *server_id) {
|
||||
self.update_paths_tx
|
||||
.unbounded_send((path.clone(), Some(*server_id)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
self.update_stale_excerpts(cx);
|
||||
}
|
||||
|
||||
fn update_excerpts(
|
||||
@@ -291,11 +293,6 @@ impl ProjectDiagnosticsEditor {
|
||||
buffer: Model<Buffer>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.paths_to_update.retain(|(path, server_id)| {
|
||||
*path != path_to_update
|
||||
|| server_to_update.map_or(false, |to_update| *server_id != to_update)
|
||||
});
|
||||
|
||||
let was_empty = self.path_states.is_empty();
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let path_ix = match self
|
||||
|
||||
@@ -800,7 +800,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
|
||||
log::info!("updating mutated diagnostics view");
|
||||
mutated_view.update(cx, |view, _| view.enqueue_update_stale_excerpts(None));
|
||||
mutated_view.update(cx, |view, cx| view.update_stale_excerpts(cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
log::info!("constructing reference diagnostics view");
|
||||
@@ -986,6 +986,7 @@ fn editor_blocks(
|
||||
em_width: px(0.),
|
||||
max_width: px(0.),
|
||||
block_id,
|
||||
selected: false,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
});
|
||||
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
|
||||
|
||||
@@ -14,12 +14,12 @@ impl Render for ToolbarControls {
|
||||
let mut has_stale_excerpts = false;
|
||||
let mut is_updating = false;
|
||||
|
||||
if let Some(editor) = self.editor() {
|
||||
let editor = editor.read(cx);
|
||||
include_warnings = editor.include_warnings;
|
||||
has_stale_excerpts = !editor.paths_to_update.is_empty();
|
||||
is_updating = !editor.update_paths_tx.is_empty()
|
||||
|| editor
|
||||
if let Some(editor) = self.diagnostics() {
|
||||
let diagnostics = editor.read(cx);
|
||||
include_warnings = diagnostics.include_warnings;
|
||||
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
|
||||
is_updating = diagnostics.update_excerpts_task.is_some()
|
||||
|| diagnostics
|
||||
.project
|
||||
.read(cx)
|
||||
.language_servers_running_disk_based_diagnostics(cx)
|
||||
@@ -49,9 +49,9 @@ impl Render for ToolbarControls {
|
||||
.disabled(is_updating)
|
||||
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(editor) = this.editor() {
|
||||
editor.update(cx, |editor, _| {
|
||||
editor.enqueue_update_stale_excerpts(None);
|
||||
if let Some(diagnostics) = this.diagnostics() {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.update_all_excerpts(cx);
|
||||
});
|
||||
}
|
||||
})),
|
||||
@@ -63,7 +63,7 @@ impl Render for ToolbarControls {
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip, cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(editor) = this.editor() {
|
||||
if let Some(editor) = this.diagnostics() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_warnings(&Default::default(), cx);
|
||||
});
|
||||
@@ -105,7 +105,7 @@ impl ToolbarControls {
|
||||
ToolbarControls { editor: None }
|
||||
}
|
||||
|
||||
fn editor(&self) -> Option<View<ProjectDiagnosticsEditor>> {
|
||||
fn diagnostics(&self) -> Option<View<ProjectDiagnosticsEditor>> {
|
||||
self.editor.as_ref()?.upgrade()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ tree-sitter-html = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
unicode-segmentation.workspace = true
|
||||
unicode-script.workspace = true
|
||||
unindent = { workspace = true, optional = true }
|
||||
ui.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
@@ -80,6 +80,8 @@ pub struct ConfirmCodeAction {
|
||||
pub struct ToggleComments {
|
||||
#[serde(default)]
|
||||
pub advance_downwards: bool,
|
||||
#[serde(default)]
|
||||
pub ignore_indent: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
@@ -157,6 +159,13 @@ pub struct DeleteToPreviousWordStart {
|
||||
pub struct FoldAtLevel {
|
||||
pub level: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct SpawnNearestTask {
|
||||
#[serde(default)]
|
||||
pub reveal: task::RevealStrategy,
|
||||
}
|
||||
|
||||
impl_actions!(
|
||||
editor,
|
||||
[
|
||||
@@ -182,6 +191,7 @@ impl_actions!(
|
||||
SelectToBeginningOfLine,
|
||||
SelectToEndOfLine,
|
||||
SelectUpByLines,
|
||||
SpawnNearestTask,
|
||||
ShowCompletions,
|
||||
ToggleCodeActions,
|
||||
ToggleComments,
|
||||
|
||||
@@ -36,7 +36,7 @@ use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use crease_map::*;
|
||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||
use fold_map::{FoldMap, FoldSnapshot};
|
||||
use fold_map::{FoldMap, FoldMapWriter, FoldOffset, FoldSnapshot};
|
||||
use gpui::{
|
||||
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||
};
|
||||
@@ -65,7 +65,7 @@ use std::{
|
||||
};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::LineIndent;
|
||||
use text::{Edit, LineIndent};
|
||||
use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wrap_map::{WrapMap, WrapSnapshot};
|
||||
@@ -206,34 +206,41 @@ impl DisplayMap {
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates folds for the given ranges.
|
||||
pub fn fold<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.fold(ranges);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
self.update_fold_map(cx, |fold_map| fold_map.fold(ranges))
|
||||
}
|
||||
|
||||
pub fn unfold<T: ToOffset>(
|
||||
/// Removes any folds with the given ranges.
|
||||
pub fn remove_folds_with_type<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
type_id: TypeId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.update_fold_map(cx, |fold_map| fold_map.remove_folds(ranges, type_id))
|
||||
}
|
||||
|
||||
/// Removes any folds whose ranges intersect any of the given ranges.
|
||||
pub fn unfold_intersecting<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.update_fold_map(cx, |fold_map| {
|
||||
fold_map.unfold_intersecting(ranges, inclusive)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_fold_map(
|
||||
&mut self,
|
||||
cx: &mut ModelContext<Self>,
|
||||
callback: impl FnOnce(&mut FoldMapWriter) -> (FoldSnapshot, Vec<Edit<FoldOffset>>),
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
@@ -245,7 +252,7 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
|
||||
let (snapshot, edits) = callback(&mut fold_map);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
@@ -660,7 +667,7 @@ impl DisplaySnapshot {
|
||||
new_start..new_end
|
||||
}
|
||||
|
||||
fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
|
||||
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||
@@ -669,7 +676,7 @@ impl DisplaySnapshot {
|
||||
DisplayPoint(block_point)
|
||||
}
|
||||
|
||||
fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
|
||||
pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
|
||||
self.inlay_snapshot
|
||||
.to_buffer_point(self.display_point_to_inlay_point(point, bias))
|
||||
}
|
||||
@@ -691,7 +698,7 @@ impl DisplaySnapshot {
|
||||
|
||||
fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
|
||||
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
|
||||
let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
|
||||
fold_point.to_inlay_point(&self.fold_snapshot)
|
||||
@@ -699,7 +706,7 @@ impl DisplaySnapshot {
|
||||
|
||||
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
|
||||
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
|
||||
self.tab_snapshot.to_fold_point(tab_point, bias).0
|
||||
}
|
||||
@@ -990,7 +997,7 @@ impl DisplaySnapshot {
|
||||
pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
|
||||
let wrap_row = self
|
||||
.block_snapshot
|
||||
.to_wrap_point(BlockPoint::new(display_row.0, 0))
|
||||
.to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
|
||||
.row();
|
||||
self.wrap_snapshot.soft_wrap_indent(wrap_row)
|
||||
}
|
||||
@@ -1172,7 +1179,7 @@ impl Sub for DisplayPoint {
|
||||
#[serde(transparent)]
|
||||
pub struct DisplayRow(pub u32);
|
||||
|
||||
impl Add for DisplayRow {
|
||||
impl Add<DisplayRow> for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
@@ -1180,7 +1187,15 @@ impl Add for DisplayRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for DisplayRow {
|
||||
impl Add<u32> for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u32) -> Self::Output {
|
||||
DisplayRow(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<DisplayRow> for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
@@ -1188,6 +1203,14 @@ impl Sub for DisplayRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<u32> for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: u32) -> Self::Output {
|
||||
DisplayRow(self.0 - other)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPoint {
|
||||
pub fn new(row: DisplayRow, column: u32) -> Self {
|
||||
Self(BlockPoint(Point::new(row.0, column)))
|
||||
@@ -1222,7 +1245,7 @@ impl DisplayPoint {
|
||||
}
|
||||
|
||||
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
|
||||
let wrap_point = map.block_snapshot.to_wrap_point(self.0);
|
||||
let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
|
||||
let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
|
||||
let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
|
||||
@@ -1426,7 +1449,7 @@ pub mod tests {
|
||||
if rng.gen() && fold_count > 0 {
|
||||
log::info!("unfolding ranges: {:?}", ranges);
|
||||
map.update(cx, |map, cx| {
|
||||
map.unfold(ranges, true, cx);
|
||||
map.unfold_intersecting(ranges, true, cx);
|
||||
});
|
||||
} else {
|
||||
log::info!("folding ranges: {:?}", ranges);
|
||||
@@ -2048,6 +2071,112 @@ pub mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
|
||||
cx.background_executor
|
||||
.set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
|
||||
cx.update(|cx| init_test(cx, |_| {}));
|
||||
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let map = cx.new_model(|cx| {
|
||||
DisplayMap::new(
|
||||
buffer.clone(),
|
||||
font("Courier"),
|
||||
px(16.0),
|
||||
None,
|
||||
true,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
FoldPlaceholder::test(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let snapshot = map.update(cx, |map, cx| {
|
||||
map.insert_blocks(
|
||||
[BlockProperties {
|
||||
placement: BlockPlacement::Replace(
|
||||
buffer_snapshot.anchor_before(Point::new(1, 2))
|
||||
..buffer_snapshot.anchor_after(Point::new(2, 3)),
|
||||
),
|
||||
height: 4,
|
||||
style: BlockStyle::Fixed,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
map.snapshot(cx)
|
||||
});
|
||||
|
||||
assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
|
||||
|
||||
let point_to_display_points = [
|
||||
(Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
|
||||
(Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
|
||||
(Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
|
||||
];
|
||||
for (buffer_point, display_point) in point_to_display_points {
|
||||
assert_eq!(
|
||||
snapshot.point_to_display_point(buffer_point, Bias::Left),
|
||||
display_point,
|
||||
"point_to_display_point({:?}, Bias::Left)",
|
||||
buffer_point
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.point_to_display_point(buffer_point, Bias::Right),
|
||||
display_point,
|
||||
"point_to_display_point({:?}, Bias::Right)",
|
||||
buffer_point
|
||||
);
|
||||
}
|
||||
|
||||
let display_points_to_points = [
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(1), 0),
|
||||
Point::new(1, 0),
|
||||
Point::new(2, 5),
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(2), 0),
|
||||
Point::new(1, 0),
|
||||
Point::new(2, 5),
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(3), 0),
|
||||
Point::new(1, 0),
|
||||
Point::new(2, 5),
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(4), 0),
|
||||
Point::new(1, 0),
|
||||
Point::new(2, 5),
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(5), 0),
|
||||
Point::new(3, 0),
|
||||
Point::new(3, 0),
|
||||
),
|
||||
];
|
||||
for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
|
||||
assert_eq!(
|
||||
snapshot.display_point_to_point(display_point, Bias::Left),
|
||||
left_buffer_point,
|
||||
"display_point_to_point({:?}, Bias::Left)",
|
||||
display_point
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.display_point_to_point(display_point, Bias::Right),
|
||||
right_buffer_point,
|
||||
"display_point_to_point({:?}, Bias::Right)",
|
||||
display_point
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// todo(linux) fails due to pixel differences in text rendering
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test]
|
||||
|
||||
@@ -265,6 +265,7 @@ pub struct BlockContext<'a, 'b> {
|
||||
pub em_width: Pixels,
|
||||
pub line_height: Pixels,
|
||||
pub block_id: BlockId,
|
||||
pub selected: bool,
|
||||
pub editor_style: &'b EditorStyle,
|
||||
}
|
||||
|
||||
@@ -1311,7 +1312,6 @@ impl BlockSnapshot {
|
||||
let (output_start_row, input_start_row) = cursor.start();
|
||||
let (output_end_row, input_end_row) = cursor.end(&());
|
||||
let output_start = Point::new(output_start_row.0, 0);
|
||||
let output_end = Point::new(output_end_row.0, 0);
|
||||
let input_start = Point::new(input_start_row.0, 0);
|
||||
let input_end = Point::new(input_end_row.0, 0);
|
||||
|
||||
@@ -1319,10 +1319,10 @@ impl BlockSnapshot {
|
||||
Some(Block::Custom(block))
|
||||
if matches!(block.placement, BlockPlacement::Replace(_)) =>
|
||||
{
|
||||
if bias == Bias::Left {
|
||||
if ((bias == Bias::Left || search_left) && output_start <= point.0)
|
||||
|| (!search_left && output_start >= point.0)
|
||||
{
|
||||
return BlockPoint(output_start);
|
||||
} else {
|
||||
return BlockPoint(Point::new(output_end.row - 1, 0));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -1364,12 +1364,7 @@ impl BlockSnapshot {
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
if transform.block.is_some() {
|
||||
let wrap_start = WrapPoint::new(cursor.start().0 .0, 0);
|
||||
if wrap_start == wrap_point {
|
||||
BlockPoint::new(cursor.start().1 .0, 0)
|
||||
} else {
|
||||
BlockPoint::new(cursor.end(&()).1 .0 - 1, 0)
|
||||
}
|
||||
BlockPoint::new(cursor.start().1 .0, 0)
|
||||
} else {
|
||||
let (input_start_row, output_start_row) = cursor.start();
|
||||
let input_start = Point::new(input_start_row.0, 0);
|
||||
@@ -1382,7 +1377,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
|
||||
pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
@@ -1391,7 +1386,9 @@ impl BlockSnapshot {
|
||||
if block.place_below() {
|
||||
let wrap_row = cursor.start().1 .0 - 1;
|
||||
WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
|
||||
} else if block.place_above() || block_point.row == cursor.start().0 .0 {
|
||||
} else if block.place_above() {
|
||||
WrapPoint::new(cursor.start().1 .0, 0)
|
||||
} else if bias == Bias::Left {
|
||||
WrapPoint::new(cursor.start().1 .0, 0)
|
||||
} else {
|
||||
let wrap_row = cursor.end(&()).1 .0 - 1;
|
||||
@@ -1766,19 +1763,19 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
snapshot.to_wrap_point(BlockPoint::new(0, 3)),
|
||||
snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
|
||||
WrapPoint::new(0, 3)
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.to_wrap_point(BlockPoint::new(1, 0)),
|
||||
snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
|
||||
WrapPoint::new(1, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.to_wrap_point(BlockPoint::new(3, 0)),
|
||||
snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
|
||||
WrapPoint::new(1, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.to_wrap_point(BlockPoint::new(7, 0)),
|
||||
snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
|
||||
WrapPoint::new(3, 3)
|
||||
);
|
||||
|
||||
@@ -2616,10 +2613,15 @@ mod tests {
|
||||
|
||||
// Ensure that conversion between block points and wrap points is stable.
|
||||
for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
|
||||
let original_wrap_point = WrapPoint::new(row, 0);
|
||||
let block_point = blocks_snapshot.to_block_point(original_wrap_point);
|
||||
let wrap_point = blocks_snapshot.to_wrap_point(block_point);
|
||||
assert_eq!(blocks_snapshot.to_block_point(wrap_point), block_point);
|
||||
let wrap_point = WrapPoint::new(row, 0);
|
||||
let block_point = blocks_snapshot.to_block_point(wrap_point);
|
||||
let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
|
||||
let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
|
||||
assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
|
||||
assert_eq!(
|
||||
blocks_snapshot.to_block_point(right_wrap_point),
|
||||
block_point
|
||||
);
|
||||
}
|
||||
|
||||
let mut block_point = BlockPoint::new(0, 0);
|
||||
@@ -2627,10 +2629,12 @@ mod tests {
|
||||
let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
|
||||
let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
|
||||
assert_eq!(
|
||||
blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
|
||||
blocks_snapshot
|
||||
.to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
|
||||
left_point,
|
||||
"wrap point: {:?}",
|
||||
blocks_snapshot.to_wrap_point(left_point)
|
||||
"block point: {:?}, wrap point: {:?}",
|
||||
block_point,
|
||||
blocks_snapshot.to_wrap_point(left_point, Bias::Left)
|
||||
);
|
||||
assert_eq!(
|
||||
left_buffer_point,
|
||||
@@ -2642,10 +2646,12 @@ mod tests {
|
||||
let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
|
||||
let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
|
||||
assert_eq!(
|
||||
blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
|
||||
blocks_snapshot
|
||||
.to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
|
||||
right_point,
|
||||
"wrap point: {:?}",
|
||||
blocks_snapshot.to_wrap_point(right_point)
|
||||
"block point: {:?}, wrap point: {:?}",
|
||||
block_point,
|
||||
blocks_snapshot.to_wrap_point(right_point, Bias::Right)
|
||||
);
|
||||
assert_eq!(
|
||||
right_buffer_point,
|
||||
@@ -2681,7 +2687,8 @@ mod tests {
|
||||
|
||||
impl BlockSnapshot {
|
||||
fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
|
||||
self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
|
||||
self.wrap_snapshot
|
||||
.to_point(self.to_wrap_point(point, bias), bias)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ use gpui::{AnyElement, ElementId, WindowContext};
|
||||
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp::{self, Ordering},
|
||||
fmt, iter,
|
||||
ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::{Bias, Cursor, FilterCursor, SumTree, Summary};
|
||||
use ui::IntoElement as _;
|
||||
use util::post_inc;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -22,17 +24,29 @@ pub struct FoldPlaceholder {
|
||||
pub constrain_width: bool,
|
||||
/// If true, merges the fold with an adjacent one.
|
||||
pub merge_adjacent: bool,
|
||||
/// Category of the fold. Useful for carefully removing from overlapping folds.
|
||||
pub type_tag: Option<TypeId>,
|
||||
}
|
||||
|
||||
impl Default for FoldPlaceholder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
render: Arc::new(|_, _, _| gpui::Empty.into_any_element()),
|
||||
constrain_width: true,
|
||||
merge_adjacent: true,
|
||||
type_tag: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FoldPlaceholder {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test() -> Self {
|
||||
use gpui::IntoElement;
|
||||
|
||||
Self {
|
||||
render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
|
||||
constrain_width: true,
|
||||
merge_adjacent: true,
|
||||
type_tag: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,9 +187,34 @@ impl<'a> FoldMapWriter<'a> {
|
||||
(self.0.snapshot.clone(), edits)
|
||||
}
|
||||
|
||||
pub(crate) fn unfold<T: ToOffset>(
|
||||
/// Removes any folds with the given ranges.
|
||||
pub(crate) fn remove_folds<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
type_id: TypeId,
|
||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||
self.remove_folds_with(
|
||||
ranges,
|
||||
|fold| fold.placeholder.type_tag == Some(type_id),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes any folds whose ranges intersect the given ranges.
|
||||
pub(crate) fn unfold_intersecting<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||
self.remove_folds_with(ranges, |_| true, inclusive)
|
||||
}
|
||||
|
||||
/// Removes any folds that intersect the given ranges and for which the given predicate
|
||||
/// returns true.
|
||||
fn remove_folds_with<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
should_unfold: impl Fn(&Fold) -> bool,
|
||||
inclusive: bool,
|
||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||
let mut edits = Vec::new();
|
||||
@@ -183,21 +222,23 @@ impl<'a> FoldMapWriter<'a> {
|
||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||
let buffer = &snapshot.buffer;
|
||||
for range in ranges.into_iter() {
|
||||
// Remove intersecting folds and add their ranges to edits that are passed to sync.
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds_cursor =
|
||||
intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive);
|
||||
intersecting_folds(&snapshot, &self.0.snapshot.folds, range.clone(), inclusive);
|
||||
while let Some(fold) = folds_cursor.item() {
|
||||
let offset_range =
|
||||
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
|
||||
if offset_range.end > offset_range.start {
|
||||
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
|
||||
..snapshot.to_inlay_offset(offset_range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
});
|
||||
if should_unfold(fold) {
|
||||
if offset_range.end > offset_range.start {
|
||||
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
|
||||
..snapshot.to_inlay_offset(offset_range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
});
|
||||
}
|
||||
fold_ixs_to_delete.push(*folds_cursor.start());
|
||||
}
|
||||
fold_ixs_to_delete.push(*folds_cursor.start());
|
||||
folds_cursor.next(buffer);
|
||||
}
|
||||
}
|
||||
@@ -665,6 +706,8 @@ impl FoldSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer = &self.inlay_snapshot.buffer;
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
|
||||
iter::from_fn(move || {
|
||||
let item = folds.item();
|
||||
@@ -821,15 +864,12 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
|
||||
}
|
||||
}
|
||||
|
||||
fn intersecting_folds<'a, T>(
|
||||
fn intersecting_folds<'a>(
|
||||
inlay_snapshot: &'a InlaySnapshot,
|
||||
folds: &'a SumTree<Fold>,
|
||||
range: Range<T>,
|
||||
range: Range<usize>,
|
||||
inclusive: bool,
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize>
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
@@ -1419,12 +1459,12 @@ mod tests {
|
||||
assert_eq!(snapshot4.text(), "123a⋯c123456eee");
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
|
||||
writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), false);
|
||||
let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||
assert_eq!(snapshot5.text(), "123a⋯c123456eee");
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
|
||||
writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), true);
|
||||
let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
|
||||
assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
|
||||
}
|
||||
@@ -1913,7 +1953,7 @@ mod tests {
|
||||
log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
|
||||
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
||||
snapshot_edits.push((snapshot, edits));
|
||||
let (snapshot, edits) = writer.unfold(to_unfold, inclusive);
|
||||
let (snapshot, edits) = writer.unfold_intersecting(to_unfold, inclusive);
|
||||
snapshot_edits.push((snapshot, edits));
|
||||
}
|
||||
_ => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -169,8 +169,12 @@ 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::local("123456", cx));
|
||||
let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
|
||||
let group_interval = Duration::from_millis(1);
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buf = language::Buffer::local("123456", cx);
|
||||
buf.set_group_interval(group_interval);
|
||||
buf
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
|
||||
|
||||
@@ -3989,6 +3993,76 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state(
|
||||
&"
|
||||
ˇzero
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
// Create a four-line block that replaces three lines of text.
|
||||
cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let snapshot = &snapshot.buffer_snapshot;
|
||||
let placement = BlockPlacement::Replace(
|
||||
snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
|
||||
);
|
||||
editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
placement,
|
||||
height: 4,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Box::new(|_| gpui::div().into_any_element()),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Move down so that the cursor touches the block.
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.move_down(&Default::default(), cx);
|
||||
});
|
||||
cx.assert_editor_state(
|
||||
&"
|
||||
zero
|
||||
«one
|
||||
two
|
||||
threeˇ»
|
||||
four
|
||||
five
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
// Move down past the block.
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.move_down(&Default::default(), cx);
|
||||
});
|
||||
cx.assert_editor_state(
|
||||
&"
|
||||
zero
|
||||
one
|
||||
two
|
||||
three
|
||||
ˇfour
|
||||
five
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_transpose(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -4089,21 +4163,48 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
let language_with_c_comments = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
let language_with_pound_comments = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["# ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
let language_with_doc_comments = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into(), "/// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
let plaintext_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Plain Text".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
// ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
|
||||
@@ -4114,29 +4215,17 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
|
||||
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
|
||||
// porttitor id. Aliquam id accumsan eros.ˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
"},
|
||||
language_with_c_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test that rewrapping works inside of a selection
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
«// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
|
||||
@@ -4147,32 +4236,20 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
|
||||
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
|
||||
// porttitor id. Aliquam id accumsan eros.ˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
"},
|
||||
language_with_c_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test that cursors that expand to the same region are collapsed.
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
// ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
// ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
|
||||
// ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
|
||||
// ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
|
||||
@@ -4182,37 +4259,25 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
// et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
|
||||
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
|
||||
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
|
||||
// porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
// porttitor id. Aliquam id accumsan eros.ˇ
|
||||
"},
|
||||
language_with_c_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test that non-contiguous selections are treated separately.
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
// ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
// ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
|
||||
//
|
||||
// ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
|
||||
// ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||
// auctor, eu lacinia sapien scelerisque.ˇˇ
|
||||
// auctor, eu lacinia sapien scelerisque.ˇ
|
||||
//
|
||||
// Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
|
||||
// tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
|
||||
@@ -4220,30 +4285,18 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
// molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
|
||||
// nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
|
||||
// porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
|
||||
// vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
// vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ
|
||||
"},
|
||||
language_with_c_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test that different comment prefixes are supported.
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["# ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
# ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
# purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
|
||||
# eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
|
||||
@@ -4254,63 +4307,39 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
# adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
|
||||
# Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
|
||||
# accumsan eros.ˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
"},
|
||||
language_with_pound_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test that rewrapping is ignored outside of comments in most languages.
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into(), "/// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
/// Adds two numbers.
|
||||
/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
|
||||
fn add(a: u32, b: u32) -> u32 {
|
||||
a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
|
||||
}
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
/// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
/// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
|
||||
fn add(a: u32, b: u32) -> u32 {
|
||||
a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
|
||||
}
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
"},
|
||||
language_with_doc_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test that rewrapping works in Markdown and Plain Text languages.
|
||||
{
|
||||
let markdown_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Markdown".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
# Hello
|
||||
|
||||
Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
# Hello
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
@@ -4320,26 +4349,16 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
|
||||
nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
|
||||
Integer sit amet scelerisque nisi.ˇ
|
||||
"};
|
||||
"},
|
||||
markdown_language,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
|
||||
let plaintext_language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Plain Text".into(),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
|
||||
eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
|
||||
@@ -4347,25 +4366,14 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
|
||||
nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
|
||||
Integer sit amet scelerisque nisi.ˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
"},
|
||||
plaintext_language,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
// Test rewrapping unaligned comments in a selection.
|
||||
{
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into(), "/// ".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
fn foo() {
|
||||
if true {
|
||||
« // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
|
||||
@@ -4375,9 +4383,8 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
//
|
||||
}
|
||||
}
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
fn foo() {
|
||||
if true {
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
|
||||
@@ -4388,13 +4395,13 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
//
|
||||
}
|
||||
}
|
||||
"};
|
||||
"},
|
||||
language_with_doc_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
|
||||
let unwrapped_text = indoc! {"
|
||||
assert_rewrap(
|
||||
indoc! {"
|
||||
fn foo() {
|
||||
if true {
|
||||
«ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
|
||||
@@ -4405,9 +4412,8 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
}
|
||||
"};
|
||||
|
||||
let wrapped_text = indoc! {"
|
||||
"},
|
||||
indoc! {"
|
||||
fn foo() {
|
||||
if true {
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
|
||||
@@ -4419,8 +4425,19 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
}
|
||||
"};
|
||||
"},
|
||||
language_with_doc_comments.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
fn assert_rewrap(
|
||||
unwrapped_text: &str,
|
||||
wrapped_text: &str,
|
||||
language: Arc<Language>,
|
||||
cx: &mut EditorTestContext,
|
||||
) {
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
@@ -8315,6 +8332,74 @@ async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "Range".into(),
|
||||
sort_text: Some("a".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "r".into(),
|
||||
sort_text: Some("b".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "ret".into(),
|
||||
sort_text: Some("c".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "return".into(),
|
||||
sort_text: Some("d".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "slice".into(),
|
||||
sort_text: Some("d".into()),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.set_state("rˇ");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.show_completions(
|
||||
&ShowCompletions {
|
||||
trigger: Some("r".into()),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["r", "ret", "Range", "return"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -8533,6 +8618,131 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
let toggle_comments = &ToggleComments {
|
||||
advance_downwards: false,
|
||||
ignore_indent: true,
|
||||
};
|
||||
|
||||
// If multiple selections intersect a line, the line is only toggled once.
|
||||
cx.set_state(indoc! {"
|
||||
fn a() {
|
||||
// «b();
|
||||
// c();
|
||||
// ˇ» d();
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn a() {
|
||||
«b();
|
||||
c();
|
||||
ˇ» d();
|
||||
}
|
||||
"});
|
||||
|
||||
// The comment prefix is inserted at the beginning of each line
|
||||
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn a() {
|
||||
// «b();
|
||||
// c();
|
||||
// ˇ» d();
|
||||
}
|
||||
"});
|
||||
|
||||
// If a selection ends at the beginning of a line, that line is not toggled.
|
||||
cx.set_selections_state(indoc! {"
|
||||
fn a() {
|
||||
// b();
|
||||
// «c();
|
||||
ˇ»// d();
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn a() {
|
||||
// b();
|
||||
«c();
|
||||
ˇ»// d();
|
||||
}
|
||||
"});
|
||||
|
||||
// If a selection span a single line and is empty, the line is toggled.
|
||||
cx.set_state(indoc! {"
|
||||
fn a() {
|
||||
a();
|
||||
b();
|
||||
ˇ
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn a() {
|
||||
a();
|
||||
b();
|
||||
//ˇ
|
||||
}
|
||||
"});
|
||||
|
||||
// If a selection span multiple lines, empty lines are not toggled.
|
||||
cx.set_state(indoc! {"
|
||||
fn a() {
|
||||
«a();
|
||||
|
||||
c();ˇ»
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn a() {
|
||||
// «a();
|
||||
|
||||
// c();ˇ»
|
||||
}
|
||||
"});
|
||||
|
||||
// If a selection includes multiple comment prefixes, all lines are uncommented.
|
||||
cx.set_state(indoc! {"
|
||||
fn a() {
|
||||
// «a();
|
||||
/// b();
|
||||
//! c();ˇ»
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn a() {
|
||||
«a();
|
||||
b();
|
||||
c();ˇ»
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -8554,6 +8764,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
|
||||
let toggle_comments = &ToggleComments {
|
||||
advance_downwards: true,
|
||||
ignore_indent: false,
|
||||
};
|
||||
|
||||
// Single cursor on one line -> advance
|
||||
@@ -13204,6 +13415,89 @@ async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::T
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
|
||||
let text = r#"
|
||||
#[cfg(test)]
|
||||
mod tests() {
|
||||
#[test]
|
||||
fn runnable_1() {
|
||||
let a = 1;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runnable_2() {
|
||||
let a = 1;
|
||||
let b = 2;
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
|
||||
let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
Editor::new(
|
||||
EditorMode::Full,
|
||||
multi_buffer,
|
||||
Some(project.clone()),
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.tasks.insert(
|
||||
(buffer.read(cx).remote_id(), 3),
|
||||
RunnableTasks {
|
||||
templates: vec![],
|
||||
offset: MultiBufferOffset(43),
|
||||
column: 0,
|
||||
extra_variables: HashMap::default(),
|
||||
context_range: BufferOffset(43)..BufferOffset(85),
|
||||
},
|
||||
);
|
||||
editor.tasks.insert(
|
||||
(buffer.read(cx).remote_id(), 8),
|
||||
RunnableTasks {
|
||||
templates: vec![],
|
||||
offset: MultiBufferOffset(86),
|
||||
column: 0,
|
||||
extra_variables: HashMap::default(),
|
||||
context_range: BufferOffset(86)..BufferOffset(191),
|
||||
},
|
||||
);
|
||||
|
||||
// Test finding task when cursor is inside function body
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
||||
});
|
||||
let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
|
||||
assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
|
||||
|
||||
// Test finding task when cursor is on function name
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
|
||||
});
|
||||
let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
|
||||
assert_eq!(row, 8, "Should find task when cursor is on function name");
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||
point..point
|
||||
|
||||
@@ -19,15 +19,14 @@ use crate::{
|
||||
BlockId, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
|
||||
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
|
||||
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts,
|
||||
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||
use gpui::Subscription;
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||
@@ -38,6 +37,7 @@ use gpui::{
|
||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use gpui::{ClickEvent, Subscription};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::{
|
||||
@@ -449,7 +449,8 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::apply_all_diff_hunks);
|
||||
register_action(view, cx, Editor::apply_selected_diff_hunks);
|
||||
register_action(view, cx, Editor::open_active_item_in_terminal);
|
||||
register_action(view, cx, Editor::reload_file)
|
||||
register_action(view, cx, Editor::reload_file);
|
||||
register_action(view, cx, Editor::spawn_nearest_task);
|
||||
}
|
||||
|
||||
fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) {
|
||||
@@ -649,12 +650,14 @@ impl EditorElement {
|
||||
cx.stop_propagation();
|
||||
} else if end_selection && pending_nonempty_selections {
|
||||
cx.stop_propagation();
|
||||
} else if cfg!(target_os = "linux") && event.button == MouseButton::Middle {
|
||||
} else if cfg!(any(target_os = "linux", target_os = "freebsd"))
|
||||
&& event.button == MouseButton::Middle
|
||||
{
|
||||
if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
if EditorSettings::get_global(cx).middle_click_paste {
|
||||
if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
|
||||
let point_for_position =
|
||||
@@ -808,10 +811,12 @@ impl EditorElement {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_selections(
|
||||
&self,
|
||||
start_anchor: Anchor,
|
||||
end_anchor: Anchor,
|
||||
local_selections: &[Selection<Point>],
|
||||
snapshot: &EditorSnapshot,
|
||||
start_row: DisplayRow,
|
||||
end_row: DisplayRow,
|
||||
@@ -826,13 +831,9 @@ impl EditorElement {
|
||||
let mut newest_selection_head = None;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if editor.show_local_selections {
|
||||
let mut local_selections: Vec<Selection<Point>> = editor
|
||||
.selections
|
||||
.disjoint_in_range(start_anchor..end_anchor, cx);
|
||||
local_selections.extend(editor.selections.pending(cx));
|
||||
let mut layouts = Vec::new();
|
||||
let newest = editor.selections.newest(cx);
|
||||
for selection in local_selections.drain(..) {
|
||||
for selection in local_selections.iter().cloned() {
|
||||
let is_empty = selection.start == selection.end;
|
||||
let is_newest = selection == newest;
|
||||
|
||||
@@ -995,6 +996,7 @@ impl EditorElement {
|
||||
&self,
|
||||
snapshot: &EditorSnapshot,
|
||||
selections: &[(PlayerColor, Vec<SelectionLayout>)],
|
||||
block_start_rows: &HashSet<DisplayRow>,
|
||||
visible_display_row_range: Range<DisplayRow>,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
text_hitbox: &Hitbox,
|
||||
@@ -1014,7 +1016,10 @@ impl EditorElement {
|
||||
let cursor_position = selection.head;
|
||||
|
||||
let in_range = visible_display_row_range.contains(&cursor_position.row());
|
||||
if (selection.is_local && !editor.show_local_cursors(cx)) || !in_range {
|
||||
if (selection.is_local && !editor.show_local_cursors(cx))
|
||||
|| !in_range
|
||||
|| block_start_rows.contains(&cursor_position.row())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1965,10 +1970,10 @@ impl EditorElement {
|
||||
|
||||
fn layout_lines(
|
||||
rows: Range<DisplayRow>,
|
||||
line_number_layouts: &[Option<ShapedLine>],
|
||||
snapshot: &EditorSnapshot,
|
||||
style: &EditorStyle,
|
||||
editor_width: Pixels,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<LineWithInvisibles> {
|
||||
if rows.start >= rows.end {
|
||||
@@ -2017,9 +2022,9 @@ impl EditorElement {
|
||||
&style.text,
|
||||
MAX_LINE_LEN,
|
||||
rows.len(),
|
||||
line_number_layouts,
|
||||
snapshot.mode,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -2067,23 +2072,43 @@ impl EditorElement {
|
||||
editor_width: Pixels,
|
||||
scroll_width: &mut Pixels,
|
||||
resized_blocks: &mut HashMap<CustomBlockId, u32>,
|
||||
selections: &[Selection<Point>],
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> (AnyElement, Size<Pixels>) {
|
||||
let mut element = match block {
|
||||
Block::Custom(block) => {
|
||||
let align_to = block
|
||||
.start()
|
||||
.to_point(&snapshot.buffer_snapshot)
|
||||
.to_display_point(snapshot);
|
||||
let block_start = block.start().to_point(&snapshot.buffer_snapshot);
|
||||
let block_end = block.end().to_point(&snapshot.buffer_snapshot);
|
||||
let align_to = block_start.to_display_point(snapshot);
|
||||
let anchor_x = text_x
|
||||
+ if rows.contains(&align_to.row()) {
|
||||
line_layouts[align_to.row().minus(rows.start) as usize]
|
||||
.x_for_index(align_to.column() as usize)
|
||||
} else {
|
||||
layout_line(align_to.row(), snapshot, &self.style, editor_width, cx)
|
||||
.x_for_index(align_to.column() as usize)
|
||||
layout_line(
|
||||
align_to.row(),
|
||||
snapshot,
|
||||
&self.style,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
.x_for_index(align_to.column() as usize)
|
||||
};
|
||||
|
||||
let selected = selections
|
||||
.binary_search_by(|selection| {
|
||||
if selection.end <= block_start {
|
||||
Ordering::Less
|
||||
} else if selection.start >= block_end {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
})
|
||||
.is_ok();
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.child(block.render(&mut BlockContext {
|
||||
@@ -2093,6 +2118,7 @@ impl EditorElement {
|
||||
line_height,
|
||||
em_width,
|
||||
block_id,
|
||||
selected,
|
||||
max_width: text_hitbox.size.width.max(*scroll_width),
|
||||
editor_style: &self.style,
|
||||
}))
|
||||
@@ -2109,14 +2135,6 @@ impl EditorElement {
|
||||
height,
|
||||
..
|
||||
} => {
|
||||
#[derive(Clone)]
|
||||
struct JumpData {
|
||||
position: Point,
|
||||
anchor: text::Anchor,
|
||||
path: ProjectPath,
|
||||
line_offset_from_top: u32,
|
||||
}
|
||||
|
||||
let icon_offset = gutter_dimensions.width
|
||||
- (gutter_dimensions.left_padding + gutter_dimensions.margin);
|
||||
|
||||
@@ -2145,11 +2163,12 @@ impl EditorElement {
|
||||
if let Some(next_excerpt) = next_excerpt {
|
||||
let buffer = &next_excerpt.buffer;
|
||||
let range = &next_excerpt.range;
|
||||
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
|
||||
let jump_path = ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path.clone(),
|
||||
};
|
||||
let jump_data = {
|
||||
let jump_path =
|
||||
project::File::from_dyn(buffer.file()).map(|file| ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path.clone(),
|
||||
});
|
||||
let jump_anchor = range
|
||||
.primary
|
||||
.as_ref()
|
||||
@@ -2164,21 +2183,20 @@ impl EditorElement {
|
||||
language::ToPoint::to_point(&jump_anchor, buffer).row;
|
||||
jump_position.row - excerpt_start_row
|
||||
};
|
||||
|
||||
let line_offset_from_top =
|
||||
block_row_start.0 + *height + offset_from_excerpt_start
|
||||
- snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32;
|
||||
|
||||
JumpData {
|
||||
position: jump_position,
|
||||
excerpt_id: next_excerpt.id,
|
||||
anchor: jump_anchor,
|
||||
position: language::ToPoint::to_point(&jump_anchor, buffer),
|
||||
path: jump_path,
|
||||
line_offset_from_top,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if *starts_new_buffer {
|
||||
let include_root = self
|
||||
@@ -2233,31 +2251,23 @@ impl EditorElement {
|
||||
}),
|
||||
),
|
||||
)
|
||||
.when_some(jump_data, |el, jump_data| {
|
||||
el.child(Icon::new(IconName::ArrowUpRight))
|
||||
.cursor_pointer()
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Jump to File",
|
||||
&OpenExcerpts,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| {
|
||||
cx.stop_propagation()
|
||||
})
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
move |editor, _, cx| {
|
||||
editor.jump(
|
||||
jump_data.path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
}),
|
||||
.child(Icon::new(IconName::ArrowUpRight))
|
||||
.cursor_pointer()
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action("Jump to File", &OpenExcerpts, cx)
|
||||
})
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| {
|
||||
cx.stop_propagation()
|
||||
})
|
||||
.on_click(cx.listener_for(&self.editor, {
|
||||
move |editor, e: &ClickEvent, cx| {
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.down.modifiers.secondary(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
),
|
||||
);
|
||||
if *show_excerpt_controls {
|
||||
@@ -2276,6 +2286,7 @@ impl EditorElement {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let editor = self.editor.clone();
|
||||
result = result.child(
|
||||
h_flex()
|
||||
.id("excerpt header block")
|
||||
@@ -2296,33 +2307,52 @@ impl EditorElement {
|
||||
}),
|
||||
)
|
||||
.cursor_pointer()
|
||||
.when_some(jump_data.clone(), |this, jump_data| {
|
||||
this.on_click(cx.listener_for(&self.editor, {
|
||||
let path = jump_data.path.clone();
|
||||
move |editor, _, cx| {
|
||||
.on_click({
|
||||
let jump_data = jump_data.clone();
|
||||
cx.listener_for(&self.editor, {
|
||||
let jump_data = jump_data.clone();
|
||||
move |editor, e: &ClickEvent, cx| {
|
||||
cx.stop_propagation();
|
||||
|
||||
editor.jump(
|
||||
path.clone(),
|
||||
jump_data.position,
|
||||
jump_data.anchor,
|
||||
jump_data.line_offset_from_top,
|
||||
editor.open_excerpts_common(
|
||||
Some(jump_data.clone()),
|
||||
e.down.modifiers.secondary(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
format!(
|
||||
"Jump to {}:L{}",
|
||||
jump_data.path.path.display(),
|
||||
jump_data.position.row + 1
|
||||
),
|
||||
&OpenExcerpts,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.tooltip({
|
||||
let jump_data = jump_data.clone();
|
||||
move |cx| {
|
||||
let jump_message = format!(
|
||||
"Jump to {}:L{}",
|
||||
match &jump_data.path {
|
||||
Some(project_path) =>
|
||||
project_path.path.display().to_string(),
|
||||
None => {
|
||||
let editor = editor.read(cx);
|
||||
editor
|
||||
.file_at(jump_data.position, cx)
|
||||
.map(|file| {
|
||||
file.full_path(cx).display().to_string()
|
||||
})
|
||||
.or_else(|| {
|
||||
Some(
|
||||
editor
|
||||
.tab_description(0, cx)?
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
"Unknown buffer".to_string()
|
||||
})
|
||||
}
|
||||
},
|
||||
jump_data.position.row + 1
|
||||
);
|
||||
Tooltip::for_action(jump_message, &OpenExcerpts, cx)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w(icon_offset)
|
||||
@@ -2430,6 +2460,8 @@ impl EditorElement {
|
||||
text_x: Pixels,
|
||||
line_height: Pixels,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
selections: &[Selection<Point>],
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
|
||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||
@@ -2466,6 +2498,8 @@ impl EditorElement {
|
||||
editor_width,
|
||||
scroll_width,
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
);
|
||||
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
|
||||
@@ -2510,6 +2544,8 @@ impl EditorElement {
|
||||
editor_width,
|
||||
scroll_width,
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -2555,6 +2591,8 @@ impl EditorElement {
|
||||
editor_width,
|
||||
scroll_width,
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -2583,6 +2621,7 @@ impl EditorElement {
|
||||
fn layout_blocks(
|
||||
&self,
|
||||
blocks: &mut Vec<BlockLayout>,
|
||||
block_starts: &mut HashSet<DisplayRow>,
|
||||
hitbox: &Hitbox,
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
@@ -2590,6 +2629,7 @@ impl EditorElement {
|
||||
) {
|
||||
for block in blocks {
|
||||
let mut origin = if let Some(row) = block.row {
|
||||
block_starts.insert(row);
|
||||
hitbox.origin
|
||||
+ point(
|
||||
Pixels::ZERO,
|
||||
@@ -4337,9 +4377,9 @@ impl LineWithInvisibles {
|
||||
text_style: &TextStyle,
|
||||
max_line_len: usize,
|
||||
max_line_count: usize,
|
||||
line_number_layouts: &[Option<ShapedLine>],
|
||||
editor_mode: EditorMode,
|
||||
text_width: Pixels,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<Self> {
|
||||
let mut layouts = Vec::with_capacity(max_line_count);
|
||||
@@ -4467,12 +4507,9 @@ impl LineWithInvisibles {
|
||||
if editor_mode == EditorMode::Full {
|
||||
// Line wrap pads its contents with fake whitespaces,
|
||||
// avoid printing them
|
||||
let inside_wrapped_string = line_number_layouts
|
||||
.get(row)
|
||||
.and_then(|layout| layout.as_ref())
|
||||
.is_none();
|
||||
let is_soft_wrapped = is_row_soft_wrapped(row);
|
||||
if highlighted_chunk.is_tab {
|
||||
if non_whitespace_added || !inside_wrapped_string {
|
||||
if non_whitespace_added || !is_soft_wrapped {
|
||||
invisibles.push(Invisible::Tab {
|
||||
line_start_offset: line.len(),
|
||||
line_end_offset: line.len() + line_chunk.len(),
|
||||
@@ -4488,7 +4525,7 @@ impl LineWithInvisibles {
|
||||
(*line_byte as char).is_whitespace();
|
||||
non_whitespace_added |= !is_whitespace;
|
||||
is_whitespace
|
||||
&& (non_whitespace_added || !inside_wrapped_string)
|
||||
&& (non_whitespace_added || !is_soft_wrapped)
|
||||
})
|
||||
.map(|(whitespace_index, _)| Invisible::Whitespace {
|
||||
line_offset: line.len() + whitespace_index,
|
||||
@@ -4851,10 +4888,10 @@ impl Element for EditorElement {
|
||||
editor_handle.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
let line = Self::layout_lines(
|
||||
DisplayRow(0)..DisplayRow(1),
|
||||
&[],
|
||||
&editor_snapshot,
|
||||
&style,
|
||||
px(f32::MAX),
|
||||
|_| false, // Single lines never soft wrap
|
||||
cx,
|
||||
)
|
||||
.pop()
|
||||
@@ -5063,6 +5100,8 @@ impl Element for EditorElement {
|
||||
.buffer_rows(start_row)
|
||||
.take((start_row..end_row).len())
|
||||
.collect::<Vec<_>>();
|
||||
let is_row_soft_wrapped =
|
||||
|row| buffer_rows.get(row).copied().flatten().is_none();
|
||||
|
||||
let start_anchor = if start_row == Default::default() {
|
||||
Anchor::min()
|
||||
@@ -5100,9 +5139,19 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
|
||||
let local_selections: Vec<Selection<Point>> =
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let mut selections = editor
|
||||
.selections
|
||||
.disjoint_in_range(start_anchor..end_anchor, cx);
|
||||
selections.extend(editor.selections.pending(cx));
|
||||
selections
|
||||
});
|
||||
|
||||
let (selections, active_rows, newest_selection_head) = self.layout_selections(
|
||||
start_anchor,
|
||||
end_anchor,
|
||||
&local_selections,
|
||||
&snapshot,
|
||||
start_row,
|
||||
end_row,
|
||||
@@ -5144,10 +5193,10 @@ impl Element for EditorElement {
|
||||
let mut max_visible_line_width = Pixels::ZERO;
|
||||
let mut line_layouts = Self::layout_lines(
|
||||
start_row..end_row,
|
||||
&line_numbers,
|
||||
&snapshot,
|
||||
&self.style,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
);
|
||||
for line_with_invisibles in &line_layouts {
|
||||
@@ -5156,9 +5205,15 @@ impl Element for EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
let longest_line_width =
|
||||
layout_line(snapshot.longest_row(), &snapshot, &style, editor_width, cx)
|
||||
.width;
|
||||
let longest_line_width = layout_line(
|
||||
snapshot.longest_row(),
|
||||
&snapshot,
|
||||
&style,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
.width;
|
||||
let mut scroll_width =
|
||||
longest_line_width.max(max_visible_line_width) + overscroll.width;
|
||||
|
||||
@@ -5175,6 +5230,8 @@ impl Element for EditorElement {
|
||||
gutter_dimensions.full_width(),
|
||||
line_height,
|
||||
&line_layouts,
|
||||
&local_selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -5314,9 +5371,11 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
|
||||
let mut block_start_rows = HashSet::default();
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
self.layout_blocks(
|
||||
&mut blocks,
|
||||
&mut block_start_rows,
|
||||
&hitbox,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
@@ -5333,6 +5392,7 @@ impl Element for EditorElement {
|
||||
let visible_cursors = self.layout_visible_cursors(
|
||||
&snapshot,
|
||||
&selections,
|
||||
&block_start_rows,
|
||||
start_row..end_row,
|
||||
&line_layouts,
|
||||
&text_hitbox,
|
||||
@@ -5930,6 +5990,7 @@ fn layout_line(
|
||||
snapshot: &EditorSnapshot,
|
||||
style: &EditorStyle,
|
||||
text_width: Pixels,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> LineWithInvisibles {
|
||||
let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
|
||||
@@ -5938,9 +5999,9 @@ fn layout_line(
|
||||
&style.text,
|
||||
MAX_LINE_LEN,
|
||||
1,
|
||||
&[],
|
||||
snapshot.mode,
|
||||
text_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
.pop()
|
||||
@@ -6625,15 +6686,22 @@ mod tests {
|
||||
"Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
|
||||
);
|
||||
|
||||
init_test(cx, |s| {
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
|
||||
});
|
||||
for show_line_numbers in [true, false] {
|
||||
init_test(cx, |s| {
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
|
||||
});
|
||||
|
||||
let actual_invisibles =
|
||||
collect_invisibles_from_new_editor(cx, EditorMode::Full, input_text, px(500.0));
|
||||
let actual_invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
EditorMode::Full,
|
||||
input_text,
|
||||
px(500.0),
|
||||
show_line_numbers,
|
||||
);
|
||||
|
||||
assert_eq!(expected_invisibles, actual_invisibles);
|
||||
assert_eq!(expected_invisibles, actual_invisibles);
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -6647,14 +6715,17 @@ mod tests {
|
||||
EditorMode::SingleLine { auto_width: false },
|
||||
EditorMode::AutoHeight { max_lines: 100 },
|
||||
] {
|
||||
let invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
editor_mode_without_invisibles,
|
||||
"\t\t\t| | a b",
|
||||
px(500.0),
|
||||
);
|
||||
assert!(invisibles.is_empty(),
|
||||
for show_line_numbers in [true, false] {
|
||||
let invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
editor_mode_without_invisibles,
|
||||
"\t\t\t| | a b",
|
||||
px(500.0),
|
||||
show_line_numbers,
|
||||
);
|
||||
assert!(invisibles.is_empty(),
|
||||
"For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6705,43 +6776,48 @@ mod tests {
|
||||
let resize_step = 10.0;
|
||||
let mut editor_width = 200.0;
|
||||
while editor_width <= 1000.0 {
|
||||
update_test_language_settings(cx, |s| {
|
||||
s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.preferred_line_length = Some(editor_width as u32);
|
||||
s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
|
||||
});
|
||||
for show_line_numbers in [true, false] {
|
||||
update_test_language_settings(cx, |s| {
|
||||
s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.preferred_line_length = Some(editor_width as u32);
|
||||
s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
|
||||
});
|
||||
|
||||
let actual_invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
EditorMode::Full,
|
||||
&input_text,
|
||||
px(editor_width),
|
||||
);
|
||||
let actual_invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
EditorMode::Full,
|
||||
&input_text,
|
||||
px(editor_width),
|
||||
show_line_numbers,
|
||||
);
|
||||
|
||||
// Whatever the editor size is, ensure it has the same invisible kinds in the same order
|
||||
// (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
|
||||
let mut i = 0;
|
||||
for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
|
||||
i = actual_index;
|
||||
match expected_invisibles.get(i) {
|
||||
Some(expected_invisible) => match (expected_invisible, actual_invisible) {
|
||||
(Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
|
||||
| (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
|
||||
_ => {
|
||||
panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
|
||||
// Whatever the editor size is, ensure it has the same invisible kinds in the same order
|
||||
// (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
|
||||
let mut i = 0;
|
||||
for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
|
||||
i = actual_index;
|
||||
match expected_invisibles.get(i) {
|
||||
Some(expected_invisible) => match (expected_invisible, actual_invisible) {
|
||||
(Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
|
||||
| (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
|
||||
_ => {
|
||||
panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!("Unexpected extra invisible {actual_invisible:?} at index {i}")
|
||||
}
|
||||
},
|
||||
None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
let missing_expected_invisibles = &expected_invisibles[i + 1..];
|
||||
assert!(
|
||||
missing_expected_invisibles.is_empty(),
|
||||
"Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
|
||||
);
|
||||
let missing_expected_invisibles = &expected_invisibles[i + 1..];
|
||||
assert!(
|
||||
missing_expected_invisibles.is_empty(),
|
||||
"Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
|
||||
);
|
||||
|
||||
editor_width += resize_step;
|
||||
editor_width += resize_step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6750,6 +6826,7 @@ mod tests {
|
||||
editor_mode: EditorMode,
|
||||
input_text: &str,
|
||||
editor_width: Pixels,
|
||||
show_line_numbers: bool,
|
||||
) -> Vec<Invisible> {
|
||||
info!(
|
||||
"Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
|
||||
@@ -6761,11 +6838,13 @@ mod tests {
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let editor = window.root(cx).unwrap();
|
||||
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_wrap_width(Some(editor_width), cx);
|
||||
editor.set_show_line_numbers(show_line_numbers, cx);
|
||||
})
|
||||
.unwrap();
|
||||
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
};
|
||||
use gpui::{
|
||||
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
|
||||
MouseButton, ParentElement, Pixels, ScrollHandle, Size, StatefulInteractiveElement,
|
||||
MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement,
|
||||
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@@ -21,7 +21,7 @@ use std::rc::Rc;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, window_is_transparent};
|
||||
use ui::{prelude::*, window_is_transparent, Scrollbar, ScrollbarState};
|
||||
use util::TryFutureExt;
|
||||
pub const HOVER_DELAY_MILLIS: u64 = 350;
|
||||
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
|
||||
@@ -144,10 +144,12 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||
let blocks = vec![inlay_hover.tooltip];
|
||||
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
|
||||
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let hover_popover = InfoPopover {
|
||||
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
|
||||
parsed_content,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
keyboard_grace: Rc::new(RefCell::new(false)),
|
||||
anchor: None,
|
||||
};
|
||||
@@ -435,12 +437,14 @@ fn show_hover(
|
||||
let language = hover_result.language;
|
||||
let parsed_content =
|
||||
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
info_popover_tasks.push((
|
||||
range.clone(),
|
||||
InfoPopover {
|
||||
symbol_range: RangeInEditor::Text(range),
|
||||
parsed_content,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
},
|
||||
@@ -611,7 +615,7 @@ impl HoverState {
|
||||
!self.info_popovers.is_empty() || self.diagnostic_popover.is_some()
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
pub(crate) fn render(
|
||||
&mut self,
|
||||
snapshot: &EditorSnapshot,
|
||||
visible_rows: Range<DisplayRow>,
|
||||
@@ -680,25 +684,25 @@ impl HoverState {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
pub struct InfoPopover {
|
||||
pub symbol_range: RangeInEditor,
|
||||
pub parsed_content: Option<View<Markdown>>,
|
||||
pub scroll_handle: ScrollHandle,
|
||||
pub keyboard_grace: Rc<RefCell<bool>>,
|
||||
pub anchor: Option<Anchor>,
|
||||
pub(crate) struct InfoPopover {
|
||||
pub(crate) symbol_range: RangeInEditor,
|
||||
pub(crate) parsed_content: Option<View<Markdown>>,
|
||||
pub(crate) scroll_handle: ScrollHandle,
|
||||
pub(crate) scrollbar_state: ScrollbarState,
|
||||
pub(crate) keyboard_grace: Rc<RefCell<bool>>,
|
||||
pub(crate) anchor: Option<Anchor>,
|
||||
}
|
||||
|
||||
impl InfoPopover {
|
||||
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
|
||||
pub(crate) fn render(
|
||||
&mut self,
|
||||
max_size: Size<Pixels>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> AnyElement {
|
||||
let keyboard_grace = Rc::clone(&self.keyboard_grace);
|
||||
let mut d = div()
|
||||
.id("info_popover")
|
||||
.elevation_2(cx)
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
// Prevent a mouse down/move on the popover from being propagated to the editor,
|
||||
// because that would dismiss the popover.
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
@@ -706,11 +710,21 @@ impl InfoPopover {
|
||||
let mut keyboard_grace = keyboard_grace.borrow_mut();
|
||||
*keyboard_grace = false;
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.p_2();
|
||||
});
|
||||
|
||||
if let Some(markdown) = &self.parsed_content {
|
||||
d = d.child(markdown.clone());
|
||||
d = d
|
||||
.child(
|
||||
div()
|
||||
.id("info-md-container")
|
||||
.overflow_y_scroll()
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
.p_2()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.child(markdown.clone()),
|
||||
)
|
||||
.child(self.render_vertical_scrollbar(cx));
|
||||
}
|
||||
d.into_any_element()
|
||||
}
|
||||
@@ -724,6 +738,38 @@ impl InfoPopover {
|
||||
cx.notify();
|
||||
self.scroll_handle.set_offset(current);
|
||||
}
|
||||
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
|
||||
div()
|
||||
.occlude()
|
||||
.id("info-popover-vertical-scroll")
|
||||
.on_mouse_move(cx.listener(|_, _, cx| {
|
||||
cx.notify();
|
||||
cx.stop_propagation()
|
||||
}))
|
||||
.on_hover(|_, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_any_mouse_down(|_, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_mouse_up(
|
||||
MouseButton::Left,
|
||||
cx.listener(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
}),
|
||||
)
|
||||
.on_scroll_wheel(cx.listener(|_, _, cx| {
|
||||
cx.notify();
|
||||
}))
|
||||
.h_full()
|
||||
.absolute()
|
||||
.right_1()
|
||||
.top_1()
|
||||
.bottom_0()
|
||||
.w(px(12.))
|
||||
.cursor_default()
|
||||
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1351,6 +1397,61 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
// https://github.com/zed-industries/zed/issues/15498
|
||||
async fn test_info_hover_with_hrs(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn fuˇnc(abc def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.lsp.handle_request::<lsp::request::HoverRequest, _, _>({
|
||||
|_, _| async move {
|
||||
Ok(Some(lsp::Hover {
|
||||
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
|
||||
kind: lsp::MarkupKind::Markdown,
|
||||
value: indoc!(
|
||||
r#"
|
||||
### function `errands_data_read`
|
||||
|
||||
---
|
||||
→ `char *`
|
||||
Function to read a file into a string
|
||||
|
||||
---
|
||||
```cpp
|
||||
static char *errands_data_read()
|
||||
```
|
||||
"#
|
||||
)
|
||||
.to_string(),
|
||||
}),
|
||||
range: None,
|
||||
}))
|
||||
}
|
||||
});
|
||||
cx.update_editor(|editor, cx| hover(editor, &Default::default(), cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
let popover = editor.hover_state.info_popovers.first().unwrap();
|
||||
let content = popover.get_rendered_text(cx);
|
||||
|
||||
assert!(content.contains("Function to read a file"));
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
|
||||
@@ -992,12 +992,15 @@ impl SerializableItem for Editor {
|
||||
};
|
||||
|
||||
// First create the empty buffer
|
||||
let buffer = project.update(&mut cx, |project, cx| {
|
||||
project.create_local_buffer("", language, cx)
|
||||
})?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |project, cx| project.create_buffer(cx))?
|
||||
.await?;
|
||||
|
||||
// Then set the text so that the dirty bit is set correctly
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
if let Some(language) = language {
|
||||
buffer.set_language(Some(language), cx);
|
||||
}
|
||||
buffer.set_text(contents, cx);
|
||||
})?;
|
||||
|
||||
@@ -1277,7 +1280,7 @@ impl SearchableItem for Editor {
|
||||
matches: &[Range<Anchor>],
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.unfold_ranges([matches[index].clone()], false, true, cx);
|
||||
self.unfold_ranges(&[matches[index].clone()], false, true, cx);
|
||||
let range = self.range_for_match(&matches[index]);
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
@@ -1285,7 +1288,7 @@ impl SearchableItem for Editor {
|
||||
}
|
||||
|
||||
fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
|
||||
self.unfold_ranges(matches.to_vec(), false, false, cx);
|
||||
self.unfold_ranges(matches, false, false, cx);
|
||||
let mut ranges = Vec::new();
|
||||
for m in matches {
|
||||
ranges.push(self.range_for_match(m))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::{ops::Range, time::Duration};
|
||||
|
||||
use collections::HashMap;
|
||||
use itertools::Itertools;
|
||||
@@ -36,35 +36,53 @@ impl LinkedEditingRanges {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Editor>) -> Option<()> {
|
||||
if this.pending_rename.is_some() {
|
||||
|
||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
// TODO do not refresh anything at all, if the settings/capabilities do not have it enabled.
|
||||
pub(super) fn refresh_linked_ranges(
|
||||
editor: &mut Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<()> {
|
||||
if editor.pending_rename.is_some() {
|
||||
return None;
|
||||
}
|
||||
let project = this.project.clone()?;
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let buffer = this.buffer.read(cx);
|
||||
let mut applicable_selections = vec![];
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
let start_position = snapshot.anchor_before(cursor_position);
|
||||
let end_position = snapshot.anchor_after(selection.tail());
|
||||
if start_position.buffer_id != end_position.buffer_id || end_position.buffer_id.is_none() {
|
||||
// Throw away selections spanning multiple buffers.
|
||||
continue;
|
||||
let project = editor.project.as_ref()?.downgrade();
|
||||
|
||||
editor.linked_editing_range_task = Some(cx.spawn(|editor, mut cx| async move {
|
||||
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
|
||||
|
||||
let mut applicable_selections = Vec::new();
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let selections = editor.selections.all::<usize>(cx);
|
||||
let snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
let buffer = editor.buffer.read(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
let start_position = snapshot.anchor_before(cursor_position);
|
||||
let end_position = snapshot.anchor_after(selection.tail());
|
||||
if start_position.buffer_id != end_position.buffer_id
|
||||
|| end_position.buffer_id.is_none()
|
||||
{
|
||||
// Throw away selections spanning multiple buffers.
|
||||
continue;
|
||||
}
|
||||
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
|
||||
applicable_selections.push((
|
||||
buffer,
|
||||
start_position.text_anchor,
|
||||
end_position.text_anchor,
|
||||
));
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
if applicable_selections.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
|
||||
applicable_selections.push((
|
||||
buffer,
|
||||
start_position.text_anchor,
|
||||
end_position.text_anchor,
|
||||
));
|
||||
}
|
||||
}
|
||||
if applicable_selections.is_empty() {
|
||||
return None;
|
||||
}
|
||||
this.linked_editing_range_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
|
||||
let highlights = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let mut linked_edits_tasks = vec![];
|
||||
@@ -110,37 +128,38 @@ pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Edit
|
||||
}
|
||||
linked_edits_tasks
|
||||
})
|
||||
.log_err()?;
|
||||
.ok()?;
|
||||
|
||||
let highlights = futures::future::join_all(highlights).await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.linked_edit_ranges.0.clear();
|
||||
if this.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
for (buffer_id, ranges) in highlights.into_iter().flatten() {
|
||||
this.linked_edit_ranges
|
||||
.0
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.extend(ranges);
|
||||
}
|
||||
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
|
||||
let Some(snapshot) = this
|
||||
.buffer
|
||||
.read(cx)
|
||||
.buffer(*buffer_id)
|
||||
.map(|buffer| buffer.read(cx).snapshot())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
|
||||
}
|
||||
editor
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.linked_edit_ranges.0.clear();
|
||||
if this.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
for (buffer_id, ranges) in highlights.into_iter().flatten() {
|
||||
this.linked_edit_ranges
|
||||
.0
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.extend(ranges);
|
||||
}
|
||||
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
|
||||
let Some(snapshot) = this
|
||||
.buffer
|
||||
.read(cx)
|
||||
.buffer(*buffer_id)
|
||||
.map(|buffer| buffer.read(cx).snapshot())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
Some(())
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
cell::Ref,
|
||||
iter, mem,
|
||||
cmp, iter, mem,
|
||||
ops::{Deref, DerefMut, Range, Sub},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -98,9 +98,9 @@ impl SelectionsCollection {
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Selection<D>> {
|
||||
self.pending_anchor()
|
||||
.as_ref()
|
||||
.map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx))))
|
||||
let map = self.display_map(cx);
|
||||
let selection = resolve_selections(self.pending_anchor().as_ref(), &map).next();
|
||||
selection
|
||||
}
|
||||
|
||||
pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
|
||||
@@ -111,12 +111,10 @@ impl SelectionsCollection {
|
||||
where
|
||||
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
|
||||
{
|
||||
let map = self.display_map(cx);
|
||||
let disjoint_anchors = &self.disjoint;
|
||||
let mut disjoint =
|
||||
resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
|
||||
|
||||
let mut disjoint = resolve_selections::<D, _>(disjoint_anchors.iter(), &map).peekable();
|
||||
let mut pending_opt = self.pending::<D>(cx);
|
||||
|
||||
iter::from_fn(move || {
|
||||
if let Some(pending) = pending_opt.as_mut() {
|
||||
while let Some(next_selection) = disjoint.peek() {
|
||||
@@ -199,34 +197,57 @@ impl SelectionsCollection {
|
||||
where
|
||||
D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
|
||||
{
|
||||
let buffer = self.buffer(cx);
|
||||
let map = self.display_map(cx);
|
||||
let start_ix = match self
|
||||
.disjoint
|
||||
.binary_search_by(|probe| probe.end.cmp(&range.start, &buffer))
|
||||
.binary_search_by(|probe| probe.end.cmp(&range.start, &map.buffer_snapshot))
|
||||
{
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
let end_ix = match self
|
||||
.disjoint
|
||||
.binary_search_by(|probe| probe.start.cmp(&range.end, &buffer))
|
||||
.binary_search_by(|probe| probe.start.cmp(&range.end, &map.buffer_snapshot))
|
||||
{
|
||||
Ok(ix) => ix + 1,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
resolve_multiple(&self.disjoint[start_ix..end_ix], &buffer).collect()
|
||||
resolve_selections(&self.disjoint[start_ix..end_ix], &map).collect()
|
||||
}
|
||||
|
||||
pub fn all_display(
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
|
||||
let display_map = self.display_map(cx);
|
||||
let selections = self
|
||||
.all::<Point>(cx)
|
||||
.into_iter()
|
||||
.map(|selection| selection.map(|point| point.to_display_point(&display_map)))
|
||||
.collect();
|
||||
(display_map, selections)
|
||||
let map = self.display_map(cx);
|
||||
let disjoint_anchors = &self.disjoint;
|
||||
let mut disjoint = resolve_selections_display(disjoint_anchors.iter(), &map).peekable();
|
||||
let mut pending_opt =
|
||||
resolve_selections_display(self.pending_anchor().as_ref(), &map).next();
|
||||
let selections = iter::from_fn(move || {
|
||||
if let Some(pending) = pending_opt.as_mut() {
|
||||
while let Some(next_selection) = disjoint.peek() {
|
||||
if pending.start <= next_selection.end && pending.end >= next_selection.start {
|
||||
let next_selection = disjoint.next().unwrap();
|
||||
if next_selection.start < pending.start {
|
||||
pending.start = next_selection.start;
|
||||
}
|
||||
if next_selection.end > pending.end {
|
||||
pending.end = next_selection.end;
|
||||
}
|
||||
} else if next_selection.end < pending.start {
|
||||
return disjoint.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pending_opt.take()
|
||||
} else {
|
||||
disjoint.next()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(map, selections)
|
||||
}
|
||||
|
||||
pub fn newest_anchor(&self) -> &Selection<Anchor> {
|
||||
@@ -241,15 +262,18 @@ impl SelectionsCollection {
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
) -> Selection<D> {
|
||||
let buffer = self.buffer(cx);
|
||||
self.newest_anchor().map(|p| p.summary::<D>(&buffer))
|
||||
let map = self.display_map(cx);
|
||||
let selection = resolve_selections([self.newest_anchor()], &map)
|
||||
.next()
|
||||
.unwrap();
|
||||
selection
|
||||
}
|
||||
|
||||
pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
|
||||
let display_map = self.display_map(cx);
|
||||
let selection = self
|
||||
.newest_anchor()
|
||||
.map(|point| point.to_display_point(&display_map));
|
||||
let map = self.display_map(cx);
|
||||
let selection = resolve_selections_display([self.newest_anchor()], &map)
|
||||
.next()
|
||||
.unwrap();
|
||||
selection
|
||||
}
|
||||
|
||||
@@ -265,8 +289,11 @@ impl SelectionsCollection {
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
) -> Selection<D> {
|
||||
let buffer = self.buffer(cx);
|
||||
self.oldest_anchor().map(|p| p.summary::<D>(&buffer))
|
||||
let map = self.display_map(cx);
|
||||
let selection = resolve_selections([self.oldest_anchor()], &map)
|
||||
.next()
|
||||
.unwrap();
|
||||
selection
|
||||
}
|
||||
|
||||
pub fn first_anchor(&self) -> Selection<Anchor> {
|
||||
@@ -538,9 +565,9 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
}
|
||||
|
||||
pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
|
||||
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||
let map = self.display_map();
|
||||
let resolved_selections =
|
||||
resolve_multiple::<usize, _>(&selections, &buffer).collect::<Vec<_>>();
|
||||
resolve_selections::<usize, _>(&selections, &map).collect::<Vec<_>>();
|
||||
self.select(resolved_selections);
|
||||
}
|
||||
|
||||
@@ -650,20 +677,16 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
) {
|
||||
let mut changed = false;
|
||||
let display_map = self.display_map();
|
||||
let selections = self
|
||||
.collection
|
||||
.all::<Point>(self.cx)
|
||||
let (_, selections) = self.collection.all_display(self.cx);
|
||||
let selections = selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let mut moved_selection =
|
||||
selection.map(|point| point.to_display_point(&display_map));
|
||||
let mut moved_selection = selection.clone();
|
||||
move_selection(&display_map, &mut moved_selection);
|
||||
let moved_selection =
|
||||
moved_selection.map(|display_point| display_point.to_point(&display_map));
|
||||
if selection != moved_selection {
|
||||
changed = true;
|
||||
}
|
||||
moved_selection
|
||||
moved_selection.map(|display_point| display_point.to_point(&display_map))
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -804,8 +827,8 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
.collect();
|
||||
|
||||
if !adjusted_disjoint.is_empty() {
|
||||
let resolved_selections =
|
||||
resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect();
|
||||
let map = self.display_map();
|
||||
let resolved_selections = resolve_selections(adjusted_disjoint.iter(), &map).collect();
|
||||
self.select::<usize>(resolved_selections);
|
||||
}
|
||||
|
||||
@@ -849,27 +872,76 @@ impl<'a> DerefMut for MutableSelectionsCollection<'a> {
|
||||
}
|
||||
|
||||
// Panics if passed selections are not in order
|
||||
pub(crate) fn resolve_multiple<'a, D, I>(
|
||||
selections: I,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = Selection<D>>
|
||||
where
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||
{
|
||||
fn resolve_selections_display<'a>(
|
||||
selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||
map: &'a DisplaySnapshot,
|
||||
) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
|
||||
let (to_summarize, selections) = selections.into_iter().tee();
|
||||
let mut summaries = snapshot
|
||||
.summaries_for_anchors::<D, _>(
|
||||
to_summarize
|
||||
.flat_map(|s| [&s.start, &s.end])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
let mut summaries = map
|
||||
.buffer_snapshot
|
||||
.summaries_for_anchors::<Point, _>(to_summarize.flat_map(|s| [&s.start, &s.end]))
|
||||
.into_iter();
|
||||
selections.map(move |s| Selection {
|
||||
id: s.id,
|
||||
start: summaries.next().unwrap(),
|
||||
end: summaries.next().unwrap(),
|
||||
reversed: s.reversed,
|
||||
goal: s.goal,
|
||||
let mut selections = selections
|
||||
.map(move |s| {
|
||||
let start = summaries.next().unwrap();
|
||||
let end = summaries.next().unwrap();
|
||||
|
||||
let display_start = map.point_to_display_point(start, Bias::Left);
|
||||
let display_end = if start == end {
|
||||
map.point_to_display_point(end, Bias::Right)
|
||||
} else {
|
||||
map.point_to_display_point(end, Bias::Left)
|
||||
};
|
||||
|
||||
Selection {
|
||||
id: s.id,
|
||||
start: display_start,
|
||||
end: display_end,
|
||||
reversed: s.reversed,
|
||||
goal: s.goal,
|
||||
}
|
||||
})
|
||||
.peekable();
|
||||
iter::from_fn(move || {
|
||||
let mut selection = selections.next()?;
|
||||
while let Some(next_selection) = selections.peek() {
|
||||
if selection.end >= next_selection.start {
|
||||
selection.end = cmp::max(selection.end, next_selection.end);
|
||||
selections.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(selection)
|
||||
})
|
||||
}
|
||||
|
||||
// Panics if passed selections are not in order
|
||||
pub(crate) fn resolve_selections<'a, D, I>(
|
||||
selections: I,
|
||||
map: &'a DisplaySnapshot,
|
||||
) -> impl 'a + Iterator<Item = Selection<D>>
|
||||
where
|
||||
D: TextDimension + Clone + Ord + Sub<D, Output = D>,
|
||||
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||
{
|
||||
let (to_convert, selections) = resolve_selections_display(selections, map).tee();
|
||||
let mut converted_endpoints =
|
||||
map.buffer_snapshot
|
||||
.dimensions_from_points::<D>(to_convert.flat_map(|s| {
|
||||
let start = map.display_point_to_point(s.start, Bias::Left);
|
||||
let end = map.display_point_to_point(s.end, Bias::Right);
|
||||
[start, end]
|
||||
}));
|
||||
selections.map(move |s| {
|
||||
let start = converted_endpoints.next().unwrap();
|
||||
let end = converted_endpoints.next().unwrap();
|
||||
Selection {
|
||||
id: s.id,
|
||||
start,
|
||||
end,
|
||||
reversed: s.reversed,
|
||||
goal: s.goal,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ languages.workspace = true
|
||||
node_runtime.workspace = true
|
||||
open_ai.workspace = true
|
||||
project.workspace = true
|
||||
semantic_index.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
zed_common.workspace = true
|
||||
|
||||
@@ -13,9 +13,6 @@ use node_runtime::NodeRuntime;
|
||||
use open_ai::OpenAiEmbeddingModel;
|
||||
use project::Project;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use semantic_index::{
|
||||
EmbeddingProvider, OpenAiEmbeddingProvider, ProjectIndex, SemanticDb, Status,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::SettingsStore;
|
||||
use smol::channel::bounded;
|
||||
@@ -33,6 +30,9 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use zed_common::semantic_index::{
|
||||
EmbeddingProvider, OpenAiEmbeddingProvider, ProjectIndex, SemanticDb, Status,
|
||||
};
|
||||
|
||||
const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net";
|
||||
const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos";
|
||||
|
||||
@@ -9,59 +9,23 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/extension_store.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
no-webrtc = ["workspace/no-webrtc"]
|
||||
path = "src/extension.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
semantic_version.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
task.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
ui.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
wasm-encoder.workspace = true
|
||||
wasmparser.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
wasmtime.workspace = true
|
||||
wit-component.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
50
crates/extension/src/extension.rs
Normal file
50
crates/extension/src/extension.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
pub mod extension_builder;
|
||||
mod extension_manifest;
|
||||
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use semantic_version::SemanticVersion;
|
||||
|
||||
pub use crate::extension_manifest::*;
|
||||
|
||||
pub fn parse_wasm_extension_version(
|
||||
extension_id: &str,
|
||||
wasm_bytes: &[u8],
|
||||
) -> Result<SemanticVersion> {
|
||||
let mut version = None;
|
||||
|
||||
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
|
||||
if let wasmparser::Payload::CustomSection(s) =
|
||||
part.context("error parsing wasm extension")?
|
||||
{
|
||||
if s.name() == "zed:api-version" {
|
||||
version = parse_wasm_extension_version_custom_section(s.data());
|
||||
if version.is_none() {
|
||||
bail!(
|
||||
"extension {} has invalid zed:api-version section: {:?}",
|
||||
extension_id,
|
||||
s.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
|
||||
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
|
||||
//
|
||||
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
|
||||
// earlier as an `Err` rather than as a panic.
|
||||
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
|
||||
}
|
||||
|
||||
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
|
||||
if data.len() == 6 {
|
||||
Some(SemanticVersion::new(
|
||||
u16::from_be_bytes([data[0], data[1]]) as _,
|
||||
u16::from_be_bytes([data[2], data[3]]) as _,
|
||||
u16::from_be_bytes([data[4], data[5]]) as _,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::wasm_host::parse_wasm_extension_version;
|
||||
use crate::ExtensionManifest;
|
||||
use crate::{extension_manifest::ExtensionLibraryKind, GrammarManifestEntry};
|
||||
use crate::{
|
||||
parse_wasm_extension_version, ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
@@ -36,7 +36,7 @@ const WASI_ADAPTER_URL: &str =
|
||||
const WASI_SDK_URL: &str = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/";
|
||||
const WASI_SDK_ASSET_NAME: Option<&str> = if cfg!(target_os = "macos") {
|
||||
Some("wasi-sdk-21.0-macos.tar.gz")
|
||||
} else if cfg!(target_os = "linux") {
|
||||
} else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
Some("wasi-sdk-21.0-linux.tar.gz")
|
||||
} else if cfg!(target_os = "windows") {
|
||||
Some("wasi-sdk-21.0.m-mingw.tar.gz")
|
||||
@@ -135,6 +135,8 @@ impl ExtensionBuilder {
|
||||
.args(options.release.then_some("--release"))
|
||||
.arg("--target-dir")
|
||||
.arg(extension_dir.join("target"))
|
||||
// WASI builds do not work with sccache and just stuck, so disable it.
|
||||
.env("RUSTC_WRAPPER", "")
|
||||
.current_dir(extension_dir)
|
||||
.output()
|
||||
.context("failed to run `cargo`")?;
|
||||
@@ -363,12 +365,15 @@ impl ExtensionBuilder {
|
||||
|
||||
let output = Command::new("rustup")
|
||||
.args(["target", "add", RUST_TARGET])
|
||||
.stderr(Stdio::inherit())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::inherit())
|
||||
.output()
|
||||
.context("failed to run `rustup target add`")?;
|
||||
if !output.status.success() {
|
||||
bail!("failed to install the `{RUST_TARGET}` target");
|
||||
bail!(
|
||||
"failed to install the `{RUST_TARGET}` target: {}",
|
||||
String::from_utf8_lossy(&rustc_output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use fs::Fs;
|
||||
use language::{LanguageName, LanguageServerName};
|
||||
use language::LanguageName;
|
||||
use lsp::LanguageServerName;
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -75,6 +76,8 @@ pub struct ExtensionManifest {
|
||||
#[serde(default)]
|
||||
pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
|
||||
#[serde(default)]
|
||||
pub context_servers: BTreeMap<Arc<str>, ContextServerManifestEntry>,
|
||||
#[serde(default)]
|
||||
pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
|
||||
#[serde(default)]
|
||||
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
|
||||
@@ -134,6 +137,9 @@ impl LanguageServerManifestEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct ContextServerManifestEntry {}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct SlashCommandManifestEntry {
|
||||
pub description: String,
|
||||
@@ -205,6 +211,7 @@ fn manifest_from_old_manifest(
|
||||
.map(|grammar_name| (grammar_name, Default::default()))
|
||||
.collect(),
|
||||
language_servers: Default::default(),
|
||||
context_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
snippets: None,
|
||||
|
||||
@@ -129,6 +129,11 @@ pub trait Extension: Send + Sync {
|
||||
Err("`run_slash_command` not implemented".to_string())
|
||||
}
|
||||
|
||||
/// Returns the command used to start a context server.
|
||||
fn context_server_command(&mut self, _context_server_id: &ContextServerId) -> Result<Command> {
|
||||
Err("`context_server_command` not implemented".to_string())
|
||||
}
|
||||
|
||||
/// Returns a list of package names as suggestions to be included in the
|
||||
/// search results of the `/docs` slash command.
|
||||
///
|
||||
@@ -270,6 +275,11 @@ impl wit::Guest for Component {
|
||||
extension().run_slash_command(command, args, worktree)
|
||||
}
|
||||
|
||||
fn context_server_command(context_server_id: String) -> Result<wit::Command> {
|
||||
let context_server_id = ContextServerId(context_server_id);
|
||||
extension().context_server_command(&context_server_id)
|
||||
}
|
||||
|
||||
fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
|
||||
extension().suggest_docs_packages(provider)
|
||||
}
|
||||
@@ -299,6 +309,22 @@ impl fmt::Display for LanguageServerId {
|
||||
}
|
||||
}
|
||||
|
||||
/// The ID of a context server.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||
pub struct ContextServerId(String);
|
||||
|
||||
impl AsRef<str> for ContextServerId {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContextServerId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeLabelSpan {
|
||||
/// Returns a [`CodeLabelSpan::CodeRange`].
|
||||
pub fn code_range(range: impl Into<wit::Range>) -> Self {
|
||||
|
||||
@@ -135,6 +135,9 @@ world extension {
|
||||
/// Returns the output from running the provided slash command.
|
||||
export run-slash-command: func(command: slash-command, args: list<string>, worktree: option<borrow<worktree>>) -> result<slash-command-output, string>;
|
||||
|
||||
/// Returns the command used to start up a context server.
|
||||
export context-server-command: func(context-server-id: string) -> result<command, string>;
|
||||
|
||||
/// Returns a list of packages as suggestions to be included in the `/docs`
|
||||
/// search results.
|
||||
///
|
||||
|
||||
@@ -2,6 +2,7 @@ interface lsp {
|
||||
/// An LSP completion.
|
||||
record completion {
|
||||
label: string,
|
||||
label-details: option<completion-label-details>,
|
||||
detail: option<string>,
|
||||
kind: option<completion-kind>,
|
||||
insert-text-format: option<insert-text-format>,
|
||||
@@ -37,6 +38,12 @@ interface lsp {
|
||||
other(s32),
|
||||
}
|
||||
|
||||
/// Label details for an LSP completion.
|
||||
record completion-label-details {
|
||||
detail: option<string>,
|
||||
description: option<string>,
|
||||
}
|
||||
|
||||
/// Defines how to interpret the insert text in a completion item.
|
||||
variant insert-text-format {
|
||||
plain-text,
|
||||
|
||||
@@ -16,7 +16,7 @@ path = "src/main.rs"
|
||||
anyhow.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
env_logger.workspace = true
|
||||
extension = { workspace = true, features = ["no-webrtc"] }
|
||||
extension.workspace = true
|
||||
fs.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -15,7 +15,6 @@ use extension::{
|
||||
};
|
||||
use language::LanguageConfig;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use theme::ThemeRegistry;
|
||||
use tree_sitter::{Language, Query, WasmStore};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -267,7 +266,7 @@ async fn test_themes(
|
||||
) -> Result<()> {
|
||||
for relative_theme_path in &manifest.themes {
|
||||
let theme_path = extension_path.join(relative_theme_path);
|
||||
let theme_family = ThemeRegistry::read_user_theme(&theme_path, fs.clone()).await?;
|
||||
let theme_family = theme::read_user_theme(&theme_path, fs.clone()).await?;
|
||||
log::info!("loaded theme family {}", theme_family.name);
|
||||
}
|
||||
|
||||
|
||||
59
crates/extension_host/Cargo.toml
Normal file
59
crates/extension_host/Cargo.toml
Normal file
@@ -0,0 +1,59 @@
|
||||
[package]
|
||||
name = "extension_host"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/extension_host.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
extension.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
semantic_version.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
task.workspace = true
|
||||
toml.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
wasmparser.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
wasmtime.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
@@ -1,25 +1,15 @@
|
||||
pub mod extension_builder;
|
||||
mod extension_indexed_docs_provider;
|
||||
mod extension_lsp_adapter;
|
||||
mod extension_manifest;
|
||||
mod extension_settings;
|
||||
mod extension_slash_command;
|
||||
mod wasm_host;
|
||||
pub mod extension_lsp_adapter;
|
||||
pub mod extension_settings;
|
||||
pub mod wasm_host;
|
||||
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
|
||||
use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
|
||||
use crate::extension_manifest::SchemaVersion;
|
||||
use crate::extension_slash_command::ExtensionSlashCommand;
|
||||
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
|
||||
use collections::{btree_map, BTreeMap, HashSet};
|
||||
use extension_builder::{CompileExtensionOptions, ExtensionBuilder};
|
||||
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
|
||||
pub use extension::ExtensionManifest;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
channel::{
|
||||
@@ -30,22 +20,21 @@ use futures::{
|
||||
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
|
||||
WeakModel,
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext,
|
||||
SharedString, Task, WeakModel,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
|
||||
LoadedLanguage, QUERY_FILENAME_PREFIXES,
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use lsp::LanguageServerName;
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
use release_channel::ReleaseChannel;
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
@@ -54,20 +43,19 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use url::Url;
|
||||
use util::{maybe, ResultExt};
|
||||
use util::ResultExt;
|
||||
use wasm_host::{
|
||||
wit::{is_supported_wasm_api_version, wasm_api_version_range},
|
||||
WasmExtension, WasmHost,
|
||||
};
|
||||
|
||||
pub use extension_manifest::{
|
||||
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, OldExtensionManifest,
|
||||
pub use extension::{
|
||||
ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion,
|
||||
};
|
||||
pub use extension_settings::ExtensionSettings;
|
||||
|
||||
const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
|
||||
|
||||
/// The current extension [`SchemaVersion`] supported by Zed.
|
||||
@@ -102,26 +90,101 @@ pub fn is_version_compatible(
|
||||
true
|
||||
}
|
||||
|
||||
pub trait DocsDatabase: Send + Sync + 'static {
|
||||
fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
|
||||
}
|
||||
|
||||
pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
|
||||
fn remove_user_themes(&self, _themes: Vec<SharedString>) {}
|
||||
|
||||
fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc<dyn Fs>) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn list_theme_names(
|
||||
&self,
|
||||
_theme_path: PathBuf,
|
||||
_fs: Arc<dyn Fs>,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, _cx: &mut AppContext) {}
|
||||
|
||||
fn register_language(
|
||||
&self,
|
||||
_language: LanguageName,
|
||||
_grammar: Option<Arc<str>>,
|
||||
_matcher: language::LanguageMatcher,
|
||||
_load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(&self, _language: LanguageName, _adapter: ExtensionLspAdapter) {}
|
||||
|
||||
fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {}
|
||||
|
||||
fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
_languages_to_remove: &[LanguageName],
|
||||
_grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
_slash_command: wit::SlashCommand,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_context_server(
|
||||
&self,
|
||||
_id: Arc<str>,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_docs_provider(
|
||||
&self,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
_provider_id: Arc<str>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
_server_name: lsp::LanguageServerName,
|
||||
_status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionStore {
|
||||
builder: Arc<ExtensionBuilder>,
|
||||
extension_index: ExtensionIndex,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
reload_tx: UnboundedSender<Option<Arc<str>>>,
|
||||
reload_complete_senders: Vec<oneshot::Sender<()>>,
|
||||
installed_dir: PathBuf,
|
||||
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||
index_path: PathBuf,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
modified_extensions: HashSet<Arc<str>>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
tasks: Vec<Task<()>>,
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub builder: Arc<ExtensionBuilder>,
|
||||
pub extension_index: ExtensionIndex,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub http_client: Arc<HttpClientWithUrl>,
|
||||
pub telemetry: Option<Arc<Telemetry>>,
|
||||
pub reload_tx: UnboundedSender<Option<Arc<str>>>,
|
||||
pub reload_complete_senders: Vec<oneshot::Sender<()>>,
|
||||
pub installed_dir: PathBuf,
|
||||
pub outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||
pub index_path: PathBuf,
|
||||
pub modified_extensions: HashSet<Arc<str>>,
|
||||
pub wasm_host: Arc<WasmHost>,
|
||||
pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
pub tasks: Vec<Task<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -160,26 +223,25 @@ pub struct ExtensionIndexEntry {
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexThemeEntry {
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexLanguageEntry {
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
matcher: LanguageMatcher,
|
||||
grammar: Option<Arc<str>>,
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
pub matcher: LanguageMatcher,
|
||||
pub grammar: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
actions!(zed, [ReloadExtensions]);
|
||||
|
||||
pub fn init(
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
ExtensionSettings::register(cx);
|
||||
@@ -188,16 +250,12 @@ pub fn init(
|
||||
ExtensionStore::new(
|
||||
paths::extensions_dir().clone(),
|
||||
None,
|
||||
registration_hooks,
|
||||
fs,
|
||||
client.http_client().clone(),
|
||||
client.http_client().clone(),
|
||||
Some(client.telemetry().clone()),
|
||||
node_runtime,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
SnippetRegistry::global(cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -224,16 +282,12 @@ impl ExtensionStore {
|
||||
pub fn new(
|
||||
extensions_dir: PathBuf,
|
||||
build_dir: Option<PathBuf>,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
builder_client: Arc<dyn HttpClient>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let work_dir = extensions_dir.join("work");
|
||||
@@ -243,6 +297,7 @@ impl ExtensionStore {
|
||||
|
||||
let (reload_tx, mut reload_rx) = unbounded();
|
||||
let mut this = Self {
|
||||
registration_hooks: extension_api.clone(),
|
||||
extension_index: Default::default(),
|
||||
installed_dir,
|
||||
index_path,
|
||||
@@ -254,7 +309,7 @@ impl ExtensionStore {
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
extension_api,
|
||||
work_dir,
|
||||
cx,
|
||||
),
|
||||
@@ -262,11 +317,6 @@ impl ExtensionStore {
|
||||
fs,
|
||||
http_client,
|
||||
telemetry,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
reload_tx,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
@@ -327,6 +377,7 @@ impl ExtensionStore {
|
||||
async move {
|
||||
load_initial_extensions.await;
|
||||
|
||||
let mut index_changed = false;
|
||||
let mut debounce_timer = cx
|
||||
.background_executor()
|
||||
.spawn(futures::future::pending())
|
||||
@@ -334,17 +385,21 @@ impl ExtensionStore {
|
||||
loop {
|
||||
select_biased! {
|
||||
_ = debounce_timer => {
|
||||
let index = this
|
||||
.update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
|
||||
.await;
|
||||
if index_changed {
|
||||
let index = this
|
||||
.update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
|
||||
.await;
|
||||
index_changed = false;
|
||||
}
|
||||
}
|
||||
extension_id = reload_rx.next() => {
|
||||
let Some(extension_id) = extension_id else { break; };
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.modified_extensions.extend(extension_id);
|
||||
})?;
|
||||
index_changed = true;
|
||||
debounce_timer = cx
|
||||
.background_executor()
|
||||
.timer(RELOAD_DEBOUNCE_DURATION)
|
||||
@@ -388,7 +443,7 @@ impl ExtensionStore {
|
||||
this
|
||||
}
|
||||
|
||||
fn reload(
|
||||
pub fn reload(
|
||||
&mut self,
|
||||
modified_extension: Option<Arc<str>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -1041,7 +1096,7 @@ impl ExtensionStore {
|
||||
grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
|
||||
for (language_server_name, config) in extension.manifest.language_servers.iter() {
|
||||
for language in config.languages() {
|
||||
self.language_registry
|
||||
self.registration_hooks
|
||||
.remove_lsp_adapter(&language, language_server_name);
|
||||
}
|
||||
}
|
||||
@@ -1049,8 +1104,8 @@ impl ExtensionStore {
|
||||
|
||||
self.wasm_extensions
|
||||
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
|
||||
self.theme_registry.remove_user_themes(&themes_to_remove);
|
||||
self.language_registry
|
||||
self.registration_hooks.remove_user_themes(themes_to_remove);
|
||||
self.registration_hooks
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
|
||||
let languages_to_add = new_index
|
||||
@@ -1085,7 +1140,7 @@ impl ExtensionStore {
|
||||
}));
|
||||
}
|
||||
|
||||
self.language_registry
|
||||
self.registration_hooks
|
||||
.register_wasm_grammars(grammars_to_add);
|
||||
|
||||
for (language_name, language) in languages_to_add {
|
||||
@@ -1094,11 +1149,11 @@ impl ExtensionStore {
|
||||
Path::new(language.extension.as_ref()),
|
||||
language.path.as_path(),
|
||||
]);
|
||||
self.language_registry.register_language(
|
||||
self.registration_hooks.register_language(
|
||||
language_name.clone(),
|
||||
language.grammar.clone(),
|
||||
language.matcher.clone(),
|
||||
move || {
|
||||
Arc::new(move || {
|
||||
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
||||
let config: LanguageConfig = ::toml::from_str(&config)?;
|
||||
let queries = load_plugin_queries(&language_path);
|
||||
@@ -1117,15 +1172,14 @@ impl ExtensionStore {
|
||||
context_provider,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
let wasm_host = self.wasm_host.clone();
|
||||
let root_dir = self.installed_dir.clone();
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
let snippet_registry = self.snippet_registry.clone();
|
||||
let api = self.registration_hooks.clone();
|
||||
let extension_entries = extensions_to_load
|
||||
.iter()
|
||||
.filter_map(|name| new_index.extensions.get(name).cloned())
|
||||
@@ -1140,18 +1194,14 @@ impl ExtensionStore {
|
||||
.spawn({
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for theme_path in &themes_to_add {
|
||||
theme_registry
|
||||
.load_user_theme(theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
for theme_path in themes_to_add.into_iter() {
|
||||
api.load_user_theme(theme_path, fs.clone()).await.log_err();
|
||||
}
|
||||
|
||||
for snippets_path in &snippets_to_add {
|
||||
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
|
||||
{
|
||||
snippet_registry
|
||||
.register_snippets(snippets_path, &snippets_contents)
|
||||
api.register_snippets(snippets_path, &snippets_contents)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
@@ -1165,30 +1215,13 @@ impl ExtensionStore {
|
||||
continue;
|
||||
};
|
||||
|
||||
let wasm_extension = maybe!(async {
|
||||
let mut path = root_dir.clone();
|
||||
path.extend([extension.manifest.clone().id.as_ref(), "extension.wasm"]);
|
||||
let mut wasm_file = fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(
|
||||
wasm_bytes,
|
||||
extension.manifest.clone().clone(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to load wasm extension {}", extension.manifest.id)
|
||||
})
|
||||
})
|
||||
let extension_path = root_dir.join(extension.manifest.id.as_ref());
|
||||
let wasm_extension = WasmExtension::load(
|
||||
extension_path,
|
||||
&extension.manifest,
|
||||
wasm_host.clone(),
|
||||
&cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(wasm_extension) = wasm_extension.log_err() {
|
||||
@@ -1207,9 +1240,9 @@ impl ExtensionStore {
|
||||
for (manifest, wasm_extension) in &wasm_extensions {
|
||||
for (language_server_id, language_server_config) in &manifest.language_servers {
|
||||
for language in language_server_config.languages() {
|
||||
this.language_registry.register_lsp_adapter(
|
||||
this.registration_hooks.register_lsp_adapter(
|
||||
language.clone(),
|
||||
Arc::new(ExtensionLspAdapter {
|
||||
ExtensionLspAdapter {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
language_server_id: language_server_id.clone(),
|
||||
@@ -1217,43 +1250,46 @@ impl ExtensionStore {
|
||||
name: language_server_id.0.to_string(),
|
||||
language_name: language.to_string(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (slash_command_name, slash_command) in &manifest.slash_commands {
|
||||
this.slash_command_registry.register_command(
|
||||
ExtensionSlashCommand {
|
||||
command: crate::wit::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
description: slash_command.description.to_string(),
|
||||
// We don't currently expose this as a configurable option, as it currently drives
|
||||
// the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
|
||||
// defined in extensions, as they are not able to be added to the menu.
|
||||
tooltip_text: String::new(),
|
||||
requires_argument: slash_command.requires_argument,
|
||||
},
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
this.registration_hooks.register_slash_command(
|
||||
crate::wit::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
description: slash_command.description.to_string(),
|
||||
// We don't currently expose this as a configurable option, as it currently drives
|
||||
// the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
|
||||
// defined in extensions, as they are not able to be added to the menu.
|
||||
tooltip_text: String::new(),
|
||||
requires_argument: slash_command.requires_argument,
|
||||
},
|
||||
false,
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
for (id, _context_server_entry) in &manifest.context_servers {
|
||||
this.registration_hooks.register_context_server(
|
||||
id.clone(),
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
for (provider_id, _provider) in &manifest.indexed_docs_providers {
|
||||
this.indexed_docs_registry.register_provider(Box::new(
|
||||
ExtensionIndexedDocsProvider {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
id: ProviderId(provider_id.clone()),
|
||||
},
|
||||
));
|
||||
this.registration_hooks.register_docs_provider(
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
provider_id.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
this.registration_hooks.reload_current_theme(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -1264,6 +1300,7 @@ impl ExtensionStore {
|
||||
let work_dir = self.wasm_host.work_dir.clone();
|
||||
let extensions_dir = self.installed_dir.clone();
|
||||
let index_path = self.index_path.clone();
|
||||
let extension_api = self.registration_hooks.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let start_time = Instant::now();
|
||||
let mut index = ExtensionIndex::default();
|
||||
@@ -1285,9 +1322,14 @@ impl ExtensionStore {
|
||||
continue;
|
||||
}
|
||||
|
||||
Self::add_extension_to_index(fs.clone(), extension_dir, &mut index)
|
||||
.await
|
||||
.log_err();
|
||||
Self::add_extension_to_index(
|
||||
fs.clone(),
|
||||
extension_dir,
|
||||
&mut index,
|
||||
extension_api.clone(),
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1307,6 +1349,7 @@ impl ExtensionStore {
|
||||
fs: Arc<dyn Fs>,
|
||||
extension_dir: PathBuf,
|
||||
index: &mut ExtensionIndex,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
) -> Result<()> {
|
||||
let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
|
||||
let extension_id = extension_manifest.id.clone();
|
||||
@@ -1358,7 +1401,8 @@ impl ExtensionStore {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(theme_family) = ThemeRegistry::read_user_theme(&theme_path, fs.clone())
|
||||
let Some(theme_families) = extension_api
|
||||
.list_theme_names(theme_path.clone(), fs.clone())
|
||||
.await
|
||||
.log_err()
|
||||
else {
|
||||
@@ -1370,9 +1414,9 @@ impl ExtensionStore {
|
||||
extension_manifest.themes.push(relative_path.clone());
|
||||
}
|
||||
|
||||
for theme in theme_family.themes {
|
||||
for theme_name in theme_families {
|
||||
index.themes.insert(
|
||||
theme.name.into(),
|
||||
theme_name.into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: extension_id.clone(),
|
||||
path: relative_path.clone(),
|
||||
@@ -8,10 +8,9 @@ use collections::HashMap;
|
||||
use futures::{Future, FutureExt};
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{
|
||||
CodeLabel, HighlightId, Language, LanguageServerName, LanguageToolchainStore, LspAdapter,
|
||||
LspAdapterDelegate,
|
||||
CodeLabel, HighlightId, Language, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
|
||||
};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::ops::Range;
|
||||
@@ -387,6 +386,7 @@ impl From<lsp::CompletionItem> for wit::Completion {
|
||||
fn from(value: lsp::CompletionItem) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
label_details: value.label_details.map(Into::into),
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
@@ -394,6 +394,15 @@ impl From<lsp::CompletionItem> for wit::Completion {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lsp::CompletionItemLabelDetails> for wit::CompletionLabelDetails {
|
||||
fn from(value: lsp::CompletionItemLabelDetails) -> Self {
|
||||
Self {
|
||||
detail: value.detail,
|
||||
description: value.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lsp::CompletionItemKind> for wit::CompletionKind {
|
||||
fn from(value: lsp::CompletionItemKind) -> Self {
|
||||
match value {
|
||||
@@ -1,6 +1,6 @@
|
||||
pub(crate) mod wit;
|
||||
pub mod wit;
|
||||
|
||||
use crate::ExtensionManifest;
|
||||
use crate::{ExtensionManifest, ExtensionRegistrationHooks};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use fs::{normalize_path, Fs};
|
||||
use futures::future::LocalBoxFuture;
|
||||
@@ -14,7 +14,6 @@ use futures::{
|
||||
};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::NodeRuntime;
|
||||
use release_channel::ReleaseChannel;
|
||||
use semantic_version::SemanticVersion;
|
||||
@@ -28,15 +27,16 @@ use wasmtime::{
|
||||
};
|
||||
use wasmtime_wasi as wasi;
|
||||
use wit::Extension;
|
||||
pub use wit::SlashCommand;
|
||||
|
||||
pub(crate) struct WasmHost {
|
||||
pub struct WasmHost {
|
||||
engine: Engine,
|
||||
release_channel: ReleaseChannel,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
pub(crate) language_registry: Arc<LanguageRegistry>,
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
pub(crate) work_dir: PathBuf,
|
||||
pub work_dir: PathBuf,
|
||||
_main_thread_message_task: Task<()>,
|
||||
main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>,
|
||||
}
|
||||
@@ -44,16 +44,16 @@ pub(crate) struct WasmHost {
|
||||
#[derive(Clone)]
|
||||
pub struct WasmExtension {
|
||||
tx: UnboundedSender<ExtensionCall>,
|
||||
pub(crate) manifest: Arc<ExtensionManifest>,
|
||||
pub manifest: Arc<ExtensionManifest>,
|
||||
#[allow(unused)]
|
||||
pub zed_api_version: SemanticVersion,
|
||||
}
|
||||
|
||||
pub(crate) struct WasmState {
|
||||
pub struct WasmState {
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
pub(crate) table: ResourceTable,
|
||||
pub table: ResourceTable,
|
||||
ctx: wasi::WasiCtx,
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
pub host: Arc<WasmHost>,
|
||||
}
|
||||
|
||||
type MainThreadCall =
|
||||
@@ -81,7 +81,7 @@ impl WasmHost {
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
work_dir: PathBuf,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
@@ -97,7 +97,7 @@ impl WasmHost {
|
||||
work_dir,
|
||||
http_client,
|
||||
node_runtime,
|
||||
language_registry,
|
||||
registration_hooks,
|
||||
release_channel: ReleaseChannel::global(cx),
|
||||
_main_thread_message_task: task,
|
||||
main_thread_message_tx: tx,
|
||||
@@ -107,10 +107,11 @@ impl WasmHost {
|
||||
pub fn load_extension(
|
||||
self: &Arc<Self>,
|
||||
wasm_bytes: Vec<u8>,
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Task<Result<WasmExtension>> {
|
||||
let this = self.clone();
|
||||
let manifest = manifest.clone();
|
||||
executor.clone().spawn(async move {
|
||||
let zed_api_version = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
|
||||
|
||||
@@ -150,7 +151,7 @@ impl WasmHost {
|
||||
.detach();
|
||||
|
||||
Ok(WasmExtension {
|
||||
manifest,
|
||||
manifest: manifest.clone(),
|
||||
tx,
|
||||
zed_api_version,
|
||||
})
|
||||
@@ -241,6 +242,31 @@ fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVe
|
||||
}
|
||||
|
||||
impl WasmExtension {
|
||||
pub async fn load(
|
||||
extension_dir: PathBuf,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let path = extension_dir.join("extension.wasm");
|
||||
|
||||
let mut wasm_file = wasm_host
|
||||
.fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(wasm_bytes, manifest, cx.background_executor().clone())
|
||||
.await
|
||||
.with_context(|| format!("failed to load wasm extension {}", manifest.id))
|
||||
}
|
||||
|
||||
pub async fn call<T, Fn>(&self, f: Fn) -> T
|
||||
where
|
||||
T: 'static + Send,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user