Compare commits
248 Commits
drop-image
...
protobuf_f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e015998a7 | ||
|
|
49eb865e8a | ||
|
|
a650fe0d77 | ||
|
|
204a989758 | ||
|
|
776cfe44d7 | ||
|
|
35798212c4 | ||
|
|
89f9a506f9 | ||
|
|
04ba75e2e5 | ||
|
|
f7b4431659 | ||
|
|
6b9eba2109 | ||
|
|
58e3b788dc | ||
|
|
9fd971d8c9 | ||
|
|
cf7679e6a0 | ||
|
|
07c0c54c28 | ||
|
|
093c9cc87b | ||
|
|
6b3c909155 | ||
|
|
7e349e52b1 | ||
|
|
84d17fb191 | ||
|
|
d3d408d47d | ||
|
|
6e477bbf56 | ||
|
|
3c2dcf50fa | ||
|
|
a15f408f0c | ||
|
|
b1cd9e4d24 | ||
|
|
254ce74036 | ||
|
|
b913cf2e02 | ||
|
|
92613a8904 | ||
|
|
96deabfb78 | ||
|
|
ad31aacb7a | ||
|
|
a04c2ecff7 | ||
|
|
f96b29ca54 | ||
|
|
9d2fc691de | ||
|
|
b084d53f8e | ||
|
|
7832883c74 | ||
|
|
eb4e7472e6 | ||
|
|
27dfb48a7b | ||
|
|
3a319e6cbe | ||
|
|
84e47fb80b | ||
|
|
7e82ca8082 | ||
|
|
b44078781d | ||
|
|
3b1f12af75 | ||
|
|
b8cf0a1ed1 | ||
|
|
3f224274da | ||
|
|
56cf32cb91 | ||
|
|
90ffd65a10 | ||
|
|
55cd99cdc4 | ||
|
|
0547748c48 | ||
|
|
54735199a4 | ||
|
|
466d3316a0 | ||
|
|
3ebb64ea1d | ||
|
|
0a9c78a58d | ||
|
|
a7eb3a9b9f | ||
|
|
47ca3401ce | ||
|
|
d1e2c6e657 | ||
|
|
181c3724aa | ||
|
|
aad3ed7f91 | ||
|
|
ad3171d16d | ||
|
|
e4bf586cff | ||
|
|
ab20681818 | ||
|
|
6819108bcb | ||
|
|
2521ef7bc4 | ||
|
|
b08ac2ae3e | ||
|
|
2ea4ede08e | ||
|
|
38f2a919f8 | ||
|
|
82427e1ffb | ||
|
|
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 |
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
@@ -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
@@ -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
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "docs/**/*"
|
||||
- ".github/workflows/community_*"
|
||||
|
||||
concurrency:
|
||||
@@ -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
|
||||
|
||||
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@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # 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@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # 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@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # 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@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
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>
|
||||
|
||||
@@ -30,6 +30,15 @@
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"Proto": {
|
||||
"tab_size": 4,
|
||||
"formatter": {
|
||||
"external": {
|
||||
"command": "clang-format",
|
||||
"arguments": ["-style={IndentWidth: 4, ColumnLimit: 0}"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rust": {
|
||||
"tasks": {
|
||||
"variables": {
|
||||
|
||||
303
Cargo.lock
generated
@@ -13,6 +13,7 @@ dependencies = [
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
"lsp",
|
||||
"project",
|
||||
"smallvec",
|
||||
"ui",
|
||||
@@ -262,9 +263,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.91"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -458,15 +459,19 @@ name = "assistant_slash_command"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"extension",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
"language_model",
|
||||
"parking_lot",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
@@ -888,15 +893,14 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-tls"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
|
||||
checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustls 0.20.9",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"webpki",
|
||||
"webpki-roots 0.22.6",
|
||||
]
|
||||
|
||||
@@ -913,9 +917,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-tungstenite"
|
||||
version = "0.24.0"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3609af4bbf701ddaf1f6bb4e6257dff4ff8932327d0e685d3f653724c258b1ac"
|
||||
checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tls",
|
||||
@@ -923,7 +927,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tungstenite 0.21.0",
|
||||
"tungstenite 0.24.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1073,7 +1077,7 @@ dependencies = [
|
||||
"fastrand 2.1.1",
|
||||
"hex",
|
||||
"http 0.2.12",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1242,7 +1246,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"p256",
|
||||
"percent-encoding",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
@@ -1636,6 +1640,15 @@ dependencies = [
|
||||
"bit-vec 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
@@ -1648,6 +1661,12 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
@@ -2285,9 +2304,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2295,9 +2314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2422,7 +2441,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
"rustls 0.20.9",
|
||||
"rustls 0.21.12",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2432,7 +2451,6 @@ dependencies = [
|
||||
"smol",
|
||||
"sysinfo",
|
||||
"telemetry_events",
|
||||
"tempfile",
|
||||
"text",
|
||||
"thiserror",
|
||||
"time",
|
||||
@@ -2554,6 +2572,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collab_ui",
|
||||
"collections",
|
||||
"context_servers",
|
||||
"ctor",
|
||||
"dashmap 6.0.1",
|
||||
"derive_more",
|
||||
@@ -2807,6 +2826,7 @@ dependencies = [
|
||||
"log",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2814,7 +2834,6 @@ dependencies = [
|
||||
"smol",
|
||||
"url",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3491,7 +3510,6 @@ dependencies = [
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
@@ -3718,6 +3736,7 @@ dependencies = [
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
"unindent",
|
||||
"url",
|
||||
@@ -4096,9 +4115,11 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-tar",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"log",
|
||||
@@ -4139,12 +4160,12 @@ name = "extension_host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"async-compression",
|
||||
"async-tar",
|
||||
"async-trait",
|
||||
"client",
|
||||
"collections",
|
||||
"context_servers",
|
||||
"ctor",
|
||||
"env_logger 0.11.5",
|
||||
"extension",
|
||||
@@ -4152,7 +4173,6 @@ dependencies = [
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"indexed_docs",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -4168,16 +4188,13 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"snippet_provider",
|
||||
"task",
|
||||
"theme",
|
||||
"toml 0.8.19",
|
||||
"ui",
|
||||
"url",
|
||||
"util",
|
||||
"wasmparser 0.215.0",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4185,28 +4202,46 @@ name = "extensions_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"async-compression",
|
||||
"async-tar",
|
||||
"client",
|
||||
"collections",
|
||||
"context_servers",
|
||||
"ctor",
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger 0.11.5",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"indexed_docs",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
"num-format",
|
||||
"parking_lot",
|
||||
"picker",
|
||||
"project",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"semantic_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"snippet_provider",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"ui",
|
||||
"util",
|
||||
"vim",
|
||||
"wasmtime-wasi",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
@@ -4226,6 +4261,17 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"regex-automata 0.4.7",
|
||||
"regex-syntax 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
@@ -4416,7 +4462,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin 0.9.8",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4464,7 +4510,7 @@ version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
|
||||
dependencies = [
|
||||
"roxmltree 0.20.0",
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5228,11 +5274,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "5.1.2"
|
||||
version = "6.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b"
|
||||
checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315"
|
||||
dependencies = [
|
||||
"log",
|
||||
"num-order",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
@@ -5606,7 +5653,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.7",
|
||||
"socket2 0.4.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -5797,15 +5844,17 @@ dependencies = [
|
||||
"gpui",
|
||||
"project",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
|
||||
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
@@ -5822,6 +5871,7 @@ dependencies = [
|
||||
"cargo_metadata",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"extension",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
@@ -6170,7 +6220,7 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
@@ -6178,12 +6228,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-serde"
|
||||
version = "0.2.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77b96de099fc23d5c21e05de32cc087c8326983895b7f6c242562af01f7d4c81"
|
||||
checksum = "dd71aa17c4fa65e6d7536ab2728881a41f8feb2ee5841c2240516c3c3d65d8b3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@@ -6264,7 +6313,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rpc",
|
||||
@@ -6403,6 +6452,7 @@ dependencies = [
|
||||
"pet",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-poetry",
|
||||
"pet-reporter",
|
||||
"project",
|
||||
@@ -6445,7 +6495,7 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin 0.9.8",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6468,9 +6518,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
version = "0.2.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -6512,7 +6562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6576,18 +6626,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linkme"
|
||||
version = "0.3.29"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70fe496a7af8c406f877635cbf3cd6a9fac9d6f443f58691cd8afe6ce0971af4"
|
||||
checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae"
|
||||
dependencies = [
|
||||
"linkme-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linkme-impl"
|
||||
version = "0.3.29"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b01f197a15988fb5b2ec0a5a9800c97e70771499c456ad757d63b3c5e9b96e75"
|
||||
checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6709,6 +6759,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"release_channel",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
@@ -6792,7 +6843,7 @@ dependencies = [
|
||||
"linkify",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
@@ -6812,7 +6863,7 @@ dependencies = [
|
||||
"linkify",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
@@ -6887,9 +6938,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mdbook"
|
||||
version = "0.4.40"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b45a38e19bd200220ef07c892b0157ad3d2365e5b5a267ca01ad12182491eea5"
|
||||
checksum = "7624879735513024d323e7267a0b3a7176aceb0db537939beb4ee31d9e8945e3"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -6899,7 +6950,7 @@ dependencies = [
|
||||
"elasticlunr-rs",
|
||||
"env_logger 0.11.5",
|
||||
"futures-util",
|
||||
"handlebars 5.1.2",
|
||||
"handlebars 6.2.0",
|
||||
"ignore",
|
||||
"log",
|
||||
"memchr",
|
||||
@@ -7177,9 +7228,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nbformat"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f8a9ab08b34237c2c1d0504b794c2ff01c08dfc46a060d160f004a7f479c31"
|
||||
checksum = "c9ffb2ca556072f114bcaf2ca01dde7f1bc8a4946097dd804cb5a22d8af7d6df"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -7453,6 +7504,21 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-modular"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
|
||||
|
||||
[[package]]
|
||||
name = "num-order"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
|
||||
dependencies = [
|
||||
"num-modular",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
@@ -7794,6 +7860,7 @@ dependencies = [
|
||||
"project",
|
||||
"rope",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"tree-sitter-rust",
|
||||
@@ -7818,6 +7885,7 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"outline",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"schemars",
|
||||
@@ -8866,6 +8934,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger 0.11.5",
|
||||
"fancy-regex 0.14.0",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"fuzzy",
|
||||
@@ -8874,6 +8943,7 @@ dependencies = [
|
||||
"globset",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"image",
|
||||
"itertools 0.13.0",
|
||||
"language",
|
||||
"log",
|
||||
@@ -9094,9 +9164,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.12.1"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "666f0f59e259aea2d72e6012290c09877a780935cc3c18b1ceded41f3890d59c"
|
||||
checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"memchr",
|
||||
@@ -9150,6 +9220,7 @@ dependencies = [
|
||||
"editor",
|
||||
"gpui",
|
||||
"markdown_preview",
|
||||
"picker",
|
||||
"repl",
|
||||
"search",
|
||||
"settings",
|
||||
@@ -9185,7 +9256,7 @@ checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
|
||||
dependencies = [
|
||||
"bytes 1.7.2",
|
||||
"rand 0.8.5",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.13",
|
||||
"slab",
|
||||
@@ -9559,6 +9630,7 @@ dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"prost",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
@@ -9610,6 +9682,7 @@ dependencies = [
|
||||
"settings",
|
||||
"shellexpand 2.1.2",
|
||||
"smol",
|
||||
"sysinfo",
|
||||
"telemetry_events",
|
||||
"toml 0.8.19",
|
||||
"util",
|
||||
@@ -9651,6 +9724,7 @@ dependencies = [
|
||||
"menu",
|
||||
"multi_buffer",
|
||||
"nbformat",
|
||||
"picker",
|
||||
"project",
|
||||
"runtimelib",
|
||||
"schemars",
|
||||
@@ -9776,9 +9850,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.41.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2327ced609dadeed3e9702fec3e6b2ddd208758a9268d13e06566c6101ba533"
|
||||
checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pico-args",
|
||||
@@ -9816,27 +9890,12 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"linkify",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted 0.7.1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@@ -9847,8 +9906,8 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -9931,12 +9990,6 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
@@ -9990,9 +10043,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "runtimelib"
|
||||
version = "0.16.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7fe3c17675445fe89de68d130be00b7115104924fbcf53a9b0a84b0283fc81"
|
||||
checksum = "fe23ba9967355bbb1be2fb9a8e51bd239ffdf9c791fad5a9b765122ee2bde2e4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
@@ -10006,7 +10059,7 @@ dependencies = [
|
||||
"glob",
|
||||
"jupyter-serde",
|
||||
"rand 0.8.5",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand 3.1.0",
|
||||
@@ -10133,18 +10186,6 @@ dependencies = [
|
||||
"rustix 0.38.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.16.20",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.12"
|
||||
@@ -10152,7 +10193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-webpki 0.101.7",
|
||||
"sct",
|
||||
]
|
||||
@@ -10164,7 +10205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"subtle",
|
||||
@@ -10227,8 +10268,8 @@ version = "0.101.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10237,9 +10278,9 @@ version = "0.102.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted 0.9.0",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10353,8 +10394,8 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11115,12 +11156,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
@@ -12209,7 +12244,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
"bstr",
|
||||
"fancy-regex",
|
||||
"fancy-regex 0.12.0",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"rustc-hash 1.1.0",
|
||||
@@ -13003,6 +13038,24 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes 1.7.2",
|
||||
"data-encoding",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.2"
|
||||
@@ -13127,9 +13180,9 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.6"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
|
||||
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -13161,12 +13214,6 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -13193,9 +13240,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.41.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c704361d822337cfc00387672c7b59eaa72a1f0744f62b2a68aa228a0c6927d"
|
||||
checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"data-url",
|
||||
@@ -13204,7 +13251,7 @@ dependencies = [
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree 0.19.0",
|
||||
"roxmltree",
|
||||
"simplecss",
|
||||
"siphasher 1.0.1",
|
||||
"strict-num",
|
||||
@@ -14041,8 +14088,8 @@ version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14190,7 +14237,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14773,6 +14820,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"sqlez",
|
||||
"strum 0.25.0",
|
||||
"task",
|
||||
"tempfile",
|
||||
"theme",
|
||||
@@ -15057,13 +15105,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.161.0"
|
||||
version = "0.163.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
"assets",
|
||||
"assistant",
|
||||
"assistant_slash_command",
|
||||
"async-watch",
|
||||
"audio",
|
||||
"auto_update",
|
||||
@@ -15079,6 +15128,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"context_servers",
|
||||
"copilot",
|
||||
"db",
|
||||
"diagnostics",
|
||||
@@ -15098,6 +15148,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"image_viewer",
|
||||
"indexed_docs",
|
||||
"inline_completion_button",
|
||||
"install_cli",
|
||||
"journal",
|
||||
|
||||
52
Cargo.toml
@@ -323,7 +323,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.5.0"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.24"
|
||||
async-tungstenite = "0.28"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
@@ -339,6 +339,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"
|
||||
@@ -350,6 +351,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"
|
||||
@@ -372,7 +374,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.2"
|
||||
nbformat = "0.5.0"
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -381,6 +383,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" }
|
||||
@@ -405,12 +408,12 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.16.1", default-features = false, features = [
|
||||
runtimelib = { version = "0.19.0", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustls = "0.20.3"
|
||||
rustls = "0.21.12"
|
||||
rustls-native-certs = "0.8.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
semver = "1.0"
|
||||
@@ -475,6 +478,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"
|
||||
@@ -558,6 +562,46 @@ rustybuzz = { opt-level = 3 }
|
||||
ttf-parser = { opt-level = 3 }
|
||||
wasmtime-cranelift = { opt-level = 3 }
|
||||
wasmtime = { opt-level = 3 }
|
||||
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
|
||||
activity_indicator = {codegen-units = 1}
|
||||
assets = {codegen-units = 1}
|
||||
breadcrumbs = {codegen-units = 1}
|
||||
collections = {codegen-units = 1}
|
||||
command_palette = {codegen-units = 1}
|
||||
command_palette_hooks = {codegen-units = 1}
|
||||
evals = {codegen-units = 1}
|
||||
extension_cli = {codegen-units = 1}
|
||||
feature_flags = {codegen-units = 1}
|
||||
file_icons = {codegen-units = 1}
|
||||
fsevent = {codegen-units = 1}
|
||||
image_viewer = {codegen-units = 1}
|
||||
inline_completion_button = {codegen-units = 1}
|
||||
install_cli = {codegen-units = 1}
|
||||
journal = {codegen-units = 1}
|
||||
menu = {codegen-units = 1}
|
||||
notifications = {codegen-units = 1}
|
||||
ollama = {codegen-units = 1}
|
||||
outline = {codegen-units = 1}
|
||||
paths = {codegen-units = 1}
|
||||
prettier = {codegen-units = 1}
|
||||
project_symbols = {codegen-units = 1}
|
||||
refineable = {codegen-units = 1}
|
||||
release_channel = {codegen-units = 1}
|
||||
reqwest_client = {codegen-units = 1}
|
||||
rich_text = {codegen-units = 1}
|
||||
semantic_version = {codegen-units = 1}
|
||||
session = {codegen-units = 1}
|
||||
snippet = {codegen-units = 1}
|
||||
snippets_ui = {codegen-units = 1}
|
||||
sqlez_macros = {codegen-units = 1}
|
||||
story = {codegen-units = 1}
|
||||
supermaven_api = {codegen-units = 1}
|
||||
telemetry_events = {codegen-units = 1}
|
||||
theme_selector = {codegen-units = 1}
|
||||
time_format = {codegen-units = 1}
|
||||
ui_input = {codegen-units = 1}
|
||||
vcs_menu = {codegen-units = 1}
|
||||
zed_actions = {codegen-units = 1}
|
||||
|
||||
[profile.release]
|
||||
debug = "limited"
|
||||
|
||||
1
assets/icons/blocks.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-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>
|
||||
|
After Width: | Height: | Size: 368 B |
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 |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4662 14.9152C13.5801 15.0291 13.7648 15.0291 13.8787 14.9152L14.9145 13.8793C15.0284 13.7654 15.0284 13.5807 14.9145 13.4667L12.9483 11.5004L14.9145 9.53392C15.0285 9.42004 15.0285 9.23533 14.9145 9.12137L13.8787 8.08547C13.7648 7.97154 13.5801 7.97154 13.4662 8.08547L11.5 10.0519L9.53376 8.08545C9.41988 7.97152 9.23517 7.97152 9.12124 8.08545L8.08543 9.12136C7.97152 9.23533 7.97152 9.42004 8.08543 9.53392L10.0517 11.5004L8.08545 13.4667C7.97155 13.5807 7.97155 13.7654 8.08545 13.8793L9.12126 14.9152C9.23517 15.0292 9.41988 15.0292 9.53376 14.9152L11.5 12.9489L13.4662 14.9152Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 756 B |
1
assets/icons/keyboard.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-keyboard"><path d="M10 8h.01"/><path d="M12 12h.01"/><path d="M14 8h.01"/><path d="M16 12h.01"/><path d="M18 8h.01"/><path d="M6 8h.01"/><path d="M7 16h10"/><path d="M8 12h.01"/><rect width="20" height="16" x="2" y="4" rx="2"/></svg>
|
||||
|
After Width: | Height: | Size: 436 B |
3
assets/icons/knockouts/dot_bg.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.1" y="2.1" width="6.8" height="6.8" rx="3.4" stroke="black" stroke-width="1.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 195 B |
3
assets/icons/knockouts/dot_fg.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3" width="5" height="5" rx="2.5" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 166 B |
10
assets/icons/knockouts/triangle_bg.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2080_948)">
|
||||
<path d="M1.52492 8.72287L1.04243 9.55H2H9H9.95757L9.47508 8.72287L5.97508 2.72287L5.5 1.90845L5.02492 2.72287L1.52492 8.72287Z" stroke="black" stroke-width="1.1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2080_948">
|
||||
<rect width="11" height="11" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 412 B |
3
assets/icons/knockouts/triangle_fg.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 9H9L5.5 3L2 9Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 146 B |
10
assets/icons/knockouts/x_bg.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2050_903)">
|
||||
<path d="M1.83327 2.89393L1.19687 3.53033L1.83327 4.16672L3.16654 5.5L1.83327 6.83327L1.19687 7.46967L1.83327 8.10606L2.89393 9.16672L3.53033 9.80312L4.16672 9.16672L5.5 7.83345L6.83327 9.16672L7.46967 9.80312L8.10606 9.16672L9.16672 8.10606L9.80312 7.46967L9.16672 6.83327L7.83345 5.5L9.16672 4.16672L9.80312 3.53033L9.16672 2.89393L8.10606 1.83327L7.46967 1.19687L6.83327 1.83327L5.5 3.16654L4.16672 1.83327L3.53033 1.19687L2.89393 1.83327L1.83327 2.89393Z" stroke="black" stroke-width="1.8"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2050_903">
|
||||
<rect width="11" height="11" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 743 B |
3
assets/icons/knockouts/x_fg.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 3L5.5 5.5M8 8L5.5 5.5M5.5 5.5L3 8M5.5 5.5L8 3" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 198 B |
1
assets/icons/phone_incoming.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-phone-incoming"><polyline points="16 2 16 8 22 8"/><line x1="22" x2="16" y1="2" y2="8"/><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||
|
After Width: | Height: | Size: 594 B |
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/swatch_book.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-swatch-book"><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>
|
||||
|
After Width: | Height: | Size: 456 B |
3
assets/icons/triangle.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4L4 12H12L8 4Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 155 B |
3
assets/icons/x.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 177 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"
|
||||
@@ -644,7 +649,19 @@
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
|
||||
"bindings": {
|
||||
"ctrl-shift-p": "file_finder::SelectPrev",
|
||||
"ctrl-k": "file_finder::OpenMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && menu_open",
|
||||
"bindings": {
|
||||
"u": "pane::SplitUp",
|
||||
"d": "pane::SplitDown",
|
||||
"l": "pane::SplitLeft",
|
||||
"r": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "TabSwitcher",
|
||||
|
||||
@@ -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"
|
||||
@@ -651,7 +649,19 @@
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": { "cmd-shift-p": "file_finder::SelectPrev" }
|
||||
"bindings": {
|
||||
"cmd-shift-p": "file_finder::SelectPrev",
|
||||
"cmd-k": "file_finder::OpenMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && menu_open",
|
||||
"bindings": {
|
||||
"u": "pane::SplitUp",
|
||||
"d": "pane::SplitDown",
|
||||
"l": "pane::SplitLeft",
|
||||
"r": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "TabSwitcher",
|
||||
|
||||
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
@@ -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
|
||||
@@ -168,6 +172,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
@@ -206,9 +211,6 @@
|
||||
"\"": ["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",
|
||||
@@ -224,6 +226,7 @@
|
||||
},
|
||||
{
|
||||
"context": "VimControl && VimCount",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"0": ["vim::Number", 0],
|
||||
":": "vim::CountCommand"
|
||||
@@ -231,10 +234,11 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == visual",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "vim::VisualCommand",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
"U": "vim::ConvertToUpperCase",
|
||||
"shift-u": "vim::ConvertToUpperCase",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
@@ -258,8 +262,8 @@
|
||||
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
||||
"shift-i": "vim::InsertBefore",
|
||||
"shift-a": "vim::InsertAfter",
|
||||
"g I": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g A": "vim::VisualInsertEndOfLine",
|
||||
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
@@ -279,6 +283,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
@@ -299,11 +304,13 @@
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||
"insert": "vim::ToggleReplace"
|
||||
"insert": "vim::ToggleReplace",
|
||||
"ctrl-o": "vim::TemporaryNormal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
@@ -311,6 +318,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
@@ -328,6 +336,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == waiting",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
@@ -341,6 +350,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == operator",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
@@ -349,6 +359,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
@@ -364,16 +375,19 @@
|
||||
"b": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"r": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument"
|
||||
"a": "vim::AngleBrackets",
|
||||
"g": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == c",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename", // zed specific
|
||||
@@ -382,6 +396,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
@@ -391,6 +406,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gu",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g u": "vim::CurrentLine",
|
||||
"u": "vim::CurrentLine"
|
||||
@@ -398,6 +414,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gU",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g shift-u": "vim::CurrentLine",
|
||||
"shift-u": "vim::CurrentLine"
|
||||
@@ -405,6 +422,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == g~",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g ~": "vim::CurrentLine",
|
||||
"~": "vim::CurrentLine"
|
||||
@@ -412,6 +430,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gq",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"g q": "vim::CurrentLine",
|
||||
"q": "vim::CurrentLine",
|
||||
@@ -421,6 +440,7 @@
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == y",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||
@@ -428,30 +448,35 @@
|
||||
},
|
||||
{
|
||||
"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"]],
|
||||
@@ -495,6 +520,7 @@
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
@@ -502,6 +528,7 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
@@ -552,6 +579,7 @@
|
||||
},
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
@@ -560,6 +588,7 @@
|
||||
{
|
||||
// netrw compatibility
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"%": "project_panel::NewFile",
|
||||
@@ -587,6 +616,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:
|
||||
//
|
||||
@@ -370,6 +383,16 @@
|
||||
/// "never"
|
||||
"show": null
|
||||
},
|
||||
/// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
"show_diagnostics": "all",
|
||||
// Settings related to indent guides in the project panel.
|
||||
"indent_guides": {
|
||||
// When to show indent guides in the project panel.
|
||||
@@ -475,7 +498,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",
|
||||
@@ -820,7 +843,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.
|
||||
@@ -1160,15 +1182,6 @@
|
||||
// }
|
||||
// ]
|
||||
"ssh_connections": [],
|
||||
// Configures the Context Server Protocol binaries
|
||||
//
|
||||
// Examples:
|
||||
// {
|
||||
// "id": "server-1",
|
||||
// "executable": "/path",
|
||||
// "args": ['arg1", "args2"]
|
||||
// }
|
||||
"experimental.context_servers": {
|
||||
"servers": []
|
||||
}
|
||||
// Configures context servers for use in the Assistant.
|
||||
"context_servers": {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ extension_host.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
smallvec.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -7,9 +7,8 @@ use gpui::{
|
||||
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||
};
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
|
||||
use lsp::LanguageServerName;
|
||||
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||
|
||||
@@ -161,10 +161,7 @@ pub async fn complete(
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
.header(
|
||||
"Anthropic-Beta",
|
||||
"tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
|
||||
)
|
||||
.header("Anthropic-Beta", "prompt-caching-2024-07-31")
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
|
||||
@@ -12,10 +12,14 @@ mod prompts;
|
||||
mod slash_command;
|
||||
pub(crate) mod slash_command_picker;
|
||||
pub mod slash_command_settings;
|
||||
mod slash_command_working_set;
|
||||
mod streaming_diff;
|
||||
mod terminal_inline_assistant;
|
||||
mod tool_working_set;
|
||||
mod tools;
|
||||
|
||||
pub use crate::slash_command_working_set::{SlashCommandId, SlashCommandWorkingSet};
|
||||
pub use crate::tool_working_set::{ToolId, ToolWorkingSet};
|
||||
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
@@ -23,12 +27,11 @@ use assistant_tool::ToolRegistry;
|
||||
use client::{proto, Client};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
pub use context::*;
|
||||
use context_servers::ContextServerRegistry;
|
||||
pub use context_store::*;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::impl_actions;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use gpui::{impl_actions, Context as _};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
@@ -41,10 +44,11 @@ use prompts::PromptLoadingParams;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use slash_command::search_command::SearchSlashCommandFeatureFlag;
|
||||
use slash_command::{
|
||||
auto_command, cargo_workspace_command, context_server_command, default_command, delta_command,
|
||||
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
|
||||
prompt_command, search_command, symbols_command, tab_command, terminal_command,
|
||||
auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
|
||||
docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
|
||||
search_command, selection_command, symbols_command, tab_command, terminal_command,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -211,21 +215,23 @@ pub fn init(
|
||||
});
|
||||
}
|
||||
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
if cx.has_flag::<SearchSlashCommandFeatureFlag>() {
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
context_store::init(&client.clone().into());
|
||||
prompt_library::init(cx);
|
||||
@@ -277,116 +283,9 @@ pub fn init(
|
||||
})
|
||||
.detach();
|
||||
|
||||
register_context_server_handlers(cx);
|
||||
|
||||
prompt_builder
|
||||
}
|
||||
|
||||
fn register_context_server_handlers(cx: &mut AppContext) {
|
||||
cx.subscribe(
|
||||
&context_servers::manager::ContextServerManager::global(cx),
|
||||
|manager, event, cx| match event {
|
||||
context_servers::manager::Event::ServerStarted { server_id } => {
|
||||
cx.update_model(
|
||||
&manager,
|
||||
|manager: &mut context_servers::manager::ContextServerManager, cx| {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let context_server_registry = ContextServerRegistry::global(cx);
|
||||
if let Some(server) = manager.get_server(server_id) {
|
||||
cx.spawn(|_, _| async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
|
||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||
for prompt in prompts
|
||||
.into_iter()
|
||||
.filter(context_server_command::acceptable_prompt)
|
||||
{
|
||||
log::info!(
|
||||
"registering context server command: {:?}",
|
||||
prompt.name
|
||||
);
|
||||
context_server_registry.register_command(
|
||||
server.id.clone(),
|
||||
prompt.name.as_str(),
|
||||
);
|
||||
slash_command_registry.register_command(
|
||||
context_server_command::ContextServerSlashCommand::new(
|
||||
&server, prompt,
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cx.update_model(
|
||||
&manager,
|
||||
|manager: &mut context_servers::manager::ContextServerManager, cx| {
|
||||
let tool_registry = ToolRegistry::global(cx);
|
||||
let context_server_registry = ContextServerRegistry::global(cx);
|
||||
if let Some(server) = manager.get_server(server_id) {
|
||||
cx.spawn(|_, _| async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
|
||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||
for tool in tools.tools {
|
||||
log::info!(
|
||||
"registering context server tool: {:?}",
|
||||
tool.name
|
||||
);
|
||||
context_server_registry.register_tool(
|
||||
server.id.clone(),
|
||||
tool.name.as_str(),
|
||||
);
|
||||
tool_registry.register_tool(
|
||||
tools::context_server_tool::ContextServerTool::new(
|
||||
server.id.clone(),
|
||||
tool
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
context_servers::manager::Event::ServerStopped { server_id } => {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let context_server_registry = ContextServerRegistry::global(cx);
|
||||
if let Some(commands) = context_server_registry.get_commands(server_id) {
|
||||
for command_name in commands {
|
||||
slash_command_registry.unregister_command_by_name(&command_name);
|
||||
context_server_registry.unregister_command(&server_id, &command_name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tools) = context_server_registry.get_tools(server_id) {
|
||||
let tool_registry = ToolRegistry::global(cx);
|
||||
for tool_name in tools {
|
||||
tool_registry.unregister_tool_by_name(&tool_name);
|
||||
context_server_registry.unregister_tool(&server_id, &tool_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn init_language_model_settings(cx: &mut AppContext) {
|
||||
update_active_language_model_from_settings(cx);
|
||||
|
||||
@@ -436,6 +335,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
slash_command_registry
|
||||
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(selection_command::SelectionCommand, true);
|
||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
||||
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
||||
|
||||
@@ -410,7 +410,7 @@ pub struct AssistantSettingsContentV2 {
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The default model to use when creating new contexts.
|
||||
/// The default model to use when creating new chats.
|
||||
default_model: Option<LanguageModelSelection>,
|
||||
/// Additional models with which to generate alternatives when performing inline assists.
|
||||
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||
@@ -498,11 +498,11 @@ pub struct LegacyAssistantSettingsContent {
|
||||
///
|
||||
/// Default: 320
|
||||
pub default_height: Option<f32>,
|
||||
/// The default OpenAI model to use when creating new contexts.
|
||||
/// The default OpenAI model to use when creating new chats.
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
/// OpenAI API base URL to use when creating new contexts.
|
||||
/// OpenAI API base URL to use when creating new chats.
|
||||
///
|
||||
/// Default: https://api.openai.com/v1
|
||||
pub openai_api_url: Option<String>,
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
use super::{AssistantEdit, MessageCacheMetadata};
|
||||
use crate::slash_command_working_set::SlashCommandWorkingSet;
|
||||
use crate::ToolWorkingSet;
|
||||
use crate::{
|
||||
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
|
||||
Context, ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
|
||||
Context, ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId,
|
||||
MessageStatus, PromptBuilder,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandRegistry, SlashCommandResult,
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
|
||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
stream::{self, StreamExt},
|
||||
};
|
||||
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
||||
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||
@@ -27,8 +34,8 @@ use std::{
|
||||
rc::Rc,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId};
|
||||
use ui::{Context as _, WindowContext};
|
||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
||||
use ui::{Context as _, IconName, WindowContext};
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
@@ -44,8 +51,17 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let buffer = context.read(cx).buffer.clone();
|
||||
|
||||
let message_1 = context.read(cx).message_anchors[0].clone();
|
||||
@@ -177,8 +193,17 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let buffer = context.read(cx).buffer.clone();
|
||||
|
||||
let message_1 = context.read(cx).message_anchors[0].clone();
|
||||
@@ -272,8 +297,17 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let buffer = context.read(cx).buffer.clone();
|
||||
|
||||
let message_1 = context.read(cx).message_anchors[0].clone();
|
||||
@@ -378,23 +412,53 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let output_ranges = Rc::new(RefCell::new(HashSet::default()));
|
||||
#[derive(Default)]
|
||||
struct ContextRanges {
|
||||
parsed_commands: HashSet<Range<language::Anchor>>,
|
||||
command_outputs: HashMap<InvokedSlashCommandId, Range<language::Anchor>>,
|
||||
output_sections: HashSet<Range<language::Anchor>>,
|
||||
}
|
||||
|
||||
let context_ranges = Rc::new(RefCell::new(ContextRanges::default()));
|
||||
context.update(cx, |_, cx| {
|
||||
cx.subscribe(&context, {
|
||||
let ranges = output_ranges.clone();
|
||||
move |_, _, event, _| match event {
|
||||
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
||||
for range in removed {
|
||||
ranges.borrow_mut().remove(range);
|
||||
let context_ranges = context_ranges.clone();
|
||||
move |context, _, event, _| {
|
||||
let mut context_ranges = context_ranges.borrow_mut();
|
||||
match event {
|
||||
ContextEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
let command = context.invoked_slash_command(command_id).unwrap();
|
||||
context_ranges
|
||||
.command_outputs
|
||||
.insert(*command_id, command.range.clone());
|
||||
}
|
||||
for command in updated {
|
||||
ranges.borrow_mut().insert(command.source_range.clone());
|
||||
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
for range in removed {
|
||||
context_ranges.parsed_commands.remove(range);
|
||||
}
|
||||
for command in updated {
|
||||
context_ranges
|
||||
.parsed_commands
|
||||
.insert(command.source_range.clone());
|
||||
}
|
||||
}
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
context_ranges.output_sections.insert(section.range.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -406,14 +470,12 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
|
||||
});
|
||||
assert_text_and_output_ranges(
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&output_ranges.borrow(),
|
||||
"
|
||||
«/file src/lib.rs»
|
||||
"
|
||||
.unindent()
|
||||
.trim_end(),
|
||||
&context_ranges,
|
||||
&"
|
||||
«/file src/lib.rs»"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -422,14 +484,12 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
let edit_offset = buffer.text().find("lib.rs").unwrap();
|
||||
buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
|
||||
});
|
||||
assert_text_and_output_ranges(
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&output_ranges.borrow(),
|
||||
"
|
||||
«/file src/main.rs»
|
||||
"
|
||||
.unindent()
|
||||
.trim_end(),
|
||||
&context_ranges,
|
||||
&"
|
||||
«/file src/main.rs»"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -442,36 +502,180 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_text_and_output_ranges(
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&output_ranges.borrow(),
|
||||
&context_ranges,
|
||||
&"
|
||||
/unknown src/main.rs"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Undoing the insertion of an non-existent slash command resorts the previous one.
|
||||
buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
«/file src/main.rs»"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let (command_output_tx, command_output_rx) = mpsc::unbounded();
|
||||
context.update(cx, |context, cx| {
|
||||
let command_source_range = context.parsed_slash_commands[0].source_range.clone();
|
||||
context.insert_command_output(
|
||||
command_source_range,
|
||||
"file",
|
||||
Task::ready(Ok(command_output_rx.boxed())),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
…⟧
|
||||
"
|
||||
/unknown src/main.rs
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::Ai,
|
||||
label: "src/main.rs".into(),
|
||||
metadata: None,
|
||||
}))
|
||||
.unwrap();
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::Content("src/main.rs".into())))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
src/main.rs…⟧
|
||||
"
|
||||
.unindent()
|
||||
.trim_end(),
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::Content("\nfn main() {}".into())))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
src/main.rs
|
||||
fn main() {}…⟧
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::EndSection))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
⟪src/main.rs
|
||||
fn main() {}⟫…⟧
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
drop(command_output_tx);
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦⟪src/main.rs
|
||||
fn main() {}⟫⟧
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
fn assert_text_and_output_ranges(
|
||||
fn assert_text_and_context_ranges(
|
||||
buffer: &Model<Buffer>,
|
||||
ranges: &HashSet<Range<language::Anchor>>,
|
||||
ranges: &RefCell<ContextRanges>,
|
||||
expected_marked_text: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
|
||||
let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
|
||||
let mut ranges = ranges
|
||||
.iter()
|
||||
.map(|range| range.to_offset(buffer))
|
||||
.collect::<Vec<_>>();
|
||||
ranges.sort_by_key(|a| a.start);
|
||||
(buffer.text(), ranges)
|
||||
let mut actual_marked_text = String::new();
|
||||
buffer.update(cx, |buffer, _| {
|
||||
struct Endpoint {
|
||||
offset: usize,
|
||||
marker: char,
|
||||
}
|
||||
|
||||
let ranges = ranges.borrow();
|
||||
let mut endpoints = Vec::new();
|
||||
for range in ranges.command_outputs.values() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.start.to_offset(buffer),
|
||||
marker: '⟦',
|
||||
});
|
||||
}
|
||||
for range in ranges.parsed_commands.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.start.to_offset(buffer),
|
||||
marker: '«',
|
||||
});
|
||||
}
|
||||
for range in ranges.output_sections.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.start.to_offset(buffer),
|
||||
marker: '⟪',
|
||||
});
|
||||
}
|
||||
|
||||
for range in ranges.output_sections.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.end.to_offset(buffer),
|
||||
marker: '⟫',
|
||||
});
|
||||
}
|
||||
for range in ranges.parsed_commands.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.end.to_offset(buffer),
|
||||
marker: '»',
|
||||
});
|
||||
}
|
||||
for range in ranges.command_outputs.values() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.end.to_offset(buffer),
|
||||
marker: '⟧',
|
||||
});
|
||||
}
|
||||
|
||||
endpoints.sort_by_key(|endpoint| endpoint.offset);
|
||||
let mut offset = 0;
|
||||
for endpoint in endpoints {
|
||||
actual_marked_text.extend(buffer.text_for_range(offset..endpoint.offset));
|
||||
actual_marked_text.push(endpoint.marker);
|
||||
offset = endpoint.offset;
|
||||
}
|
||||
actual_marked_text.extend(buffer.text_for_range(offset..buffer.len()));
|
||||
});
|
||||
|
||||
assert_eq!(actual_text, expected_text);
|
||||
assert_eq!(actual_ranges, expected_ranges);
|
||||
assert_eq!(actual_marked_text, expected_marked_text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,6 +709,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
Some(project),
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -768,6 +974,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
Default::default(),
|
||||
registry.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
@@ -876,8 +1084,17 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
cx.update(assistant_panel::init);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let buffer = context.read_with(cx, |context, _| context.buffer.clone());
|
||||
let message_0 = context.read_with(cx, |context, _| context.message_anchors[0].id);
|
||||
let message_1 = context.update(cx, |context, cx| {
|
||||
@@ -917,6 +1134,8 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
Default::default(),
|
||||
registry.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
@@ -975,6 +1194,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
language::Capability::ReadWrite,
|
||||
registry.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
@@ -1063,44 +1284,57 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
offset + 1..offset + 1 + command_text.len()
|
||||
});
|
||||
|
||||
let output_len = rng.gen_range(1..=10);
|
||||
let output_text = RandomCharIter::new(&mut rng)
|
||||
.filter(|c| *c != '\r')
|
||||
.take(output_len)
|
||||
.take(10)
|
||||
.collect::<String>();
|
||||
|
||||
let mut events = vec![Ok(SlashCommandEvent::StartMessage {
|
||||
role: Role::User,
|
||||
merge_same_roles: true,
|
||||
})];
|
||||
|
||||
let num_sections = rng.gen_range(0..=3);
|
||||
let mut sections = Vec::with_capacity(num_sections);
|
||||
let mut section_start = 0;
|
||||
for _ in 0..num_sections {
|
||||
let section_start = rng.gen_range(0..output_len);
|
||||
let section_end = rng.gen_range(section_start..=output_len);
|
||||
sections.push(SlashCommandOutputSection {
|
||||
range: section_start..section_end,
|
||||
icon: ui::IconName::Ai,
|
||||
let mut section_end = rng.gen_range(section_start..=output_text.len());
|
||||
while !output_text.is_char_boundary(section_end) {
|
||||
section_end += 1;
|
||||
}
|
||||
events.push(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::Ai,
|
||||
label: "section".into(),
|
||||
metadata: None,
|
||||
});
|
||||
}));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: output_text[section_start..section_end].to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
events.push(Ok(SlashCommandEvent::EndSection));
|
||||
section_start = section_end;
|
||||
}
|
||||
|
||||
if section_start < output_text.len() {
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: output_text[section_start..].to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Context {}: insert slash command output at {:?} with {:?}",
|
||||
"Context {}: insert slash command output at {:?} with {:?} events",
|
||||
context_index,
|
||||
command_range,
|
||||
sections
|
||||
events.len()
|
||||
);
|
||||
|
||||
let command_range = context.buffer.read(cx).anchor_after(command_range.start)
|
||||
..context.buffer.read(cx).anchor_after(command_range.end);
|
||||
context.insert_command_output(
|
||||
command_range,
|
||||
Task::ready(Ok(SlashCommandOutput {
|
||||
text: output_text,
|
||||
sections,
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())),
|
||||
"/command",
|
||||
Task::ready(Ok(stream::iter(events).boxed())),
|
||||
true,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -1178,7 +1412,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
let first_context = contexts[0].read(cx);
|
||||
for context in &contexts[1..] {
|
||||
let context = context.read(cx);
|
||||
assert!(context.pending_ops.is_empty());
|
||||
assert!(context.pending_ops.is_empty(), "pending ops: {:?}", context.pending_ops);
|
||||
assert_eq!(
|
||||
context.buffer.read(cx).text(),
|
||||
first_context.buffer.read(cx).text(),
|
||||
@@ -1215,8 +1449,17 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let buffer = context.read(cx).buffer.clone();
|
||||
|
||||
// Create a test cache configuration
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use crate::slash_command::context_server_command;
|
||||
use crate::{
|
||||
prompts::PromptBuilder, Context, ContextEvent, ContextId, ContextOperation, ContextVersion,
|
||||
SavedContext, SavedContextMetadata,
|
||||
prompts::PromptBuilder, slash_command_working_set::SlashCommandWorkingSet, Context,
|
||||
ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext, SavedContextMetadata,
|
||||
};
|
||||
use crate::{tools, SlashCommandId, ToolId, ToolWorkingSet};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||
use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use context_servers::manager::ContextServerManager;
|
||||
use context_servers::ContextServerFactoryRegistry;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
@@ -43,9 +48,14 @@ pub struct RemoteContextMetadata {
|
||||
pub struct ContextStore {
|
||||
contexts: Vec<ContextHandle>,
|
||||
contexts_metadata: Vec<SavedContextMetadata>,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
|
||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||
host_contexts: Vec<RemoteContextMetadata>,
|
||||
fs: Arc<dyn Fs>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
_watch_updates: Task<Option<()>>,
|
||||
client: Arc<Client>,
|
||||
@@ -87,6 +97,8 @@ impl ContextStore {
|
||||
pub fn new(
|
||||
project: Model<Project>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
let fs = project.read(cx).fs().clone();
|
||||
@@ -97,12 +109,22 @@ impl ContextStore {
|
||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
let context_server_factory_registry =
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
let context_server_manager = cx.new_model(|cx| {
|
||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||
});
|
||||
let mut this = Self {
|
||||
contexts: Vec::new(),
|
||||
contexts_metadata: Vec::new(),
|
||||
context_server_manager,
|
||||
context_server_slash_command_ids: HashMap::default(),
|
||||
context_server_tool_ids: HashMap::default(),
|
||||
host_contexts: Vec::new(),
|
||||
fs,
|
||||
languages,
|
||||
slash_commands,
|
||||
tools,
|
||||
telemetry,
|
||||
_watch_updates: cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
@@ -125,13 +147,15 @@ impl ContextStore {
|
||||
project: project.clone(),
|
||||
prompt_builder,
|
||||
};
|
||||
this.handle_project_changed(project, cx);
|
||||
this.handle_project_changed(project.clone(), cx);
|
||||
this.synchronize_contexts(cx);
|
||||
this.register_context_server_handlers(cx);
|
||||
this
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
Ok(this)
|
||||
})
|
||||
}
|
||||
@@ -342,6 +366,8 @@ impl ContextStore {
|
||||
Some(self.project.clone()),
|
||||
Some(self.telemetry.clone()),
|
||||
self.prompt_builder.clone(),
|
||||
self.slash_commands.clone(),
|
||||
self.tools.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -364,6 +390,8 @@ impl ContextStore {
|
||||
let project = self.project.clone();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
let request = self.client.request(proto::CreateContext { project_id });
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
@@ -376,6 +404,8 @@ impl ContextStore {
|
||||
capability,
|
||||
language_registry,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
@@ -425,6 +455,8 @@ impl ContextStore {
|
||||
}
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let saved_context = load.await?;
|
||||
@@ -434,6 +466,8 @@ impl ContextStore {
|
||||
path.clone(),
|
||||
languages,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
@@ -500,6 +534,8 @@ impl ContextStore {
|
||||
context_id: context_id.to_proto(),
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
@@ -510,6 +546,8 @@ impl ContextStore {
|
||||
capability,
|
||||
language_registry,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
@@ -745,4 +783,114 @@ impl ContextStore {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) {
|
||||
cx.update_model(
|
||||
&self.context_server_manager,
|
||||
|context_server_manager, cx| {
|
||||
for server in context_server_manager.servers() {
|
||||
context_server_manager
|
||||
.restart_server(&server.id(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
|
||||
cx.subscribe(
|
||||
&self.context_server_manager.clone(),
|
||||
Self::handle_context_server_event,
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_context_server_event(
|
||||
&mut self,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
event: &context_servers::manager::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let slash_command_working_set = self.slash_commands.clone();
|
||||
let tool_working_set = self.tools.clone();
|
||||
match event {
|
||||
context_servers::manager::Event::ServerStarted { server_id } => {
|
||||
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
||||
let context_server_manager = context_server_manager.clone();
|
||||
cx.spawn({
|
||||
let server = server.clone();
|
||||
let server_id = server_id.clone();
|
||||
|this, mut cx| async move {
|
||||
let Some(protocol) = server.client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
|
||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||
let slash_command_ids = prompts
|
||||
.into_iter()
|
||||
.filter(context_server_command::acceptable_prompt)
|
||||
.map(|prompt| {
|
||||
log::info!(
|
||||
"registering context server command: {:?}",
|
||||
prompt.name
|
||||
);
|
||||
slash_command_working_set.insert(Arc::new(
|
||||
context_server_command::ContextServerSlashCommand::new(
|
||||
context_server_manager.clone(),
|
||||
&server,
|
||||
prompt,
|
||||
),
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.context_server_slash_command_ids
|
||||
.insert(server_id.clone(), slash_command_ids);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
|
||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||
let tool_ids = tools.tools.into_iter().map(|tool| {
|
||||
log::info!("registering context server tool: {:?}", tool.name);
|
||||
tool_working_set.insert(
|
||||
Arc::new(tools::context_server_tool::ContextServerTool::new(
|
||||
context_server_manager.clone(),
|
||||
server.id(),
|
||||
tool,
|
||||
)),
|
||||
)
|
||||
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.context_server_tool_ids
|
||||
.insert(server_id, tool_ids);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
context_servers::manager::Event::ServerStopped { server_id } => {
|
||||
if let Some(slash_command_ids) =
|
||||
self.context_server_slash_command_ids.remove(server_id)
|
||||
{
|
||||
slash_command_working_set.remove(&slash_command_ids);
|
||||
}
|
||||
|
||||
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
||||
tool_working_set.remove(&tool_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ use futures::{
|
||||
join, SinkExt, Stream, StreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext, Subscription, Task,
|
||||
TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
|
||||
use language_model::{
|
||||
@@ -53,7 +53,9 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
|
||||
use ui::{
|
||||
prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
|
||||
};
|
||||
use util::{RangeExt, ResultExt};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
|
||||
@@ -84,7 +86,7 @@ pub struct InlineAssistant {
|
||||
confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
@@ -105,7 +107,7 @@ impl InlineAssistant {
|
||||
confirmed_assists: HashMap::default(),
|
||||
prompt_history: VecDeque::default(),
|
||||
prompt_builder,
|
||||
telemetry: Some(telemetry),
|
||||
telemetry,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
@@ -241,19 +243,17 @@ impl InlineAssistant {
|
||||
codegen_ranges.push(start..end);
|
||||
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
telemetry.report_assistant_event(AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
}
|
||||
self.telemetry.report_assistant_event(AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ impl InlineAssistant {
|
||||
style: BlockStyle::Sticky,
|
||||
placement: BlockPlacement::Below(range.end),
|
||||
height: 0,
|
||||
render: Box::new(|cx| {
|
||||
render: Arc::new(|cx| {
|
||||
v_flex()
|
||||
.h_full()
|
||||
.w_full()
|
||||
@@ -816,7 +816,7 @@ impl InlineAssistant {
|
||||
error_message: None,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
},
|
||||
self.telemetry.clone(),
|
||||
Some(self.telemetry.clone()),
|
||||
cx.http_client(),
|
||||
model.api_key(cx),
|
||||
cx.background_executor(),
|
||||
@@ -1197,8 +1197,9 @@ impl InlineAssistant {
|
||||
placement: BlockPlacement::Above(new_row),
|
||||
height,
|
||||
style: BlockStyle::Flex,
|
||||
render: Box::new(move |cx| {
|
||||
render: Arc::new(move |cx| {
|
||||
div()
|
||||
.block_mouse_down()
|
||||
.bg(cx.theme().status().deleted_background)
|
||||
.size_full()
|
||||
.h(height as f32 * cx.line_height())
|
||||
@@ -1317,7 +1318,7 @@ impl InlineAssistGroup {
|
||||
|
||||
fn build_assist_editor_renderer(editor: &View<PromptEditor>) -> RenderBlock {
|
||||
let editor = editor.clone();
|
||||
Box::new(move |cx: &mut BlockContext| {
|
||||
Arc::new(move |cx: &mut BlockContext| {
|
||||
*editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
|
||||
editor.clone().into_any_element()
|
||||
})
|
||||
@@ -1480,6 +1481,8 @@ impl Render for PromptEditor {
|
||||
h_flex()
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_down()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.size_full()
|
||||
@@ -1757,6 +1760,20 @@ impl PromptEditor {
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } => {
|
||||
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let is_via_ssh = workspace
|
||||
.project()
|
||||
.update(cx, |project, _| project.is_via_ssh());
|
||||
|
||||
workspace
|
||||
.client()
|
||||
.telemetry()
|
||||
.log_edit_event("inline assist", is_via_ssh);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
if self
|
||||
.prompt_history_ix
|
||||
@@ -1899,21 +1916,58 @@ impl PromptEditor {
|
||||
let codegen = self.codegen.read(cx);
|
||||
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
||||
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let default_model = model_registry.active_model();
|
||||
let alternative_models = model_registry.inline_alternative_models();
|
||||
|
||||
let get_model_name = |index: usize| -> String {
|
||||
let name = |model: &Arc<dyn LanguageModel>| model.name().0.to_string();
|
||||
|
||||
match index {
|
||||
0 => default_model.as_ref().map_or_else(String::new, name),
|
||||
index if index <= alternative_models.len() => alternative_models
|
||||
.get(index - 1)
|
||||
.map_or_else(String::new, name),
|
||||
_ => String::new(),
|
||||
}
|
||||
};
|
||||
|
||||
let total_models = alternative_models.len() + 1;
|
||||
|
||||
if total_models <= 1 {
|
||||
return div().into_any_element();
|
||||
}
|
||||
|
||||
let current_index = codegen.active_alternative;
|
||||
let prev_index = (current_index + total_models - 1) % total_models;
|
||||
let next_index = (current_index + 1) % total_models;
|
||||
|
||||
let prev_model_name = get_model_name(prev_index);
|
||||
let next_model_name = get_model_name(next_index);
|
||||
|
||||
h_flex()
|
||||
.child(
|
||||
IconButton::new("previous", IconName::ChevronLeft)
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(disabled)
|
||||
.disabled(disabled || current_index == 0)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Previous Alternative",
|
||||
&CyclePreviousInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
cx.new_view(|cx| {
|
||||
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CyclePreviousInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
if !disabled && current_index != 0 {
|
||||
tooltip = tooltip.meta(prev_model_name.clone());
|
||||
}
|
||||
tooltip
|
||||
})
|
||||
.into()
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
@@ -1937,17 +1991,25 @@ impl PromptEditor {
|
||||
.child(
|
||||
IconButton::new("next", IconName::ChevronRight)
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(disabled)
|
||||
.disabled(disabled || current_index == total_models - 1)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Next Alternative",
|
||||
&CycleNextInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
cx.new_view(|cx| {
|
||||
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CycleNextInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
if !disabled && current_index != total_models - 1 {
|
||||
tooltip = tooltip.meta(next_model_name.clone());
|
||||
}
|
||||
tooltip
|
||||
})
|
||||
.into()
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
@@ -2290,7 +2352,7 @@ pub struct Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
is_insertion: bool,
|
||||
}
|
||||
@@ -2300,7 +2362,7 @@ impl Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
@@ -2309,7 +2371,7 @@ impl Codegen {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
false,
|
||||
telemetry.clone(),
|
||||
Some(telemetry.clone()),
|
||||
builder.clone(),
|
||||
cx,
|
||||
)
|
||||
@@ -2400,7 +2462,7 @@ impl Codegen {
|
||||
self.buffer.clone(),
|
||||
self.range.clone(),
|
||||
false,
|
||||
self.telemetry.clone(),
|
||||
Some(self.telemetry.clone()),
|
||||
self.builder.clone(),
|
||||
cx,
|
||||
)
|
||||
@@ -2503,6 +2565,7 @@ pub struct CodegenAlternative {
|
||||
line_operations: Vec<LineOperation>,
|
||||
request: Option<LanguageModelRequest>,
|
||||
elapsed_time: Option<f64>,
|
||||
completion: Option<String>,
|
||||
message_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -2578,6 +2641,7 @@ impl CodegenAlternative {
|
||||
range,
|
||||
request: None,
|
||||
elapsed_time: None,
|
||||
completion: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2790,6 +2854,9 @@ impl CodegenAlternative {
|
||||
self.diff = Diff::default();
|
||||
self.status = CodegenStatus::Pending;
|
||||
let mut edit_start = self.range.start.to_offset(&snapshot);
|
||||
let completion = Arc::new(Mutex::new(String::new()));
|
||||
let completion_clone = completion.clone();
|
||||
|
||||
self.generation = cx.spawn(|codegen, mut cx| {
|
||||
async move {
|
||||
let stream = stream.await;
|
||||
@@ -2821,6 +2888,7 @@ impl CodegenAlternative {
|
||||
response_latency = Some(request_start.elapsed());
|
||||
}
|
||||
let chunk = chunk?;
|
||||
completion_clone.lock().push_str(&chunk);
|
||||
|
||||
let mut lines = chunk.split('\n').peekable();
|
||||
while let Some(line) = lines.next() {
|
||||
@@ -2990,6 +3058,7 @@ impl CodegenAlternative {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
this.elapsed_time = Some(elapsed_time);
|
||||
this.completion = Some(completion.lock().clone());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
})
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
use feature_flags::ZedPro;
|
||||
use gpui::Action;
|
||||
use gpui::DismissEvent;
|
||||
|
||||
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
|
||||
use proto::Plan;
|
||||
use workspace::ShowConfiguration;
|
||||
|
||||
use std::sync::Arc;
|
||||
use ui::ListItemSpacing;
|
||||
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use fs::Fs;
|
||||
use gpui::SharedString;
|
||||
use gpui::Task;
|
||||
use gpui::{Action, AnyElement, DismissEvent, SharedString, Task};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use settings::update_settings_file;
|
||||
use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
|
||||
|
||||
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
|
||||
|
||||
@@ -85,14 +81,36 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let all_models = self.all_models.clone();
|
||||
|
||||
let llm_registry = LanguageModelRegistry::global(cx);
|
||||
|
||||
let configured_models: Vec<_> = llm_registry
|
||||
.read(cx)
|
||||
.providers()
|
||||
.iter()
|
||||
.filter(|provider| provider.is_authenticated(cx))
|
||||
.map(|provider| provider.id())
|
||||
.collect();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let filtered_models = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
if query.is_empty() {
|
||||
let displayed_models = if configured_models.is_empty() {
|
||||
all_models
|
||||
} else {
|
||||
all_models
|
||||
.into_iter()
|
||||
.filter(|model_info| {
|
||||
configured_models.contains(&model_info.model.provider_id())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
if query.is_empty() {
|
||||
displayed_models
|
||||
} else {
|
||||
displayed_models
|
||||
.into_iter()
|
||||
.filter(|model_info| {
|
||||
model_info
|
||||
@@ -141,6 +159,29 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn render_header(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
|
||||
let configured_models_count = LanguageModelRegistry::global(cx)
|
||||
.read(cx)
|
||||
.providers()
|
||||
.iter()
|
||||
.filter(|provider| provider.is_authenticated(cx))
|
||||
.count();
|
||||
|
||||
if configured_models_count > 0 {
|
||||
Some(
|
||||
Label::new("Configured Models")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.mt_1()
|
||||
.mb_0p5()
|
||||
.ml_3()
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
@@ -148,9 +189,10 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
let model_info = self.filtered_models.get(ix)?;
|
||||
let show_badges = cx.has_flag::<ZedPro>();
|
||||
let provider_name: String = model_info.model.provider_name().0.into();
|
||||
|
||||
let model_info = self.filtered_models.get(ix)?;
|
||||
let provider_name: String = model_info.model.provider_name().0.clone().into();
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
@@ -165,27 +207,32 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().justify_between().min_w(px(200.)).child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Label::new(model_info.model.name().0.clone()))
|
||||
.child(
|
||||
Label::new(provider_name)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.children(match model_info.availability {
|
||||
LanguageModelAvailability::Public => None,
|
||||
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
|
||||
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
|
||||
show_badges.then(|| {
|
||||
Label::new("Pro")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
})
|
||||
}
|
||||
}),
|
||||
),
|
||||
h_flex()
|
||||
.w_full()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.min_w(px(200.))
|
||||
.child(Label::new(model_info.model.name().0.clone()))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(provider_name)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.children(match model_info.availability {
|
||||
LanguageModelAvailability::Public => None,
|
||||
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
|
||||
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
|
||||
show_badges.then(|| {
|
||||
Label::new("Pro")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
})
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.end_slot(div().when(model_info.is_selected, |this| {
|
||||
this.child(
|
||||
@@ -213,7 +260,7 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
.justify_between()
|
||||
.when(cx.has_flag::<ZedPro>(), |this| {
|
||||
this.child(match plan {
|
||||
// Already a zed pro subscriber
|
||||
// Already a Zed Pro subscriber
|
||||
Plan::ZedPro => Button::new("zed-pro", "Zed Pro")
|
||||
.icon(IconName::ZedAssistant)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -254,6 +301,7 @@ impl<T: PopoverTrigger> RenderOnce for ModelSelector<T> {
|
||||
let selected_provider = LanguageModelRegistry::read_global(cx)
|
||||
.active_provider()
|
||||
.map(|m| m.id());
|
||||
|
||||
let selected_model = LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|m| m.id());
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::SlashCommandWorkingSet;
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssistant};
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -522,7 +523,11 @@ impl PromptLibrary {
|
||||
editor.set_use_modal_editing(false);
|
||||
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
||||
editor.set_completion_provider(Some(Box::new(
|
||||
SlashCommandCompletionProvider::new(None, None),
|
||||
SlashCommandCompletionProvider::new(
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
)));
|
||||
if focus {
|
||||
editor.focus(cx);
|
||||
@@ -825,7 +830,7 @@ impl PromptLibrary {
|
||||
.overflow_x_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.p(Spacing::Small.rems(cx))
|
||||
.p(DynamicSpacing::Base04.rems(cx))
|
||||
.h_9()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
@@ -866,17 +871,17 @@ impl PromptLibrary {
|
||||
.size_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.pl(Spacing::XXLarge.rems(cx))
|
||||
.pt(Spacing::Large.rems(cx))
|
||||
.pl(DynamicSpacing::Base16.rems(cx))
|
||||
.pt(DynamicSpacing::Base08.rems(cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.focus(&focus_handle);
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.group("active-editor-header")
|
||||
.pr(Spacing::XXLarge.rems(cx))
|
||||
.pt(Spacing::XSmall.rems(cx))
|
||||
.pb(Spacing::Large.rems(cx))
|
||||
.pr(DynamicSpacing::Base16.rems(cx))
|
||||
.pt(DynamicSpacing::Base02.rems(cx))
|
||||
.pb(DynamicSpacing::Base08.rems(cx))
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex().gap_1().child(
|
||||
@@ -938,13 +943,13 @@ impl PromptLibrary {
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.gap(DynamicSpacing::Base16.rems(cx))
|
||||
.child(div()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.gap(DynamicSpacing::Base16.rems(cx))
|
||||
.children(prompt_editor.token_count.map(
|
||||
|token_count| {
|
||||
let token_count: SharedString =
|
||||
|
||||
@@ -149,7 +149,7 @@ impl PromptBuilder {
|
||||
if file_path.to_string_lossy().ends_with(".hbs") {
|
||||
if let Ok(content) = params.fs.load(&file_path).await {
|
||||
let file_name = file_path.file_stem().unwrap().to_string_lossy();
|
||||
log::info!("Registering prompt template override: {}", file_name);
|
||||
log::debug!("Registering prompt template override: {}", file_name);
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ impl PromptBuilder {
|
||||
for path in Assets.list("prompts")? {
|
||||
if let Some(id) = path.split('/').last().and_then(|s| s.strip_suffix(".hbs")) {
|
||||
if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() {
|
||||
log::info!("Registering built-in prompt template: {}", id);
|
||||
log::debug!("Registering built-in prompt template: {}", id);
|
||||
let prompt = String::from_utf8_lossy(prompt.as_ref());
|
||||
handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))?
|
||||
}
|
||||
@@ -310,7 +310,7 @@ impl PromptBuilder {
|
||||
.render("terminal_assistant_prompt", &context)
|
||||
}
|
||||
|
||||
pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
|
||||
pub fn generate_suggest_edits_prompt(&self) -> Result<String, RenderError> {
|
||||
self.handlebars.lock().render("suggest_edits", &())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::AfterCompletion;
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
|
||||
use editor::{CompletionProvider, Editor};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
|
||||
@@ -31,6 +32,7 @@ pub mod now_command;
|
||||
pub mod project_command;
|
||||
pub mod prompt_command;
|
||||
pub mod search_command;
|
||||
pub mod selection_command;
|
||||
pub mod streaming_example_command;
|
||||
pub mod symbols_command;
|
||||
pub mod tab_command;
|
||||
@@ -38,6 +40,7 @@ pub mod terminal_command;
|
||||
|
||||
pub(crate) struct SlashCommandCompletionProvider {
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
}
|
||||
@@ -51,11 +54,13 @@ pub(crate) struct SlashCommandLine {
|
||||
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
|
||||
slash_commands,
|
||||
editor,
|
||||
workspace,
|
||||
}
|
||||
@@ -68,9 +73,9 @@ impl SlashCommandCompletionProvider {
|
||||
name_range: Range<Anchor>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<project::Completion>>> {
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
let candidates = commands
|
||||
.command_names()
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let candidates = slash_commands
|
||||
.command_names(cx)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, def)| StringMatchCandidate {
|
||||
@@ -97,7 +102,7 @@ impl SlashCommandCompletionProvider {
|
||||
matches
|
||||
.into_iter()
|
||||
.filter_map(|mat| {
|
||||
let command = commands.command(&mat.string)?;
|
||||
let command = slash_commands.command(&mat.string, cx)?;
|
||||
let mut new_text = mat.string.clone();
|
||||
let requires_argument = command.requires_argument();
|
||||
let accepts_arguments = command.accepts_arguments();
|
||||
@@ -126,7 +131,6 @@ impl SlashCommandCompletionProvider {
|
||||
&command_name,
|
||||
&[],
|
||||
true,
|
||||
false,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -167,8 +171,7 @@ impl SlashCommandCompletionProvider {
|
||||
let mut flag = self.cancel_flag.lock();
|
||||
flag.store(true, SeqCst);
|
||||
*flag = new_cancel_flag.clone();
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
if let Some(command) = commands.command(command_name) {
|
||||
if let Some(command) = self.slash_commands.command(command_name, cx) {
|
||||
let completions = command.complete_argument(
|
||||
arguments,
|
||||
new_cancel_flag.clone(),
|
||||
@@ -211,7 +214,6 @@ impl SlashCommandCompletionProvider {
|
||||
&command_name,
|
||||
&completed_arguments,
|
||||
true,
|
||||
false,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ use context_servers::{
|
||||
manager::{ContextServer, ContextServerManager},
|
||||
types::Prompt,
|
||||
};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
@@ -19,15 +19,21 @@ use workspace::Workspace;
|
||||
use crate::slash_command::create_label_for_command;
|
||||
|
||||
pub struct ContextServerSlashCommand {
|
||||
server_id: String,
|
||||
server_manager: Model<ContextServerManager>,
|
||||
server_id: Arc<str>,
|
||||
prompt: Prompt,
|
||||
}
|
||||
|
||||
impl ContextServerSlashCommand {
|
||||
pub fn new(server: &Arc<ContextServer>, prompt: Prompt) -> Self {
|
||||
pub fn new(
|
||||
server_manager: Model<ContextServerManager>,
|
||||
server: &Arc<ContextServer>,
|
||||
prompt: Prompt,
|
||||
) -> Self {
|
||||
Self {
|
||||
server_id: server.id.clone(),
|
||||
server_id: server.id(),
|
||||
prompt,
|
||||
server_manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,20 +80,16 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
|
||||
return Task::ready(Err(anyhow!("Failed to complete argument")));
|
||||
};
|
||||
|
||||
let server_id = self.server_id.clone();
|
||||
let prompt_name = self.prompt.name.clone();
|
||||
let manager = ContextServerManager::global(cx);
|
||||
let manager = manager.read(cx);
|
||||
|
||||
let (arg_name, arg_val) = match completion_argument(&self.prompt, arguments) {
|
||||
Ok(tp) => tp,
|
||||
Err(e) => {
|
||||
return Task::ready(Err(e));
|
||||
}
|
||||
};
|
||||
if let Some(server) = manager.get_server(&server_id) {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&server_id) {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
let Some(protocol) = server.client() else {
|
||||
return Err(anyhow!("Context server not initialized"));
|
||||
};
|
||||
|
||||
@@ -100,7 +102,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
},
|
||||
),
|
||||
arg_name,
|
||||
arg_val,
|
||||
arg_value,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -138,11 +140,10 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
|
||||
let manager = ContextServerManager::global(cx);
|
||||
let manager = manager.read(cx);
|
||||
let manager = self.server_manager.read(cx);
|
||||
if let Some(server) = manager.get_server(&server_id) {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
let Some(protocol) = server.client() else {
|
||||
return Err(anyhow!("Context server not initialized"));
|
||||
};
|
||||
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
||||
|
||||
@@ -58,6 +58,7 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
let mut paths = HashSet::default();
|
||||
let mut file_command_old_outputs = Vec::new();
|
||||
let mut file_command_new_outputs = Vec::new();
|
||||
|
||||
for section in context_slash_command_output_sections.iter().rev() {
|
||||
if let Some(metadata) = section
|
||||
.metadata
|
||||
@@ -84,6 +85,7 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut output = SlashCommandOutput::default();
|
||||
let mut changes_detected = false;
|
||||
|
||||
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
|
||||
for (old_text, new_output) in file_command_old_outputs
|
||||
@@ -96,6 +98,7 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
if let Some(file_command_range) = new_output.sections.first() {
|
||||
let new_text = &new_output.text[file_command_range.range.clone()];
|
||||
if old_text.chars().ne(new_text.chars()) {
|
||||
changes_detected = true;
|
||||
output.sections.extend(new_output.sections.into_iter().map(
|
||||
|section| SlashCommandOutputSection {
|
||||
range: output.text.len() + section.range.start
|
||||
@@ -112,6 +115,10 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if !changes_detected {
|
||||
return Err(anyhow!("no new changes detected"));
|
||||
}
|
||||
|
||||
Ok(output.to_event_stream())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::slash_command::diagnostics_command::collect_buffer_diagnostics;
|
||||
|
||||
pub(crate) struct FileSlashCommand;
|
||||
|
||||
impl FileSlashCommand {
|
||||
@@ -258,8 +256,7 @@ fn collect_files(
|
||||
break;
|
||||
}
|
||||
directory_stack.pop().unwrap();
|
||||
events_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
@@ -364,7 +361,7 @@ fn collect_files(
|
||||
}
|
||||
|
||||
while let Some(_) = directory_stack.pop() {
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,8 +540,6 @@ pub fn append_buffer_to_output(
|
||||
output.text.push('\n');
|
||||
|
||||
let section_ix = output.sections.len();
|
||||
collect_buffer_diagnostics(output, buffer, false);
|
||||
|
||||
output.sections.insert(
|
||||
section_ix,
|
||||
build_entry_output_section(prev_len..output.text.len(), path, false, None),
|
||||
|
||||
@@ -21,6 +21,10 @@ pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||
|
||||
impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "search-slash-command";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SearchSlashCommand;
|
||||
|
||||
98
crates/assistant/src/slash_command/selection_command.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::assistant_panel::selections_creases;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
|
||||
SlashCommandOutputSection, SlashCommandResult,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use ui::{IconName, SharedString, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SelectionCommand;
|
||||
|
||||
impl SlashCommand for SelectionCommand {
|
||||
fn name(&self) -> String {
|
||||
"selection".into()
|
||||
}
|
||||
|
||||
fn label(&self, _cx: &AppContext) -> CodeLabel {
|
||||
CodeLabel::plain(self.name(), None)
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Insert editor selection".into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Quote
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.description()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn accepts_arguments(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let mut events = vec![];
|
||||
|
||||
let Some(creases) = workspace
|
||||
.update(cx, selections_creases)
|
||||
.unwrap_or_else(|e| {
|
||||
events.push(Err(e));
|
||||
None
|
||||
})
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no active selection")));
|
||||
};
|
||||
|
||||
for (text, title) in creases {
|
||||
events.push(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::TextSnippet,
|
||||
label: SharedString::from(title),
|
||||
metadata: None,
|
||||
}));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text,
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
events.push(Ok(SlashCommandEvent::EndSection));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
}
|
||||
|
||||
let result = futures::stream::iter(events).boxed();
|
||||
|
||||
Task::ready(Ok(result))
|
||||
}
|
||||
}
|
||||
@@ -74,13 +74,7 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
|
||||
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
|
||||
@@ -95,13 +89,7 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
|
||||
|
||||
for n in 1..=10 {
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
@@ -117,14 +105,7 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
events_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
|
||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
|
||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||
use ui::{prelude::*, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::QuoteSelection;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
||||
registry: Arc<SlashCommandRegistry>,
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
trigger: T,
|
||||
}
|
||||
@@ -32,7 +30,6 @@ enum SlashCommandEntry {
|
||||
renderer: fn(&mut WindowContext<'_>) -> AnyElement,
|
||||
on_confirm: fn(&mut WindowContext<'_>),
|
||||
},
|
||||
QuoteButton,
|
||||
}
|
||||
|
||||
impl AsRef<str> for SlashCommandEntry {
|
||||
@@ -40,7 +37,6 @@ impl AsRef<str> for SlashCommandEntry {
|
||||
match self {
|
||||
SlashCommandEntry::Info(SlashCommandInfo { name, .. })
|
||||
| SlashCommandEntry::Advert { name, .. } => name,
|
||||
SlashCommandEntry::QuoteButton => "Quote Selection",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,12 +50,12 @@ pub(crate) struct SlashCommandDelegate {
|
||||
|
||||
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
||||
pub(crate) fn new(
|
||||
registry: Arc<SlashCommandRegistry>,
|
||||
working_set: Arc<SlashCommandWorkingSet>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
trigger: T,
|
||||
) -> Self {
|
||||
SlashCommandSelector {
|
||||
registry,
|
||||
working_set,
|
||||
active_context_editor,
|
||||
trigger,
|
||||
}
|
||||
@@ -153,9 +149,6 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
SlashCommandEntry::QuoteButton => {
|
||||
cx.dispatch_action(Box::new(QuoteSelection));
|
||||
}
|
||||
SlashCommandEntry::Advert { on_confirm, .. } => {
|
||||
on_confirm(cx);
|
||||
}
|
||||
@@ -223,40 +216,6 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
),
|
||||
),
|
||||
),
|
||||
SlashCommandEntry::QuoteButton => {
|
||||
let focus = cx.focus_handle();
|
||||
let key_binding = KeyBinding::for_action_in(&QuoteSelection, &focus, cx);
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Dense)
|
||||
.selected(selected)
|
||||
.child(
|
||||
v_flex()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::Quote).size(IconSize::XSmall))
|
||||
.child(
|
||||
div().font_buffer(cx).child(
|
||||
Label::new("selection").size(LabelSize::Small),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Insert editor selection")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.children(key_binding.map(|kb| kb.render(cx))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
SlashCommandEntry::Advert { renderer, .. } => Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
@@ -271,11 +230,11 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let all_models = self
|
||||
.registry
|
||||
.featured_command_names()
|
||||
.working_set
|
||||
.featured_command_names(cx)
|
||||
.into_iter()
|
||||
.filter_map(|command_name| {
|
||||
let command = self.registry.command(&command_name)?;
|
||||
let command = self.working_set.command(&command_name, cx)?;
|
||||
let menu_text = SharedString::from(Arc::from(command.menu_text()));
|
||||
let label = command.label(cx);
|
||||
let args = label.filter_range.end.ne(&label.text.len()).then(|| {
|
||||
@@ -290,47 +249,44 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
icon: command.icon(),
|
||||
}))
|
||||
})
|
||||
.chain([
|
||||
SlashCommandEntry::Advert {
|
||||
name: "create-your-command".into(),
|
||||
renderer: |cx| {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.font_buffer(cx)
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::Plus).size(IconSize::XSmall))
|
||||
.child(
|
||||
div().font_buffer(cx).child(
|
||||
Label::new("create-your-command")
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
.chain([SlashCommandEntry::Advert {
|
||||
name: "create-your-command".into(),
|
||||
renderer: |cx| {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.font_buffer(cx)
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::Plus).size(IconSize::XSmall))
|
||||
.child(
|
||||
div().font_buffer(cx).child(
|
||||
Label::new("create-your-command")
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ArrowUpRight)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Create your custom command")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
},
|
||||
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ArrowUpRight)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Create your custom command")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
},
|
||||
SlashCommandEntry::QuoteButton,
|
||||
])
|
||||
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
|
||||
}])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let delegate = SlashCommandDelegate {
|
||||
|
||||
79
crates/assistant/src/slash_command_working_set.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandRegistry};
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct SlashCommandId(usize);
|
||||
|
||||
/// A working set of slash commands for use in one instance of the Assistant Panel.
|
||||
#[derive(Default)]
|
||||
pub struct SlashCommandWorkingSet {
|
||||
state: Mutex<WorkingSetState>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WorkingSetState {
|
||||
context_server_commands_by_id: HashMap<SlashCommandId, Arc<dyn SlashCommand>>,
|
||||
context_server_commands_by_name: HashMap<Arc<str>, Arc<dyn SlashCommand>>,
|
||||
next_command_id: SlashCommandId,
|
||||
}
|
||||
|
||||
impl SlashCommandWorkingSet {
|
||||
pub fn command(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn SlashCommand>> {
|
||||
self.state
|
||||
.lock()
|
||||
.context_server_commands_by_name
|
||||
.get(name)
|
||||
.cloned()
|
||||
.or_else(|| SlashCommandRegistry::global(cx).command(name))
|
||||
}
|
||||
|
||||
pub fn command_names(&self, cx: &AppContext) -> Vec<Arc<str>> {
|
||||
let mut command_names = SlashCommandRegistry::global(cx).command_names();
|
||||
command_names.extend(
|
||||
self.state
|
||||
.lock()
|
||||
.context_server_commands_by_name
|
||||
.keys()
|
||||
.cloned(),
|
||||
);
|
||||
|
||||
command_names
|
||||
}
|
||||
|
||||
pub fn featured_command_names(&self, cx: &AppContext) -> Vec<Arc<str>> {
|
||||
SlashCommandRegistry::global(cx).featured_command_names()
|
||||
}
|
||||
|
||||
pub fn insert(&self, command: Arc<dyn SlashCommand>) -> SlashCommandId {
|
||||
let mut state = self.state.lock();
|
||||
let command_id = state.next_command_id;
|
||||
state.next_command_id.0 += 1;
|
||||
state
|
||||
.context_server_commands_by_id
|
||||
.insert(command_id, command.clone());
|
||||
state.slash_commands_changed();
|
||||
command_id
|
||||
}
|
||||
|
||||
pub fn remove(&self, command_ids_to_remove: &[SlashCommandId]) {
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.context_server_commands_by_id
|
||||
.retain(|id, _| !command_ids_to_remove.contains(id));
|
||||
state.slash_commands_changed();
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkingSetState {
|
||||
fn slash_commands_changed(&mut self) {
|
||||
self.context_server_commands_by_name.clear();
|
||||
self.context_server_commands_by_name.extend(
|
||||
self.context_server_commands_by_id
|
||||
.values()
|
||||
.map(|command| (command.name().into(), command.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,36 @@ impl Matrix {
|
||||
self.cols = cols;
|
||||
}
|
||||
|
||||
fn swap_columns(&mut self, col1: usize, col2: usize) {
|
||||
if col1 == col2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if col1 >= self.cols {
|
||||
panic!("column out of bounds");
|
||||
}
|
||||
|
||||
if col2 >= self.cols {
|
||||
panic!("column out of bounds");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = self.cells.as_mut_ptr();
|
||||
std::ptr::swap_nonoverlapping(
|
||||
ptr.add(col1 * self.rows),
|
||||
ptr.add(col2 * self.rows),
|
||||
self.rows,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, row: usize, col: usize) -> f64 {
|
||||
if row >= self.rows {
|
||||
panic!("row out of bounds")
|
||||
}
|
||||
|
||||
if col >= self.cols {
|
||||
panic!("col out of bounds")
|
||||
panic!("column out of bounds")
|
||||
}
|
||||
self.cells[col * self.rows + row]
|
||||
}
|
||||
@@ -45,7 +68,7 @@ impl Matrix {
|
||||
}
|
||||
|
||||
if col >= self.cols {
|
||||
panic!("col out of bounds")
|
||||
panic!("column out of bounds")
|
||||
}
|
||||
|
||||
self.cells[col * self.rows + row] = value;
|
||||
@@ -106,26 +129,32 @@ impl StreamingDiff {
|
||||
|
||||
pub fn push_new(&mut self, text: &str) -> Vec<CharOperation> {
|
||||
self.new.extend(text.chars());
|
||||
self.scores.resize(self.old.len() + 1, self.new.len() + 1);
|
||||
self.scores.swap_columns(0, self.scores.cols - 1);
|
||||
self.scores
|
||||
.resize(self.old.len() + 1, self.new.len() - self.new_text_ix + 1);
|
||||
self.equal_runs.retain(|(_i, j), _| *j == self.new_text_ix);
|
||||
|
||||
for j in self.new_text_ix + 1..=self.new.len() {
|
||||
self.scores.set(0, j, j as f64 * Self::INSERTION_SCORE);
|
||||
let relative_j = j - self.new_text_ix;
|
||||
|
||||
self.scores
|
||||
.set(0, relative_j, j as f64 * Self::INSERTION_SCORE);
|
||||
for i in 1..=self.old.len() {
|
||||
let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
|
||||
let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_SCORE;
|
||||
let insertion_score = self.scores.get(i, relative_j - 1) + Self::INSERTION_SCORE;
|
||||
let deletion_score = self.scores.get(i - 1, relative_j) + Self::DELETION_SCORE;
|
||||
let equality_score = if self.old[i - 1] == self.new[j - 1] {
|
||||
let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
|
||||
equal_run += 1;
|
||||
self.equal_runs.insert((i, j), equal_run);
|
||||
|
||||
let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
|
||||
self.scores.get(i - 1, j - 1) + Self::EQUALITY_BASE.powi(exponent)
|
||||
self.scores.get(i - 1, relative_j - 1) + Self::EQUALITY_BASE.powi(exponent)
|
||||
} else {
|
||||
f64::NEG_INFINITY
|
||||
};
|
||||
|
||||
let score = insertion_score.max(deletion_score).max(equality_score);
|
||||
self.scores.set(i, j, score);
|
||||
self.scores.set(i, relative_j, score);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +162,7 @@ impl StreamingDiff {
|
||||
let mut next_old_text_ix = self.old_text_ix;
|
||||
let next_new_text_ix = self.new.len();
|
||||
for i in self.old_text_ix..=self.old.len() {
|
||||
let score = self.scores.get(i, next_new_text_ix);
|
||||
let score = self.scores.get(i, next_new_text_ix - self.new_text_ix);
|
||||
if score > max_score {
|
||||
max_score = score;
|
||||
next_old_text_ix = i;
|
||||
@@ -174,7 +203,9 @@ impl StreamingDiff {
|
||||
|
||||
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
|
||||
.iter()
|
||||
.max_by_key(|cell| cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j))))
|
||||
.max_by_key(|cell| {
|
||||
cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j - self.new_text_ix)))
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
|
||||
75
crates/assistant/src/tool_working_set.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use assistant_tool::{Tool, ToolRegistry};
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct ToolId(usize);
|
||||
|
||||
/// A working set of tools for use in one instance of the Assistant Panel.
|
||||
#[derive(Default)]
|
||||
pub struct ToolWorkingSet {
|
||||
state: Mutex<WorkingSetState>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WorkingSetState {
|
||||
context_server_tools_by_id: HashMap<ToolId, Arc<dyn Tool>>,
|
||||
context_server_tools_by_name: HashMap<String, Arc<dyn Tool>>,
|
||||
next_tool_id: ToolId,
|
||||
}
|
||||
|
||||
impl ToolWorkingSet {
|
||||
pub fn tool(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn Tool>> {
|
||||
self.state
|
||||
.lock()
|
||||
.context_server_tools_by_name
|
||||
.get(name)
|
||||
.cloned()
|
||||
.or_else(|| ToolRegistry::global(cx).tool(name))
|
||||
}
|
||||
|
||||
pub fn tools(&self, cx: &AppContext) -> Vec<Arc<dyn Tool>> {
|
||||
let mut tools = ToolRegistry::global(cx).tools();
|
||||
tools.extend(
|
||||
self.state
|
||||
.lock()
|
||||
.context_server_tools_by_id
|
||||
.values()
|
||||
.cloned(),
|
||||
);
|
||||
|
||||
tools
|
||||
}
|
||||
|
||||
pub fn insert(&self, command: Arc<dyn Tool>) -> ToolId {
|
||||
let mut state = self.state.lock();
|
||||
let command_id = state.next_tool_id;
|
||||
state.next_tool_id.0 += 1;
|
||||
state
|
||||
.context_server_tools_by_id
|
||||
.insert(command_id, command.clone());
|
||||
state.tools_changed();
|
||||
command_id
|
||||
}
|
||||
|
||||
pub fn remove(&self, command_ids_to_remove: &[ToolId]) {
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.context_server_tools_by_id
|
||||
.retain(|id, _| !command_ids_to_remove.contains(id));
|
||||
state.tools_changed();
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkingSetState {
|
||||
fn tools_changed(&mut self) {
|
||||
self.context_server_tools_by_name.clear();
|
||||
self.context_server_tools_by_name.extend(
|
||||
self.context_server_tools_by_id
|
||||
.values()
|
||||
.map(|command| (command.name(), command.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,25 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use assistant_tool::Tool;
|
||||
use context_servers::manager::ContextServerManager;
|
||||
use context_servers::types;
|
||||
use gpui::Task;
|
||||
use gpui::{Model, Task};
|
||||
|
||||
pub struct ContextServerTool {
|
||||
server_id: String,
|
||||
server_manager: Model<ContextServerManager>,
|
||||
server_id: Arc<str>,
|
||||
tool: types::Tool,
|
||||
}
|
||||
|
||||
impl ContextServerTool {
|
||||
pub fn new(server_id: impl Into<String>, tool: types::Tool) -> Self {
|
||||
pub fn new(
|
||||
server_manager: Model<ContextServerManager>,
|
||||
server_id: impl Into<Arc<str>>,
|
||||
tool: types::Tool,
|
||||
) -> Self {
|
||||
Self {
|
||||
server_manager,
|
||||
server_id: server_id.into(),
|
||||
tool,
|
||||
}
|
||||
@@ -45,13 +53,11 @@ impl Tool for ContextServerTool {
|
||||
_workspace: gpui::WeakView<workspace::Workspace>,
|
||||
cx: &mut ui::WindowContext,
|
||||
) -> gpui::Task<gpui::Result<String>> {
|
||||
let manager = ContextServerManager::global(cx);
|
||||
let manager = manager.read(cx);
|
||||
if let Some(server) = manager.get_server(&self.server_id) {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
cx.foreground_executor().spawn({
|
||||
let tool_name = self.tool.name.clone();
|
||||
async move {
|
||||
let Some(protocol) = server.client.read().clone() else {
|
||||
let Some(protocol) = server.client() else {
|
||||
bail!("Context server not initialized");
|
||||
};
|
||||
|
||||
|
||||
@@ -13,14 +13,18 @@ path = "src/assistant_slash_command.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
extension.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
mod extension_slash_command;
|
||||
mod slash_command_registry;
|
||||
|
||||
pub use crate::extension_slash_command::*;
|
||||
pub use crate::slash_command_registry::*;
|
||||
use anyhow::Result;
|
||||
use futures::stream::{self, BoxStream};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||
pub use language_model::Role;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use slash_command_registry::*;
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
@@ -103,7 +106,7 @@ pub type RenderFoldPlaceholder = Arc<
|
||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||
>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SlashCommandContent {
|
||||
Text {
|
||||
text: String,
|
||||
@@ -111,17 +114,28 @@ pub enum SlashCommandContent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
impl<'a> From<&'a str> for SlashCommandContent {
|
||||
fn from(text: &'a str) -> Self {
|
||||
Self::Text {
|
||||
text: text.into(),
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SlashCommandEvent {
|
||||
StartMessage {
|
||||
role: Role,
|
||||
merge_same_roles: bool,
|
||||
},
|
||||
StartSection {
|
||||
icon: IconName,
|
||||
label: SharedString,
|
||||
metadata: Option<serde_json::Value>,
|
||||
},
|
||||
Content(SlashCommandContent),
|
||||
EndSection {
|
||||
metadata: Option<serde_json::Value>,
|
||||
},
|
||||
EndSection,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
@@ -150,43 +164,37 @@ impl SlashCommandOutput {
|
||||
self.ensure_valid_section_ranges();
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut last_section_end = 0;
|
||||
|
||||
let mut section_endpoints = Vec::new();
|
||||
for section in self.sections {
|
||||
if last_section_end < section.range.start {
|
||||
section_endpoints.push((
|
||||
section.range.start,
|
||||
SlashCommandEvent::StartSection {
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
metadata: section.metadata,
|
||||
},
|
||||
));
|
||||
section_endpoints.push((section.range.end, SlashCommandEvent::EndSection));
|
||||
}
|
||||
section_endpoints.sort_by_key(|(offset, _)| *offset);
|
||||
|
||||
let mut content_offset = 0;
|
||||
for (endpoint_offset, endpoint) in section_endpoints {
|
||||
if content_offset < endpoint_offset {
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: self
|
||||
.text
|
||||
.get(last_section_end..section.range.start)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
text: self.text[content_offset..endpoint_offset].to_string(),
|
||||
run_commands_in_text: self.run_commands_in_text,
|
||||
})));
|
||||
content_offset = endpoint_offset;
|
||||
}
|
||||
|
||||
events.push(Ok(SlashCommandEvent::StartSection {
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
metadata: section.metadata.clone(),
|
||||
}));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: self
|
||||
.text
|
||||
.get(section.range.start..section.range.end)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
run_commands_in_text: self.run_commands_in_text,
|
||||
})));
|
||||
events.push(Ok(SlashCommandEvent::EndSection {
|
||||
metadata: section.metadata,
|
||||
}));
|
||||
|
||||
last_section_end = section.range.end;
|
||||
events.push(Ok(endpoint));
|
||||
}
|
||||
|
||||
if last_section_end < self.text.len() {
|
||||
if content_offset < self.text.len() {
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: self.text[last_section_end..].to_string(),
|
||||
text: self.text[content_offset..].to_string(),
|
||||
run_commands_in_text: self.run_commands_in_text,
|
||||
})));
|
||||
}
|
||||
@@ -226,12 +234,12 @@ impl SlashCommandOutput {
|
||||
section.range.end = output.text.len();
|
||||
}
|
||||
}
|
||||
SlashCommandEvent::EndSection { metadata } => {
|
||||
if let Some(mut section) = section_stack.pop() {
|
||||
section.metadata = metadata;
|
||||
SlashCommandEvent::EndSection => {
|
||||
if let Some(section) = section_stack.pop() {
|
||||
output.sections.push(section);
|
||||
}
|
||||
}
|
||||
SlashCommandEvent::StartMessage { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +307,7 @@ mod tests {
|
||||
text: "Hello, world!".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection { metadata: None }
|
||||
SlashCommandEvent::EndSection
|
||||
]
|
||||
);
|
||||
|
||||
@@ -351,7 +359,7 @@ mod tests {
|
||||
text: "Apple\n".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection { metadata: None },
|
||||
SlashCommandEvent::EndSection,
|
||||
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "Cucumber\n".into(),
|
||||
run_commands_in_text: false
|
||||
@@ -365,7 +373,7 @@ mod tests {
|
||||
text: "Banana\n".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection { metadata: None }
|
||||
SlashCommandEvent::EndSection
|
||||
]
|
||||
);
|
||||
|
||||
@@ -429,9 +437,7 @@ mod tests {
|
||||
text: "Line 1".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection {
|
||||
metadata: Some(json!({ "a": true }))
|
||||
},
|
||||
SlashCommandEvent::EndSection,
|
||||
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false
|
||||
@@ -445,9 +451,7 @@ mod tests {
|
||||
text: "Line 2".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection {
|
||||
metadata: Some(json!({ "b": true }))
|
||||
},
|
||||
SlashCommandEvent::EndSection,
|
||||
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false
|
||||
@@ -461,9 +465,7 @@ mod tests {
|
||||
text: "Line 3".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection {
|
||||
metadata: Some(json!({ "c": true }))
|
||||
},
|
||||
SlashCommandEvent::EndSection,
|
||||
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false
|
||||
@@ -477,9 +479,7 @@ mod tests {
|
||||
text: "Line 4".into(),
|
||||
run_commands_in_text: false
|
||||
}),
|
||||
SlashCommandEvent::EndSection {
|
||||
metadata: Some(json!({ "d": true }))
|
||||
},
|
||||
SlashCommandEvent::EndSection,
|
||||
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false
|
||||
|
||||
143
crates/assistant_slash_command/src/extension_slash_command.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::{Extension, WorktreeDelegate};
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
};
|
||||
|
||||
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
|
||||
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
|
||||
|
||||
#[async_trait]
|
||||
impl WorktreeDelegate for WorktreeDelegateAdapter {
|
||||
fn id(&self) -> u64 {
|
||||
self.0.worktree_id().to_proto()
|
||||
}
|
||||
|
||||
fn root_path(&self) -> String {
|
||||
self.0.worktree_root_path().to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
|
||||
self.0.read_text_file(path).await
|
||||
}
|
||||
|
||||
async fn which(&self, binary_name: String) -> Option<String> {
|
||||
self.0
|
||||
.which(binary_name.as_ref())
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
async fn shell_env(&self) -> Vec<(String, String)> {
|
||||
self.0.shell_env().await.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionSlashCommand {
|
||||
extension: Arc<dyn Extension>,
|
||||
command: extension::SlashCommand,
|
||||
}
|
||||
|
||||
impl ExtensionSlashCommand {
|
||||
pub fn new(extension: Arc<dyn Extension>, command: extension::SlashCommand) -> Self {
|
||||
Self { extension, command }
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for ExtensionSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
self.command.name.clone()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
self.command.description.clone()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.command.tooltip_text.clone()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
self.command.requires_argument
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let command = self.command.clone();
|
||||
let arguments = arguments.to_owned();
|
||||
cx.background_executor().spawn(async move {
|
||||
let completions = self
|
||||
.extension
|
||||
.complete_slash_command_argument(command, arguments)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|completion| ArgumentCompletion {
|
||||
label: completion.label.into(),
|
||||
new_text: completion.new_text,
|
||||
replace_previous_arguments: false,
|
||||
after_completion: completion.run_command.into(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let command = self.command.clone();
|
||||
let arguments = arguments.to_owned();
|
||||
let output = cx.background_executor().spawn(async move {
|
||||
let delegate =
|
||||
delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _);
|
||||
let output = self
|
||||
.extension
|
||||
.run_slash_command(command, arguments, delegate)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(output)
|
||||
});
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let output = output.await?;
|
||||
Ok(SlashCommandOutput {
|
||||
text: output.text,
|
||||
sections: output
|
||||
.sections
|
||||
.into_iter()
|
||||
.map(|section| SlashCommandOutputSection {
|
||||
range: section.range,
|
||||
icon: IconName::Code,
|
||||
label: section.label.into(),
|
||||
metadata: None,
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -432,6 +432,9 @@ impl AutoUpdater {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// If you are packaging Zed and need to override the place it downloads SSH remotes from,
|
||||
// you can override this function. You should also update get_remote_server_release_url to return
|
||||
// Ok(None).
|
||||
pub async fn download_remote_server_release(
|
||||
os: &str,
|
||||
arch: &str,
|
||||
@@ -482,7 +485,7 @@ impl AutoUpdater {
|
||||
release_channel: ReleaseChannel,
|
||||
version: Option<SemanticVersion>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<(JsonRelease, String)> {
|
||||
) -> Result<Option<(String, String)>> {
|
||||
let this = cx.update(|cx| {
|
||||
cx.default_global::<GlobalAutoUpdate>()
|
||||
.0
|
||||
@@ -504,7 +507,7 @@ impl AutoUpdater {
|
||||
let update_request_body = build_remote_server_update_request_body(cx)?;
|
||||
let body = serde_json::to_string(&update_request_body)?;
|
||||
|
||||
Ok((release, body))
|
||||
Ok(Some((release.url, body)))
|
||||
}
|
||||
|
||||
async fn get_release(
|
||||
|
||||
@@ -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(
|
||||
@@ -645,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)?;
|
||||
|
||||
@@ -78,6 +78,7 @@ uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant = { workspace = true, features = ["test-support"] }
|
||||
context_servers.workspace = true
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant::{ContextStore, PromptBuilder};
|
||||
use assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet, ToolWorkingSet};
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -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(),
|
||||
@@ -6487,17 +6486,35 @@ async fn test_context_collaboration_with_reconnect(
|
||||
assert_eq!(project.collaborators().len(), 1);
|
||||
});
|
||||
|
||||
cx_a.update(context_servers::init);
|
||||
cx_b.update(context_servers::init);
|
||||
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;
|
||||
|
||||
@@ -39,11 +39,13 @@ impl CommandPaletteFilter {
|
||||
}
|
||||
|
||||
/// Updates the global [`CommandPaletteFilter`] using the given closure.
|
||||
pub fn update_global<F, R>(cx: &mut AppContext, update: F) -> R
|
||||
pub fn update_global<F>(cx: &mut AppContext, update: F)
|
||||
where
|
||||
F: FnOnce(&mut Self, &mut AppContext) -> R,
|
||||
F: FnOnce(&mut Self, &mut AppContext),
|
||||
{
|
||||
cx.update_global(|this: &mut GlobalCommandPaletteFilter, cx| update(&mut this.0, cx))
|
||||
if cx.has_global::<GlobalCommandPaletteFilter>() {
|
||||
cx.update_global(|this: &mut GlobalCommandPaletteFilter, cx| update(&mut this.0, cx))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the given [`Action`] is hidden by the filter.
|
||||
|
||||
@@ -20,6 +20,7 @@ gpui.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -27,4 +28,3 @@ settings.workspace = true
|
||||
smol.workspace = true
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -53,13 +53,22 @@ pub struct Client {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ContextServerId(pub String);
|
||||
pub struct ContextServerId(pub Arc<str>);
|
||||
|
||||
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,26 @@
|
||||
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;
|
||||
|
||||
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,38 +14,47 @@
|
||||
//! The module also includes initialization logic to set up the context server system
|
||||
//! and react to changes in settings.
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, 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 crate::CONTEXT_SERVERS_NAMESPACE;
|
||||
use anyhow::{bail, Result};
|
||||
use collections::HashMap;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Subscription, Task, WeakModel};
|
||||
use log;
|
||||
use parking_lot::RwLock;
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
client::{self, Client},
|
||||
types,
|
||||
types, ContextServerFactoryRegistry, CONTEXT_SERVERS_NAMESPACE,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||
pub struct ContextServerSettings {
|
||||
pub servers: Vec<ServerConfig>,
|
||||
#[serde(default)]
|
||||
pub context_servers: HashMap<Arc<str>, ServerConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
|
||||
pub struct ServerConfig {
|
||||
pub command: Option<ServerCommand>,
|
||||
pub settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||
pub struct ServerConfig {
|
||||
pub id: String,
|
||||
pub executable: String,
|
||||
pub struct ServerCommand {
|
||||
pub path: String,
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Settings for ContextServerSettings {
|
||||
const KEY: Option<&'static str> = Some("experimental.context_servers");
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = Self;
|
||||
|
||||
@@ -58,28 +67,43 @@ impl Settings for ContextServerSettings {
|
||||
}
|
||||
|
||||
pub struct ContextServer {
|
||||
pub id: String,
|
||||
pub config: ServerConfig,
|
||||
pub id: Arc<str>,
|
||||
pub config: Arc<ServerConfig>,
|
||||
pub client: RwLock<Option<Arc<crate::protocol::InitializedContextServerProtocol>>>,
|
||||
}
|
||||
|
||||
impl ContextServer {
|
||||
fn new(config: ServerConfig) -> Self {
|
||||
pub fn new(id: Arc<str>, config: Arc<ServerConfig>) -> Self {
|
||||
Self {
|
||||
id: config.id.clone(),
|
||||
id,
|
||||
config,
|
||||
client: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn start(&self, cx: &AsyncAppContext) -> anyhow::Result<()> {
|
||||
log::info!("starting context server {}", self.config.id,);
|
||||
pub fn id(&self) -> Arc<str> {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Arc<ServerConfig> {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Option<Arc<crate::protocol::InitializedContextServerProtocol>> {
|
||||
self.client.read().clone()
|
||||
}
|
||||
|
||||
pub async fn start(self: Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
|
||||
log::info!("starting context server {}", self.id);
|
||||
let Some(command) = &self.config.command else {
|
||||
bail!("no command specified for server {}", self.id);
|
||||
};
|
||||
let client = Client::new(
|
||||
client::ContextServerId(self.config.id.clone()),
|
||||
client::ContextServerId(self.id.clone()),
|
||||
client::ModelContextServerBinary {
|
||||
executable: Path::new(&self.config.executable).to_path_buf(),
|
||||
args: self.config.args.clone(),
|
||||
env: self.config.env.clone(),
|
||||
executable: Path::new(&command.path).to_path_buf(),
|
||||
args: command.args.clone(),
|
||||
env: command.env.clone(),
|
||||
},
|
||||
cx.clone(),
|
||||
)?;
|
||||
@@ -93,7 +117,7 @@ impl ContextServer {
|
||||
|
||||
log::debug!(
|
||||
"context server {} initialized: {:?}",
|
||||
self.config.id,
|
||||
self.id,
|
||||
initialized_protocol.initialize,
|
||||
);
|
||||
|
||||
@@ -101,7 +125,7 @@ impl ContextServer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn stop(&self) -> anyhow::Result<()> {
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
let mut client = self.client.write();
|
||||
if let Some(protocol) = client.take() {
|
||||
drop(protocol);
|
||||
@@ -110,107 +134,96 @@ impl ContextServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Context server manager manages the starting and stopping
|
||||
/// of all servers. To obtain a server to interact with, a crate
|
||||
/// 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<ContextServer>>,
|
||||
project: Model<Project>,
|
||||
registry: Model<ContextServerFactoryRegistry>,
|
||||
update_servers_task: Option<Task<Result<()>>>,
|
||||
needs_server_update: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextServerManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
servers: HashMap::default(),
|
||||
pending_servers: HashSet::default(),
|
||||
}
|
||||
}
|
||||
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||
cx.global::<GlobalContextServerManager>().0.clone()
|
||||
}
|
||||
|
||||
pub fn add_server(
|
||||
&mut self,
|
||||
config: ServerConfig,
|
||||
pub fn new(
|
||||
registry: Model<ContextServerFactoryRegistry>,
|
||||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let server_id = config.id.clone();
|
||||
|
||||
if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
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?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.servers.insert(server_id.clone(), server);
|
||||
this.pending_servers.remove(&server_id);
|
||||
cx.emit(Event::ServerStarted {
|
||||
server_id: server_id.clone(),
|
||||
});
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
_subscriptions: vec![
|
||||
cx.observe(®istry, |this, _registry, cx| {
|
||||
this.available_context_servers_changed(cx);
|
||||
}),
|
||||
cx.observe_global::<SettingsStore>(|this, cx| {
|
||||
this.available_context_servers_changed(cx);
|
||||
}),
|
||||
],
|
||||
project,
|
||||
registry,
|
||||
needs_server_update: false,
|
||||
servers: HashMap::default(),
|
||||
update_servers_task: None,
|
||||
};
|
||||
this.available_context_servers_changed(cx);
|
||||
this
|
||||
}
|
||||
|
||||
self.pending_servers.insert(server_id);
|
||||
task
|
||||
fn available_context_servers_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if self.update_servers_task.is_some() {
|
||||
self.needs_server_update = true;
|
||||
} else {
|
||||
self.update_servers_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.needs_server_update = false;
|
||||
})?;
|
||||
|
||||
Self::maintain_servers(this.clone(), cx.clone()).await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let has_any_context_servers = !this.servers().is_empty();
|
||||
if has_any_context_servers {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.show_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
});
|
||||
}
|
||||
|
||||
this.update_servers_task.take();
|
||||
if this.needs_server_update {
|
||||
this.available_context_servers_changed(cx);
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_server(&self, id: &str) -> Option<Arc<ContextServer>> {
|
||||
self.servers.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn remove_server(
|
||||
&mut self,
|
||||
id: &str,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let id = id.to_string();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? {
|
||||
server.stop().await?;
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_servers.remove(&id);
|
||||
cx.emit(Event::ServerStopped {
|
||||
server_id: id.clone(),
|
||||
})
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
self.servers
|
||||
.get(id)
|
||||
.filter(|server| server.client().is_some())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
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(ContextServer::new(id.clone(), config));
|
||||
new_server.clone().start(&cx).await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.servers.insert(id.clone(), new_server);
|
||||
cx.emit(Event::ServerStopped {
|
||||
@@ -226,78 +239,82 @@ impl ContextServerManager {
|
||||
}
|
||||
|
||||
pub fn servers(&self) -> Vec<Arc<ContextServer>> {
|
||||
self.servers.values().cloned().collect()
|
||||
self.servers
|
||||
.values()
|
||||
.filter(|server| server.client().is_some())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn model(cx: &mut AppContext) -> Model<Self> {
|
||||
cx.new_model(|_cx| ContextServerManager::new())
|
||||
}
|
||||
}
|
||||
async fn maintain_servers(this: WeakModel<Self>, mut cx: AsyncAppContext) -> Result<()> {
|
||||
let mut desired_servers = HashMap::default();
|
||||
|
||||
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);
|
||||
let (registry, project) = this.update(&mut cx, |this, cx| {
|
||||
let location = this.project.read(cx).worktrees(cx).next().map(|worktree| {
|
||||
settings::SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: Path::new(""),
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
let settings = ContextServerSettings::get(location, cx);
|
||||
desired_servers = settings.context_servers.clone();
|
||||
|
||||
(this.registry.clone(), this.project.clone())
|
||||
})?;
|
||||
|
||||
for (id, factory) in
|
||||
registry.read_with(&cx, |registry, _| registry.context_server_factories())?
|
||||
{
|
||||
let config = desired_servers.entry(id).or_default();
|
||||
if config.command.is_none() {
|
||||
if let Some(extension_command) = factory(project.clone(), &cx).await.log_err() {
|
||||
config.command = Some(extension_command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut servers_to_start = HashMap::default();
|
||||
let mut servers_to_stop = HashMap::default();
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.servers.retain(|id, server| {
|
||||
if desired_servers.contains_key(id) {
|
||||
true
|
||||
} else {
|
||||
servers_to_stop.insert(id.clone(), server.clone());
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
for (id, config) in desired_servers {
|
||||
let existing_config = this.servers.get(&id).map(|server| server.config());
|
||||
if existing_config.as_deref() != Some(&config) {
|
||||
let config = Arc::new(config);
|
||||
let server = Arc::new(ContextServer::new(id.clone(), config));
|
||||
servers_to_start.insert(id.clone(), server.clone());
|
||||
let old_server = this.servers.insert(id.clone(), server);
|
||||
if let Some(old_server) = old_server {
|
||||
servers_to_stop.insert(id, old_server);
|
||||
}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
for (id, server) in servers_to_stop {
|
||||
server.stop().log_err();
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.emit(Event::ServerStopped { server_id: id })
|
||||
})?;
|
||||
}
|
||||
|
||||
for (id, server) in servers_to_start {
|
||||
if server.start(&cx).await.log_err().is_some() {
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.emit(Event::ServerStarted { server_id: id })
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,62 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, Global, ReadGlobal};
|
||||
use parking_lot::RwLock;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ReadGlobal, Task};
|
||||
use project::Project;
|
||||
|
||||
struct GlobalContextServerRegistry(Arc<ContextServerRegistry>);
|
||||
use crate::manager::ServerCommand;
|
||||
|
||||
impl Global for GlobalContextServerRegistry {}
|
||||
pub type ContextServerFactory = Arc<
|
||||
dyn Fn(Model<Project>, &AsyncAppContext) -> Task<Result<ServerCommand>> + Send + Sync + 'static,
|
||||
>;
|
||||
|
||||
pub struct ContextServerRegistry {
|
||||
command_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
tool_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
|
||||
struct GlobalContextServerFactoryRegistry(Model<ContextServerFactoryRegistry>);
|
||||
|
||||
impl Global for GlobalContextServerFactoryRegistry {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContextServerFactoryRegistry {
|
||||
context_servers: HashMap<Arc<str>, ContextServerFactory>,
|
||||
}
|
||||
|
||||
impl ContextServerRegistry {
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
GlobalContextServerRegistry::global(cx).0.clone()
|
||||
impl ContextServerFactoryRegistry {
|
||||
/// Returns the global [`ContextServerFactoryRegistry`].
|
||||
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||
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) -> Model<Self> {
|
||||
if !cx.has_global::<GlobalContextServerFactoryRegistry>() {
|
||||
let registry = cx.new_model(|_| Self::new());
|
||||
cx.set_global(GlobalContextServerFactoryRegistry(registry));
|
||||
}
|
||||
cx.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 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 new() -> Self {
|
||||
Self {
|
||||
context_servers: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_commands(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
|
||||
let registry = self.command_registry.read();
|
||||
registry.get(server_id).cloned()
|
||||
pub fn context_server_factories(&self) -> Vec<(Arc<str>, ContextServerFactory)> {
|
||||
self.context_servers
|
||||
.iter()
|
||||
.map(|(id, factory)| (id.clone(), factory.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
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());
|
||||
/// Registers the provided [`ContextServerFactory`].
|
||||
pub fn register_server_factory(&mut self, id: Arc<str>, factory: ContextServerFactory) {
|
||||
self.context_servers.insert(id, factory);
|
||||
}
|
||||
|
||||
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(&mut self, server_id: &str) {
|
||||
self.context_servers.remove(server_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
|
||||
ModelContext, Task, WeakModel,
|
||||
};
|
||||
use http_client::github::latest_github_release;
|
||||
use http_client::github::get_release_by_tag_name;
|
||||
use http_client::HttpClient;
|
||||
use language::{
|
||||
language_settings::{all_language_settings, language_settings, InlineCompletionProvider},
|
||||
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,
|
||||
@@ -987,12 +989,12 @@ async fn clear_copilot_dir() {
|
||||
}
|
||||
|
||||
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
const SERVER_PATH: &str = "dist/agent.js";
|
||||
const SERVER_PATH: &str = "dist/language-server.js";
|
||||
|
||||
///Check for the latest copilot language server and download it if we haven't already
|
||||
async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
let release =
|
||||
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
|
||||
get_release_by_tag_name("zed-industries/copilot", "v0.7.0", http.clone()).await?;
|
||||
|
||||
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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,
|
||||
@@ -36,6 +32,7 @@ use std::{
|
||||
cmp::Ordering,
|
||||
mem,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
pub use toolbar_controls::ToolbarControls;
|
||||
@@ -62,11 +59,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 +125,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 +140,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 +167,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 +185,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 +251,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 +263,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 +294,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
|
||||
@@ -793,10 +791,11 @@ const DIAGNOSTIC_HEADER: &str = "diagnostic header";
|
||||
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
|
||||
let message: SharedString = message;
|
||||
Box::new(move |cx| {
|
||||
Arc::new(move |cx| {
|
||||
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
|
||||
h_flex()
|
||||
.id(DIAGNOSTIC_HEADER)
|
||||
.block_mouse_down()
|
||||
.h(2. * cx.line_height())
|
||||
.pl_10()
|
||||
.pr_5()
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -197,18 +197,93 @@ impl DisplayMap {
|
||||
other
|
||||
.folds_in_range(0..other.buffer_snapshot.len())
|
||||
.map(|fold| {
|
||||
(
|
||||
Crease::simple(
|
||||
fold.range.to_offset(&other.buffer_snapshot),
|
||||
fold.placeholder.clone(),
|
||||
)
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn fold<T: ToOffset>(
|
||||
/// Creates folds for the given creases.
|
||||
pub fn fold<T: Clone + ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||
creases: Vec<Crease<T>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let buffer_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(buffer_snapshot.clone(), 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 inline = creases.iter().filter_map(|crease| {
|
||||
if let Crease::Inline {
|
||||
range, placeholder, ..
|
||||
} = crease
|
||||
{
|
||||
Some((range.clone(), placeholder.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let (snapshot, edits) = fold_map.fold(inline);
|
||||
|
||||
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));
|
||||
let mut block_map = self.block_map.write(snapshot, edits);
|
||||
let blocks = creases.into_iter().filter_map(|crease| {
|
||||
if let Crease::Block {
|
||||
range,
|
||||
block_height,
|
||||
render_block,
|
||||
block_style,
|
||||
block_priority,
|
||||
..
|
||||
} = crease
|
||||
{
|
||||
Some((
|
||||
range,
|
||||
render_block,
|
||||
block_height,
|
||||
block_style,
|
||||
block_priority,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
block_map.insert(
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|(range, render, height, style, priority)| {
|
||||
let start = buffer_snapshot.anchor_before(range.start);
|
||||
let end = buffer_snapshot.anchor_after(range.end);
|
||||
BlockProperties {
|
||||
placement: BlockPlacement::Replace(start..end),
|
||||
render,
|
||||
height,
|
||||
style,
|
||||
priority,
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
@@ -221,21 +296,26 @@ impl DisplayMap {
|
||||
.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) = fold_map.remove_folds(ranges, type_id);
|
||||
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.block_map.write(snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn unfold<T: ToOffset>(
|
||||
/// 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>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let offset_ranges = ranges
|
||||
.into_iter()
|
||||
.map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
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);
|
||||
@@ -245,17 +325,20 @@ 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) =
|
||||
fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
|
||||
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 mut block_map = self.block_map.write(snapshot, edits);
|
||||
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
|
||||
}
|
||||
|
||||
pub fn insert_creases(
|
||||
&mut self,
|
||||
creases: impl IntoIterator<Item = Crease>,
|
||||
creases: impl IntoIterator<Item = Crease<Anchor>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Vec<CreaseId> {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
@@ -589,7 +672,7 @@ impl DisplaySnapshot {
|
||||
) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
|
||||
self.block_snapshot
|
||||
.buffer_rows(BlockRow(start_row.0))
|
||||
.map(|row| row.map(|row| MultiBufferRow(row.0)))
|
||||
.map(|row| row.map(MultiBufferRow))
|
||||
}
|
||||
|
||||
pub fn max_buffer_row(&self) -> MultiBufferRow {
|
||||
@@ -980,7 +1063,12 @@ impl DisplaySnapshot {
|
||||
}
|
||||
|
||||
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
|
||||
self.fold_snapshot.is_line_folded(buffer_row)
|
||||
self.block_snapshot.is_line_replaced(buffer_row)
|
||||
|| self.fold_snapshot.is_line_folded(buffer_row)
|
||||
}
|
||||
|
||||
pub fn is_line_replaced(&self, buffer_row: MultiBufferRow) -> bool {
|
||||
self.block_snapshot.is_line_replaced(buffer_row)
|
||||
}
|
||||
|
||||
pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
|
||||
@@ -1054,19 +1142,42 @@ impl DisplaySnapshot {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn foldable_range(
|
||||
&self,
|
||||
buffer_row: MultiBufferRow,
|
||||
) -> Option<(Range<Point>, FoldPlaceholder)> {
|
||||
pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
|
||||
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
||||
if let Some(crease) = self
|
||||
.crease_snapshot
|
||||
.query_row(buffer_row, &self.buffer_snapshot)
|
||||
{
|
||||
Some((
|
||||
crease.range.to_point(&self.buffer_snapshot),
|
||||
crease.placeholder.clone(),
|
||||
))
|
||||
match crease {
|
||||
Crease::Inline {
|
||||
range,
|
||||
placeholder,
|
||||
render_toggle,
|
||||
render_trailer,
|
||||
metadata,
|
||||
} => Some(Crease::Inline {
|
||||
range: range.to_point(&self.buffer_snapshot),
|
||||
placeholder: placeholder.clone(),
|
||||
render_toggle: render_toggle.clone(),
|
||||
render_trailer: render_trailer.clone(),
|
||||
metadata: metadata.clone(),
|
||||
}),
|
||||
Crease::Block {
|
||||
range,
|
||||
block_height,
|
||||
block_style,
|
||||
render_block,
|
||||
block_priority,
|
||||
render_toggle,
|
||||
} => Some(Crease::Block {
|
||||
range: range.to_point(&self.buffer_snapshot),
|
||||
block_height: *block_height,
|
||||
block_style: *block_style,
|
||||
render_block: render_block.clone(),
|
||||
block_priority: *block_priority,
|
||||
render_toggle: render_toggle.clone(),
|
||||
}),
|
||||
}
|
||||
} else if self.starts_indent(MultiBufferRow(start.row))
|
||||
&& !self.is_line_folded(MultiBufferRow(start.row))
|
||||
{
|
||||
@@ -1103,7 +1214,13 @@ impl DisplaySnapshot {
|
||||
.line_len(MultiBufferRow(row_before_line_breaks.row)),
|
||||
);
|
||||
|
||||
Some((start..row_before_line_breaks, self.fold_placeholder.clone()))
|
||||
Some(Crease::Inline {
|
||||
range: start..row_before_line_breaks,
|
||||
placeholder: self.fold_placeholder.clone(),
|
||||
render_toggle: None,
|
||||
render_trailer: None,
|
||||
metadata: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1172,7 +1289,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 +1297,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 +1313,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)))
|
||||
@@ -1395,7 +1528,7 @@ pub mod tests {
|
||||
placement,
|
||||
style: BlockStyle::Fixed,
|
||||
height,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority,
|
||||
}
|
||||
})
|
||||
@@ -1426,7 +1559,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);
|
||||
@@ -1434,7 +1567,8 @@ pub mod tests {
|
||||
map.fold(
|
||||
ranges
|
||||
.into_iter()
|
||||
.map(|range| (range, FoldPlaceholder::test())),
|
||||
.map(|range| Crease::simple(range, FoldPlaceholder::test()))
|
||||
.collect(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -1809,7 +1943,7 @@ pub mod tests {
|
||||
|
||||
map.update(cx, |map, cx| {
|
||||
map.fold(
|
||||
vec![(
|
||||
vec![Crease::simple(
|
||||
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
||||
FoldPlaceholder::test(),
|
||||
)],
|
||||
@@ -1899,7 +2033,7 @@ pub mod tests {
|
||||
),
|
||||
height: 1,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
cx,
|
||||
@@ -2005,7 +2139,7 @@ pub mod tests {
|
||||
),
|
||||
height: 1,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
cx,
|
||||
@@ -2081,7 +2215,7 @@ pub mod tests {
|
||||
),
|
||||
height: 4,
|
||||
style: BlockStyle::Fixed,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
cx,
|
||||
@@ -2230,7 +2364,7 @@ pub mod tests {
|
||||
|
||||
map.update(cx, |map, cx| {
|
||||
map.fold(
|
||||
vec![(
|
||||
vec![Crease::simple(
|
||||
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
||||
FoldPlaceholder::test(),
|
||||
)],
|
||||
@@ -2429,7 +2563,7 @@ pub mod tests {
|
||||
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
|
||||
|
||||
map.crease_map.insert(
|
||||
[Crease::new(
|
||||
[Crease::inline(
|
||||
range,
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _status, _toggle, _cx| div(),
|
||||
|
||||
@@ -7,7 +7,7 @@ use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
|
||||
use language::{Chunk, Patch, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, MultiBufferSnapshot, ToPoint as _,
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, MultiBufferSnapshot, ToOffset, ToPoint as _,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
@@ -77,7 +77,7 @@ pub struct BlockRow(pub(super) u32);
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
struct WrapRow(u32);
|
||||
|
||||
pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
|
||||
pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum BlockPlacement<T> {
|
||||
@@ -352,6 +352,13 @@ impl Block {
|
||||
Block::ExcerptBoundary { next_excerpt, .. } => next_excerpt.is_none(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_replacement(&self) -> bool {
|
||||
match self {
|
||||
Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
|
||||
Block::ExcerptBoundary { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Block {
|
||||
@@ -1119,6 +1126,64 @@ impl<'a> BlockMapWriter<'a> {
|
||||
.retain(|id, _| !block_ids.contains(id));
|
||||
self.0.sync(wrap_snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn remove_intersecting_replace_blocks<T>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
) where
|
||||
T: ToOffset,
|
||||
{
|
||||
let wrap_snapshot = self.0.wrap_snapshot.borrow();
|
||||
let mut blocks_to_remove = HashSet::default();
|
||||
for range in ranges {
|
||||
let range = range.start.to_offset(wrap_snapshot.buffer_snapshot())
|
||||
..range.end.to_offset(wrap_snapshot.buffer_snapshot());
|
||||
for block in self.blocks_intersecting_buffer_range(range, inclusive) {
|
||||
if matches!(block.placement, BlockPlacement::Replace(_)) {
|
||||
blocks_to_remove.insert(block.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(wrap_snapshot);
|
||||
self.remove(blocks_to_remove);
|
||||
}
|
||||
|
||||
fn blocks_intersecting_buffer_range(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
inclusive: bool,
|
||||
) -> &[Arc<CustomBlock>] {
|
||||
let wrap_snapshot = self.0.wrap_snapshot.borrow();
|
||||
let buffer = wrap_snapshot.buffer_snapshot();
|
||||
let start_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
|
||||
probe
|
||||
.end()
|
||||
.to_offset(buffer)
|
||||
.cmp(&range.start)
|
||||
.then(if inclusive {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
})
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
let end_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
|
||||
probe
|
||||
.start()
|
||||
.to_offset(buffer)
|
||||
.cmp(&range.end)
|
||||
.then(if inclusive {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
})
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
&self.0.custom_blocks[start_block_ix..end_block_ix]
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockSnapshot {
|
||||
@@ -1298,6 +1363,21 @@ impl BlockSnapshot {
|
||||
cursor.item().map_or(false, |t| t.block.is_some())
|
||||
}
|
||||
|
||||
pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(Point::new(row.0, 0), Bias::Left);
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
||||
cursor.item().map_or(false, |transform| {
|
||||
if let Some(Block::Custom(block)) = transform.block.as_ref() {
|
||||
matches!(block.placement, BlockPlacement::Replace(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&BlockRow(point.row), Bias::Right, &());
|
||||
@@ -1515,7 +1595,7 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
type Item = Option<BlockRow>;
|
||||
type Item = Option<u32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.started {
|
||||
@@ -1538,16 +1618,25 @@ impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.transforms.item()?.block.is_none() {
|
||||
let transform = self.transforms.item()?;
|
||||
if transform
|
||||
.block
|
||||
.as_ref()
|
||||
.map_or(true, |block| block.is_replacement())
|
||||
{
|
||||
self.input_buffer_rows.seek(self.transforms.start().1 .0);
|
||||
}
|
||||
}
|
||||
|
||||
let transform = self.transforms.item()?;
|
||||
if transform.block.is_some() {
|
||||
Some(None)
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.is_replacement() && self.transforms.start().0 == self.output_row {
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
} else {
|
||||
Some(None)
|
||||
}
|
||||
} else {
|
||||
Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1709,21 +1798,21 @@ mod tests {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
|
||||
height: 2,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
|
||||
height: 3,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
@@ -1821,10 +1910,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.buffer_rows(BlockRow(0))
|
||||
.map(|row| row.map(|r| r.0))
|
||||
.collect::<Vec<_>>(),
|
||||
snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
&[
|
||||
Some(0),
|
||||
None,
|
||||
@@ -1960,21 +2046,21 @@ mod tests {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
|
||||
height: 2,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
|
||||
height: 3,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
@@ -2062,14 +2148,14 @@ mod tests {
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
height: 1,
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
height: 1,
|
||||
priority: 0,
|
||||
},
|
||||
@@ -2109,7 +2195,7 @@ mod tests {
|
||||
..buffer_snapshot.anchor_before(Point::new(3, 1)),
|
||||
),
|
||||
height: 4,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}]);
|
||||
|
||||
@@ -2162,14 +2248,14 @@ mod tests {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
@@ -2183,21 +2269,21 @@ mod tests {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
},
|
||||
]);
|
||||
@@ -2302,7 +2388,7 @@ mod tests {
|
||||
style: BlockStyle::Fixed,
|
||||
placement,
|
||||
height,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}
|
||||
})
|
||||
@@ -2321,7 +2407,7 @@ mod tests {
|
||||
placement: props.placement.clone(),
|
||||
height: props.height,
|
||||
style: props.style,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}));
|
||||
}
|
||||
@@ -2409,6 +2495,7 @@ mod tests {
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut expected_text = String::new();
|
||||
let mut expected_block_positions = Vec::new();
|
||||
let mut expected_replaced_buffer_rows = HashSet::default();
|
||||
let input_text = wraps_snapshot.text();
|
||||
|
||||
// Loop over the input lines, creating (N - 1) empty lines for
|
||||
@@ -2422,6 +2509,9 @@ mod tests {
|
||||
let mut block_row = 0;
|
||||
while let Some((wrap_row, input_line)) = input_text_lines.next() {
|
||||
let wrap_row = wrap_row as u32;
|
||||
let multibuffer_row = wraps_snapshot
|
||||
.to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
|
||||
.row;
|
||||
|
||||
// Create empty lines for the above block
|
||||
while let Some((placement, block)) = sorted_blocks_iter.peek() {
|
||||
@@ -2451,30 +2541,33 @@ mod tests {
|
||||
{
|
||||
if wrap_row >= replace_range.start.0 {
|
||||
is_in_replace_block = true;
|
||||
|
||||
if wrap_row == replace_range.start.0 {
|
||||
expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
|
||||
}
|
||||
|
||||
if wrap_row == replace_range.end.0 {
|
||||
expected_block_positions.push((block_row, block.id()));
|
||||
if block.height() > 0 {
|
||||
let text = "\n".repeat((block.height() - 1) as usize);
|
||||
if block_row > 0 {
|
||||
expected_text.push('\n');
|
||||
}
|
||||
expected_text.push_str(&text);
|
||||
for _ in 0..block.height() {
|
||||
expected_buffer_rows.push(None);
|
||||
}
|
||||
block_row += block.height();
|
||||
let text = "\n".repeat((block.height() - 1) as usize);
|
||||
if block_row > 0 {
|
||||
expected_text.push('\n');
|
||||
}
|
||||
expected_text.push_str(&text);
|
||||
|
||||
for _ in 1..block.height() {
|
||||
expected_buffer_rows.push(None);
|
||||
}
|
||||
block_row += block.height();
|
||||
|
||||
sorted_blocks_iter.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_in_replace_block {
|
||||
let buffer_row = input_buffer_rows[wraps_snapshot
|
||||
.to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
|
||||
.row as usize];
|
||||
|
||||
if is_in_replace_block {
|
||||
expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
|
||||
} else {
|
||||
let buffer_row = input_buffer_rows[multibuffer_row as usize];
|
||||
let soft_wrapped = wraps_snapshot
|
||||
.to_tab_point(WrapPoint::new(wrap_row, 0))
|
||||
.column()
|
||||
@@ -2543,9 +2636,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.buffer_rows(BlockRow(start_row as u32))
|
||||
.map(|row| row.map(|r| r.0))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_row..]
|
||||
&expected_buffer_rows[start_row..],
|
||||
"incorrect buffer_rows starting at row {:?}",
|
||||
start_row
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2666,6 +2760,16 @@ mod tests {
|
||||
block_point.column += c.len_utf8() as u32;
|
||||
}
|
||||
}
|
||||
|
||||
for buffer_row in 0..=buffer_snapshot.max_point().row {
|
||||
let buffer_row = MultiBufferRow(buffer_row);
|
||||
assert_eq!(
|
||||
blocks_snapshot.is_line_replaced(buffer_row),
|
||||
expected_replaced_buffer_rows.contains(&buffer_row),
|
||||
"incorrect is_line_replaced({:?})",
|
||||
buffer_row
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ use collections::HashMap;
|
||||
use gpui::{AnyElement, IntoElement};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Ordering, ops::Range, sync::Arc};
|
||||
use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
|
||||
use sum_tree::{Bias, SeekTarget, SumTree};
|
||||
use text::Point;
|
||||
use ui::{IconName, SharedString, WindowContext};
|
||||
|
||||
use crate::FoldPlaceholder;
|
||||
use crate::{BlockStyle, FoldPlaceholder, RenderBlock};
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub struct CreaseId(usize);
|
||||
@@ -45,15 +45,15 @@ impl CreaseSnapshot {
|
||||
&'a self,
|
||||
row: MultiBufferRow,
|
||||
snapshot: &'a MultiBufferSnapshot,
|
||||
) -> Option<&'a Crease> {
|
||||
) -> Option<&'a Crease<Anchor>> {
|
||||
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
|
||||
cursor.seek(&start, Bias::Left, snapshot);
|
||||
while let Some(item) = cursor.item() {
|
||||
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
|
||||
match Ord::cmp(&item.crease.range().start.to_point(snapshot).row, &row.0) {
|
||||
Ordering::Less => cursor.next(snapshot),
|
||||
Ordering::Equal => {
|
||||
if item.crease.range.start.is_valid(snapshot) {
|
||||
if item.crease.range().start.is_valid(snapshot) {
|
||||
return Some(&item.crease);
|
||||
} else {
|
||||
cursor.next(snapshot);
|
||||
@@ -69,7 +69,7 @@ impl CreaseSnapshot {
|
||||
&'a self,
|
||||
range: Range<MultiBufferRow>,
|
||||
snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = &'a Crease> {
|
||||
) -> impl 'a + Iterator<Item = &'a Crease<Anchor>> {
|
||||
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
|
||||
cursor.seek(&start, Bias::Left, snapshot);
|
||||
@@ -77,8 +77,9 @@ impl CreaseSnapshot {
|
||||
std::iter::from_fn(move || {
|
||||
while let Some(item) = cursor.item() {
|
||||
cursor.next(snapshot);
|
||||
let crease_start = item.crease.range.start.to_point(snapshot);
|
||||
let crease_end = item.crease.range.end.to_point(snapshot);
|
||||
let crease_range = item.crease.range();
|
||||
let crease_start = crease_range.start.to_point(snapshot);
|
||||
let crease_end = crease_range.end.to_point(snapshot);
|
||||
if crease_end.row > range.end.0 {
|
||||
continue;
|
||||
}
|
||||
@@ -99,8 +100,9 @@ impl CreaseSnapshot {
|
||||
|
||||
cursor.next(snapshot);
|
||||
while let Some(item) = cursor.item() {
|
||||
let start_point = item.crease.range.start.to_point(snapshot);
|
||||
let end_point = item.crease.range.end.to_point(snapshot);
|
||||
let crease_range = item.crease.range();
|
||||
let start_point = crease_range.start.to_point(snapshot);
|
||||
let end_point = crease_range.end.to_point(snapshot);
|
||||
results.push((item.id, start_point..end_point));
|
||||
cursor.next(snapshot);
|
||||
}
|
||||
@@ -123,12 +125,22 @@ type RenderTrailerFn =
|
||||
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Crease {
|
||||
pub range: Range<Anchor>,
|
||||
pub placeholder: FoldPlaceholder,
|
||||
pub render_toggle: RenderToggleFn,
|
||||
pub render_trailer: RenderTrailerFn,
|
||||
pub metadata: Option<CreaseMetadata>,
|
||||
pub enum Crease<T> {
|
||||
Inline {
|
||||
range: Range<T>,
|
||||
placeholder: FoldPlaceholder,
|
||||
render_toggle: Option<RenderToggleFn>,
|
||||
render_trailer: Option<RenderTrailerFn>,
|
||||
metadata: Option<CreaseMetadata>,
|
||||
},
|
||||
Block {
|
||||
range: Range<T>,
|
||||
block_height: u32,
|
||||
block_style: BlockStyle,
|
||||
render_block: RenderBlock,
|
||||
block_priority: usize,
|
||||
render_toggle: Option<RenderToggleFn>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Metadata about a [`Crease`], that is used for serialization.
|
||||
@@ -138,9 +150,30 @@ pub struct CreaseMetadata {
|
||||
pub label: SharedString,
|
||||
}
|
||||
|
||||
impl Crease {
|
||||
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
||||
range: Range<Anchor>,
|
||||
impl<T> Crease<T> {
|
||||
pub fn simple(range: Range<T>, placeholder: FoldPlaceholder) -> Self {
|
||||
Crease::Inline {
|
||||
range,
|
||||
placeholder,
|
||||
render_toggle: None,
|
||||
render_trailer: None,
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block(range: Range<T>, height: u32, style: BlockStyle, render: RenderBlock) -> Self {
|
||||
Self::Block {
|
||||
range,
|
||||
block_height: height,
|
||||
block_style: style,
|
||||
render_block: render,
|
||||
block_priority: 0,
|
||||
render_toggle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
||||
range: Range<T>,
|
||||
placeholder: FoldPlaceholder,
|
||||
render_toggle: RenderToggle,
|
||||
render_trailer: RenderTrailer,
|
||||
@@ -164,37 +197,76 @@ impl Crease {
|
||||
+ 'static,
|
||||
TrailerElement: IntoElement,
|
||||
{
|
||||
Crease {
|
||||
Crease::Inline {
|
||||
range,
|
||||
placeholder,
|
||||
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
||||
render_toggle: Some(Arc::new(move |row, folded, toggle, cx| {
|
||||
render_toggle(row, folded, toggle, cx).into_any_element()
|
||||
}),
|
||||
render_trailer: Arc::new(move |row, folded, cx| {
|
||||
})),
|
||||
render_trailer: Some(Arc::new(move |row, folded, cx| {
|
||||
render_trailer(row, folded, cx).into_any_element()
|
||||
}),
|
||||
})),
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_metadata(mut self, metadata: CreaseMetadata) -> Self {
|
||||
self.metadata = Some(metadata);
|
||||
self
|
||||
pub fn with_metadata(self, metadata: CreaseMetadata) -> Self {
|
||||
match self {
|
||||
Crease::Inline {
|
||||
range,
|
||||
placeholder,
|
||||
render_toggle,
|
||||
render_trailer,
|
||||
..
|
||||
} => Crease::Inline {
|
||||
range,
|
||||
placeholder,
|
||||
render_toggle,
|
||||
render_trailer,
|
||||
metadata: Some(metadata),
|
||||
},
|
||||
Crease::Block { .. } => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(&self) -> &Range<T> {
|
||||
match self {
|
||||
Crease::Inline { range, .. } => range,
|
||||
Crease::Block { range, .. } => range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Crease {
|
||||
impl<T> std::fmt::Debug for Crease<T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Crease")
|
||||
.field("range", &self.range)
|
||||
.finish()
|
||||
match self {
|
||||
Crease::Inline {
|
||||
range, metadata, ..
|
||||
} => f
|
||||
.debug_struct("Crease::Inline")
|
||||
.field("range", range)
|
||||
.field("metadata", metadata)
|
||||
.finish_non_exhaustive(),
|
||||
Crease::Block {
|
||||
range,
|
||||
block_height,
|
||||
..
|
||||
} => f
|
||||
.debug_struct("Crease::Block")
|
||||
.field("range", range)
|
||||
.field("height", block_height)
|
||||
.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CreaseItem {
|
||||
id: CreaseId,
|
||||
crease: Crease,
|
||||
crease: Crease<Anchor>,
|
||||
}
|
||||
|
||||
impl CreaseMap {
|
||||
@@ -204,7 +276,7 @@ impl CreaseMap {
|
||||
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
creases: impl IntoIterator<Item = Crease>,
|
||||
creases: impl IntoIterator<Item = Crease<Anchor>>,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Vec<CreaseId> {
|
||||
let mut new_ids = Vec::new();
|
||||
@@ -212,11 +284,12 @@ impl CreaseMap {
|
||||
let mut new_creases = SumTree::new(snapshot);
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
|
||||
for crease in creases {
|
||||
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
|
||||
let crease_range = crease.range().clone();
|
||||
new_creases.append(cursor.slice(&crease_range, Bias::Left, snapshot), snapshot);
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id.0 += 1;
|
||||
self.id_to_range.insert(id, crease.range.clone());
|
||||
self.id_to_range.insert(id, crease_range);
|
||||
new_creases.push(CreaseItem { crease, id }, snapshot);
|
||||
new_ids.push(id);
|
||||
}
|
||||
@@ -293,7 +366,7 @@ impl sum_tree::Item for CreaseItem {
|
||||
|
||||
fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
|
||||
ItemSummary {
|
||||
range: self.crease.range.clone(),
|
||||
range: self.crease.range().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,13 +399,13 @@ mod test {
|
||||
|
||||
// Insert creases
|
||||
let creases = [
|
||||
Crease::new(
|
||||
Crease::inline(
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
Crease::new(
|
||||
Crease::inline(
|
||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
@@ -372,19 +445,19 @@ mod test {
|
||||
let mut crease_map = CreaseMap::new(&snapshot);
|
||||
|
||||
let creases = [
|
||||
Crease::new(
|
||||
Crease::inline(
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
Crease::new(
|
||||
Crease::inline(
|
||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
Crease::new(
|
||||
Crease::inline(
|
||||
snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
@@ -402,12 +475,12 @@ mod test {
|
||||
let range = MultiBufferRow(2)..MultiBufferRow(5);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
assert_eq!(creases.len(), 1);
|
||||
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 3);
|
||||
assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
|
||||
|
||||
let range = MultiBufferRow(0)..MultiBufferRow(2);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
assert_eq!(creases.len(), 1);
|
||||
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 1);
|
||||
assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
|
||||
|
||||
let range = MultiBufferRow(6)..MultiBufferRow(7);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -592,10 +596,10 @@ fn test_clone(cx: &mut TestAppContext) {
|
||||
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
|
||||
editor.fold_ranges(
|
||||
[
|
||||
(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
|
||||
(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
|
||||
editor.fold_creases(
|
||||
vec![
|
||||
Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
|
||||
],
|
||||
true,
|
||||
cx,
|
||||
@@ -1279,11 +1283,11 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
|
||||
assert_eq!('α'.len_utf8(), 2);
|
||||
|
||||
_ = view.update(cx, |view, cx| {
|
||||
view.fold_ranges(
|
||||
view.fold_creases(
|
||||
vec![
|
||||
(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
|
||||
(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
|
||||
(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
|
||||
],
|
||||
true,
|
||||
cx,
|
||||
@@ -3871,11 +3875,11 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
_ = view.update(cx, |view, cx| {
|
||||
view.fold_ranges(
|
||||
view.fold_creases(
|
||||
vec![
|
||||
(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
|
||||
(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||
(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
|
||||
],
|
||||
true,
|
||||
cx,
|
||||
@@ -3976,7 +3980,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
|
||||
height: 1,
|
||||
render: Box::new(|_| div().into_any()),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
Some(Autoscroll::fit()),
|
||||
@@ -4018,7 +4022,7 @@ async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
|
||||
placement,
|
||||
height: 4,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Box::new(|_| gpui::div().into_any_element()),
|
||||
render: Arc::new(|_| gpui::div().into_any_element()),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
@@ -4159,22 +4163,49 @@ 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! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
"},
|
||||
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.
|
||||
@@ -4183,31 +4214,19 @@ 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 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! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
"},
|
||||
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.
|
||||
@@ -4216,105 +4235,69 @@ 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 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! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
"},
|
||||
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
|
||||
// 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
|
||||
// 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.ˇ
|
||||
"};
|
||||
|
||||
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! {"
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
"},
|
||||
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
|
||||
// ˇ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
|
||||
// ˇ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.ˇ
|
||||
"};
|
||||
|
||||
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! {"
|
||||
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
"},
|
||||
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
|
||||
@@ -4323,119 +4306,74 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
# 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);
|
||||
}
|
||||
# accumsan eros.
|
||||
"},
|
||||
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
|
||||
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.ˇ
|
||||
"};
|
||||
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! {"
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||
"},
|
||||
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.ˇ
|
||||
"};
|
||||
|
||||
cx.set_state(unwrapped_text);
|
||||
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||
cx.assert_editor_state(wrapped_text);
|
||||
}
|
||||
Integer sit amet scelerisque nisi.
|
||||
"},
|
||||
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.
|
||||
@@ -4445,26 +4383,25 @@ 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
|
||||
« // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
|
||||
// mollis elit purus, a ornare lacus gravida vitae. Praesent semper
|
||||
// egestas tellus id dignissim.ˇ
|
||||
// egestas tellus id dignissim.ˇ»
|
||||
do_something();
|
||||
} else {
|
||||
//
|
||||
}
|
||||
}
|
||||
"};
|
||||
"},
|
||||
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.
|
||||
@@ -4475,22 +4412,32 @@ 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
|
||||
«ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
|
||||
// mollis elit purus, a ornare lacus gravida vitae. Praesent semper
|
||||
// egestas tellus id dignissim.ˇ
|
||||
// egestas tellus id dignissim.»
|
||||
do_something();
|
||||
} else {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
"};
|
||||
"},
|
||||
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);
|
||||
@@ -4770,11 +4717,11 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
|
||||
build_editor(buffer, cx)
|
||||
});
|
||||
_ = view.update(cx, |view, cx| {
|
||||
view.fold_ranges(
|
||||
view.fold_creases(
|
||||
vec![
|
||||
(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
|
||||
(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||
(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||
Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
|
||||
],
|
||||
true,
|
||||
cx,
|
||||
@@ -5451,13 +5398,13 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||
// Ensure that we keep expanding the selection if the larger selection starts or ends within
|
||||
// a fold.
|
||||
editor.update(cx, |view, cx| {
|
||||
view.fold_ranges(
|
||||
view.fold_creases(
|
||||
vec![
|
||||
(
|
||||
Crease::simple(
|
||||
Point::new(0, 21)..Point::new(0, 24),
|
||||
FoldPlaceholder::test(),
|
||||
),
|
||||
(
|
||||
Crease::simple(
|
||||
Point::new(3, 20)..Point::new(3, 22),
|
||||
FoldPlaceholder::test(),
|
||||
),
|
||||
@@ -8385,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, |_| {});
|
||||
@@ -13124,7 +13139,7 @@ fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||
}
|
||||
|
||||
let crease = Crease::new(
|
||||
let crease = Crease::inline(
|
||||
range,
|
||||
FoldPlaceholder::test(),
|
||||
{
|
||||
@@ -13143,7 +13158,8 @@ fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
|
||||
editor.insert_creases(Some(crease), cx);
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
|
||||
let _div =
|
||||
snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
|
||||
snapshot
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -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, 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::{
|
||||
@@ -650,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 =
|
||||
@@ -1225,9 +1227,9 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint_gutter_fold_toggles(
|
||||
fn prepaint_crease_toggles(
|
||||
&self,
|
||||
toggles: &mut [Option<AnyElement>],
|
||||
crease_toggles: &mut [Option<AnyElement>],
|
||||
line_height: Pixels,
|
||||
gutter_dimensions: &GutterDimensions,
|
||||
gutter_settings: crate::editor_settings::Gutter,
|
||||
@@ -1235,25 +1237,25 @@ impl EditorElement {
|
||||
gutter_hitbox: &Hitbox,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
for (ix, fold_indicator) in toggles.iter_mut().enumerate() {
|
||||
if let Some(fold_indicator) = fold_indicator {
|
||||
for (ix, crease_toggle) in crease_toggles.iter_mut().enumerate() {
|
||||
if let Some(crease_toggle) = crease_toggle {
|
||||
debug_assert!(gutter_settings.folds);
|
||||
let available_space = size(
|
||||
AvailableSpace::MinContent,
|
||||
AvailableSpace::Definite(line_height * 0.55),
|
||||
);
|
||||
let fold_indicator_size = fold_indicator.layout_as_root(available_space, cx);
|
||||
let crease_toggle_size = crease_toggle.layout_as_root(available_space, cx);
|
||||
|
||||
let position = point(
|
||||
gutter_dimensions.width - gutter_dimensions.right_padding,
|
||||
ix as f32 * line_height - (scroll_pixel_position.y % line_height),
|
||||
);
|
||||
let centering_offset = point(
|
||||
(gutter_dimensions.fold_area_width() - fold_indicator_size.width) / 2.,
|
||||
(line_height - fold_indicator_size.height) / 2.,
|
||||
(gutter_dimensions.fold_area_width() - crease_toggle_size.width) / 2.,
|
||||
(line_height - crease_toggle_size.height) / 2.,
|
||||
);
|
||||
let origin = gutter_hitbox.origin + position + centering_offset;
|
||||
fold_indicator.prepaint_as_root(origin, available_space, cx);
|
||||
crease_toggle.prepaint_as_root(origin, available_space, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1913,7 +1915,7 @@ impl EditorElement {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn layout_gutter_fold_toggles(
|
||||
fn layout_crease_toggles(
|
||||
&self,
|
||||
rows: Range<DisplayRow>,
|
||||
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
@@ -1932,7 +1934,7 @@ impl EditorElement {
|
||||
if let Some(multibuffer_row) = row {
|
||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||
let active = active_rows.contains_key(&display_row);
|
||||
snapshot.render_fold_toggle(
|
||||
snapshot.render_crease_toggle(
|
||||
multibuffer_row,
|
||||
active,
|
||||
self.editor.clone(),
|
||||
@@ -1968,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 {
|
||||
@@ -2020,9 +2022,9 @@ impl EditorElement {
|
||||
&style.text,
|
||||
MAX_LINE_LEN,
|
||||
rows.len(),
|
||||
line_number_layouts,
|
||||
snapshot.mode,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -2071,6 +2073,7 @@ impl EditorElement {
|
||||
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 {
|
||||
@@ -2083,8 +2086,15 @@ impl EditorElement {
|
||||
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
|
||||
@@ -2112,9 +2122,7 @@ impl EditorElement {
|
||||
max_width: text_hitbox.size.width.max(*scroll_width),
|
||||
editor_style: &self.style,
|
||||
}))
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||
.into_any_element()
|
||||
.into_any()
|
||||
}
|
||||
|
||||
Block::ExcerptBoundary {
|
||||
@@ -2125,14 +2133,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);
|
||||
|
||||
@@ -2161,11 +2161,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()
|
||||
@@ -2180,21 +2181,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
|
||||
@@ -2249,31 +2249,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 {
|
||||
@@ -2292,6 +2284,7 @@ impl EditorElement {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let editor = self.editor.clone();
|
||||
result = result.child(
|
||||
h_flex()
|
||||
.id("excerpt header block")
|
||||
@@ -2312,33 +2305,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)
|
||||
@@ -2447,6 +2459,7 @@ impl EditorElement {
|
||||
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
|
||||
@@ -2484,6 +2497,7 @@ impl EditorElement {
|
||||
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);
|
||||
@@ -2529,6 +2543,7 @@ impl EditorElement {
|
||||
scroll_width,
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -2575,6 +2590,7 @@ impl EditorElement {
|
||||
scroll_width,
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -3336,9 +3352,9 @@ impl EditorElement {
|
||||
|
||||
fn paint_gutter_indicators(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
|
||||
fold_indicator.paint(cx);
|
||||
cx.with_element_namespace("crease_toggles", |cx| {
|
||||
for crease_toggle in layout.crease_toggles.iter_mut().flatten() {
|
||||
crease_toggle.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4359,9 +4375,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);
|
||||
@@ -4489,12 +4505,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(),
|
||||
@@ -4510,7 +4523,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,
|
||||
@@ -4873,10 +4886,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()
|
||||
@@ -5085,6 +5098,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()
|
||||
@@ -5150,16 +5165,15 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
|
||||
let mut gutter_fold_toggles =
|
||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||
self.layout_gutter_fold_toggles(
|
||||
start_row..end_row,
|
||||
buffer_rows.iter().copied(),
|
||||
&active_rows,
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let mut crease_toggles = cx.with_element_namespace("crease_toggles", |cx| {
|
||||
self.layout_crease_toggles(
|
||||
start_row..end_row,
|
||||
buffer_rows.iter().copied(),
|
||||
&active_rows,
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
|
||||
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
||||
});
|
||||
@@ -5176,10 +5190,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 {
|
||||
@@ -5188,9 +5202,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;
|
||||
|
||||
@@ -5208,6 +5228,7 @@ impl Element for EditorElement {
|
||||
line_height,
|
||||
&line_layouts,
|
||||
&local_selections,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -5532,9 +5553,9 @@ impl Element for EditorElement {
|
||||
let mouse_context_menu =
|
||||
self.layout_mouse_context_menu(&snapshot, start_row..end_row, cx);
|
||||
|
||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||
self.prepaint_gutter_fold_toggles(
|
||||
&mut gutter_fold_toggles,
|
||||
cx.with_element_namespace("crease_toggles", |cx| {
|
||||
self.prepaint_crease_toggles(
|
||||
&mut crease_toggles,
|
||||
line_height,
|
||||
&gutter_dimensions,
|
||||
gutter_settings,
|
||||
@@ -5614,7 +5635,7 @@ impl Element for EditorElement {
|
||||
mouse_context_menu,
|
||||
test_indicators,
|
||||
code_actions_indicator,
|
||||
gutter_fold_toggles,
|
||||
crease_toggles,
|
||||
crease_trailers,
|
||||
tab_invisible,
|
||||
space_invisible,
|
||||
@@ -5647,7 +5668,6 @@ impl Element for EditorElement {
|
||||
line_height: Some(self.style.text.line_height),
|
||||
..Default::default()
|
||||
};
|
||||
let mouse_position = cx.mouse_position();
|
||||
let hovered_hunk = layout
|
||||
.display_hunks
|
||||
.iter()
|
||||
@@ -5661,7 +5681,7 @@ impl Element for EditorElement {
|
||||
} => {
|
||||
if hunk_hitbox
|
||||
.as_ref()
|
||||
.map(|hitbox| hitbox.contains(&mouse_position))
|
||||
.map(|hitbox| hitbox.is_hovered(cx))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(HoveredHunk {
|
||||
@@ -5754,7 +5774,7 @@ pub struct EditorLayout {
|
||||
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
||||
code_actions_indicator: Option<AnyElement>,
|
||||
test_indicators: Vec<AnyElement>,
|
||||
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
||||
crease_toggles: Vec<Option<AnyElement>>,
|
||||
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
|
||||
mouse_context_menu: Option<AnyElement>,
|
||||
tab_invisible: ShapedLine,
|
||||
@@ -5966,6 +5986,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);
|
||||
@@ -5974,9 +5995,9 @@ fn layout_line(
|
||||
&style.text,
|
||||
MAX_LINE_LEN,
|
||||
1,
|
||||
&[],
|
||||
snapshot.mode,
|
||||
text_width,
|
||||
is_row_soft_wrapped,
|
||||
cx,
|
||||
)
|
||||
.pop()
|
||||
@@ -6598,7 +6619,7 @@ mod tests {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(Anchor::min()),
|
||||
height: 3,
|
||||
render: Box::new(|cx| div().h(3. * cx.line_height()).into_any()),
|
||||
render: Arc::new(|cx| div().h(3. * cx.line_height()).into_any()),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
@@ -6661,15 +6682,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]
|
||||
@@ -6683,14 +6711,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:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6741,43 +6772,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6786,6 +6822,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}'",
|
||||
@@ -6797,11 +6834,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)]
|
||||
|
||||
@@ -425,7 +425,7 @@ impl Editor {
|
||||
height: 1,
|
||||
style: BlockStyle::Sticky,
|
||||
priority: 0,
|
||||
render: Box::new({
|
||||
render: Arc::new({
|
||||
let editor = cx.view().clone();
|
||||
let hunk = hunk.clone();
|
||||
|
||||
@@ -435,6 +435,7 @@ impl Editor {
|
||||
|
||||
h_flex()
|
||||
.id(cx.block_id)
|
||||
.block_mouse_down()
|
||||
.h(cx.line_height())
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
@@ -707,12 +708,13 @@ impl Editor {
|
||||
height,
|
||||
style: BlockStyle::Flex,
|
||||
priority: 0,
|
||||
render: Box::new(move |cx| {
|
||||
render: Arc::new(move |cx| {
|
||||
let width = EditorElement::diff_hunk_strip_width(cx.line_height());
|
||||
let gutter_dimensions = editor.read(cx.context).gutter_dimensions;
|
||||
|
||||
h_flex()
|
||||
.id(cx.block_id)
|
||||
.block_mouse_down()
|
||||
.bg(deleted_hunk_color)
|
||||
.h(height as f32 * cx.line_height())
|
||||
.w_full()
|
||||
|
||||
@@ -18,6 +18,7 @@ use gpui::{
|
||||
use language::{
|
||||
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, Point, SelectionGoal,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::AnchorRangeExt;
|
||||
use project::{
|
||||
lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Item as _,
|
||||
@@ -39,7 +40,7 @@ use std::{
|
||||
};
|
||||
use text::{BufferId, Selection};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, Label};
|
||||
use ui::{h_flex, prelude::*, IconDecorationKind, Label};
|
||||
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
||||
use workspace::item::{BreadcrumbText, FollowEvent};
|
||||
use workspace::{
|
||||
@@ -992,12 +993,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 +1281,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 +1289,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))
|
||||
@@ -1512,6 +1516,26 @@ pub fn entry_label_color(selected: bool) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_diagnostic_aware_icon_name_and_color(
|
||||
diagnostic_severity: Option<DiagnosticSeverity>,
|
||||
) -> Option<(IconName, Color)> {
|
||||
match diagnostic_severity {
|
||||
Some(DiagnosticSeverity::ERROR) => Some((IconName::X, Color::Error)),
|
||||
Some(DiagnosticSeverity::WARNING) => Some((IconName::Triangle, Color::Warning)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_diagnostic_aware_icon_decoration_and_color(
|
||||
diagnostic_severity: Option<DiagnosticSeverity>,
|
||||
) -> Option<(IconDecorationKind, Color)> {
|
||||
match diagnostic_severity {
|
||||
Some(DiagnosticSeverity::ERROR) => Some((IconDecorationKind::X, Color::Error)),
|
||||
Some(DiagnosticSeverity::WARNING) => Some((IconDecorationKind::Triangle, Color::Warning)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_git_aware_label_color(
|
||||
git_status: Option<GitFileStatus>,
|
||||
ignored: bool,
|
||||
|
||||
@@ -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(())
|
||||
}));
|
||||
|
||||