Compare commits
371 Commits
quick-comm
...
copilot_ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcb21f93fe | ||
|
|
2c2849bea8 | ||
|
|
470748be29 | ||
|
|
71aeb6a636 | ||
|
|
cdd2128311 | ||
|
|
20b60e8dd2 | ||
|
|
37366ac907 | ||
|
|
083f06322d | ||
|
|
16cbff9118 | ||
|
|
e62d60c84c | ||
|
|
4f62ebe4be | ||
|
|
b33ae888c0 | ||
|
|
029d08350e | ||
|
|
6d0aa72226 | ||
|
|
12afd1264e | ||
|
|
b526d69387 | ||
|
|
4f06f5b8fe | ||
|
|
555a219f11 | ||
|
|
74540231e5 | ||
|
|
5c0ecc09fb | ||
|
|
5b59ef3456 | ||
|
|
fda3e4c69a | ||
|
|
028c2a8249 | ||
|
|
a86c4deb78 | ||
|
|
e645aa9d20 | ||
|
|
216ea4ddc4 | ||
|
|
29c5ea0a50 | ||
|
|
b129e18396 | ||
|
|
f6fbf662b4 | ||
|
|
a409123342 | ||
|
|
b0b29d91f9 | ||
|
|
290c9113b7 | ||
|
|
36427e0a87 | ||
|
|
e16d5c3a68 | ||
|
|
608addf641 | ||
|
|
f22e56ff42 | ||
|
|
449e20de3d | ||
|
|
1aac35cc1c | ||
|
|
815385cc5f | ||
|
|
eca3424cb5 | ||
|
|
71b3633c1b | ||
|
|
21f778c6db | ||
|
|
1eb6fb0b5d | ||
|
|
484e5df2ee | ||
|
|
fef7df667c | ||
|
|
1c84fd1fef | ||
|
|
b6adab84a0 | ||
|
|
d3a49f6d8f | ||
|
|
c2cf4c45c0 | ||
|
|
bd03dea296 | ||
|
|
3f777f0c68 | ||
|
|
846aec701f | ||
|
|
cfce6a8fbf | ||
|
|
803e5d4c9f | ||
|
|
c10c35ffda | ||
|
|
38b1940251 | ||
|
|
50069a2153 | ||
|
|
b23835b5a5 | ||
|
|
e47b305ca7 | ||
|
|
7931342455 | ||
|
|
282f6241b7 | ||
|
|
73bbdd4456 | ||
|
|
236498408f | ||
|
|
86ff6e2c8e | ||
|
|
4bf6fb217e | ||
|
|
c527f2e212 | ||
|
|
47defa2849 | ||
|
|
6dfff1b46d | ||
|
|
66e06616db | ||
|
|
765626a007 | ||
|
|
27cdc6cb93 | ||
|
|
87ba5fd7bc | ||
|
|
7c72929f0b | ||
|
|
9e49894ed6 | ||
|
|
2364804f17 | ||
|
|
db11a3b554 | ||
|
|
d0ca49f0e1 | ||
|
|
d6fcd9853a | ||
|
|
bc3550d991 | ||
|
|
b8501199c2 | ||
|
|
7fb9549098 | ||
|
|
4097118070 | ||
|
|
a26c0a8537 | ||
|
|
f8bd6c66f4 | ||
|
|
02b1e3a3c1 | ||
|
|
83ad28dfe7 | ||
|
|
17b9d1976f | ||
|
|
3856599853 | ||
|
|
81dd4ca1c9 | ||
|
|
2965119201 | ||
|
|
258cf6c746 | ||
|
|
369de400be | ||
|
|
dc02894db4 | ||
|
|
966b18e142 | ||
|
|
2c7e71028d | ||
|
|
24dba07a9b | ||
|
|
16e9b4ceeb | ||
|
|
bc4bd2e168 | ||
|
|
4d3a18cbdc | ||
|
|
cfcbfc1d82 | ||
|
|
c9ec235b12 | ||
|
|
8196db6022 | ||
|
|
dc5fad52a3 | ||
|
|
77de20c23a | ||
|
|
e1cb8a66f0 | ||
|
|
7025d3f29d | ||
|
|
4bbde40267 | ||
|
|
15e7b67559 | ||
|
|
f0aeab7d00 | ||
|
|
4bbddcad31 | ||
|
|
1e944a51ff | ||
|
|
d90770c673 | ||
|
|
6316151a83 | ||
|
|
49a0a11c4f | ||
|
|
376a45528d | ||
|
|
20eeb78251 | ||
|
|
67be6ec3b5 | ||
|
|
070e5914c9 | ||
|
|
c41a8e33a0 | ||
|
|
25443a91ca | ||
|
|
8e00caf23b | ||
|
|
9a869f0c5f | ||
|
|
de2483e132 | ||
|
|
95259bf9fe | ||
|
|
3b76ba6d5a | ||
|
|
773a3b335e | ||
|
|
b5c38e9a09 | ||
|
|
273173ec8a | ||
|
|
4084ba36f9 | ||
|
|
770886880f | ||
|
|
ea44c510a3 | ||
|
|
c8f1969916 | ||
|
|
a960344301 | ||
|
|
af9e7f1f96 | ||
|
|
c04c439d23 | ||
|
|
d3cd8f8f14 | ||
|
|
cd8d776fe1 | ||
|
|
6de2330253 | ||
|
|
0e264b5a68 | ||
|
|
1af5304074 | ||
|
|
dde692eb88 | ||
|
|
cec72b837e | ||
|
|
95842c7987 | ||
|
|
183e3664cc | ||
|
|
08b124c8d4 | ||
|
|
ea08026cd0 | ||
|
|
daa9939c03 | ||
|
|
f757e5a6c3 | ||
|
|
ecb874db62 | ||
|
|
75f1862268 | ||
|
|
f8ab86f930 | ||
|
|
155854d9a9 | ||
|
|
5b6578247f | ||
|
|
b87c4a1e13 | ||
|
|
a0988508f0 | ||
|
|
a347c4def7 | ||
|
|
9c77bcc827 | ||
|
|
8d1f377bf0 | ||
|
|
f766f6ceae | ||
|
|
9dad897d49 | ||
|
|
5b6401519b | ||
|
|
293e080f03 | ||
|
|
633b665379 | ||
|
|
7fd334fddb | ||
|
|
10226a3992 | ||
|
|
383e868af0 | ||
|
|
40802d91d4 | ||
|
|
6d5784daa6 | ||
|
|
f80eb264fb | ||
|
|
3d956ca68b | ||
|
|
7ce131aaf8 | ||
|
|
60be47d115 | ||
|
|
bd187883da | ||
|
|
4f9217bca0 | ||
|
|
ce5222f1df | ||
|
|
cf7b0c8971 | ||
|
|
7bc4cb9868 | ||
|
|
f84f3ffeb7 | ||
|
|
c564a4a26c | ||
|
|
515fd7b75f | ||
|
|
662a4440cc | ||
|
|
5dee43b05c | ||
|
|
c8003c0697 | ||
|
|
83e2889d63 | ||
|
|
d49cd0019f | ||
|
|
0ba40bdfb8 | ||
|
|
f6cd97f6fd | ||
|
|
774a8bf039 | ||
|
|
4431ef1870 | ||
|
|
b3f0ba1430 | ||
|
|
a5f52f0f04 | ||
|
|
63524a2354 | ||
|
|
90edb7189f | ||
|
|
518f6b529b | ||
|
|
fb97e462de | ||
|
|
5b7fa05a87 | ||
|
|
d310a1269f | ||
|
|
9818835c9d | ||
|
|
f3b7f5944d | ||
|
|
fc5cde9434 | ||
|
|
6ea4662326 | ||
|
|
9d12308d06 | ||
|
|
21137d2ba7 | ||
|
|
273cb1921f | ||
|
|
cfa20ff221 | ||
|
|
759d136fe6 | ||
|
|
322aa41ad6 | ||
|
|
3e2f1d733c | ||
|
|
3fed738d2f | ||
|
|
5893e85708 | ||
|
|
1356665ed3 | ||
|
|
9739da8de3 | ||
|
|
249c8a4d96 | ||
|
|
f919fa92de | ||
|
|
21b58643fa | ||
|
|
6a0bcca9ec | ||
|
|
84328c303b | ||
|
|
f7b2b41df9 | ||
|
|
7a6b6435c4 | ||
|
|
bdb54decdc | ||
|
|
b5c41eeb98 | ||
|
|
719a7f7890 | ||
|
|
1b84fee708 | ||
|
|
58e5d4ff02 | ||
|
|
85ff03cde0 | ||
|
|
a3f0bb4547 | ||
|
|
93b20008e0 | ||
|
|
188a893fd0 | ||
|
|
052b746fbd | ||
|
|
80f89059aa | ||
|
|
826d83edfe | ||
|
|
f5d5fab2c8 | ||
|
|
fab2f22a89 | ||
|
|
a451bcc3c4 | ||
|
|
5e9ff3e313 | ||
|
|
cc81f19c68 | ||
|
|
5e89fba681 | ||
|
|
67eb652bf1 | ||
|
|
e0ea9a9ab5 | ||
|
|
ff29a34298 | ||
|
|
6686f66949 | ||
|
|
8a96ea25c4 | ||
|
|
cdddb4d360 | ||
|
|
03bd95405b | ||
|
|
177dfdf900 | ||
|
|
2ab0b3b819 | ||
|
|
888fec9299 | ||
|
|
e86b096b92 | ||
|
|
ffe36c9beb | ||
|
|
2d16d2d036 | ||
|
|
c69da2df70 | ||
|
|
5506669b06 | ||
|
|
b13940720a | ||
|
|
db61711753 | ||
|
|
c12a9f2673 | ||
|
|
2e32f1c8a1 | ||
|
|
03a1c8d2b8 | ||
|
|
d7a277607b | ||
|
|
fc8a72cdd8 | ||
|
|
1acebb3c47 | ||
|
|
78ed0c9312 | ||
|
|
98d2e5fe73 | ||
|
|
4325819075 | ||
|
|
c19c89e6df | ||
|
|
507929cb79 | ||
|
|
7d0a7aff44 | ||
|
|
92ba18342c | ||
|
|
6de5ace116 | ||
|
|
c9db1b9a7b | ||
|
|
24cb694494 | ||
|
|
85bdd9329b | ||
|
|
d40ea8fc81 | ||
|
|
5f9a1482f1 | ||
|
|
5c2238c7a5 | ||
|
|
5769065f27 | ||
|
|
0173479d18 | ||
|
|
08a3c54bac | ||
|
|
3617873431 | ||
|
|
6eb6788201 | ||
|
|
ebc3031fd9 | ||
|
|
42a7402cc5 | ||
|
|
6cd5c9e32f | ||
|
|
d45b830412 | ||
|
|
3a9c071e6e | ||
|
|
ca861bb1bb | ||
|
|
454d3dd52b | ||
|
|
3ec015b325 | ||
|
|
02718284ef | ||
|
|
b5f816dde5 | ||
|
|
499e1459eb | ||
|
|
b5aea548a8 | ||
|
|
3c6a505166 | ||
|
|
efc4d3efdf | ||
|
|
4214ed927f | ||
|
|
e040b200bc | ||
|
|
1dba50f42f | ||
|
|
0ffc92ab65 | ||
|
|
d30361537e | ||
|
|
510c71d41b | ||
|
|
013d2d52fd | ||
|
|
eee91f3f1b | ||
|
|
e87d5e145d | ||
|
|
291af664e1 | ||
|
|
9c0dba4ce1 | ||
|
|
8bfd27b00b | ||
|
|
c6f08dea89 | ||
|
|
9dfe4a30bb | ||
|
|
69b12f4e33 | ||
|
|
c3860804ff | ||
|
|
6a0c19fcf9 | ||
|
|
622c266160 | ||
|
|
dc4396b79c | ||
|
|
1a59b6413b | ||
|
|
992155c60c | ||
|
|
e633f62eaf | ||
|
|
b85af0e533 | ||
|
|
bce1b7a10a | ||
|
|
c292bdd2ca | ||
|
|
1cb9f64917 | ||
|
|
1bded42b2a | ||
|
|
7f64f0454d | ||
|
|
375bc88f95 | ||
|
|
d53a86b01d | ||
|
|
6c7e79eff6 | ||
|
|
d0bc84eb33 | ||
|
|
fabc14355c | ||
|
|
07e086b41e | ||
|
|
48674ec54c | ||
|
|
fc7874e64e | ||
|
|
dcb0da0a7d | ||
|
|
a9f48bd9d1 | ||
|
|
33197608ed | ||
|
|
f951410ef0 | ||
|
|
47ade2f9f9 | ||
|
|
263e143d1b | ||
|
|
21a44d74bd | ||
|
|
efd485cbb8 | ||
|
|
7a6550c1d1 | ||
|
|
23ad470daf | ||
|
|
6dcec47235 | ||
|
|
e93d62680d | ||
|
|
5dbf68ddc4 | ||
|
|
d8d84bf5d4 | ||
|
|
6f6893a93a | ||
|
|
7ae25d10c8 | ||
|
|
d80683f2bf | ||
|
|
ab98d4889b | ||
|
|
ce11ca9d49 | ||
|
|
edda149d75 | ||
|
|
291ca2c32c | ||
|
|
3ba2af289b | ||
|
|
d8d8c908ed | ||
|
|
680b3dd80b | ||
|
|
270e13bb9a | ||
|
|
b3aa7055e4 | ||
|
|
f16461d7d0 | ||
|
|
970f8db5c4 | ||
|
|
bc9086c9af | ||
|
|
a367c6de6e | ||
|
|
27d1a566d0 | ||
|
|
4f52077d97 | ||
|
|
6e485453d0 | ||
|
|
9bae93cd39 | ||
|
|
1a4b253ee5 | ||
|
|
89f6b65ee6 | ||
|
|
a2c6b4ad2f | ||
|
|
6b7d85b769 | ||
|
|
cb3eb75712 | ||
|
|
bae85d858e | ||
|
|
755fd695f5 | ||
|
|
74e25c11f1 |
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.
|
description: A clear and concise description of what you want to happen.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
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.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: |
|
label: |
|
||||||
|
|||||||
11
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -2,7 +2,7 @@ name: Bug Report
|
|||||||
description: |
|
description: |
|
||||||
Use this template for **non-crash-related** bug reports.
|
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.
|
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:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
@@ -38,9 +38,12 @@ body:
|
|||||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
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.
|
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||||
value: |
|
value: |
|
||||||
<details><summary>Zed.log</summary><pre>
|
<details><summary>Zed.log</summary>
|
||||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
|
||||||
|
|
||||||
<!-- 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:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
11
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: Crash Report
|
name: Crash Report
|
||||||
description: |
|
description: |
|
||||||
Use this template for crash reports.
|
Use this template for crash reports.
|
||||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
labels: ["admin read", "triage", "bug", "panic / crash"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
@@ -31,9 +31,12 @@ body:
|
|||||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
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.
|
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||||
value: |
|
value: |
|
||||||
<details><summary>Zed.log</summary><pre>
|
<details><summary>Zed.log</summary>
|
||||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
|
||||||
|
|
||||||
<!-- 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:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
|||||||
cargo install cargo-nextest
|
cargo install cargo-nextest
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/bump_collab_staging.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/bump_patch_version.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch }}
|
ref: ${{ github.event.inputs.branch }}
|
||||||
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}
|
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}
|
||||||
@@ -43,6 +43,8 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
which cargo-set-version > /dev/null || cargo install cargo-edit
|
which cargo-set-version > /dev/null || cargo install cargo-edit
|
||||||
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
||||||
|
export GIT_COMMITTER_NAME="Zed Bot"
|
||||||
|
export GIT_COMMITTER_EMAIL="hi@zed.dev"
|
||||||
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
||||||
git tag v${output}${tag_suffix}
|
git tag v${output}${tag_suffix}
|
||||||
git push origin HEAD v${output}${tag_suffix}
|
git push origin HEAD v${output}${tag_suffix}
|
||||||
|
|||||||
47
.github/workflows/ci.yml
vendored
@@ -13,7 +13,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "docs/**"
|
- "docs/**/*"
|
||||||
- ".github/workflows/community_*"
|
- ".github/workflows/community_*"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -25,6 +25,7 @@ env:
|
|||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
RUSTFLAGS: "-D warnings"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
migration_checks:
|
migration_checks:
|
||||||
@@ -36,7 +37,7 @@ jobs:
|
|||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
fetch-depth: 0 # fetch full history
|
fetch-depth: 0 # fetch full history
|
||||||
@@ -78,25 +79,26 @@ jobs:
|
|||||||
- buildjet-8vcpu-ubuntu-2204
|
- buildjet-8vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- name: Run style checks
|
- name: Run style checks
|
||||||
uses: ./.github/actions/check_style
|
uses: ./.github/actions/check_style
|
||||||
|
|
||||||
- name: Check for typos
|
- name: Check for typos
|
||||||
uses: crate-ci/typos@v1.24.6
|
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
||||||
with:
|
with:
|
||||||
config: ./typos.toml
|
config: ./typos.toml
|
||||||
|
|
||||||
macos_tests:
|
macos_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (macOS) Run Clippy and tests
|
name: (macOS) Run Clippy and tests
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -115,17 +117,18 @@ jobs:
|
|||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
|
|
||||||
- name: Build collab
|
- name: Build collab
|
||||||
run: RUSTFLAGS="-D warnings" cargo build -p collab
|
run: cargo build -p collab
|
||||||
|
|
||||||
- name: Build other binaries and features
|
- name: Build other binaries and features
|
||||||
run: |
|
run: |
|
||||||
RUSTFLAGS="-D warnings" cargo build --workspace --bins --all-features
|
cargo build --workspace --bins --all-features
|
||||||
cargo check -p gpui --features "macos-blade"
|
cargo check -p gpui --features "macos-blade"
|
||||||
RUSTFLAGS="-D warnings" cargo build -p remote_server
|
cargo build -p remote_server
|
||||||
|
|
||||||
linux_tests:
|
linux_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Linux) Run Clippy and tests
|
name: (Linux) Run Clippy and tests
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
@@ -133,7 +136,7 @@ jobs:
|
|||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -153,11 +156,12 @@ jobs:
|
|||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
|
|
||||||
- name: Build Zed
|
- name: Build Zed
|
||||||
run: RUSTFLAGS="-D warnings" cargo build -p zed
|
run: cargo build -p zed
|
||||||
|
|
||||||
build_remote_server:
|
build_remote_server:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Linux) Build Remote Server
|
name: (Linux) Build Remote Server
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
@@ -165,7 +169,7 @@ jobs:
|
|||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -179,16 +183,20 @@ jobs:
|
|||||||
run: ./script/remote-server && ./script/install-mold 2.34.0
|
run: ./script/remote-server && ./script/install-mold 2.34.0
|
||||||
|
|
||||||
- name: Build Remote Server
|
- name: Build Remote Server
|
||||||
run: RUSTFLAGS="-D warnings" cargo build -p remote_server
|
run: cargo build -p remote_server
|
||||||
|
|
||||||
# todo(windows): Actually run the tests
|
# todo(windows): Actually run the tests
|
||||||
windows_tests:
|
windows_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Windows) Run Clippy and tests
|
name: (Windows) Run Clippy and tests
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on: hosted-windows-1
|
runs-on: hosted-windows-1
|
||||||
steps:
|
steps:
|
||||||
|
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||||
|
- name: Enable longer pathnames for git
|
||||||
|
run: git config --system core.longpaths true
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -203,7 +211,7 @@ jobs:
|
|||||||
run: cargo xtask clippy
|
run: cargo xtask clippy
|
||||||
|
|
||||||
- name: Build Zed
|
- name: Build Zed
|
||||||
run: $env:RUSTFLAGS="-D warnings"; cargo build
|
run: cargo build
|
||||||
|
|
||||||
bundle-mac:
|
bundle-mac:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@@ -224,12 +232,12 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
# We need to fetch more than one commit so that `script/draft-release-notes`
|
# We need to fetch more than one commit so that `script/draft-release-notes`
|
||||||
# is able to diff between the current and previous tag.
|
# is able to diff between the current and previous tag.
|
||||||
@@ -252,7 +260,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p target/
|
mkdir -p target/
|
||||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
# 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
|
- name: Generate license file
|
||||||
run: script/generate-licenses
|
run: script/generate-licenses
|
||||||
@@ -314,7 +322,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -361,7 +369,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -386,7 +394,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload app bundle to release
|
- name: Upload app bundle to release
|
||||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ jobs:
|
|||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
Hi there! 👋
|
Hi there! 👋
|
||||||
|
|
||||||
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
|
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. If you are able to reproduce this issue in the latest version of Zed, please let us know by commenting on this issue, and we will keep it open. If you can't reproduce it, feel free to close the issue yourself. Otherwise, we'll close it in 7 days.
|
||||||
|
|
||||||
Thanks for your help!
|
Thanks for your help!
|
||||||
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
|
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
|
||||||
# We will increase `days-before-stale` to 365 on or after Jan 24th,
|
# We will increase `days-before-stale` to 365 on or after Jan 24th,
|
||||||
# 2024. This date marks one year since migrating issues from
|
# 2024. This date marks one year since migrating issues from
|
||||||
# 'community' to 'zed' repository. The migration added activity to all
|
# 'community' to 'zed' repository. The migration added activity to all
|
||||||
# issues, preventing 365 days from working until then.
|
# issues, preventing 365 days from working until then.
|
||||||
days-before-stale: 180
|
days-before-stale: 180
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
any-of-issue-labels: "defect,panic / crash"
|
any-of-issue-labels: "bug,panic / crash"
|
||||||
operations-per-run: 1000
|
operations-per-run: 1000
|
||||||
ascending: true
|
ascending: true
|
||||||
enable-statistics: true
|
enable-statistics: true
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
|
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
version: "latest"
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
|
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
version: "latest"
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|||||||
4
.github/workflows/danger.yml
vendored
@@ -14,14 +14,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
10
.github/workflows/deploy_cloudflare.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -37,28 +37,28 @@ jobs:
|
|||||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||||
|
|
||||||
- name: Deploy Docs
|
- name: Deploy Docs
|
||||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: pages deploy target/deploy --project-name=docs
|
command: pages deploy target/deploy --project-name=docs
|
||||||
|
|
||||||
- name: Deploy Install
|
- name: Deploy Install
|
||||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||||
|
|
||||||
- name: Deploy Docs Workers
|
- name: Deploy Docs Workers
|
||||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||||
|
|
||||||
- name: Deploy Install Workers
|
- name: Deploy Install Workers
|
||||||
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
|||||||
8
.github/workflows/deploy_collab.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
needs: style
|
needs: style
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
run: doctl registry login
|
run: doctl registry login
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/docs.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
- name: Check for Typos with Typos-CLI
|
- name: Check for Typos with Typos-CLI
|
||||||
uses: crate-ci/typos@v1.24.6
|
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
||||||
with:
|
with:
|
||||||
config: ./typos.toml
|
config: ./typos.toml
|
||||||
files: ./docs/
|
files: ./docs/
|
||||||
|
|||||||
2
.github/workflows/publish_extension_cli.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/randomized_tests.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
|||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
|
|||||||
14
.github/workflows/release_nightly.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
needs: style
|
needs: style
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -70,12 +70,12 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ jobs:
|
|||||||
- bundle-linux-arm
|
- bundle-linux-arm
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
2
.mailmap
@@ -60,6 +60,8 @@ Max Brunsfeld <maxbrunsfeld@gmail.com>
|
|||||||
Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
|
Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
|
||||||
Max Linke <maxlinke88@gmail.com>
|
Max Linke <maxlinke88@gmail.com>
|
||||||
Max Linke <maxlinke88@gmail.com> <kain88-de@users.noreply.github.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 Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
||||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Code of Conduct
|
# Code of Conduct
|
||||||
|
|
||||||
The Code of Conduct for this repository can be found online at [zed.dev/docs/code-of-conduct](https://zed.dev/docs/code-of-conduct).
|
The Code of Conduct for this repository can be found online at [zed.dev/code-of-conduct](https://zed.dev/code-of-conduct).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor!
|
Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor!
|
||||||
|
|
||||||
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/docs/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
|
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
|
||||||
|
|
||||||
## Contribution ideas
|
## Contribution ideas
|
||||||
|
|
||||||
|
|||||||
1468
Cargo.lock
generated
22
Cargo.toml
@@ -23,7 +23,6 @@ members = [
|
|||||||
"crates/context_servers",
|
"crates/context_servers",
|
||||||
"crates/copilot",
|
"crates/copilot",
|
||||||
"crates/db",
|
"crates/db",
|
||||||
"crates/dev_server_projects",
|
|
||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
"crates/docs_preprocessor",
|
"crates/docs_preprocessor",
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
@@ -31,6 +30,7 @@ members = [
|
|||||||
"crates/extension",
|
"crates/extension",
|
||||||
"crates/extension_api",
|
"crates/extension_api",
|
||||||
"crates/extension_cli",
|
"crates/extension_cli",
|
||||||
|
"crates/extension_host",
|
||||||
"crates/extensions_ui",
|
"crates/extensions_ui",
|
||||||
"crates/feature_flags",
|
"crates/feature_flags",
|
||||||
"crates/feedback",
|
"crates/feedback",
|
||||||
@@ -45,7 +45,6 @@ members = [
|
|||||||
"crates/google_ai",
|
"crates/google_ai",
|
||||||
"crates/gpui",
|
"crates/gpui",
|
||||||
"crates/gpui_macros",
|
"crates/gpui_macros",
|
||||||
"crates/headless",
|
|
||||||
"crates/html_to_markdown",
|
"crates/html_to_markdown",
|
||||||
"crates/http_client",
|
"crates/http_client",
|
||||||
"crates/image_viewer",
|
"crates/image_viewer",
|
||||||
@@ -119,6 +118,7 @@ members = [
|
|||||||
"crates/theme_selector",
|
"crates/theme_selector",
|
||||||
"crates/time_format",
|
"crates/time_format",
|
||||||
"crates/title_bar",
|
"crates/title_bar",
|
||||||
|
"crates/toolchain_selector",
|
||||||
"crates/ui",
|
"crates/ui",
|
||||||
"crates/ui_input",
|
"crates/ui_input",
|
||||||
"crates/ui_macros",
|
"crates/ui_macros",
|
||||||
@@ -139,7 +139,6 @@ members = [
|
|||||||
"extensions/astro",
|
"extensions/astro",
|
||||||
"extensions/clojure",
|
"extensions/clojure",
|
||||||
"extensions/csharp",
|
"extensions/csharp",
|
||||||
"extensions/dart",
|
|
||||||
"extensions/deno",
|
"extensions/deno",
|
||||||
"extensions/elixir",
|
"extensions/elixir",
|
||||||
"extensions/elm",
|
"extensions/elm",
|
||||||
@@ -201,10 +200,10 @@ command_palette_hooks = { path = "crates/command_palette_hooks" }
|
|||||||
context_servers = { path = "crates/context_servers" }
|
context_servers = { path = "crates/context_servers" }
|
||||||
copilot = { path = "crates/copilot" }
|
copilot = { path = "crates/copilot" }
|
||||||
db = { path = "crates/db" }
|
db = { path = "crates/db" }
|
||||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
|
||||||
diagnostics = { path = "crates/diagnostics" }
|
diagnostics = { path = "crates/diagnostics" }
|
||||||
editor = { path = "crates/editor" }
|
editor = { path = "crates/editor" }
|
||||||
extension = { path = "crates/extension" }
|
extension = { path = "crates/extension" }
|
||||||
|
extension_host = { path = "crates/extension_host" }
|
||||||
extensions_ui = { path = "crates/extensions_ui" }
|
extensions_ui = { path = "crates/extensions_ui" }
|
||||||
feature_flags = { path = "crates/feature_flags" }
|
feature_flags = { path = "crates/feature_flags" }
|
||||||
feedback = { path = "crates/feedback" }
|
feedback = { path = "crates/feedback" }
|
||||||
@@ -219,7 +218,6 @@ go_to_line = { path = "crates/go_to_line" }
|
|||||||
google_ai = { path = "crates/google_ai" }
|
google_ai = { path = "crates/google_ai" }
|
||||||
gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
|
gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
|
||||||
gpui_macros = { path = "crates/gpui_macros" }
|
gpui_macros = { path = "crates/gpui_macros" }
|
||||||
headless = { path = "crates/headless" }
|
|
||||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||||
http_client = { path = "crates/http_client" }
|
http_client = { path = "crates/http_client" }
|
||||||
image_viewer = { path = "crates/image_viewer" }
|
image_viewer = { path = "crates/image_viewer" }
|
||||||
@@ -294,6 +292,7 @@ theme_importer = { path = "crates/theme_importer" }
|
|||||||
theme_selector = { path = "crates/theme_selector" }
|
theme_selector = { path = "crates/theme_selector" }
|
||||||
time_format = { path = "crates/time_format" }
|
time_format = { path = "crates/time_format" }
|
||||||
title_bar = { path = "crates/title_bar" }
|
title_bar = { path = "crates/title_bar" }
|
||||||
|
toolchain_selector = { path = "crates/toolchain_selector" }
|
||||||
ui = { path = "crates/ui" }
|
ui = { path = "crates/ui" }
|
||||||
ui_input = { path = "crates/ui_input" }
|
ui_input = { path = "crates/ui_input" }
|
||||||
ui_macros = { path = "crates/ui_macros" }
|
ui_macros = { path = "crates/ui_macros" }
|
||||||
@@ -373,6 +372,7 @@ linkify = "0.10.0"
|
|||||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
|
nbformat = "0.3.2"
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
@@ -380,6 +380,12 @@ ordered-float = "2.1.1"
|
|||||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
pathdiff = "0.2"
|
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" }
|
||||||
|
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||||
postage = { version = "0.5", features = ["futures-traits"] }
|
postage = { version = "0.5", features = ["futures-traits"] }
|
||||||
pretty_assertions = "1.3.0"
|
pretty_assertions = "1.3.0"
|
||||||
profiling = "1"
|
profiling = "1"
|
||||||
@@ -388,6 +394,7 @@ prost-build = "0.9"
|
|||||||
prost-types = "0.9"
|
prost-types = "0.9"
|
||||||
pulldown-cmark = { version = "0.12.0", default-features = false }
|
pulldown-cmark = { version = "0.12.0", default-features = false }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
rayon = "1.8"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
repair_json = "0.1.0"
|
repair_json = "0.1.0"
|
||||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
|
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
|
||||||
@@ -399,7 +406,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
|||||||
"stream",
|
"stream",
|
||||||
] }
|
] }
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.6"
|
||||||
runtimelib = { version = "0.15", default-features = false, features = [
|
runtimelib = { version = "0.16.1", default-features = false, features = [
|
||||||
"async-dispatcher-runtime",
|
"async-dispatcher-runtime",
|
||||||
] }
|
] }
|
||||||
rustc-demangle = "0.1.23"
|
rustc-demangle = "0.1.23"
|
||||||
@@ -459,7 +466,7 @@ tree-sitter-diff = "0.1.0"
|
|||||||
tree-sitter-html = "0.20"
|
tree-sitter-html = "0.20"
|
||||||
tree-sitter-jsdoc = "0.23"
|
tree-sitter-jsdoc = "0.23"
|
||||||
tree-sitter-json = "0.23"
|
tree-sitter-json = "0.23"
|
||||||
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
|
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||||
tree-sitter-python = "0.23"
|
tree-sitter-python = "0.23"
|
||||||
tree-sitter-regex = "0.23"
|
tree-sitter-regex = "0.23"
|
||||||
tree-sitter-ruby = "0.23"
|
tree-sitter-ruby = "0.23"
|
||||||
@@ -469,6 +476,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
|
|||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
|
unicode-script = "0.5.7"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||||
wasmparser = "0.215"
|
wasmparser = "0.215"
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"gitignore": "vcs",
|
"gitignore": "vcs",
|
||||||
"gitkeep": "vcs",
|
"gitkeep": "vcs",
|
||||||
"gitmodules": "vcs",
|
"gitmodules": "vcs",
|
||||||
|
"gleam": "gleam",
|
||||||
"go": "go",
|
"go": "go",
|
||||||
"gql": "graphql",
|
"gql": "graphql",
|
||||||
"graphql": "graphql",
|
"graphql": "graphql",
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
"h": "c",
|
"h": "c",
|
||||||
"handlebars": "code",
|
"handlebars": "code",
|
||||||
"hbs": "template",
|
"hbs": "template",
|
||||||
|
"hcl": "hcl",
|
||||||
"heex": "elixir",
|
"heex": "elixir",
|
||||||
"heic": "image",
|
"heic": "image",
|
||||||
"heif": "image",
|
"heif": "image",
|
||||||
@@ -82,6 +84,7 @@
|
|||||||
"j2k": "image",
|
"j2k": "image",
|
||||||
"java": "java",
|
"java": "java",
|
||||||
"jfif": "image",
|
"jfif": "image",
|
||||||
|
"jl": "julia",
|
||||||
"jp2": "image",
|
"jp2": "image",
|
||||||
"jpeg": "image",
|
"jpeg": "image",
|
||||||
"jpg": "image",
|
"jpg": "image",
|
||||||
@@ -116,6 +119,7 @@
|
|||||||
"myd": "storage",
|
"myd": "storage",
|
||||||
"myi": "storage",
|
"myi": "storage",
|
||||||
"nim": "nim",
|
"nim": "nim",
|
||||||
|
"nix": "nix",
|
||||||
"nu": "terminal",
|
"nu": "terminal",
|
||||||
"odp": "document",
|
"odp": "document",
|
||||||
"ods": "document",
|
"ods": "document",
|
||||||
@@ -143,12 +147,15 @@
|
|||||||
"rb": "ruby",
|
"rb": "ruby",
|
||||||
"rebar.config": "erlang",
|
"rebar.config": "erlang",
|
||||||
"rkt": "code",
|
"rkt": "code",
|
||||||
|
"roc": "roc",
|
||||||
"rs": "rust",
|
"rs": "rust",
|
||||||
"rtf": "document",
|
"rtf": "document",
|
||||||
|
"sass": "sass",
|
||||||
"sav": "storage",
|
"sav": "storage",
|
||||||
"sc": "scala",
|
"sc": "scala",
|
||||||
"scala": "scala",
|
"scala": "scala",
|
||||||
"scm": "code",
|
"scm": "code",
|
||||||
|
"scss": "sass",
|
||||||
"sdf": "storage",
|
"sdf": "storage",
|
||||||
"sh": "terminal",
|
"sh": "terminal",
|
||||||
"sql": "storage",
|
"sql": "storage",
|
||||||
@@ -182,6 +189,7 @@
|
|||||||
"yaml": "settings",
|
"yaml": "settings",
|
||||||
"yml": "settings",
|
"yml": "settings",
|
||||||
"yrl": "erlang",
|
"yrl": "erlang",
|
||||||
|
"zig": "zig",
|
||||||
"zlogin": "terminal",
|
"zlogin": "terminal",
|
||||||
"zsh": "terminal",
|
"zsh": "terminal",
|
||||||
"zsh_aliases": "terminal",
|
"zsh_aliases": "terminal",
|
||||||
@@ -257,6 +265,9 @@
|
|||||||
"fsharp": {
|
"fsharp": {
|
||||||
"icon": "icons/file_icons/fsharp.svg"
|
"icon": "icons/file_icons/fsharp.svg"
|
||||||
},
|
},
|
||||||
|
"gleam": {
|
||||||
|
"icon": "icons/file_icons/gleam.svg"
|
||||||
|
},
|
||||||
"go": {
|
"go": {
|
||||||
"icon": "icons/file_icons/go.svg"
|
"icon": "icons/file_icons/go.svg"
|
||||||
},
|
},
|
||||||
@@ -266,6 +277,9 @@
|
|||||||
"haskell": {
|
"haskell": {
|
||||||
"icon": "icons/file_icons/haskell.svg"
|
"icon": "icons/file_icons/haskell.svg"
|
||||||
},
|
},
|
||||||
|
"hcl": {
|
||||||
|
"icon": "icons/file_icons/hcl.svg"
|
||||||
|
},
|
||||||
"heroku": {
|
"heroku": {
|
||||||
"icon": "icons/file_icons/heroku.svg"
|
"icon": "icons/file_icons/heroku.svg"
|
||||||
},
|
},
|
||||||
@@ -278,6 +292,9 @@
|
|||||||
"javascript": {
|
"javascript": {
|
||||||
"icon": "icons/file_icons/javascript.svg"
|
"icon": "icons/file_icons/javascript.svg"
|
||||||
},
|
},
|
||||||
|
"julia": {
|
||||||
|
"icon": "icons/file_icons/julia.svg"
|
||||||
|
},
|
||||||
"kotlin": {
|
"kotlin": {
|
||||||
"icon": "icons/file_icons/kotlin.svg"
|
"icon": "icons/file_icons/kotlin.svg"
|
||||||
},
|
},
|
||||||
@@ -293,6 +310,9 @@
|
|||||||
"nim": {
|
"nim": {
|
||||||
"icon": "icons/file_icons/nim.svg"
|
"icon": "icons/file_icons/nim.svg"
|
||||||
},
|
},
|
||||||
|
"nix": {
|
||||||
|
"icon": "icons/file_icons/nix.svg"
|
||||||
|
},
|
||||||
"ocaml": {
|
"ocaml": {
|
||||||
"icon": "icons/file_icons/ocaml.svg"
|
"icon": "icons/file_icons/ocaml.svg"
|
||||||
},
|
},
|
||||||
@@ -317,12 +337,18 @@
|
|||||||
"react": {
|
"react": {
|
||||||
"icon": "icons/file_icons/react.svg"
|
"icon": "icons/file_icons/react.svg"
|
||||||
},
|
},
|
||||||
|
"roc": {
|
||||||
|
"icon": "icons/file_icons/roc.svg"
|
||||||
|
},
|
||||||
"ruby": {
|
"ruby": {
|
||||||
"icon": "icons/file_icons/ruby.svg"
|
"icon": "icons/file_icons/ruby.svg"
|
||||||
},
|
},
|
||||||
"rust": {
|
"rust": {
|
||||||
"icon": "icons/file_icons/rust.svg"
|
"icon": "icons/file_icons/rust.svg"
|
||||||
},
|
},
|
||||||
|
"sass": {
|
||||||
|
"icon": "icons/file_icons/sass.svg"
|
||||||
|
},
|
||||||
"scala": {
|
"scala": {
|
||||||
"icon": "icons/file_icons/scala.svg"
|
"icon": "icons/file_icons/scala.svg"
|
||||||
},
|
},
|
||||||
@@ -361,6 +387,9 @@
|
|||||||
},
|
},
|
||||||
"vue": {
|
"vue": {
|
||||||
"icon": "icons/file_icons/vue.svg"
|
"icon": "icons/file_icons/vue.svg"
|
||||||
|
},
|
||||||
|
"zig": {
|
||||||
|
"icon": "icons/file_icons/zig.svg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
assets/icons/file_icons/gleam.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.3848 9.30444C7.3848 9.30444 7.53254 10.2646 8.53248 10.0882C9.53242 9.91193 9.36378 8.95549 9.36378 8.95549" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="#FF7676" stroke-opacity="0.52" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="6.25098" cy="7.75" r="0.75" fill="black"/>
|
||||||
|
<circle cx="10.1035" cy="7.25" r="0.75" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
3
assets/icons/file_icons/hcl.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 fill-rule="evenodd" clip-rule="evenodd" d="M7.11466 3.11809C7.21859 3.37393 7.09545 3.66558 6.83961 3.76952L4.31181 4.79643C4.1233 4.87302 4 5.05619 4 5.25967V11.5C4 11.7761 3.77614 12 3.5 12H2.5C2.22386 12 2 11.7761 2 11.5V4.41827C2 3.90959 2.30825 3.45164 2.77953 3.26018L6.08686 1.91658C6.34269 1.81265 6.63434 1.93579 6.73828 2.19163L7.11466 3.11809ZM10.5 1.99999C10.7761 1.99999 11 2.22384 11 2.49999V10.5C11 10.7761 10.7761 11 10.5 11H9.5C9.22386 11 9 10.7761 9 10.5V9.49999C9 9.22384 8.77614 8.99999 8.5 8.99999H7.5C7.22386 8.99999 7 9.22384 7 9.49999V13.5C7 13.7761 6.77614 14 6.5 14H5.5C5.22386 14 5 13.7761 5 13.5V5.53124C5 5.25509 5.22386 5.03124 5.5 5.03124H6.5C6.77614 5.03124 7 5.25509 7 5.53124V6.49999C7 6.77613 7.22386 6.99999 7.5 6.99999H8.5C8.77614 6.99999 9 6.77613 9 6.49999V2.49999C9 2.22384 9.22386 1.99999 9.5 1.99999H10.5ZM13.5 4.03124C13.7761 4.03124 14 4.2551 14 4.53124L14 11.5847C14 12.0859 13.7006 12.5386 13.2394 12.7349L9.99399 14.1159C9.7399 14.224 9.44626 14.1057 9.33813 13.8516L8.94658 12.9315C8.83845 12.6774 8.95678 12.3837 9.21087 12.2756L11.6958 11.2182C11.8802 11.1397 12 10.9586 12 10.7581L12 4.53124C12 4.2551 12.2238 4.03124 12.5 4.03124L13.5 4.03124Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
5
assets/icons/file_icons/julia.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="8" cy="5" r="2.75" fill="black"/>
|
||||||
|
<circle cx="4.75" cy="11" r="2.75" fill="black" fill-opacity="0.5"/>
|
||||||
|
<circle cx="11.25" cy="11" r="2.75" fill="black" fill-opacity="0.75"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 289 B |
8
assets/icons/file_icons/nix.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.00005 4.76556L4.76569 2.74996M6.00005 4.76556L3.75 4.76563M6.00005 4.76556L7.25006 4.7656" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M10.0232 11.2311L11.2675 13.2406M10.0232 11.2311L12.2732 11.2199M10.0232 11.2311L8.7732 11.2373" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M9.99025 4.91551L10.9985 2.77781M9.99025 4.91551L8.75599 3.03419M9.99025 4.91551L10.6759 5.9607" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M6.0323 11.1009L5.03465 13.2436M6.0323 11.1009L7.27585 12.9761M6.0323 11.1009L5.34151 10.0592" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M11.883 8.19023L14.2466 8.19287M11.883 8.19023L13.0602 6.27268M11.883 8.19023L11.229 9.25547" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M4.12354 7.8356L1.76002 7.84465M4.12354 7.8356L2.95585 9.75894M4.12354 7.8356L4.7723 6.76713" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
7
assets/icons/file_icons/roc.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.51497 2.02702L1.92042 1.95067C1.69543 1.94589 1.57917 2.21756 1.73796 2.37702L6.24865 6.9068C6.42388 7.08277 6.72071 6.92326 6.67067 6.68002L5.75454 2.22659C5.73103 2.11231 5.63161 2.02949 5.51497 2.02702Z" fill="black" fill-opacity="0.5"/>
|
||||||
|
<path d="M8.05816 7.38492L12.1366 8.02844C12.3704 8.06532 12.5198 7.78697 12.3599 7.61255L7.30439 2.09814C7.13336 1.91159 6.82522 2.06811 6.87499 2.31624L7.852 7.18714C7.87257 7.28971 7.95483 7.36862 8.05816 7.38492Z" fill="black"/>
|
||||||
|
<path d="M9.0952 10.9797L11.3824 9.35081C11.564 9.22151 11.4983 8.93722 11.2785 8.90058L8.496 8.43683C8.31974 8.40746 8.17047 8.56712 8.21162 8.74101L8.70689 10.8337C8.74777 11.0064 8.95062 11.0827 9.0952 10.9797Z" fill="black" fill-opacity="0.5"/>
|
||||||
|
<path d="M5.10282 13.9632L7.59108 12.4532C7.68331 12.3972 7.72923 12.2884 7.70498 12.1832L6.75736 8.07484C6.699 7.8218 6.34133 7.81448 6.27266 8.06491L4.73201 13.6834C4.67223 13.9014 4.90954 14.0805 5.10282 13.9632Z" fill="black"/>
|
||||||
|
<path d="M11.3183 4.89351L13.1588 7.03149L15.535 6.14302C15.7099 6.07761 15.754 5.85043 15.6161 5.72438L13.7222 3.99219L11.4546 4.48614C11.2695 4.52645 11.1947 4.74995 11.3183 4.89351Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
3
assets/icons/file_icons/sass.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 fill-rule="evenodd" clip-rule="evenodd" d="M6.92096 7.00668C7.87408 7.83549 10.0987 7.48203 10.9376 7.06254C12.8751 6.09381 13.9407 4.39379 12.6407 2.90629C11.0157 1.04692 6.24221 2.49998 4.89844 3.40625C3.55467 4.31252 2.67972 5.53126 2.89071 7.1719C3.1017 8.81254 4.68758 9.7422 6.03128 10.3203C5.38786 10.5616 3.8517 11.0388 3.3125 11.7188C2.71341 12.4742 3.04343 14 4.51577 14C7.15639 14 7.59539 11.1486 7.14847 10.4375C7.88773 10.1295 8.49597 9.96169 9.40138 9.77081C9.63831 9.72087 9.65457 9.46395 9.41295 9.44827C8.80252 9.40864 7.30567 9.8489 6.92096 9.97657C5.78909 9.35157 4.51016 7.93818 4.59378 6.87501C4.68676 5.6928 5.27676 5.07603 6.84508 4.21876C8.01705 3.57813 10.258 3.10695 11.25 3.62501C12.6563 4.35936 10.7875 5.75599 9.92969 6.32031C9.28179 6.74656 8.21971 6.77513 7.22979 6.61435C6.99371 6.576 6.74048 6.84974 6.92096 7.00668ZM5.6719 12.4643C6.35508 11.9894 6.45471 11.1076 6.29955 10.8844C5.76663 11.0874 4.36593 11.9102 4.75111 12.4643C4.90628 12.6875 5.31358 12.7134 5.6719 12.4643Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
5
assets/icons/file_icons/zig.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.25 12H11C10.794 12 10.6764 11.7648 10.8 11.6L11.925 10.1C11.9722 10.037 12.0463 10 12.125 10H12.75C12.8881 10 13 9.88807 13 9.75V6.25C13 6.11193 12.8881 6 12.75 6H12.4045C12.2187 6 12.0978 5.80442 12.1809 5.6382L12.9309 4.1382C12.9732 4.0535 13.0598 4 13.1545 4H14.25C14.3881 4 14.5 4.11193 14.5 4.25V11.75C14.5 11.8881 14.3881 12 14.25 12Z" fill="black"/>
|
||||||
|
<path d="M1.75 4H5C5.20601 4 5.32361 4.23519 5.2 4.4L4.075 5.9C4.02779 5.96295 3.95369 6 3.875 6H3.25C3.11193 6 3 6.11193 3 6.25V9.75C3 9.88807 3.11193 10 3.25 10H3.59549C3.78134 10 3.90221 10.1956 3.8191 10.3618L3.0691 11.8618C3.02675 11.9465 2.94018 12 2.84549 12H1.75C1.61193 12 1.5 11.8881 1.5 11.75V4.25C1.5 4.11193 1.61193 4 1.75 4Z" fill="black"/>
|
||||||
|
<path d="M7.55748 6H5.95006C5.74177 6 5.62482 5.76022 5.75306 5.59609L6.92493 4.09609C6.97231 4.03544 7.04498 4 7.12194 4H9.93075C9.97607 4 10.0205 3.98769 10.0594 3.96437L11.6408 3.0155C11.8641 2.88154 12.1179 3.13555 11.9837 3.3587L8.22612 9.6083C8.12629 9.77433 8.24508 9.98591 8.43881 9.98712L10.0039 9.9969C10.2092 9.99818 10.3255 10.2327 10.2023 10.3969L9.075 11.9C9.02779 11.963 8.95369 12 8.875 12H6.55383C6.51835 12 6.48328 12.0076 6.45094 12.0222L4.32473 12.9824C4.10122 13.0833 3.88113 12.8356 4.00771 12.6255L7.77161 6.37903C7.87201 6.2124 7.75202 6 7.55748 6Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
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 |
7
assets/icons/list_x.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.33333 8H3" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.6667 4H3" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.6667 12H3" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.6667 6.66663L11 9.33329" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11 6.66663L13.6667 9.33329" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 579 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/wand.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wand"><path d="M15 4V2"/><path d="M15 16v-2"/><path d="M8 9h2"/><path d="M20 9h2"/><path d="M17.8 11.8 19 13"/><path d="M15 9h.01"/><path d="M17.8 6.2 19 5"/><path d="m3 21 9-9"/><path d="M12.2 6.2 11 5"/></svg>
|
||||||
|
After Width: | Height: | Size: 414 B |
@@ -251,10 +251,10 @@
|
|||||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||||
"ctrl-w": "pane::CloseActiveItem",
|
"ctrl-w": "pane::CloseActiveItem",
|
||||||
"ctrl-f4": "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",
|
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-k u": "pane::CloseCleanItems",
|
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||||
"ctrl-k w": "pane::CloseAllItems",
|
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||||
"ctrl-alt-g": "search::SelectNextMatch",
|
"ctrl-alt-g": "search::SelectNextMatch",
|
||||||
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
||||||
@@ -313,6 +313,15 @@
|
|||||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||||
|
"ctrl-k ctrl-1": ["editor::FoldAtLevel", { "level": 1 }],
|
||||||
|
"ctrl-k ctrl-2": ["editor::FoldAtLevel", { "level": 2 }],
|
||||||
|
"ctrl-k ctrl-3": ["editor::FoldAtLevel", { "level": 3 }],
|
||||||
|
"ctrl-k ctrl-4": ["editor::FoldAtLevel", { "level": 4 }],
|
||||||
|
"ctrl-k ctrl-5": ["editor::FoldAtLevel", { "level": 5 }],
|
||||||
|
"ctrl-k ctrl-6": ["editor::FoldAtLevel", { "level": 6 }],
|
||||||
|
"ctrl-k ctrl-7": ["editor::FoldAtLevel", { "level": 7 }],
|
||||||
|
"ctrl-k ctrl-8": ["editor::FoldAtLevel", { "level": 8 }],
|
||||||
|
"ctrl-k ctrl-9": ["editor::FoldAtLevel", { "level": 9 }],
|
||||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
@@ -505,6 +514,13 @@
|
|||||||
"ctrl-enter": "assistant::InlineAssist"
|
"ctrl-enter": "assistant::InlineAssist"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "ProposedChangesEditor",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-shift-y": "editor::ApplyDiffHunk",
|
||||||
|
"ctrl-alt-a": "editor::ApplyAllDiffHunks"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && jupyter && !ContextEditor",
|
"context": "Editor && jupyter && !ContextEditor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -516,6 +532,7 @@
|
|||||||
"context": "ContextEditor > Editor",
|
"context": "ContextEditor > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "assistant::Assist",
|
"ctrl-enter": "assistant::Assist",
|
||||||
|
"ctrl-shift-enter": "assistant::Edit",
|
||||||
"ctrl-s": "workspace::Save",
|
"ctrl-s": "workspace::Save",
|
||||||
"ctrl->": "assistant::QuoteSelection",
|
"ctrl->": "assistant::QuoteSelection",
|
||||||
"ctrl-<": "assistant::InsertIntoEditor",
|
"ctrl-<": "assistant::InsertIntoEditor",
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
"context": "ContextEditor > Editor",
|
"context": "ContextEditor > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "assistant::Assist",
|
"cmd-enter": "assistant::Assist",
|
||||||
|
"cmd-shift-enter": "assistant::Edit",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
"cmd->": "assistant::QuoteSelection",
|
"cmd->": "assistant::QuoteSelection",
|
||||||
"cmd-<": "assistant::InsertIntoEditor",
|
"cmd-<": "assistant::InsertIntoEditor",
|
||||||
@@ -290,10 +291,10 @@
|
|||||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||||
"cmd-w": "pane::CloseActiveItem",
|
"cmd-w": "pane::CloseActiveItem",
|
||||||
"alt-cmd-t": "pane::CloseInactiveItems",
|
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"cmd-k u": "pane::CloseCleanItems",
|
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||||
"cmd-k cmd-w": "pane::CloseAllItems",
|
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||||
"cmd-f": "project_search::ToggleFocus",
|
"cmd-f": "project_search::ToggleFocus",
|
||||||
"cmd-g": "search::SelectNextMatch",
|
"cmd-g": "search::SelectNextMatch",
|
||||||
"cmd-shift-g": "search::SelectPrevMatch",
|
"cmd-shift-g": "search::SelectPrevMatch",
|
||||||
@@ -350,6 +351,15 @@
|
|||||||
"cmd-k cmd-l": "editor::ToggleFold",
|
"cmd-k cmd-l": "editor::ToggleFold",
|
||||||
"cmd-k cmd-[": "editor::FoldRecursive",
|
"cmd-k cmd-[": "editor::FoldRecursive",
|
||||||
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
||||||
|
"cmd-k cmd-1": ["editor::FoldAtLevel", { "level": 1 }],
|
||||||
|
"cmd-k cmd-2": ["editor::FoldAtLevel", { "level": 2 }],
|
||||||
|
"cmd-k cmd-3": ["editor::FoldAtLevel", { "level": 3 }],
|
||||||
|
"cmd-k cmd-4": ["editor::FoldAtLevel", { "level": 4 }],
|
||||||
|
"cmd-k cmd-5": ["editor::FoldAtLevel", { "level": 5 }],
|
||||||
|
"cmd-k cmd-6": ["editor::FoldAtLevel", { "level": 6 }],
|
||||||
|
"cmd-k cmd-7": ["editor::FoldAtLevel", { "level": 7 }],
|
||||||
|
"cmd-k cmd-8": ["editor::FoldAtLevel", { "level": 8 }],
|
||||||
|
"cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
|
||||||
"cmd-k cmd-0": "editor::FoldAll",
|
"cmd-k cmd-0": "editor::FoldAll",
|
||||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
@@ -538,6 +548,13 @@
|
|||||||
"ctrl-enter": "assistant::InlineAssist"
|
"ctrl-enter": "assistant::InlineAssist"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "ProposedChangesEditor",
|
||||||
|
"bindings": {
|
||||||
|
"cmd-shift-y": "editor::ApplyDiffHunk",
|
||||||
|
"cmd-shift-a": "editor::ApplyAllDiffHunks"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "PromptEditor",
|
"context": "PromptEditor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
"alt-j": ["editor::SelectNext", { "replace_newest": false }],
|
"alt-j": ["editor::SelectNext", { "replace_newest": false }],
|
||||||
"alt-shift-j": ["editor::SelectPrevious", { "replace_newest": false }],
|
"alt-shift-j": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
"ctrl-w": "editor::SelectLargerSyntaxNode",
|
||||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
"ctrl-shift-w": "editor::SelectSmallerSyntaxNode",
|
||||||
"shift-alt-up": "editor::MoveLineUp",
|
"shift-alt-up": "editor::MoveLineUp",
|
||||||
"shift-alt-down": "editor::MoveLineDown",
|
"shift-alt-down": "editor::MoveLineDown",
|
||||||
"ctrl-alt-l": "editor::Format",
|
"ctrl-alt-l": "editor::Format",
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
|
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
|
||||||
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
|
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||||
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
"cmd-up": "editor::SelectLargerSyntaxNode",
|
||||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
"cmd-down": "editor::SelectSmallerSyntaxNode",
|
||||||
"shift-alt-up": "editor::MoveLineUp",
|
"shift-alt-up": "editor::MoveLineUp",
|
||||||
"shift-alt-down": "editor::MoveLineDown",
|
"shift-alt-down": "editor::MoveLineDown",
|
||||||
"cmd-alt-l": "editor::Format",
|
"cmd-alt-l": "editor::Format",
|
||||||
@@ -58,6 +58,12 @@
|
|||||||
"alt-enter": "editor::ToggleCodeActions"
|
"alt-enter": "editor::ToggleCodeActions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "BufferSearchBar > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"shift-enter": "search::SelectPrevMatch"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -127,6 +127,9 @@
|
|||||||
"shift-h": "vim::WindowTop",
|
"shift-h": "vim::WindowTop",
|
||||||
"shift-m": "vim::WindowMiddle",
|
"shift-m": "vim::WindowMiddle",
|
||||||
"shift-l": "vim::WindowBottom",
|
"shift-l": "vim::WindowBottom",
|
||||||
|
"q": "vim::ToggleRecord",
|
||||||
|
"shift-q": "vim::ReplayLastRecording",
|
||||||
|
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||||
// z commands
|
// z commands
|
||||||
"z enter": ["workspace::SendKeystrokes", "z t ^"],
|
"z enter": ["workspace::SendKeystrokes", "z t ^"],
|
||||||
"z -": ["workspace::SendKeystrokes", "z b ^"],
|
"z -": ["workspace::SendKeystrokes", "z b ^"],
|
||||||
@@ -137,14 +140,14 @@
|
|||||||
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
||||||
"z b": "editor::ScrollCursorBottom",
|
"z b": "editor::ScrollCursorBottom",
|
||||||
"z a": "editor::ToggleFold",
|
"z a": "editor::ToggleFold",
|
||||||
"z A": "editor::ToggleFoldRecursive",
|
"z shift-a": "editor::ToggleFoldRecursive",
|
||||||
"z c": "editor::Fold",
|
"z c": "editor::Fold",
|
||||||
"z C": "editor::FoldRecursive",
|
"z shift-c": "editor::FoldRecursive",
|
||||||
"z o": "editor::UnfoldLines",
|
"z o": "editor::UnfoldLines",
|
||||||
"z O": "editor::UnfoldRecursive",
|
"z shift-o": "editor::UnfoldRecursive",
|
||||||
"z f": "editor::FoldSelectedRanges",
|
"z f": "editor::FoldSelectedRanges",
|
||||||
"z M": "editor::FoldAll",
|
"z shift-m": "editor::FoldAll",
|
||||||
"z R": "editor::UnfoldAll",
|
"z shift-r": "editor::UnfoldAll",
|
||||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||||
// Count support
|
// Count support
|
||||||
@@ -157,51 +160,6 @@
|
|||||||
"7": ["vim::Number", 7],
|
"7": ["vim::Number", 7],
|
||||||
"8": ["vim::Number", 8],
|
"8": ["vim::Number", 8],
|
||||||
"9": ["vim::Number", 9],
|
"9": ["vim::Number", 9],
|
||||||
// window related commands (ctrl-w X)
|
|
||||||
"ctrl-w": null,
|
|
||||||
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
|
|
||||||
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
|
|
||||||
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
|
|
||||||
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
|
|
||||||
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
|
||||||
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
|
||||||
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
|
||||||
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
|
||||||
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
|
||||||
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
|
||||||
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
|
||||||
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
|
||||||
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
|
||||||
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
|
||||||
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
|
||||||
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
|
||||||
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
|
|
||||||
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
|
|
||||||
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
|
|
||||||
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
|
|
||||||
"ctrl-w g t": "pane::ActivateNextItem",
|
|
||||||
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
|
||||||
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
|
||||||
"ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
|
|
||||||
"ctrl-w w": "workspace::ActivateNextPane",
|
|
||||||
"ctrl-w ctrl-w": "workspace::ActivateNextPane",
|
|
||||||
"ctrl-w p": "workspace::ActivatePreviousPane",
|
|
||||||
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
|
|
||||||
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
|
|
||||||
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
|
|
||||||
"ctrl-w v": "pane::SplitVertical",
|
|
||||||
"ctrl-w ctrl-v": "pane::SplitVertical",
|
|
||||||
"ctrl-w s": "pane::SplitHorizontal",
|
|
||||||
"ctrl-w shift-s": "pane::SplitHorizontal",
|
|
||||||
"ctrl-w ctrl-s": "pane::SplitHorizontal",
|
|
||||||
"ctrl-w c": "pane::CloseAllItems",
|
|
||||||
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
|
||||||
"ctrl-w q": "pane::CloseAllItems",
|
|
||||||
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
|
||||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
|
||||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
|
||||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
|
||||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
|
||||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||||
@@ -251,9 +209,6 @@
|
|||||||
"\"": ["vim::PushOperator", "Register"],
|
"\"": ["vim::PushOperator", "Register"],
|
||||||
"g q": ["vim::PushOperator", "Rewrap"],
|
"g q": ["vim::PushOperator", "Rewrap"],
|
||||||
"g w": ["vim::PushOperator", "Rewrap"],
|
"g w": ["vim::PushOperator", "Rewrap"],
|
||||||
"q": "vim::ToggleRecord",
|
|
||||||
"shift-q": "vim::ReplayLastRecording",
|
|
||||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
|
||||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||||
"insert": "vim::InsertBefore",
|
"insert": "vim::InsertBefore",
|
||||||
@@ -279,7 +234,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
":": "vim::VisualCommand",
|
":": "vim::VisualCommand",
|
||||||
"u": "vim::ConvertToLowerCase",
|
"u": "vim::ConvertToLowerCase",
|
||||||
"U": "vim::ConvertToUpperCase",
|
"shift-u": "vim::ConvertToUpperCase",
|
||||||
"o": "vim::OtherEnd",
|
"o": "vim::OtherEnd",
|
||||||
"shift-o": "vim::OtherEnd",
|
"shift-o": "vim::OtherEnd",
|
||||||
"d": "vim::VisualDelete",
|
"d": "vim::VisualDelete",
|
||||||
@@ -303,8 +258,8 @@
|
|||||||
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
||||||
"shift-i": "vim::InsertBefore",
|
"shift-i": "vim::InsertBefore",
|
||||||
"shift-a": "vim::InsertAfter",
|
"shift-a": "vim::InsertAfter",
|
||||||
"g I": "vim::VisualInsertFirstNonWhiteSpace",
|
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
|
||||||
"g A": "vim::VisualInsertEndOfLine",
|
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||||
"shift-j": "vim::JoinLines",
|
"shift-j": "vim::JoinLines",
|
||||||
"r": ["vim::PushOperator", "Replace"],
|
"r": ["vim::PushOperator", "Replace"],
|
||||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||||
@@ -339,6 +294,10 @@
|
|||||||
"ctrl-t": "vim::Indent",
|
"ctrl-t": "vim::Indent",
|
||||||
"ctrl-d": "vim::Outdent",
|
"ctrl-d": "vim::Outdent",
|
||||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||||
|
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||||
|
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||||
|
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||||
|
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||||
"ctrl-r": ["vim::PushOperator", "Register"],
|
"ctrl-r": ["vim::PushOperator", "Register"],
|
||||||
"insert": "vim::ToggleReplace"
|
"insert": "vim::ToggleReplace"
|
||||||
}
|
}
|
||||||
@@ -357,6 +316,10 @@
|
|||||||
"ctrl-c": "vim::NormalBefore",
|
"ctrl-c": "vim::NormalBefore",
|
||||||
"ctrl-[": "vim::NormalBefore",
|
"ctrl-[": "vim::NormalBefore",
|
||||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||||
|
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||||
|
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||||
|
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
|
||||||
|
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
|
||||||
"backspace": "vim::UndoReplace",
|
"backspace": "vim::UndoReplace",
|
||||||
"tab": "vim::Tab",
|
"tab": "vim::Tab",
|
||||||
"enter": "vim::Enter",
|
"enter": "vim::Enter",
|
||||||
@@ -371,7 +334,9 @@
|
|||||||
"escape": "vim::ClearOperators",
|
"escape": "vim::ClearOperators",
|
||||||
"ctrl-c": "vim::ClearOperators",
|
"ctrl-c": "vim::ClearOperators",
|
||||||
"ctrl-[": "vim::ClearOperators",
|
"ctrl-[": "vim::ClearOperators",
|
||||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }]
|
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||||
|
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||||
|
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -399,12 +364,14 @@
|
|||||||
"b": "vim::Parentheses",
|
"b": "vim::Parentheses",
|
||||||
"[": "vim::SquareBrackets",
|
"[": "vim::SquareBrackets",
|
||||||
"]": "vim::SquareBrackets",
|
"]": "vim::SquareBrackets",
|
||||||
|
"r": "vim::SquareBrackets",
|
||||||
"{": "vim::CurlyBrackets",
|
"{": "vim::CurlyBrackets",
|
||||||
"}": "vim::CurlyBrackets",
|
"}": "vim::CurlyBrackets",
|
||||||
"shift-b": "vim::CurlyBrackets",
|
"shift-b": "vim::CurlyBrackets",
|
||||||
"<": "vim::AngleBrackets",
|
"<": "vim::AngleBrackets",
|
||||||
">": "vim::AngleBrackets",
|
">": "vim::AngleBrackets",
|
||||||
"a": "vim::Argument"
|
"a": "vim::AngleBrackets",
|
||||||
|
"g": "vim::Argument"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -485,6 +452,49 @@
|
|||||||
"c": "vim::CurrentLine"
|
"c": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "vim_mode == literal",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
|
||||||
|
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
|
||||||
|
"ctrl-b": ["vim::Literal", ["ctrl-b", "\u0002"]],
|
||||||
|
"ctrl-c": ["vim::Literal", ["ctrl-c", "\u0003"]],
|
||||||
|
"ctrl-d": ["vim::Literal", ["ctrl-d", "\u0004"]],
|
||||||
|
"ctrl-e": ["vim::Literal", ["ctrl-e", "\u0005"]],
|
||||||
|
"ctrl-f": ["vim::Literal", ["ctrl-f", "\u0006"]],
|
||||||
|
"ctrl-g": ["vim::Literal", ["ctrl-g", "\u0007"]],
|
||||||
|
"ctrl-h": ["vim::Literal", ["ctrl-h", "\u0008"]],
|
||||||
|
"ctrl-i": ["vim::Literal", ["ctrl-i", "\u0009"]],
|
||||||
|
"ctrl-j": ["vim::Literal", ["ctrl-j", "\u000A"]],
|
||||||
|
"ctrl-k": ["vim::Literal", ["ctrl-k", "\u000B"]],
|
||||||
|
"ctrl-l": ["vim::Literal", ["ctrl-l", "\u000C"]],
|
||||||
|
"ctrl-m": ["vim::Literal", ["ctrl-m", "\u000D"]],
|
||||||
|
"ctrl-n": ["vim::Literal", ["ctrl-n", "\u000E"]],
|
||||||
|
"ctrl-o": ["vim::Literal", ["ctrl-o", "\u000F"]],
|
||||||
|
"ctrl-p": ["vim::Literal", ["ctrl-p", "\u0010"]],
|
||||||
|
"ctrl-q": ["vim::Literal", ["ctrl-q", "\u0011"]],
|
||||||
|
"ctrl-r": ["vim::Literal", ["ctrl-r", "\u0012"]],
|
||||||
|
"ctrl-s": ["vim::Literal", ["ctrl-s", "\u0013"]],
|
||||||
|
"ctrl-t": ["vim::Literal", ["ctrl-t", "\u0014"]],
|
||||||
|
"ctrl-u": ["vim::Literal", ["ctrl-u", "\u0015"]],
|
||||||
|
"ctrl-v": ["vim::Literal", ["ctrl-v", "\u0016"]],
|
||||||
|
"ctrl-w": ["vim::Literal", ["ctrl-w", "\u0017"]],
|
||||||
|
"ctrl-x": ["vim::Literal", ["ctrl-x", "\u0018"]],
|
||||||
|
"ctrl-y": ["vim::Literal", ["ctrl-y", "\u0019"]],
|
||||||
|
"ctrl-z": ["vim::Literal", ["ctrl-z", "\u001A"]],
|
||||||
|
"ctrl-[": ["vim::Literal", ["ctrl-[", "\u001B"]],
|
||||||
|
"ctrl-\\": ["vim::Literal", ["ctrl-\\", "\u001C"]],
|
||||||
|
"ctrl-]": ["vim::Literal", ["ctrl-]", "\u001D"]],
|
||||||
|
"ctrl-^": ["vim::Literal", ["ctrl-^", "\u001E"]],
|
||||||
|
"ctrl-_": ["vim::Literal", ["ctrl-_", "\u001F"]],
|
||||||
|
"escape": ["vim::Literal", ["escape", "\u001B"]],
|
||||||
|
"enter": ["vim::Literal", ["enter", "\u000D"]],
|
||||||
|
"tab": ["vim::Literal", ["tab", "\u0009"]],
|
||||||
|
// zed extensions:
|
||||||
|
"backspace": ["vim::Literal", ["backspace", "\u0008"]],
|
||||||
|
"delete": ["vim::Literal", ["delete", "\u007F"]]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && !in_replace",
|
"context": "BufferSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -493,7 +503,57 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "EmptyPane || SharedScreen",
|
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||||
|
"bindings": {
|
||||||
|
// window related commands (ctrl-w X)
|
||||||
|
"ctrl-w": null,
|
||||||
|
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||||
|
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||||
|
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||||
|
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||||
|
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||||
|
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||||
|
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||||
|
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||||
|
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||||
|
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||||
|
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||||
|
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||||
|
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||||
|
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||||
|
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||||
|
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||||
|
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
|
||||||
|
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
|
||||||
|
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
|
||||||
|
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
|
||||||
|
"ctrl-w g t": "pane::ActivateNextItem",
|
||||||
|
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
||||||
|
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
||||||
|
"ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
|
||||||
|
"ctrl-w w": "workspace::ActivateNextPane",
|
||||||
|
"ctrl-w ctrl-w": "workspace::ActivateNextPane",
|
||||||
|
"ctrl-w p": "workspace::ActivatePreviousPane",
|
||||||
|
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
|
||||||
|
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
|
||||||
|
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
|
||||||
|
"ctrl-w v": "pane::SplitVertical",
|
||||||
|
"ctrl-w ctrl-v": "pane::SplitVertical",
|
||||||
|
"ctrl-w s": "pane::SplitHorizontal",
|
||||||
|
"ctrl-w shift-s": "pane::SplitHorizontal",
|
||||||
|
"ctrl-w ctrl-s": "pane::SplitHorizontal",
|
||||||
|
"ctrl-w c": "pane::CloseAllItems",
|
||||||
|
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
||||||
|
"ctrl-w q": "pane::CloseAllItems",
|
||||||
|
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||||
|
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
|
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
|
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||||
|
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
":": "command_palette::Toggle",
|
":": "command_palette::Toggle",
|
||||||
"g /": "pane::DeploySearch"
|
"g /": "pane::DeploySearch"
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ origin: (f64, f64),
|
|||||||
|
|
||||||
<edit>
|
<edit>
|
||||||
<path>src/shapes/rectangle.rs</path>
|
<path>src/shapes/rectangle.rs</path>
|
||||||
<description>Update the Rectangle's new function to take an origin parameter</description>
|
|
||||||
<operation>update</operation>
|
<operation>update</operation>
|
||||||
<old_text>
|
<old_text>
|
||||||
fn new(width: f64, height: f64) -> Self {
|
fn new(width: f64, height: f64) -> Self {
|
||||||
@@ -117,7 +116,6 @@ pub struct Circle {
|
|||||||
|
|
||||||
<edit>
|
<edit>
|
||||||
<path>src/shapes/circle.rs</path>
|
<path>src/shapes/circle.rs</path>
|
||||||
<description>Update the Circle's new function to take an origin parameter</description>
|
|
||||||
<operation>update</operation>
|
<operation>update</operation>
|
||||||
<old_text>
|
<old_text>
|
||||||
fn new(radius: f64) -> Self {
|
fn new(radius: f64) -> Self {
|
||||||
@@ -134,7 +132,6 @@ fn new(origin: (f64, f64), radius: f64) -> Self {
|
|||||||
|
|
||||||
<edit>
|
<edit>
|
||||||
<path>src/shapes/rectangle.rs</path>
|
<path>src/shapes/rectangle.rs</path>
|
||||||
<description>Add an import for the std::fmt module</description>
|
|
||||||
<operation>insert_before</operation>
|
<operation>insert_before</operation>
|
||||||
<old_text>
|
<old_text>
|
||||||
struct Rectangle {
|
struct Rectangle {
|
||||||
@@ -147,7 +144,10 @@ use std::fmt;
|
|||||||
|
|
||||||
<edit>
|
<edit>
|
||||||
<path>src/shapes/rectangle.rs</path>
|
<path>src/shapes/rectangle.rs</path>
|
||||||
<description>Add a Display implementation for Rectangle</description>
|
<description>
|
||||||
|
Add a manual Display implementation for Rectangle.
|
||||||
|
Currently, this is the same as a derived Display implementation.
|
||||||
|
</description>
|
||||||
<operation>insert_after</operation>
|
<operation>insert_after</operation>
|
||||||
<old_text>
|
<old_text>
|
||||||
Rectangle { width, height }
|
Rectangle { width, height }
|
||||||
@@ -169,7 +169,6 @@ impl fmt::Display for Rectangle {
|
|||||||
|
|
||||||
<edit>
|
<edit>
|
||||||
<path>src/shapes/circle.rs</path>
|
<path>src/shapes/circle.rs</path>
|
||||||
<description>Add an import for the `std::fmt` module</description>
|
|
||||||
<operation>insert_before</operation>
|
<operation>insert_before</operation>
|
||||||
<old_text>
|
<old_text>
|
||||||
struct Circle {
|
struct Circle {
|
||||||
@@ -181,7 +180,6 @@ use std::fmt;
|
|||||||
|
|
||||||
<edit>
|
<edit>
|
||||||
<path>src/shapes/circle.rs</path>
|
<path>src/shapes/circle.rs</path>
|
||||||
<description>Add a Display implementation for Circle</description>
|
|
||||||
<operation>insert_after</operation>
|
<operation>insert_after</operation>
|
||||||
<old_text>
|
<old_text>
|
||||||
Circle { radius }
|
Circle { radius }
|
||||||
@@ -68,9 +68,17 @@
|
|||||||
"ui_font_size": 16,
|
"ui_font_size": 16,
|
||||||
// How much to fade out unused code.
|
// How much to fade out unused code.
|
||||||
"unnecessary_code_fade": 0.3,
|
"unnecessary_code_fade": 0.3,
|
||||||
// The factor to grow the active pane by. Defaults to 1.0
|
// Active pane styling settings.
|
||||||
// which gives the same size as all other panes.
|
"active_pane_modifiers": {
|
||||||
"active_pane_magnification": 1.0,
|
// 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"
|
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||||
"pane_split_direction_horizontal": "up",
|
"pane_split_direction_horizontal": "up",
|
||||||
// The direction that you want to split panes horizontally. Defaults to "left"
|
// The direction that you want to split panes horizontally. Defaults to "left"
|
||||||
@@ -152,7 +160,7 @@
|
|||||||
"show_signature_help_after_edits": true,
|
"show_signature_help_after_edits": true,
|
||||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
// 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.
|
// additional guides as specified by the 'wrap_guides' setting.
|
||||||
"show_wrap_guides": true,
|
"show_wrap_guides": true,
|
||||||
// Character counts at which to show wrap guides in the editor.
|
// Character counts at which to show wrap guides in the editor.
|
||||||
@@ -174,6 +182,8 @@
|
|||||||
// bracket, brace, single or double quote characters.
|
// bracket, brace, single or double quote characters.
|
||||||
// For example, when you select text and type (, Zed will surround the text with ().
|
// For example, when you select text and type (, Zed will surround the text with ().
|
||||||
"use_auto_surround": true,
|
"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.
|
// Controls how the editor handles the autoclosed characters.
|
||||||
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
||||||
// happen only for auto-inserted characters.
|
// happen only for auto-inserted characters.
|
||||||
@@ -369,6 +379,17 @@
|
|||||||
/// 5. Never show the scrollbar:
|
/// 5. Never show the scrollbar:
|
||||||
/// "never"
|
/// "never"
|
||||||
"show": null
|
"show": null
|
||||||
|
},
|
||||||
|
// Settings related to indent guides in the project panel.
|
||||||
|
"indent_guides": {
|
||||||
|
// When to show indent guides in the project panel.
|
||||||
|
// This setting can take two values:
|
||||||
|
//
|
||||||
|
// 1. Always show indent guides:
|
||||||
|
// "always"
|
||||||
|
// 2. Never show indent guides:
|
||||||
|
// "never"
|
||||||
|
"show": "always"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outline_panel": {
|
"outline_panel": {
|
||||||
@@ -392,7 +413,35 @@
|
|||||||
"auto_reveal_entries": true,
|
"auto_reveal_entries": true,
|
||||||
/// Whether to fold directories automatically
|
/// Whether to fold directories automatically
|
||||||
/// when a directory has only one directory inside.
|
/// when a directory has only one directory inside.
|
||||||
"auto_fold_dirs": true
|
"auto_fold_dirs": true,
|
||||||
|
// Settings related to indent guides in the outline panel.
|
||||||
|
"indent_guides": {
|
||||||
|
// When to show indent guides in the outline panel.
|
||||||
|
// This setting can take two values:
|
||||||
|
//
|
||||||
|
// 1. Always show indent guides:
|
||||||
|
// "always"
|
||||||
|
// 2. Never show indent guides:
|
||||||
|
// "never"
|
||||||
|
"show": "always"
|
||||||
|
},
|
||||||
|
/// Scrollbar-related settings
|
||||||
|
"scrollbar": {
|
||||||
|
/// When to show the scrollbar in the project panel.
|
||||||
|
/// This setting can take four values:
|
||||||
|
///
|
||||||
|
/// 1. null (default): Inherit editor settings
|
||||||
|
/// 2. Show the scrollbar if there's important information or
|
||||||
|
/// follow the system's configured behavior (default):
|
||||||
|
/// "auto"
|
||||||
|
/// 3. Match the system's configured behavior:
|
||||||
|
/// "system"
|
||||||
|
/// 4. Always show the scrollbar:
|
||||||
|
/// "always"
|
||||||
|
/// 5. Never show the scrollbar:
|
||||||
|
/// "never"
|
||||||
|
"show": null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"collaboration_panel": {
|
"collaboration_panel": {
|
||||||
// Whether to show the collaboration panel button in the status bar.
|
// Whether to show the collaboration panel button in the status bar.
|
||||||
@@ -436,7 +485,7 @@
|
|||||||
"default_width": 640,
|
"default_width": 640,
|
||||||
// Default height when the assistant is docked to the bottom.
|
// Default height when the assistant is docked to the bottom.
|
||||||
"default_height": 320,
|
"default_height": 320,
|
||||||
// The default model to use when creating new contexts.
|
// The default model to use when creating new chats.
|
||||||
"default_model": {
|
"default_model": {
|
||||||
// The provider to use.
|
// The provider to use.
|
||||||
"provider": "zed.dev",
|
"provider": "zed.dev",
|
||||||
@@ -613,6 +662,12 @@
|
|||||||
// Sets a delay after which the inline blame information is shown.
|
// Sets a delay after which the inline blame information is shown.
|
||||||
// Delay is restarted with every cursor movement.
|
// Delay is restarted with every cursor movement.
|
||||||
// "delay_ms": 600
|
// "delay_ms": 600
|
||||||
|
//
|
||||||
|
// Whether or not do display the git commit summary on the same line.
|
||||||
|
// "show_commit_summary": false
|
||||||
|
//
|
||||||
|
// The minimum column number to show the inline blame information at
|
||||||
|
// "min_column": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
||||||
@@ -803,7 +858,7 @@
|
|||||||
/// You can override this to use a version of node that is not in $PATH with:
|
/// You can override this to use a version of node that is not in $PATH with:
|
||||||
/// {
|
/// {
|
||||||
/// "node": {
|
/// "node": {
|
||||||
/// "node_path": "/path/to/node"
|
/// "path": "/path/to/node"
|
||||||
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
@@ -1099,13 +1154,13 @@
|
|||||||
// }
|
// }
|
||||||
"command_aliases": {},
|
"command_aliases": {},
|
||||||
// ssh_connections is an array of ssh connections.
|
// ssh_connections is an array of ssh connections.
|
||||||
// By default this setting is null, which disables the direct ssh connection support.
|
|
||||||
// You can configure these from `project: Open Remote` in the command palette.
|
// You can configure these from `project: Open Remote` in the command palette.
|
||||||
// Zed's ssh support will pull configuration from your ~/.ssh too.
|
// Zed's ssh support will pull configuration from your ~/.ssh too.
|
||||||
// Examples:
|
// Examples:
|
||||||
// [
|
// [
|
||||||
// {
|
// {
|
||||||
// "host": "example-box",
|
// "host": "example-box",
|
||||||
|
// // "port": 22, "username": "test", "args": ["-i", "/home/user/.ssh/id_rsa"]
|
||||||
// "projects": [
|
// "projects": [
|
||||||
// {
|
// {
|
||||||
// "paths": ["/home/user/code/zed"]
|
// "paths": ["/home/user/code/zed"]
|
||||||
@@ -1113,7 +1168,7 @@
|
|||||||
// ]
|
// ]
|
||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
"ssh_connections": null,
|
"ssh_connections": [],
|
||||||
// Configures the Context Server Protocol binaries
|
// Configures the Context Server Protocol binaries
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"allow_concurrent_runs": false,
|
"allow_concurrent_runs": false,
|
||||||
// What to do with the terminal pane and tab, after the command was started:
|
// What to do with the terminal pane and tab, after the command was started:
|
||||||
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
||||||
|
// * `no_focus` — always show the terminal pane, add/reuse the task's tab there, but don't focus it
|
||||||
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
// What to do with the terminal pane and tab, after the command had finished:
|
// What to do with the terminal pane and tab, after the command had finished:
|
||||||
|
|||||||
@@ -27,15 +27,15 @@
|
|||||||
"ghost_element.active": "#454a56ff",
|
"ghost_element.active": "#454a56ff",
|
||||||
"ghost_element.selected": "#454a56ff",
|
"ghost_element.selected": "#454a56ff",
|
||||||
"ghost_element.disabled": "#2e343eff",
|
"ghost_element.disabled": "#2e343eff",
|
||||||
"text": "#c8ccd4ff",
|
"text": "#dce0e5ff",
|
||||||
"text.muted": "#838994ff",
|
"text.muted": "#a9afbcff",
|
||||||
"text.placeholder": "#696B77ff",
|
"text.placeholder": "#878a98ff",
|
||||||
"text.disabled": "#696B77ff",
|
"text.disabled": "#878a98ff",
|
||||||
"text.accent": "#74ade8ff",
|
"text.accent": "#74ade8ff",
|
||||||
"icon": "#c8ccd4ff",
|
"icon": "#dce0e5ff",
|
||||||
"icon.muted": "#838994ff",
|
"icon.muted": "#a9afbcff",
|
||||||
"icon.disabled": "#696B77ff",
|
"icon.disabled": "#878a98ff",
|
||||||
"icon.placeholder": "#838994ff",
|
"icon.placeholder": "#a9afbcff",
|
||||||
"icon.accent": "#74ade8ff",
|
"icon.accent": "#74ade8ff",
|
||||||
"status_bar.background": "#3b414dff",
|
"status_bar.background": "#3b414dff",
|
||||||
"title_bar.background": "#3b414dff",
|
"title_bar.background": "#3b414dff",
|
||||||
@@ -60,19 +60,19 @@
|
|||||||
"editor.active_line.background": "#2f343ebf",
|
"editor.active_line.background": "#2f343ebf",
|
||||||
"editor.highlighted_line.background": "#2f343eff",
|
"editor.highlighted_line.background": "#2f343eff",
|
||||||
"editor.line_number": "#c8ccd459",
|
"editor.line_number": "#c8ccd459",
|
||||||
"editor.active_line_number": "#c8ccd4ff",
|
"editor.active_line_number": "#dce0e5ff",
|
||||||
"editor.invisible": "#696B77ff",
|
"editor.invisible": "#878a98ff",
|
||||||
"editor.wrap_guide": "#c8ccd40d",
|
"editor.wrap_guide": "#c8ccd40d",
|
||||||
"editor.active_wrap_guide": "#c8ccd41a",
|
"editor.active_wrap_guide": "#c8ccd41a",
|
||||||
"editor.document_highlight.read_background": "#74ade81a",
|
"editor.document_highlight.read_background": "#74ade81a",
|
||||||
"editor.document_highlight.write_background": "#555a6366",
|
"editor.document_highlight.write_background": "#555a6366",
|
||||||
"terminal.background": "#282c33ff",
|
"terminal.background": "#282c33ff",
|
||||||
"terminal.foreground": "#c8ccd4ff",
|
"terminal.foreground": "#dce0e5ff",
|
||||||
"terminal.bright_foreground": "#c8ccd4ff",
|
"terminal.bright_foreground": "#dce0e5ff",
|
||||||
"terminal.dim_foreground": "#282c33ff",
|
"terminal.dim_foreground": "#282c33ff",
|
||||||
"terminal.ansi.black": "#282c33ff",
|
"terminal.ansi.black": "#282c33ff",
|
||||||
"terminal.ansi.bright_black": "#525561ff",
|
"terminal.ansi.bright_black": "#525561ff",
|
||||||
"terminal.ansi.dim_black": "#c8ccd4ff",
|
"terminal.ansi.dim_black": "#dce0e5ff",
|
||||||
"terminal.ansi.red": "#d07277ff",
|
"terminal.ansi.red": "#d07277ff",
|
||||||
"terminal.ansi.bright_red": "#673a3cff",
|
"terminal.ansi.bright_red": "#673a3cff",
|
||||||
"terminal.ansi.dim_red": "#eab7b9ff",
|
"terminal.ansi.dim_red": "#eab7b9ff",
|
||||||
@@ -91,8 +91,8 @@
|
|||||||
"terminal.ansi.cyan": "#6eb4bfff",
|
"terminal.ansi.cyan": "#6eb4bfff",
|
||||||
"terminal.ansi.bright_cyan": "#3a565bff",
|
"terminal.ansi.bright_cyan": "#3a565bff",
|
||||||
"terminal.ansi.dim_cyan": "#b9d9dfff",
|
"terminal.ansi.dim_cyan": "#b9d9dfff",
|
||||||
"terminal.ansi.white": "#c8ccd4ff",
|
"terminal.ansi.white": "#dce0e5ff",
|
||||||
"terminal.ansi.bright_white": "#c8ccd4ff",
|
"terminal.ansi.bright_white": "#dce0e5ff",
|
||||||
"terminal.ansi.dim_white": "#575d65ff",
|
"terminal.ansi.dim_white": "#575d65ff",
|
||||||
"link_text.hover": "#74ade8ff",
|
"link_text.hover": "#74ade8ff",
|
||||||
"conflict": "#dec184ff",
|
"conflict": "#dec184ff",
|
||||||
@@ -107,14 +107,14 @@
|
|||||||
"error": "#d07277ff",
|
"error": "#d07277ff",
|
||||||
"error.background": "#d072771a",
|
"error.background": "#d072771a",
|
||||||
"error.border": "#4c2b2cff",
|
"error.border": "#4c2b2cff",
|
||||||
"hidden": "#696B77ff",
|
"hidden": "#878a98ff",
|
||||||
"hidden.background": "#696B771a",
|
"hidden.background": "#696b771a",
|
||||||
"hidden.border": "#414754ff",
|
"hidden.border": "#414754ff",
|
||||||
"hint": "#5a6f89ff",
|
"hint": "#788ca6ff",
|
||||||
"hint.background": "#5a6f891a",
|
"hint.background": "#5a6f891a",
|
||||||
"hint.border": "#293b5bff",
|
"hint.border": "#293b5bff",
|
||||||
"ignored": "#696B77ff",
|
"ignored": "#878a98ff",
|
||||||
"ignored.background": "#696B771a",
|
"ignored.background": "#696b771a",
|
||||||
"ignored.border": "#464b57ff",
|
"ignored.border": "#464b57ff",
|
||||||
"info": "#74ade8ff",
|
"info": "#74ade8ff",
|
||||||
"info.background": "#74ade81a",
|
"info.background": "#74ade81a",
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"success": "#a1c181ff",
|
"success": "#a1c181ff",
|
||||||
"success.background": "#a1c1811a",
|
"success.background": "#a1c1811a",
|
||||||
"success.border": "#38482fff",
|
"success.border": "#38482fff",
|
||||||
"unreachable": "#838994ff",
|
"unreachable": "#a9afbcff",
|
||||||
"unreachable.background": "#8389941a",
|
"unreachable.background": "#8389941a",
|
||||||
"unreachable.border": "#464b57ff",
|
"unreachable.border": "#464b57ff",
|
||||||
"warning": "#dec184ff",
|
"warning": "#dec184ff",
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"embedded": {
|
"embedded": {
|
||||||
"color": "#c8ccd4ff",
|
"color": "#dce0e5ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"hint": {
|
"hint": {
|
||||||
"color": "#5a6f89ff",
|
"color": "#788ca6ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": 700
|
||||||
},
|
},
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"preproc": {
|
"preproc": {
|
||||||
"color": "#c8ccd4ff",
|
"color": "#dce0e5ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -361,7 +361,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"variable": {
|
"variable": {
|
||||||
"color": "#c8ccd4ff",
|
"color": "#dce0e5ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -402,15 +402,15 @@
|
|||||||
"ghost_element.active": "#cacacaff",
|
"ghost_element.active": "#cacacaff",
|
||||||
"ghost_element.selected": "#cacacaff",
|
"ghost_element.selected": "#cacacaff",
|
||||||
"ghost_element.disabled": "#ebebecff",
|
"ghost_element.disabled": "#ebebecff",
|
||||||
"text": "#383a41ff",
|
"text": "#242529ff",
|
||||||
"text.muted": "#7e8087ff",
|
"text.muted": "#58585aff",
|
||||||
"text.placeholder": "#a1a1a3ff",
|
"text.placeholder": "#7e8086ff",
|
||||||
"text.disabled": "#a1a1a3ff",
|
"text.disabled": "#7e8086ff",
|
||||||
"text.accent": "#5c78e2ff",
|
"text.accent": "#5c78e2ff",
|
||||||
"icon": "#383a41ff",
|
"icon": "#242529ff",
|
||||||
"icon.muted": "#7e8087ff",
|
"icon.muted": "#58585aff",
|
||||||
"icon.disabled": "#a1a1a3ff",
|
"icon.disabled": "#7e8086ff",
|
||||||
"icon.placeholder": "#7e8087ff",
|
"icon.placeholder": "#58585aff",
|
||||||
"icon.accent": "#5c78e2ff",
|
"icon.accent": "#5c78e2ff",
|
||||||
"status_bar.background": "#dcdcddff",
|
"status_bar.background": "#dcdcddff",
|
||||||
"title_bar.background": "#dcdcddff",
|
"title_bar.background": "#dcdcddff",
|
||||||
@@ -428,26 +428,26 @@
|
|||||||
"scrollbar.thumb.border": "#dfdfe0ff",
|
"scrollbar.thumb.border": "#dfdfe0ff",
|
||||||
"scrollbar.track.background": "#00000000",
|
"scrollbar.track.background": "#00000000",
|
||||||
"scrollbar.track.border": "#eeeeeeff",
|
"scrollbar.track.border": "#eeeeeeff",
|
||||||
"editor.foreground": "#383a41ff",
|
"editor.foreground": "#242529ff",
|
||||||
"editor.background": "#fafafaff",
|
"editor.background": "#fafafaff",
|
||||||
"editor.gutter.background": "#fafafaff",
|
"editor.gutter.background": "#fafafaff",
|
||||||
"editor.subheader.background": "#ebebecff",
|
"editor.subheader.background": "#ebebecff",
|
||||||
"editor.active_line.background": "#ebebecbf",
|
"editor.active_line.background": "#ebebecbf",
|
||||||
"editor.highlighted_line.background": "#ebebecff",
|
"editor.highlighted_line.background": "#ebebecff",
|
||||||
"editor.line_number": "#383a4159",
|
"editor.line_number": "#383a4159",
|
||||||
"editor.active_line_number": "#383a41ff",
|
"editor.active_line_number": "#242529ff",
|
||||||
"editor.invisible": "#a3a3a4ff",
|
"editor.invisible": "#a3a3a4ff",
|
||||||
"editor.wrap_guide": "#383a410d",
|
"editor.wrap_guide": "#383a410d",
|
||||||
"editor.active_wrap_guide": "#383a411a",
|
"editor.active_wrap_guide": "#383a411a",
|
||||||
"editor.document_highlight.read_background": "#5c78e21a",
|
"editor.document_highlight.read_background": "#5c78e21a",
|
||||||
"editor.document_highlight.write_background": "#a3a3a466",
|
"editor.document_highlight.write_background": "#a3a3a466",
|
||||||
"terminal.background": "#fafafaff",
|
"terminal.background": "#fafafaff",
|
||||||
"terminal.foreground": "#383a41ff",
|
"terminal.foreground": "#242529ff",
|
||||||
"terminal.bright_foreground": "#383a41ff",
|
"terminal.bright_foreground": "#242529ff",
|
||||||
"terminal.dim_foreground": "#fafafaff",
|
"terminal.dim_foreground": "#fafafaff",
|
||||||
"terminal.ansi.black": "#fafafaff",
|
"terminal.ansi.black": "#fafafaff",
|
||||||
"terminal.ansi.bright_black": "#aaaaaaff",
|
"terminal.ansi.bright_black": "#aaaaaaff",
|
||||||
"terminal.ansi.dim_black": "#383a41ff",
|
"terminal.ansi.dim_black": "#242529ff",
|
||||||
"terminal.ansi.red": "#d36151ff",
|
"terminal.ansi.red": "#d36151ff",
|
||||||
"terminal.ansi.bright_red": "#f0b0a4ff",
|
"terminal.ansi.bright_red": "#f0b0a4ff",
|
||||||
"terminal.ansi.dim_red": "#6f312aff",
|
"terminal.ansi.dim_red": "#6f312aff",
|
||||||
@@ -466,11 +466,11 @@
|
|||||||
"terminal.ansi.cyan": "#3a82b7ff",
|
"terminal.ansi.cyan": "#3a82b7ff",
|
||||||
"terminal.ansi.bright_cyan": "#a3bedaff",
|
"terminal.ansi.bright_cyan": "#a3bedaff",
|
||||||
"terminal.ansi.dim_cyan": "#254058ff",
|
"terminal.ansi.dim_cyan": "#254058ff",
|
||||||
"terminal.ansi.white": "#383a41ff",
|
"terminal.ansi.white": "#242529ff",
|
||||||
"terminal.ansi.bright_white": "#383a41ff",
|
"terminal.ansi.bright_white": "#242529ff",
|
||||||
"terminal.ansi.dim_white": "#97979aff",
|
"terminal.ansi.dim_white": "#97979aff",
|
||||||
"link_text.hover": "#5c78e2ff",
|
"link_text.hover": "#5c78e2ff",
|
||||||
"conflict": "#dec184ff",
|
"conflict": "#a48819ff",
|
||||||
"conflict.background": "#faf2e6ff",
|
"conflict.background": "#faf2e6ff",
|
||||||
"conflict.border": "#f4e7d1ff",
|
"conflict.border": "#f4e7d1ff",
|
||||||
"created": "#669f59ff",
|
"created": "#669f59ff",
|
||||||
@@ -482,19 +482,19 @@
|
|||||||
"error": "#d36151ff",
|
"error": "#d36151ff",
|
||||||
"error.background": "#fbdfd9ff",
|
"error.background": "#fbdfd9ff",
|
||||||
"error.border": "#f6c6bdff",
|
"error.border": "#f6c6bdff",
|
||||||
"hidden": "#a1a1a3ff",
|
"hidden": "#7e8086ff",
|
||||||
"hidden.background": "#dcdcddff",
|
"hidden.background": "#dcdcddff",
|
||||||
"hidden.border": "#d3d3d4ff",
|
"hidden.border": "#d3d3d4ff",
|
||||||
"hint": "#9294beff",
|
"hint": "#7274a7ff",
|
||||||
"hint.background": "#e2e2faff",
|
"hint.background": "#e2e2faff",
|
||||||
"hint.border": "#cbcdf6ff",
|
"hint.border": "#cbcdf6ff",
|
||||||
"ignored": "#a1a1a3ff",
|
"ignored": "#7e8086ff",
|
||||||
"ignored.background": "#dcdcddff",
|
"ignored.background": "#dcdcddff",
|
||||||
"ignored.border": "#c9c9caff",
|
"ignored.border": "#c9c9caff",
|
||||||
"info": "#5c78e2ff",
|
"info": "#5c78e2ff",
|
||||||
"info.background": "#e2e2faff",
|
"info.background": "#e2e2faff",
|
||||||
"info.border": "#cbcdf6ff",
|
"info.border": "#cbcdf6ff",
|
||||||
"modified": "#a47a23ff",
|
"modified": "#a48819ff",
|
||||||
"modified.background": "#faf2e6ff",
|
"modified.background": "#faf2e6ff",
|
||||||
"modified.border": "#f4e7d1ff",
|
"modified.border": "#f4e7d1ff",
|
||||||
"predictive": "#9b9ec6ff",
|
"predictive": "#9b9ec6ff",
|
||||||
@@ -506,10 +506,10 @@
|
|||||||
"success": "#669f59ff",
|
"success": "#669f59ff",
|
||||||
"success.background": "#dfeadbff",
|
"success.background": "#dfeadbff",
|
||||||
"success.border": "#c8dcc1ff",
|
"success.border": "#c8dcc1ff",
|
||||||
"unreachable": "#7e8087ff",
|
"unreachable": "#58585aff",
|
||||||
"unreachable.background": "#dcdcddff",
|
"unreachable.background": "#dcdcddff",
|
||||||
"unreachable.border": "#c9c9caff",
|
"unreachable.border": "#c9c9caff",
|
||||||
"warning": "#dec184ff",
|
"warning": "#a48819ff",
|
||||||
"warning.background": "#faf2e6ff",
|
"warning.background": "#faf2e6ff",
|
||||||
"warning.border": "#f4e7d1ff",
|
"warning.border": "#f4e7d1ff",
|
||||||
"players": [
|
"players": [
|
||||||
@@ -544,7 +544,7 @@
|
|||||||
"selection": "#d361513d"
|
"selection": "#d361513d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cursor": "#dec184ff",
|
"cursor": "#a48819ff",
|
||||||
"background": "#dec184ff",
|
"background": "#dec184ff",
|
||||||
"selection": "#dec1843d"
|
"selection": "#dec1843d"
|
||||||
},
|
},
|
||||||
@@ -586,7 +586,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"embedded": {
|
"embedded": {
|
||||||
"color": "#383a41ff",
|
"color": "#242529ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -611,7 +611,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"hint": {
|
"hint": {
|
||||||
"color": "#9294beff",
|
"color": "#7274a7ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": 700
|
||||||
},
|
},
|
||||||
@@ -651,12 +651,12 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"preproc": {
|
"preproc": {
|
||||||
"color": "#383a41ff",
|
"color": "#242529ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"primary": {
|
"primary": {
|
||||||
"color": "#383a41ff",
|
"color": "#242529ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"punctuation": {
|
"punctuation": {
|
||||||
"color": "#383a41ff",
|
"color": "#242529ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -736,7 +736,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"variable": {
|
"variable": {
|
||||||
"color": "#383a41ff",
|
"color": "#242529ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -519,8 +519,8 @@
|
|||||||
"selection": "#d337813d"
|
"selection": "#d337813d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cursor": "#cb4b17ff",
|
"cursor": "#cb4b16ff",
|
||||||
"background": "#cb4b17ff",
|
"background": "#cb4b16ff",
|
||||||
"selection": "#cb4b173d"
|
"selection": "#cb4b173d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
"font_weight": 700
|
"font_weight": 700
|
||||||
},
|
},
|
||||||
"enum": {
|
"enum": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -621,7 +621,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"link_text": {
|
"link_text": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": "italic",
|
"font_style": "italic",
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -636,7 +636,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"operator": {
|
"operator": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -686,7 +686,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"string": {
|
"string": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -696,17 +696,17 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"string.regex": {
|
"string.regex": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"string.special": {
|
"string.special": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"string.special.symbol": {
|
"string.special.symbol": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -716,7 +716,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"text.literal": {
|
"text.literal": {
|
||||||
"color": "#cb4b17ff",
|
"color": "#cb4b16ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ doctest = false
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
auto_update.workspace = true
|
auto_update.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
extension.workspace = true
|
extension_host.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
|
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use extension::ExtensionStore;
|
use extension_host::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||||
@@ -13,7 +13,8 @@ use language::{
|
|||||||
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
|
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||||
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle};
|
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||||
|
use util::truncate_and_trailoff;
|
||||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||||
|
|
||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
@@ -351,7 +352,10 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||||
on_click: Some(Arc::new(|_, cx| {
|
on_click: Some(Arc::new(|indicator, cx| {
|
||||||
|
indicator.project.update(cx, |project, cx| {
|
||||||
|
project.reset_last_formatting_failure(cx);
|
||||||
|
});
|
||||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
@@ -446,6 +450,8 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
impl EventEmitter<Event> for ActivityIndicator {}
|
impl EventEmitter<Event> for ActivityIndicator {}
|
||||||
|
|
||||||
|
const MAX_MESSAGE_LEN: usize = 50;
|
||||||
|
|
||||||
impl Render for ActivityIndicator {
|
impl Render for ActivityIndicator {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let result = h_flex()
|
let result = h_flex()
|
||||||
@@ -456,6 +462,7 @@ impl Render for ActivityIndicator {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
let this = cx.view().downgrade();
|
let this = cx.view().downgrade();
|
||||||
|
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
|
||||||
result.gap_2().child(
|
result.gap_2().child(
|
||||||
PopoverMenu::new("activity-indicator-popover")
|
PopoverMenu::new("activity-indicator-popover")
|
||||||
.trigger(
|
.trigger(
|
||||||
@@ -464,7 +471,21 @@ impl Render for ActivityIndicator {
|
|||||||
.id("activity-indicator-status")
|
.id("activity-indicator-status")
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.children(content.icon)
|
.children(content.icon)
|
||||||
.child(Label::new(content.message).size(LabelSize::Small))
|
.map(|button| {
|
||||||
|
if truncate_content {
|
||||||
|
button
|
||||||
|
.child(
|
||||||
|
Label::new(truncate_and_trailoff(
|
||||||
|
&content.message,
|
||||||
|
MAX_MESSAGE_LEN,
|
||||||
|
))
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
)
|
||||||
|
.tooltip(move |cx| Tooltip::text(&content.message, cx))
|
||||||
|
} else {
|
||||||
|
button.child(Label::new(content.message).size(LabelSize::Small))
|
||||||
|
}
|
||||||
|
})
|
||||||
.when_some(content.on_click, |this, handler| {
|
.when_some(content.on_click, |this, handler| {
|
||||||
this.on_click(cx.listener(move |this, _, cx| {
|
this.on_click(cx.listener(move |this, _, cx| {
|
||||||
handler(this, cx);
|
handler(this, cx);
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ pub struct AnthropicModelCacheConfiguration {
|
|||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||||
pub enum Model {
|
pub enum Model {
|
||||||
#[default]
|
#[default]
|
||||||
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-20240620")]
|
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
|
||||||
Claude3_5Sonnet,
|
Claude3_5Sonnet,
|
||||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-20240229")]
|
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||||
Claude3Opus,
|
Claude3Opus,
|
||||||
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-20240229")]
|
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
|
||||||
Claude3Sonnet,
|
Claude3Sonnet,
|
||||||
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-20240307")]
|
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-latest")]
|
||||||
Claude3Haiku,
|
Claude3Haiku,
|
||||||
#[serde(rename = "custom")]
|
#[serde(rename = "custom")]
|
||||||
Custom {
|
Custom {
|
||||||
@@ -69,10 +69,10 @@ impl Model {
|
|||||||
|
|
||||||
pub fn id(&self) -> &str {
|
pub fn id(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
|
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||||
Model::Claude3Opus => "claude-3-opus-20240229",
|
Model::Claude3Opus => "claude-3-opus-latest",
|
||||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
Model::Claude3Sonnet => "claude-3-sonnet-latest",
|
||||||
Model::Claude3Haiku => "claude-3-haiku-20240307",
|
Model::Claude3Haiku => "claude-3-haiku-latest",
|
||||||
Self::Custom { name, .. } => name,
|
Self::Custom { name, .. } => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,24 +41,26 @@ use prompts::PromptLoadingParams;
|
|||||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{update_settings_file, Settings, SettingsStore};
|
use settings::{update_settings_file, Settings, SettingsStore};
|
||||||
use slash_command::workflow_command::WorkflowSlashCommand;
|
use slash_command::search_command::SearchSlashCommandFeatureFlag;
|
||||||
use slash_command::{
|
use slash_command::{
|
||||||
auto_command, cargo_workspace_command, context_server_command, default_command, delta_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,
|
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
|
||||||
prompt_command, search_command, symbols_command, tab_command, terminal_command,
|
prompt_command, search_command, selection_command, symbols_command, tab_command,
|
||||||
workflow_command,
|
terminal_command,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
pub(crate) use streaming_diff::*;
|
pub(crate) use streaming_diff::*;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
use crate::slash_command::streaming_example_command;
|
||||||
use crate::slash_command_settings::SlashCommandSettings;
|
use crate::slash_command_settings::SlashCommandSettings;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
assistant,
|
assistant,
|
||||||
[
|
[
|
||||||
Assist,
|
Assist,
|
||||||
|
Edit,
|
||||||
Split,
|
Split,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
CycleMessageRole,
|
CycleMessageRole,
|
||||||
@@ -211,21 +213,23 @@ pub fn init(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.spawn(|mut cx| {
|
if cx.has_flag::<SearchSlashCommandFeatureFlag>() {
|
||||||
let client = client.clone();
|
cx.spawn(|mut cx| {
|
||||||
async move {
|
let client = client.clone();
|
||||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
async move {
|
||||||
let semantic_index = SemanticDb::new(
|
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
let semantic_index = SemanticDb::new(
|
||||||
Arc::new(embedding_provider),
|
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||||
&mut cx,
|
Arc::new(embedding_provider),
|
||||||
)
|
&mut cx,
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
cx.update(|cx| cx.set_global(semantic_index))
|
cx.update(|cx| cx.set_global(semantic_index))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
context_store::init(&client.clone().into());
|
context_store::init(&client.clone().into());
|
||||||
prompt_library::init(cx);
|
prompt_library::init(cx);
|
||||||
@@ -298,25 +302,64 @@ fn register_context_server_handlers(cx: &mut AppContext) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
|
||||||
for prompt in prompts
|
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||||
.into_iter()
|
for prompt in prompts
|
||||||
.filter(context_server_command::acceptable_prompt)
|
.into_iter()
|
||||||
{
|
.filter(context_server_command::acceptable_prompt)
|
||||||
log::info!(
|
{
|
||||||
"registering context server command: {:?}",
|
log::info!(
|
||||||
prompt.name
|
"registering context server command: {:?}",
|
||||||
);
|
prompt.name
|
||||||
context_server_registry.register_command(
|
);
|
||||||
server.id.clone(),
|
context_server_registry.register_command(
|
||||||
prompt.name.as_str(),
|
server.id.clone(),
|
||||||
);
|
prompt.name.as_str(),
|
||||||
slash_command_registry.register_command(
|
);
|
||||||
context_server_command::ContextServerSlashCommand::new(
|
slash_command_registry.register_command(
|
||||||
&server, prompt,
|
context_server_command::ContextServerSlashCommand::new(
|
||||||
),
|
&server, prompt,
|
||||||
true,
|
),
|
||||||
);
|
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
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -334,6 +377,14 @@ fn register_context_server_handlers(cx: &mut AppContext) {
|
|||||||
context_server_registry.unregister_command(&server_id, &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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -389,6 +440,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
slash_command_registry
|
slash_command_registry
|
||||||
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, 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(default_command::DefaultSlashCommand, false);
|
||||||
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
||||||
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
||||||
@@ -397,22 +449,6 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||||
|
|
||||||
if let Some(prompt_builder) = prompt_builder {
|
if let Some(prompt_builder) = prompt_builder {
|
||||||
cx.observe_global::<SettingsStore>({
|
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
|
||||||
let prompt_builder = prompt_builder.clone();
|
|
||||||
move |cx| {
|
|
||||||
if AssistantSettings::get_global(cx).are_live_diffs_enabled(cx) {
|
|
||||||
slash_command_registry.register_command(
|
|
||||||
workflow_command::WorkflowSlashCommand::new(prompt_builder.clone()),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
slash_command_registry.unregister_command_by_name(WorkflowSlashCommand::NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
|
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
let slash_command_registry = slash_command_registry.clone();
|
||||||
move |is_enabled, _cx| {
|
move |is_enabled, _cx| {
|
||||||
@@ -438,6 +474,19 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
cx.observe_flag::<streaming_example_command::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||||
|
let slash_command_registry = slash_command_registry.clone();
|
||||||
|
move |is_enabled, _cx| {
|
||||||
|
if is_enabled {
|
||||||
|
slash_command_registry.register_command(
|
||||||
|
streaming_example_command::StreamingExampleSlashCommand,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
update_slash_commands_from_settings(cx);
|
update_slash_commands_from_settings(cx);
|
||||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ pub struct AssistantSettingsContentV2 {
|
|||||||
///
|
///
|
||||||
/// Default: 320
|
/// Default: 320
|
||||||
default_height: Option<f32>,
|
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>,
|
default_model: Option<LanguageModelSelection>,
|
||||||
/// Additional models with which to generate alternatives when performing inline assists.
|
/// Additional models with which to generate alternatives when performing inline assists.
|
||||||
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||||
@@ -498,11 +498,11 @@ pub struct LegacyAssistantSettingsContent {
|
|||||||
///
|
///
|
||||||
/// Default: 320
|
/// Default: 320
|
||||||
pub default_height: Option<f32>,
|
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
|
/// Default: gpt-4-1106-preview
|
||||||
pub default_open_ai_model: Option<OpenAiModel>,
|
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
|
/// Default: https://api.openai.com/v1
|
||||||
pub openai_api_url: Option<String>,
|
pub openai_api_url: Option<String>,
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ use super::{AssistantEdit, MessageCacheMetadata};
|
|||||||
use crate::{
|
use crate::{
|
||||||
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
|
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
|
||||||
Context, ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
|
Context, ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
|
||||||
|
SlashCommandId,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
|
||||||
SlashCommandRegistry,
|
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
|
||||||
};
|
};
|
||||||
use collections::HashSet;
|
use collections::{HashMap, HashSet};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
|
use futures::{
|
||||||
|
channel::mpsc,
|
||||||
|
stream::{self, StreamExt},
|
||||||
|
};
|
||||||
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
||||||
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
||||||
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||||
@@ -27,8 +32,8 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId};
|
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
||||||
use ui::{Context as _, WindowContext};
|
use ui::{Context as _, IconName, WindowContext};
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::{
|
use util::{
|
||||||
test::{generate_marked_text, marked_text_ranges},
|
test::{generate_marked_text, marked_text_ranges},
|
||||||
@@ -381,20 +386,41 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
|||||||
let context =
|
let context =
|
||||||
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
|
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
|
||||||
|
|
||||||
let output_ranges = Rc::new(RefCell::new(HashSet::default()));
|
#[derive(Default)]
|
||||||
|
struct ContextRanges {
|
||||||
|
parsed_commands: HashSet<Range<language::Anchor>>,
|
||||||
|
command_outputs: HashMap<SlashCommandId, Range<language::Anchor>>,
|
||||||
|
output_sections: HashSet<Range<language::Anchor>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let context_ranges = Rc::new(RefCell::new(ContextRanges::default()));
|
||||||
context.update(cx, |_, cx| {
|
context.update(cx, |_, cx| {
|
||||||
cx.subscribe(&context, {
|
cx.subscribe(&context, {
|
||||||
let ranges = output_ranges.clone();
|
let context_ranges = context_ranges.clone();
|
||||||
move |_, _, event, _| match event {
|
move |context, _, event, _| {
|
||||||
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
let mut context_ranges = context_ranges.borrow_mut();
|
||||||
for range in removed {
|
match event {
|
||||||
ranges.borrow_mut().remove(range);
|
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 {
|
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||||
ranges.borrow_mut().insert(command.source_range.clone());
|
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();
|
.detach();
|
||||||
@@ -406,14 +432,12 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
|||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
|
buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
|
||||||
});
|
});
|
||||||
assert_text_and_output_ranges(
|
assert_text_and_context_ranges(
|
||||||
&buffer,
|
&buffer,
|
||||||
&output_ranges.borrow(),
|
&context_ranges,
|
||||||
"
|
&"
|
||||||
«/file src/lib.rs»
|
«/file src/lib.rs»"
|
||||||
"
|
.unindent(),
|
||||||
.unindent()
|
|
||||||
.trim_end(),
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -422,14 +446,12 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
|||||||
let edit_offset = buffer.text().find("lib.rs").unwrap();
|
let edit_offset = buffer.text().find("lib.rs").unwrap();
|
||||||
buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
|
buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
|
||||||
});
|
});
|
||||||
assert_text_and_output_ranges(
|
assert_text_and_context_ranges(
|
||||||
&buffer,
|
&buffer,
|
||||||
&output_ranges.borrow(),
|
&context_ranges,
|
||||||
"
|
&"
|
||||||
«/file src/main.rs»
|
«/file src/main.rs»"
|
||||||
"
|
.unindent(),
|
||||||
.unindent()
|
|
||||||
.trim_end(),
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -442,36 +464,180 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
assert_text_and_output_ranges(
|
assert_text_and_context_ranges(
|
||||||
&buffer,
|
&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()
|
.unindent(),
|
||||||
.trim_end(),
|
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 { metadata: None }))
|
||||||
|
.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,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_text_and_output_ranges(
|
fn assert_text_and_context_ranges(
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
ranges: &HashSet<Range<language::Anchor>>,
|
ranges: &RefCell<ContextRanges>,
|
||||||
expected_marked_text: &str,
|
expected_marked_text: &str,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) {
|
) {
|
||||||
let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
|
let mut actual_marked_text = String::new();
|
||||||
let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
|
buffer.update(cx, |buffer, _| {
|
||||||
let mut ranges = ranges
|
struct Endpoint {
|
||||||
.iter()
|
offset: usize,
|
||||||
.map(|range| range.to_offset(buffer))
|
marker: char,
|
||||||
.collect::<Vec<_>>();
|
}
|
||||||
ranges.sort_by_key(|a| a.start);
|
|
||||||
(buffer.text(), ranges)
|
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_marked_text, expected_marked_text);
|
||||||
assert_eq!(actual_ranges, expected_ranges);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,7 +802,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
kind: AssistantEditKind::InsertAfter {
|
kind: AssistantEditKind::InsertAfter {
|
||||||
old_text: "fn one".into(),
|
old_text: "fn one".into(),
|
||||||
new_text: "fn two() {}".into(),
|
new_text: "fn two() {}".into(),
|
||||||
description: "add a `two` function".into(),
|
description: Some("add a `two` function".into()),
|
||||||
},
|
},
|
||||||
}]],
|
}]],
|
||||||
cx,
|
cx,
|
||||||
@@ -690,7 +856,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
kind: AssistantEditKind::InsertAfter {
|
kind: AssistantEditKind::InsertAfter {
|
||||||
old_text: "fn zero".into(),
|
old_text: "fn zero".into(),
|
||||||
new_text: "fn two() {}".into(),
|
new_text: "fn two() {}".into(),
|
||||||
description: "add a `two` function".into(),
|
description: Some("add a `two` function".into()),
|
||||||
},
|
},
|
||||||
}]],
|
}]],
|
||||||
cx,
|
cx,
|
||||||
@@ -754,7 +920,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
kind: AssistantEditKind::InsertAfter {
|
kind: AssistantEditKind::InsertAfter {
|
||||||
old_text: "fn zero".into(),
|
old_text: "fn zero".into(),
|
||||||
new_text: "fn two() {}".into(),
|
new_text: "fn two() {}".into(),
|
||||||
description: "add a `two` function".into(),
|
description: Some("add a `two` function".into()),
|
||||||
},
|
},
|
||||||
}]],
|
}]],
|
||||||
cx,
|
cx,
|
||||||
@@ -798,7 +964,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
kind: AssistantEditKind::InsertAfter {
|
kind: AssistantEditKind::InsertAfter {
|
||||||
old_text: "fn zero".into(),
|
old_text: "fn zero".into(),
|
||||||
new_text: "fn two() {}".into(),
|
new_text: "fn two() {}".into(),
|
||||||
description: "add a `two` function".into(),
|
description: Some("add a `two` function".into()),
|
||||||
},
|
},
|
||||||
}]],
|
}]],
|
||||||
cx,
|
cx,
|
||||||
@@ -1063,43 +1229,57 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
offset + 1..offset + 1 + command_text.len()
|
offset + 1..offset + 1 + command_text.len()
|
||||||
});
|
});
|
||||||
|
|
||||||
let output_len = rng.gen_range(1..=10);
|
|
||||||
let output_text = RandomCharIter::new(&mut rng)
|
let output_text = RandomCharIter::new(&mut rng)
|
||||||
.filter(|c| *c != '\r')
|
.filter(|c| *c != '\r')
|
||||||
.take(output_len)
|
.take(10)
|
||||||
.collect::<String>();
|
.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 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 {
|
for _ in 0..num_sections {
|
||||||
let section_start = rng.gen_range(0..output_len);
|
let mut section_end = rng.gen_range(section_start..=output_text.len());
|
||||||
let section_end = rng.gen_range(section_start..=output_len);
|
while !output_text.is_char_boundary(section_end) {
|
||||||
sections.push(SlashCommandOutputSection {
|
section_end += 1;
|
||||||
range: section_start..section_end,
|
}
|
||||||
icon: ui::IconName::Ai,
|
events.push(Ok(SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::Ai,
|
||||||
label: "section".into(),
|
label: "section".into(),
|
||||||
metadata: None,
|
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 { metadata: None }));
|
||||||
|
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!(
|
log::info!(
|
||||||
"Context {}: insert slash command output at {:?} with {:?}",
|
"Context {}: insert slash command output at {:?} with {:?} events",
|
||||||
context_index,
|
context_index,
|
||||||
command_range,
|
command_range,
|
||||||
sections
|
events.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
let command_range = context.buffer.read(cx).anchor_after(command_range.start)
|
let command_range = context.buffer.read(cx).anchor_after(command_range.start)
|
||||||
..context.buffer.read(cx).anchor_after(command_range.end);
|
..context.buffer.read(cx).anchor_after(command_range.end);
|
||||||
context.insert_command_output(
|
context.insert_command_output(
|
||||||
command_range,
|
command_range,
|
||||||
Task::ready(Ok(SlashCommandOutput {
|
"/command",
|
||||||
text: output_text,
|
Task::ready(Ok(stream::iter(events).boxed())),
|
||||||
sections,
|
|
||||||
run_commands_in_text: false,
|
|
||||||
})),
|
|
||||||
true,
|
true,
|
||||||
false,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1177,7 +1357,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
let first_context = contexts[0].read(cx);
|
let first_context = contexts[0].read(cx);
|
||||||
for context in &contexts[1..] {
|
for context in &contexts[1..] {
|
||||||
let context = context.read(cx);
|
let context = context.read(cx);
|
||||||
assert!(context.pending_ops.is_empty());
|
assert!(context.pending_ops.is_empty(), "pending ops: {:?}", context.pending_ops);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
context.buffer.read(cx).text(),
|
context.buffer.read(cx).text(),
|
||||||
first_context.buffer.read(cx).text(),
|
first_context.buffer.read(cx).text(),
|
||||||
@@ -1416,11 +1596,12 @@ impl SlashCommand for FakeSlashCommand {
|
|||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
Task::ready(Ok(SlashCommandOutput {
|
Task::ready(Ok(SlashCommandOutput {
|
||||||
text: format!("Executed fake command: {}", self.0),
|
text: format!("Executed fake command: {}", self.0),
|
||||||
sections: vec![],
|
sections: vec![],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}
|
||||||
|
.to_event_stream()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
|
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
|
||||||
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
|
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
|
||||||
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, StreamingDiff,
|
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use client::{telemetry::Telemetry, ErrorExt};
|
use client::{telemetry::Telemetry, ErrorExt};
|
||||||
@@ -9,7 +9,7 @@ use collections::{hash_map, HashMap, HashSet, VecDeque};
|
|||||||
use editor::{
|
use editor::{
|
||||||
actions::{MoveDown, MoveUp, SelectAll},
|
actions::{MoveDown, MoveUp, SelectAll},
|
||||||
display_map::{
|
display_map::{
|
||||||
BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||||
ToDisplayPoint,
|
ToDisplayPoint,
|
||||||
},
|
},
|
||||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
|
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
|
||||||
@@ -21,9 +21,7 @@ use fs::Fs;
|
|||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
future::{BoxFuture, LocalBoxFuture},
|
future::{BoxFuture, LocalBoxFuture},
|
||||||
join,
|
join, SinkExt, Stream, StreamExt,
|
||||||
stream::{self, BoxStream},
|
|
||||||
SinkExt, Stream, StreamExt,
|
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, EventEmitter, FocusHandle,
|
anchored, deferred, point, AnyElement, AppContext, ClickEvent, EventEmitter, FocusHandle,
|
||||||
@@ -32,7 +30,8 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
|
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
logging::report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||||
|
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -54,7 +53,9 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
|||||||
use terminal_view::terminal_panel::TerminalPanel;
|
use terminal_view::terminal_panel::TerminalPanel;
|
||||||
use text::{OffsetRangeExt, ToPoint as _};
|
use text::{OffsetRangeExt, ToPoint as _};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
|
use ui::{
|
||||||
|
prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
|
||||||
|
};
|
||||||
use util::{RangeExt, ResultExt};
|
use util::{RangeExt, ResultExt};
|
||||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||||
|
|
||||||
@@ -189,11 +190,16 @@ impl InlineAssistant {
|
|||||||
initial_prompt: Option<String>,
|
initial_prompt: Option<String>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
||||||
|
(
|
||||||
|
editor.buffer().read(cx).snapshot(cx),
|
||||||
|
editor.selections.all::<Point>(cx),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let mut selections = Vec::<Selection<Point>>::new();
|
let mut selections = Vec::<Selection<Point>>::new();
|
||||||
let mut newest_selection = None;
|
let mut newest_selection = None;
|
||||||
for mut selection in editor.read(cx).selections.all::<Point>(cx) {
|
for mut selection in initial_selections {
|
||||||
if selection.end > selection.start {
|
if selection.end > selection.start {
|
||||||
selection.start.column = 0;
|
selection.start.column = 0;
|
||||||
// If the selection ends at the start of the line, we don't want to include it.
|
// If the selection ends at the start of the line, we don't want to include it.
|
||||||
@@ -236,12 +242,13 @@ impl InlineAssistant {
|
|||||||
};
|
};
|
||||||
codegen_ranges.push(start..end);
|
codegen_ranges.push(start..end);
|
||||||
|
|
||||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||||
telemetry.report_assistant_event(AssistantEvent {
|
telemetry.report_assistant_event(AssistantEvent {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
kind: AssistantKind::Inline,
|
kind: AssistantKind::Inline,
|
||||||
phase: AssistantPhase::Invoked,
|
phase: AssistantPhase::Invoked,
|
||||||
|
message_id: None,
|
||||||
model: model.telemetry_id(),
|
model: model.telemetry_id(),
|
||||||
model_provider: model.provider_id().to_string(),
|
model_provider: model.provider_id().to_string(),
|
||||||
response_latency: None,
|
response_latency: None,
|
||||||
@@ -446,15 +453,14 @@ impl InlineAssistant {
|
|||||||
let assist_blocks = vec![
|
let assist_blocks = vec![
|
||||||
BlockProperties {
|
BlockProperties {
|
||||||
style: BlockStyle::Sticky,
|
style: BlockStyle::Sticky,
|
||||||
position: range.start,
|
placement: BlockPlacement::Above(range.start),
|
||||||
height: prompt_editor_height,
|
height: prompt_editor_height,
|
||||||
render: build_assist_editor_renderer(prompt_editor),
|
render: build_assist_editor_renderer(prompt_editor),
|
||||||
disposition: BlockDisposition::Above,
|
|
||||||
priority: 0,
|
priority: 0,
|
||||||
},
|
},
|
||||||
BlockProperties {
|
BlockProperties {
|
||||||
style: BlockStyle::Sticky,
|
style: BlockStyle::Sticky,
|
||||||
position: range.end,
|
placement: BlockPlacement::Below(range.end),
|
||||||
height: 0,
|
height: 0,
|
||||||
render: Box::new(|cx| {
|
render: Box::new(|cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -464,7 +470,6 @@ impl InlineAssistant {
|
|||||||
.border_color(cx.theme().status().info_border)
|
.border_color(cx.theme().status().info_border)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
disposition: BlockDisposition::Below,
|
|
||||||
priority: 0,
|
priority: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -568,10 +573,13 @@ impl InlineAssistant {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let editor = editor.read(cx);
|
if editor.read(cx).selections.count() == 1 {
|
||||||
if editor.selections.count() == 1 {
|
let (selection, buffer) = editor.update(cx, |editor, cx| {
|
||||||
let selection = editor.selections.newest::<usize>(cx);
|
(
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
editor.selections.newest::<usize>(cx),
|
||||||
|
editor.buffer().read(cx).snapshot(cx),
|
||||||
|
)
|
||||||
|
});
|
||||||
for assist_id in &editor_assists.assist_ids {
|
for assist_id in &editor_assists.assist_ids {
|
||||||
let assist = &self.assists[assist_id];
|
let assist = &self.assists[assist_id];
|
||||||
let assist_range = assist.range.to_offset(&buffer);
|
let assist_range = assist.range.to_offset(&buffer);
|
||||||
@@ -596,10 +604,13 @@ impl InlineAssistant {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let editor = editor.read(cx);
|
if editor.read(cx).selections.count() == 1 {
|
||||||
if editor.selections.count() == 1 {
|
let (selection, buffer) = editor.update(cx, |editor, cx| {
|
||||||
let selection = editor.selections.newest::<usize>(cx);
|
(
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
editor.selections.newest::<usize>(cx),
|
||||||
|
editor.buffer().read(cx).snapshot(cx),
|
||||||
|
)
|
||||||
|
});
|
||||||
let mut closest_assist_fallback = None;
|
let mut closest_assist_fallback = None;
|
||||||
for assist_id in &editor_assists.assist_ids {
|
for assist_id in &editor_assists.assist_ids {
|
||||||
let assist = &self.assists[assist_id];
|
let assist = &self.assists[assist_id];
|
||||||
@@ -745,33 +756,6 @@ impl InlineAssistant {
|
|||||||
|
|
||||||
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
|
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
|
||||||
if let Some(assist) = self.assists.get(&assist_id) {
|
if let Some(assist) = self.assists.get(&assist_id) {
|
||||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
|
||||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
|
||||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
|
||||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
|
||||||
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
|
|
||||||
ranges
|
|
||||||
.first()
|
|
||||||
.and_then(|(buffer, _, _)| buffer.read(cx).language())
|
|
||||||
.map(|language| language.name())
|
|
||||||
});
|
|
||||||
telemetry.report_assistant_event(AssistantEvent {
|
|
||||||
conversation_id: None,
|
|
||||||
kind: AssistantKind::Inline,
|
|
||||||
phase: if undo {
|
|
||||||
AssistantPhase::Rejected
|
|
||||||
} else {
|
|
||||||
AssistantPhase::Accepted
|
|
||||||
},
|
|
||||||
model: model.telemetry_id(),
|
|
||||||
model_provider: model.provider_id().to_string(),
|
|
||||||
response_latency: None,
|
|
||||||
error_message: None,
|
|
||||||
language_name: language_name.map(|name| name.to_proto()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let assist_group_id = assist.group_id;
|
let assist_group_id = assist.group_id;
|
||||||
if self.assist_groups[&assist_group_id].linked {
|
if self.assist_groups[&assist_group_id].linked {
|
||||||
for assist_id in self.unlink_assist_group(assist_group_id, cx) {
|
for assist_id in self.unlink_assist_group(assist_group_id, cx) {
|
||||||
@@ -806,12 +790,45 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let active_alternative = assist.codegen.read(cx).active_alternative().clone();
|
||||||
|
let message_id = active_alternative.read(cx).message_id.clone();
|
||||||
|
|
||||||
|
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||||
|
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||||
|
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||||
|
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
|
||||||
|
ranges
|
||||||
|
.first()
|
||||||
|
.and_then(|(buffer, _, _)| buffer.read(cx).language())
|
||||||
|
.map(|language| language.name())
|
||||||
|
});
|
||||||
|
report_assistant_event(
|
||||||
|
AssistantEvent {
|
||||||
|
conversation_id: None,
|
||||||
|
kind: AssistantKind::Inline,
|
||||||
|
message_id,
|
||||||
|
phase: if undo {
|
||||||
|
AssistantPhase::Rejected
|
||||||
|
} else {
|
||||||
|
AssistantPhase::Accepted
|
||||||
|
},
|
||||||
|
model: model.telemetry_id(),
|
||||||
|
model_provider: model.provider_id().to_string(),
|
||||||
|
response_latency: None,
|
||||||
|
error_message: None,
|
||||||
|
language_name: language_name.map(|name| name.to_proto()),
|
||||||
|
},
|
||||||
|
self.telemetry.clone(),
|
||||||
|
cx.http_client(),
|
||||||
|
model.api_key(cx),
|
||||||
|
cx.background_executor(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if undo {
|
if undo {
|
||||||
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
|
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
|
||||||
} else {
|
} else {
|
||||||
let confirmed_alternative = assist.codegen.read(cx).active_alternative().clone();
|
self.confirmed_assists.insert(assist_id, active_alternative);
|
||||||
self.confirmed_assists
|
|
||||||
.insert(assist_id, confirmed_alternative);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1179,7 +1196,7 @@ impl InlineAssistant {
|
|||||||
let height =
|
let height =
|
||||||
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
|
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
|
||||||
new_blocks.push(BlockProperties {
|
new_blocks.push(BlockProperties {
|
||||||
position: new_row,
|
placement: BlockPlacement::Above(new_row),
|
||||||
height,
|
height,
|
||||||
style: BlockStyle::Flex,
|
style: BlockStyle::Flex,
|
||||||
render: Box::new(move |cx| {
|
render: Box::new(move |cx| {
|
||||||
@@ -1191,7 +1208,6 @@ impl InlineAssistant {
|
|||||||
.child(deleted_lines_editor.clone())
|
.child(deleted_lines_editor.clone())
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
disposition: BlockDisposition::Above,
|
|
||||||
priority: 0,
|
priority: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1599,7 +1615,7 @@ impl PromptEditor {
|
|||||||
// always show the cursor (even when it isn't focused) because
|
// always show the cursor (even when it isn't focused) because
|
||||||
// typing in one will make what you typed appear in all of them.
|
// typing in one will make what you typed appear in all of them.
|
||||||
editor.set_show_cursor_when_unfocused(true, cx);
|
editor.set_show_cursor_when_unfocused(true, cx);
|
||||||
editor.set_placeholder_text("Add a prompt…", cx);
|
editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1656,6 +1672,7 @@ impl PromptEditor {
|
|||||||
self.editor = cx.new_view(|cx| {
|
self.editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
|
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
|
||||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||||
|
editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
|
||||||
editor.set_placeholder_text("Add a prompt…", cx);
|
editor.set_placeholder_text("Add a prompt…", cx);
|
||||||
editor.set_text(prompt, cx);
|
editor.set_text(prompt, cx);
|
||||||
if focus {
|
if focus {
|
||||||
@@ -1666,6 +1683,20 @@ impl PromptEditor {
|
|||||||
self.subscribe_to_editor(cx);
|
self.subscribe_to_editor(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
|
||||||
|
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
|
||||||
|
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let action = if codegen.is_insertion {
|
||||||
|
"Generate"
|
||||||
|
} else {
|
||||||
|
"Transform"
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{action}…{context_keybinding} • ↓↑ for history")
|
||||||
|
}
|
||||||
|
|
||||||
fn prompt(&self, cx: &AppContext) -> String {
|
fn prompt(&self, cx: &AppContext) -> String {
|
||||||
self.editor.read(cx).text(cx)
|
self.editor.read(cx).text(cx)
|
||||||
}
|
}
|
||||||
@@ -1870,21 +1901,58 @@ impl PromptEditor {
|
|||||||
let codegen = self.codegen.read(cx);
|
let codegen = self.codegen.read(cx);
|
||||||
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
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()
|
h_flex()
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("previous", IconName::ChevronLeft)
|
IconButton::new("previous", IconName::ChevronLeft)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.disabled(disabled)
|
.disabled(disabled || current_index == 0)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
move |cx| {
|
move |cx| {
|
||||||
Tooltip::for_action_in(
|
cx.new_view(|cx| {
|
||||||
"Previous Alternative",
|
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
||||||
&CyclePreviousInlineAssist,
|
KeyBinding::for_action_in(
|
||||||
&focus_handle,
|
&CyclePreviousInlineAssist,
|
||||||
cx,
|
&focus_handle,
|
||||||
)
|
cx,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if !disabled && current_index != 0 {
|
||||||
|
tooltip = tooltip.meta(prev_model_name.clone());
|
||||||
|
}
|
||||||
|
tooltip
|
||||||
|
})
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
@@ -1908,17 +1976,25 @@ impl PromptEditor {
|
|||||||
.child(
|
.child(
|
||||||
IconButton::new("next", IconName::ChevronRight)
|
IconButton::new("next", IconName::ChevronRight)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.disabled(disabled)
|
.disabled(disabled || current_index == total_models - 1)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
move |cx| {
|
move |cx| {
|
||||||
Tooltip::for_action_in(
|
cx.new_view(|cx| {
|
||||||
"Next Alternative",
|
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
||||||
&CycleNextInlineAssist,
|
KeyBinding::for_action_in(
|
||||||
&focus_handle,
|
&CycleNextInlineAssist,
|
||||||
cx,
|
&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| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
@@ -2222,7 +2298,7 @@ impl InlineAssist {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.active_context(cx)?
|
.active_context(cx)?
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.to_completion_request(cx),
|
.to_completion_request(RequestType::Chat, cx),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -2256,12 +2332,14 @@ pub enum CodegenEvent {
|
|||||||
pub struct Codegen {
|
pub struct Codegen {
|
||||||
alternatives: Vec<Model<CodegenAlternative>>,
|
alternatives: Vec<Model<CodegenAlternative>>,
|
||||||
active_alternative: usize,
|
active_alternative: usize,
|
||||||
|
seen_alternatives: HashSet<usize>,
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
buffer: Model<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
initial_transaction_id: Option<TransactionId>,
|
initial_transaction_id: Option<TransactionId>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
|
is_insertion: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Codegen {
|
impl Codegen {
|
||||||
@@ -2284,8 +2362,10 @@ impl Codegen {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
|
is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
|
||||||
alternatives: vec![codegen],
|
alternatives: vec![codegen],
|
||||||
active_alternative: 0,
|
active_alternative: 0,
|
||||||
|
seen_alternatives: HashSet::default(),
|
||||||
subscriptions: Vec::new(),
|
subscriptions: Vec::new(),
|
||||||
buffer,
|
buffer,
|
||||||
range,
|
range,
|
||||||
@@ -2338,6 +2418,7 @@ impl Codegen {
|
|||||||
fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
|
fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
|
||||||
self.active_alternative()
|
self.active_alternative()
|
||||||
.update(cx, |codegen, cx| codegen.set_active(false, cx));
|
.update(cx, |codegen, cx| codegen.set_active(false, cx));
|
||||||
|
self.seen_alternatives.insert(index);
|
||||||
self.active_alternative = index;
|
self.active_alternative = index;
|
||||||
self.active_alternative()
|
self.active_alternative()
|
||||||
.update(cx, |codegen, cx| codegen.set_active(true, cx));
|
.update(cx, |codegen, cx| codegen.set_active(true, cx));
|
||||||
@@ -2467,6 +2548,9 @@ pub struct CodegenAlternative {
|
|||||||
active: bool,
|
active: bool,
|
||||||
edits: Vec<(Range<Anchor>, String)>,
|
edits: Vec<(Range<Anchor>, String)>,
|
||||||
line_operations: Vec<LineOperation>,
|
line_operations: Vec<LineOperation>,
|
||||||
|
request: Option<LanguageModelRequest>,
|
||||||
|
elapsed_time: Option<f64>,
|
||||||
|
message_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CodegenStatus {
|
enum CodegenStatus {
|
||||||
@@ -2525,6 +2609,7 @@ impl CodegenAlternative {
|
|||||||
buffer: buffer.clone(),
|
buffer: buffer.clone(),
|
||||||
old_buffer,
|
old_buffer,
|
||||||
edit_position: None,
|
edit_position: None,
|
||||||
|
message_id: None,
|
||||||
snapshot,
|
snapshot,
|
||||||
last_equal_ranges: Default::default(),
|
last_equal_ranges: Default::default(),
|
||||||
transformation_transaction_id: None,
|
transformation_transaction_id: None,
|
||||||
@@ -2538,6 +2623,8 @@ impl CodegenAlternative {
|
|||||||
edits: Vec::new(),
|
edits: Vec::new(),
|
||||||
line_operations: Vec::new(),
|
line_operations: Vec::new(),
|
||||||
range,
|
range,
|
||||||
|
request: None,
|
||||||
|
elapsed_time: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2627,19 +2714,20 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
|
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
|
||||||
|
|
||||||
|
let api_key = model.api_key(cx);
|
||||||
let telemetry_id = model.telemetry_id();
|
let telemetry_id = model.telemetry_id();
|
||||||
let provider_id = model.provider_id();
|
let provider_id = model.provider_id();
|
||||||
let chunks: LocalBoxFuture<Result<BoxStream<Result<String>>>> =
|
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||||
if user_prompt.trim().to_lowercase() == "delete" {
|
if user_prompt.trim().to_lowercase() == "delete" {
|
||||||
async { Ok(stream::empty().boxed()) }.boxed_local()
|
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||||
} else {
|
} else {
|
||||||
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
|
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
|
||||||
|
self.request = Some(request.clone());
|
||||||
|
|
||||||
let chunks = cx
|
cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
|
||||||
.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await });
|
.boxed_local()
|
||||||
async move { Ok(chunks.await?.boxed()) }.boxed_local()
|
|
||||||
};
|
};
|
||||||
self.handle_stream(telemetry_id, provider_id.to_string(), chunks, cx);
|
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2678,7 +2766,7 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
let prompt = self
|
let prompt = self
|
||||||
.builder
|
.builder
|
||||||
.generate_content_prompt(user_prompt, language_name, buffer, range)
|
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
|
||||||
|
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
@@ -2704,9 +2792,11 @@ impl CodegenAlternative {
|
|||||||
&mut self,
|
&mut self,
|
||||||
model_telemetry_id: String,
|
model_telemetry_id: String,
|
||||||
model_provider_id: String,
|
model_provider_id: String,
|
||||||
stream: impl 'static + Future<Output = Result<BoxStream<'static, Result<String>>>>,
|
model_api_key: Option<String>,
|
||||||
|
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
let start_time = Instant::now();
|
||||||
let snapshot = self.snapshot.clone();
|
let snapshot = self.snapshot.clone();
|
||||||
let selected_text = snapshot
|
let selected_text = snapshot
|
||||||
.text_for_range(self.range.start..self.range.end)
|
.text_for_range(self.range.start..self.range.end)
|
||||||
@@ -2733,6 +2823,7 @@ impl CodegenAlternative {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let http_client = cx.http_client().clone();
|
||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
let language_name = {
|
let language_name = {
|
||||||
let multibuffer = self.buffer.read(cx);
|
let multibuffer = self.buffer.read(cx);
|
||||||
@@ -2748,15 +2839,21 @@ impl CodegenAlternative {
|
|||||||
let mut edit_start = self.range.start.to_offset(&snapshot);
|
let mut edit_start = self.range.start.to_offset(&snapshot);
|
||||||
self.generation = cx.spawn(|codegen, mut cx| {
|
self.generation = cx.spawn(|codegen, mut cx| {
|
||||||
async move {
|
async move {
|
||||||
let chunks = stream.await;
|
let stream = stream.await;
|
||||||
|
let message_id = stream
|
||||||
|
.as_ref()
|
||||||
|
.ok()
|
||||||
|
.and_then(|stream| stream.message_id.clone());
|
||||||
let generate = async {
|
let generate = async {
|
||||||
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
let message_id = message_id.clone();
|
||||||
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
let mut response_latency = None;
|
let mut response_latency = None;
|
||||||
let request_start = Instant::now();
|
let request_start = Instant::now();
|
||||||
let diff = async {
|
let diff = async {
|
||||||
let chunks = StripInvalidSpans::new(chunks?);
|
let chunks = StripInvalidSpans::new(stream?.stream);
|
||||||
futures::pin_mut!(chunks);
|
futures::pin_mut!(chunks);
|
||||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||||
let mut line_diff = LineDiff::default();
|
let mut line_diff = LineDiff::default();
|
||||||
@@ -2852,9 +2949,10 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
let error_message =
|
let error_message =
|
||||||
result.as_ref().err().map(|error| error.to_string());
|
result.as_ref().err().map(|error| error.to_string());
|
||||||
if let Some(telemetry) = telemetry {
|
report_assistant_event(
|
||||||
telemetry.report_assistant_event(AssistantEvent {
|
AssistantEvent {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
|
message_id,
|
||||||
kind: AssistantKind::Inline,
|
kind: AssistantKind::Inline,
|
||||||
phase: AssistantPhase::Response,
|
phase: AssistantPhase::Response,
|
||||||
model: model_telemetry_id,
|
model: model_telemetry_id,
|
||||||
@@ -2862,8 +2960,12 @@ impl CodegenAlternative {
|
|||||||
response_latency,
|
response_latency,
|
||||||
error_message,
|
error_message,
|
||||||
language_name: language_name.map(|name| name.to_proto()),
|
language_name: language_name.map(|name| name.to_proto()),
|
||||||
});
|
},
|
||||||
}
|
telemetry,
|
||||||
|
http_client,
|
||||||
|
model_api_key,
|
||||||
|
&executor,
|
||||||
|
);
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -2923,14 +3025,18 @@ impl CodegenAlternative {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let result = generate.await;
|
let result = generate.await;
|
||||||
|
let elapsed_time = start_time.elapsed().as_secs_f64();
|
||||||
|
|
||||||
codegen
|
codegen
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
|
this.message_id = message_id;
|
||||||
this.last_equal_ranges.clear();
|
this.last_equal_ranges.clear();
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
this.status = CodegenStatus::Error(error);
|
this.status = CodegenStatus::Error(error);
|
||||||
} else {
|
} else {
|
||||||
this.status = CodegenStatus::Done;
|
this.status = CodegenStatus::Done;
|
||||||
}
|
}
|
||||||
|
this.elapsed_time = Some(elapsed_time);
|
||||||
cx.emit(CodegenEvent::Finished);
|
cx.emit(CodegenEvent::Finished);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -3277,6 +3383,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
range: Range<text::Anchor>,
|
range: Range<text::Anchor>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<Vec<CodeAction>>> {
|
) -> Task<Result<Vec<CodeAction>>> {
|
||||||
|
if !AssistantSettings::get_global(cx).enabled {
|
||||||
|
return Task::ready(Ok(Vec::new()));
|
||||||
|
}
|
||||||
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
let mut range = range.to_point(&snapshot);
|
let mut range = range.to_point(&snapshot);
|
||||||
|
|
||||||
@@ -3471,15 +3581,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
|
||||||
codegen.update(cx, |codegen, cx| {
|
|
||||||
codegen.handle_stream(
|
|
||||||
String::new(),
|
|
||||||
String::new(),
|
|
||||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut new_text = concat!(
|
let mut new_text = concat!(
|
||||||
" let mut x = 0;\n",
|
" let mut x = 0;\n",
|
||||||
@@ -3543,15 +3645,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
|
||||||
codegen.update(cx, |codegen, cx| {
|
|
||||||
codegen.handle_stream(
|
|
||||||
String::new(),
|
|
||||||
String::new(),
|
|
||||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
@@ -3618,15 +3712,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
|
||||||
codegen.update(cx, |codegen, cx| {
|
|
||||||
codegen.handle_stream(
|
|
||||||
String::new(),
|
|
||||||
String::new(),
|
|
||||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
@@ -3692,16 +3778,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
|
||||||
codegen.update(cx, |codegen, cx| {
|
|
||||||
codegen.handle_stream(
|
|
||||||
String::new(),
|
|
||||||
String::new(),
|
|
||||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let new_text = concat!(
|
let new_text = concat!(
|
||||||
"func main() {\n",
|
"func main() {\n",
|
||||||
"\tx := 0\n",
|
"\tx := 0\n",
|
||||||
@@ -3756,16 +3833,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
|
||||||
codegen.update(cx, |codegen, cx| {
|
|
||||||
codegen.handle_stream(
|
|
||||||
String::new(),
|
|
||||||
String::new(),
|
|
||||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
chunks_tx
|
chunks_tx
|
||||||
.unbounded_send("let mut x = 0;\nx += 1;".to_string())
|
.unbounded_send("let mut x = 0;\nx += 1;".to_string())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3839,6 +3907,26 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn simulate_response_stream(
|
||||||
|
codegen: Model<CodegenAlternative>,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> mpsc::UnboundedSender<String> {
|
||||||
|
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||||
|
codegen.update(cx, |codegen, cx| {
|
||||||
|
codegen.handle_stream(
|
||||||
|
String::new(),
|
||||||
|
String::new(),
|
||||||
|
None,
|
||||||
|
future::ready(Ok(LanguageModelTextStream {
|
||||||
|
message_id: None,
|
||||||
|
stream: chunks_rx.map(Ok).boxed(),
|
||||||
|
})),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
chunks_tx
|
||||||
|
}
|
||||||
|
|
||||||
fn rust_lang() -> Language {
|
fn rust_lang() -> Language {
|
||||||
Language::new(
|
Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
|
|||||||
@@ -158,39 +158,34 @@ impl PickerDelegate for ModelPickerDelegate {
|
|||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.start_slot(
|
.start_slot(
|
||||||
div().pr_1().child(
|
div().pr_0p5().child(
|
||||||
Icon::new(model_info.icon)
|
Icon::new(model_info.icon)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.size(IconSize::Medium),
|
.size(IconSize::Medium),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex().w_full().justify_between().min_w(px(200.)).child(
|
||||||
.w_full()
|
h_flex()
|
||||||
.justify_between()
|
.gap_1p5()
|
||||||
.font_buffer(cx)
|
.child(Label::new(model_info.model.name().0.clone()))
|
||||||
.min_w(px(240.))
|
.child(
|
||||||
.child(
|
Label::new(provider_name)
|
||||||
h_flex()
|
.size(LabelSize::XSmall)
|
||||||
.gap_2()
|
.color(Color::Muted),
|
||||||
.child(Label::new(model_info.model.name().0.clone()))
|
)
|
||||||
.child(
|
.children(match model_info.availability {
|
||||||
Label::new(provider_name)
|
LanguageModelAvailability::Public => None,
|
||||||
.size(LabelSize::XSmall)
|
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
|
||||||
.color(Color::Muted),
|
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
|
||||||
)
|
show_badges.then(|| {
|
||||||
.children(match model_info.availability {
|
Label::new("Pro")
|
||||||
LanguageModelAvailability::Public => None,
|
.size(LabelSize::XSmall)
|
||||||
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
|
.color(Color::Muted)
|
||||||
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| {
|
.end_slot(div().when(model_info.is_selected, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
@@ -212,7 +207,7 @@ impl PickerDelegate for ModelPickerDelegate {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.p_1()
|
.p_1()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
|||||||
@@ -33,21 +33,21 @@ pub enum AssistantEditKind {
|
|||||||
Update {
|
Update {
|
||||||
old_text: String,
|
old_text: String,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
description: String,
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
Create {
|
Create {
|
||||||
new_text: String,
|
new_text: String,
|
||||||
description: String,
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
InsertBefore {
|
InsertBefore {
|
||||||
old_text: String,
|
old_text: String,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
description: String,
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
InsertAfter {
|
InsertAfter {
|
||||||
old_text: String,
|
old_text: String,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
description: String,
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
Delete {
|
Delete {
|
||||||
old_text: String,
|
old_text: String,
|
||||||
@@ -86,19 +86,37 @@ enum SearchDirection {
|
|||||||
Diagonal,
|
Diagonal,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A measure of the currently quality of an in-progress fuzzy search.
|
|
||||||
//
|
|
||||||
// Uses 60 bits to store a numeric cost, and 4 bits to store the preceding
|
|
||||||
// operation in the search.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
struct SearchState {
|
struct SearchState {
|
||||||
score: u32,
|
cost: u32,
|
||||||
direction: SearchDirection,
|
direction: SearchDirection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchState {
|
impl SearchState {
|
||||||
fn new(score: u32, direction: SearchDirection) -> Self {
|
fn new(cost: u32, direction: SearchDirection) -> Self {
|
||||||
Self { score, direction }
|
Self { cost, direction }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchMatrix {
|
||||||
|
cols: usize,
|
||||||
|
data: Vec<SearchState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchMatrix {
|
||||||
|
fn new(rows: usize, cols: usize) -> Self {
|
||||||
|
SearchMatrix {
|
||||||
|
cols,
|
||||||
|
data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, row: usize, col: usize) -> SearchState {
|
||||||
|
self.data[row * self.cols + col]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, row: usize, col: usize, cost: SearchState) {
|
||||||
|
self.data[row * self.cols + col] = cost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,23 +205,23 @@ impl AssistantEdit {
|
|||||||
"update" => AssistantEditKind::Update {
|
"update" => AssistantEditKind::Update {
|
||||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
description,
|
||||||
},
|
},
|
||||||
"insert_before" => AssistantEditKind::InsertBefore {
|
"insert_before" => AssistantEditKind::InsertBefore {
|
||||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
description,
|
||||||
},
|
},
|
||||||
"insert_after" => AssistantEditKind::InsertAfter {
|
"insert_after" => AssistantEditKind::InsertAfter {
|
||||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
description,
|
||||||
},
|
},
|
||||||
"delete" => AssistantEditKind::Delete {
|
"delete" => AssistantEditKind::Delete {
|
||||||
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
|
||||||
},
|
},
|
||||||
"create" => AssistantEditKind::Create {
|
"create" => AssistantEditKind::Create {
|
||||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
description,
|
||||||
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
|
||||||
},
|
},
|
||||||
_ => Err(anyhow!("unknown operation {operation:?}"))?,
|
_ => Err(anyhow!("unknown operation {operation:?}"))?,
|
||||||
@@ -264,7 +282,7 @@ impl AssistantEditKind {
|
|||||||
ResolvedEdit {
|
ResolvedEdit {
|
||||||
range,
|
range,
|
||||||
new_text,
|
new_text,
|
||||||
description: Some(description),
|
description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Create {
|
Self::Create {
|
||||||
@@ -272,7 +290,7 @@ impl AssistantEditKind {
|
|||||||
description,
|
description,
|
||||||
} => ResolvedEdit {
|
} => ResolvedEdit {
|
||||||
range: text::Anchor::MIN..text::Anchor::MAX,
|
range: text::Anchor::MIN..text::Anchor::MAX,
|
||||||
description: Some(description),
|
description,
|
||||||
new_text,
|
new_text,
|
||||||
},
|
},
|
||||||
Self::InsertBefore {
|
Self::InsertBefore {
|
||||||
@@ -285,7 +303,7 @@ impl AssistantEditKind {
|
|||||||
ResolvedEdit {
|
ResolvedEdit {
|
||||||
range: range.start..range.start,
|
range: range.start..range.start,
|
||||||
new_text,
|
new_text,
|
||||||
description: Some(description),
|
description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::InsertAfter {
|
Self::InsertAfter {
|
||||||
@@ -298,7 +316,7 @@ impl AssistantEditKind {
|
|||||||
ResolvedEdit {
|
ResolvedEdit {
|
||||||
range: range.end..range.end,
|
range: range.end..range.end,
|
||||||
new_text,
|
new_text,
|
||||||
description: Some(description),
|
description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Delete { old_text } => {
|
Self::Delete { old_text } => {
|
||||||
@@ -314,44 +332,29 @@ impl AssistantEditKind {
|
|||||||
|
|
||||||
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
|
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
|
||||||
const INSERTION_COST: u32 = 3;
|
const INSERTION_COST: u32 = 3;
|
||||||
|
const DELETION_COST: u32 = 10;
|
||||||
const WHITESPACE_INSERTION_COST: u32 = 1;
|
const WHITESPACE_INSERTION_COST: u32 = 1;
|
||||||
const DELETION_COST: u32 = 3;
|
|
||||||
const WHITESPACE_DELETION_COST: u32 = 1;
|
const WHITESPACE_DELETION_COST: u32 = 1;
|
||||||
const EQUALITY_BONUS: u32 = 5;
|
|
||||||
|
|
||||||
struct Matrix {
|
|
||||||
cols: usize,
|
|
||||||
data: Vec<SearchState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Matrix {
|
|
||||||
fn new(rows: usize, cols: usize) -> Self {
|
|
||||||
Matrix {
|
|
||||||
cols,
|
|
||||||
data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self, row: usize, col: usize) -> SearchState {
|
|
||||||
self.data[row * self.cols + col]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(&mut self, row: usize, col: usize, cost: SearchState) {
|
|
||||||
self.data[row * self.cols + col] = cost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer_len = buffer.len();
|
let buffer_len = buffer.len();
|
||||||
let query_len = search_query.len();
|
let query_len = search_query.len();
|
||||||
let mut matrix = Matrix::new(query_len + 1, buffer_len + 1);
|
let mut matrix = SearchMatrix::new(query_len + 1, buffer_len + 1);
|
||||||
|
let mut leading_deletion_cost = 0_u32;
|
||||||
for (row, query_byte) in search_query.bytes().enumerate() {
|
for (row, query_byte) in search_query.bytes().enumerate() {
|
||||||
|
let deletion_cost = if query_byte.is_ascii_whitespace() {
|
||||||
|
WHITESPACE_DELETION_COST
|
||||||
|
} else {
|
||||||
|
DELETION_COST
|
||||||
|
};
|
||||||
|
|
||||||
|
leading_deletion_cost = leading_deletion_cost.saturating_add(deletion_cost);
|
||||||
|
matrix.set(
|
||||||
|
row + 1,
|
||||||
|
0,
|
||||||
|
SearchState::new(leading_deletion_cost, SearchDirection::Diagonal),
|
||||||
|
);
|
||||||
|
|
||||||
for (col, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
|
for (col, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
|
||||||
let deletion_cost = if query_byte.is_ascii_whitespace() {
|
|
||||||
WHITESPACE_DELETION_COST
|
|
||||||
} else {
|
|
||||||
DELETION_COST
|
|
||||||
};
|
|
||||||
let insertion_cost = if buffer_byte.is_ascii_whitespace() {
|
let insertion_cost = if buffer_byte.is_ascii_whitespace() {
|
||||||
WHITESPACE_INSERTION_COST
|
WHITESPACE_INSERTION_COST
|
||||||
} else {
|
} else {
|
||||||
@@ -359,38 +362,35 @@ impl AssistantEditKind {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let up = SearchState::new(
|
let up = SearchState::new(
|
||||||
matrix.get(row, col + 1).score.saturating_sub(deletion_cost),
|
matrix.get(row, col + 1).cost.saturating_add(deletion_cost),
|
||||||
SearchDirection::Up,
|
SearchDirection::Up,
|
||||||
);
|
);
|
||||||
let left = SearchState::new(
|
let left = SearchState::new(
|
||||||
matrix
|
matrix.get(row + 1, col).cost.saturating_add(insertion_cost),
|
||||||
.get(row + 1, col)
|
|
||||||
.score
|
|
||||||
.saturating_sub(insertion_cost),
|
|
||||||
SearchDirection::Left,
|
SearchDirection::Left,
|
||||||
);
|
);
|
||||||
let diagonal = SearchState::new(
|
let diagonal = SearchState::new(
|
||||||
if query_byte == *buffer_byte {
|
if query_byte == *buffer_byte {
|
||||||
matrix.get(row, col).score.saturating_add(EQUALITY_BONUS)
|
matrix.get(row, col).cost
|
||||||
} else {
|
} else {
|
||||||
matrix
|
matrix
|
||||||
.get(row, col)
|
.get(row, col)
|
||||||
.score
|
.cost
|
||||||
.saturating_sub(deletion_cost + insertion_cost)
|
.saturating_add(deletion_cost + insertion_cost)
|
||||||
},
|
},
|
||||||
SearchDirection::Diagonal,
|
SearchDirection::Diagonal,
|
||||||
);
|
);
|
||||||
matrix.set(row + 1, col + 1, up.max(left).max(diagonal));
|
matrix.set(row + 1, col + 1, up.min(left).min(diagonal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traceback to find the best match
|
// Traceback to find the best match
|
||||||
let mut best_buffer_end = buffer_len;
|
let mut best_buffer_end = buffer_len;
|
||||||
let mut best_score = 0;
|
let mut best_cost = u32::MAX;
|
||||||
for col in 1..=buffer_len {
|
for col in 1..=buffer_len {
|
||||||
let score = matrix.get(query_len, col).score;
|
let cost = matrix.get(query_len, col).cost;
|
||||||
if score > best_score {
|
if cost < best_cost {
|
||||||
best_score = score;
|
best_cost = cost;
|
||||||
best_buffer_end = col;
|
best_buffer_end = col;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,89 +560,84 @@ mod tests {
|
|||||||
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
|
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
|
||||||
};
|
};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use text::{OffsetRangeExt, Point};
|
|
||||||
use ui::BorrowAppContext;
|
use ui::BorrowAppContext;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
|
use util::test::{generate_marked_text, marked_text_ranges};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_resolve_location(cx: &mut AppContext) {
|
fn test_resolve_location(cx: &mut AppContext) {
|
||||||
{
|
assert_location_resolution(
|
||||||
let buffer = cx.new_model(|cx| {
|
concat!(
|
||||||
Buffer::local(
|
" Lorem\n",
|
||||||
concat!(
|
"« ipsum\n",
|
||||||
" Lorem\n",
|
" dolor sit amet»\n",
|
||||||
" ipsum\n",
|
" consecteur",
|
||||||
" dolor sit amet\n",
|
),
|
||||||
" consecteur",
|
"ipsum\ndolor",
|
||||||
),
|
cx,
|
||||||
cx,
|
);
|
||||||
)
|
|
||||||
});
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
|
||||||
assert_eq!(
|
|
||||||
AssistantEditKind::resolve_location(&snapshot, "ipsum\ndolor").to_point(&snapshot),
|
|
||||||
Point::new(1, 0)..Point::new(2, 18)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
assert_location_resolution(
|
||||||
let buffer = cx.new_model(|cx| {
|
&"
|
||||||
Buffer::local(
|
«fn foo1(a: usize) -> usize {
|
||||||
concat!(
|
40
|
||||||
"fn foo1(a: usize) -> usize {\n",
|
}»
|
||||||
" 40\n",
|
|
||||||
"}\n",
|
|
||||||
"\n",
|
|
||||||
"fn foo2(b: usize) -> usize {\n",
|
|
||||||
" 42\n",
|
|
||||||
"}\n",
|
|
||||||
),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
|
||||||
assert_eq!(
|
|
||||||
AssistantEditKind::resolve_location(&snapshot, "fn foo1(b: usize) {\n40\n}")
|
|
||||||
.to_point(&snapshot),
|
|
||||||
Point::new(0, 0)..Point::new(2, 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
fn foo2(b: usize) -> usize {
|
||||||
let buffer = cx.new_model(|cx| {
|
42
|
||||||
Buffer::local(
|
}
|
||||||
concat!(
|
"
|
||||||
"fn main() {\n",
|
.unindent(),
|
||||||
" Foo\n",
|
"fn foo1(b: usize) {\n40\n}",
|
||||||
" .bar()\n",
|
cx,
|
||||||
" .baz()\n",
|
);
|
||||||
" .qux()\n",
|
|
||||||
"}\n",
|
assert_location_resolution(
|
||||||
"\n",
|
&"
|
||||||
"fn foo2(b: usize) -> usize {\n",
|
fn main() {
|
||||||
" 42\n",
|
« Foo
|
||||||
"}\n",
|
.bar()
|
||||||
),
|
.baz()
|
||||||
cx,
|
.qux()»
|
||||||
)
|
}
|
||||||
});
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
fn foo2(b: usize) -> usize {
|
||||||
assert_eq!(
|
42
|
||||||
AssistantEditKind::resolve_location(&snapshot, "Foo.bar.baz.qux()")
|
}
|
||||||
.to_point(&snapshot),
|
"
|
||||||
Point::new(1, 0)..Point::new(4, 14)
|
.unindent(),
|
||||||
);
|
"Foo.bar.baz.qux()",
|
||||||
}
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_location_resolution(
|
||||||
|
&"
|
||||||
|
class Something {
|
||||||
|
one() { return 1; }
|
||||||
|
« two() { return 2222; }
|
||||||
|
three() { return 333; }
|
||||||
|
four() { return 4444; }
|
||||||
|
five() { return 5555; }
|
||||||
|
six() { return 6666; }
|
||||||
|
» seven() { return 7; }
|
||||||
|
eight() { return 8; }
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
&"
|
||||||
|
two() { return 2222; }
|
||||||
|
four() { return 4444; }
|
||||||
|
five() { return 5555; }
|
||||||
|
six() { return 6666; }
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_resolve_edits(cx: &mut AppContext) {
|
fn test_resolve_edits(cx: &mut AppContext) {
|
||||||
let settings_store = SettingsStore::test(cx);
|
init_test(cx);
|
||||||
cx.set_global(settings_store);
|
|
||||||
language::init(cx);
|
|
||||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
|
||||||
settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_edits(
|
assert_edits(
|
||||||
"
|
"
|
||||||
@@ -675,7 +670,7 @@ mod tests {
|
|||||||
last_name: String,
|
last_name: String,
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
description: "".into(),
|
description: None,
|
||||||
},
|
},
|
||||||
AssistantEditKind::Update {
|
AssistantEditKind::Update {
|
||||||
old_text: "
|
old_text: "
|
||||||
@@ -690,7 +685,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
description: "".into(),
|
description: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"
|
"
|
||||||
@@ -717,7 +712,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Ensure InsertBefore merges correctly with Update of the same text
|
// Ensure InsertBefore merges correctly with Update of the same text
|
||||||
|
|
||||||
assert_edits(
|
assert_edits(
|
||||||
"
|
"
|
||||||
fn foo() {
|
fn foo() {
|
||||||
@@ -735,7 +729,7 @@ mod tests {
|
|||||||
qux();
|
qux();
|
||||||
}"
|
}"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
description: "implement bar".into(),
|
description: Some("implement bar".into()),
|
||||||
},
|
},
|
||||||
AssistantEditKind::Update {
|
AssistantEditKind::Update {
|
||||||
old_text: "
|
old_text: "
|
||||||
@@ -748,7 +742,7 @@ mod tests {
|
|||||||
bar();
|
bar();
|
||||||
}"
|
}"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
description: "call bar in foo".into(),
|
description: Some("call bar in foo".into()),
|
||||||
},
|
},
|
||||||
AssistantEditKind::InsertAfter {
|
AssistantEditKind::InsertAfter {
|
||||||
old_text: "
|
old_text: "
|
||||||
@@ -763,7 +757,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
description: "implement qux".into(),
|
description: Some("implement qux".into()),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"
|
"
|
||||||
@@ -782,6 +776,153 @@ mod tests {
|
|||||||
.unindent(),
|
.unindent(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Correctly indent new text when replacing multiple adjacent indented blocks.
|
||||||
|
assert_edits(
|
||||||
|
"
|
||||||
|
impl Numbers {
|
||||||
|
fn one() {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn two() {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn three() {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
vec![
|
||||||
|
AssistantEditKind::Update {
|
||||||
|
old_text: "
|
||||||
|
fn one() {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
new_text: "
|
||||||
|
fn one() {
|
||||||
|
101
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
AssistantEditKind::Update {
|
||||||
|
old_text: "
|
||||||
|
fn two() {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
new_text: "
|
||||||
|
fn two() {
|
||||||
|
102
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
AssistantEditKind::Update {
|
||||||
|
old_text: "
|
||||||
|
fn three() {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
new_text: "
|
||||||
|
fn three() {
|
||||||
|
103
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"
|
||||||
|
impl Numbers {
|
||||||
|
fn one() {
|
||||||
|
101
|
||||||
|
}
|
||||||
|
|
||||||
|
fn two() {
|
||||||
|
102
|
||||||
|
}
|
||||||
|
|
||||||
|
fn three() {
|
||||||
|
103
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_edits(
|
||||||
|
"
|
||||||
|
impl Person {
|
||||||
|
fn set_name(&mut self, name: String) {
|
||||||
|
self.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
return self.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
vec![
|
||||||
|
AssistantEditKind::Update {
|
||||||
|
old_text: "self.name = name;".unindent(),
|
||||||
|
new_text: "self._name = name;".unindent(),
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
AssistantEditKind::Update {
|
||||||
|
old_text: "return self.name;\n".unindent(),
|
||||||
|
new_text: "return self._name;\n".unindent(),
|
||||||
|
description: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"
|
||||||
|
impl Person {
|
||||||
|
fn set_name(&mut self, name: String) {
|
||||||
|
self._name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
return self._name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_test(cx: &mut AppContext) {
|
||||||
|
let settings_store = SettingsStore::test(cx);
|
||||||
|
cx.set_global(settings_store);
|
||||||
|
language::init(cx);
|
||||||
|
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||||
|
settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_location_resolution(
|
||||||
|
text_with_expected_range: &str,
|
||||||
|
query: &str,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) {
|
||||||
|
let (text, _) = marked_text_ranges(text_with_expected_range, false);
|
||||||
|
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
|
||||||
|
let text_with_actual_range = generate_marked_text(&text, &[range], false);
|
||||||
|
pretty_assertions::assert_eq!(text_with_actual_range, text_with_expected_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ impl PromptBuilder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_content_prompt(
|
pub fn generate_inline_transformation_prompt(
|
||||||
&self,
|
&self,
|
||||||
user_prompt: String,
|
user_prompt: String,
|
||||||
language_name: Option<&LanguageName>,
|
language_name: Option<&LanguageName>,
|
||||||
@@ -310,8 +310,8 @@ impl PromptBuilder {
|
|||||||
.render("terminal_assistant_prompt", &context)
|
.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("edit_workflow", &())
|
self.handlebars.lock().render("suggest_edits", &())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_project_slash_command_prompt(
|
pub fn generate_project_slash_command_prompt(
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ pub mod now_command;
|
|||||||
pub mod project_command;
|
pub mod project_command;
|
||||||
pub mod prompt_command;
|
pub mod prompt_command;
|
||||||
pub mod search_command;
|
pub mod search_command;
|
||||||
|
pub mod selection_command;
|
||||||
|
pub mod streaming_example_command;
|
||||||
pub mod symbols_command;
|
pub mod symbols_command;
|
||||||
pub mod tab_command;
|
pub mod tab_command;
|
||||||
pub mod terminal_command;
|
pub mod terminal_command;
|
||||||
pub mod workflow_command;
|
|
||||||
|
|
||||||
pub(crate) struct SlashCommandCompletionProvider {
|
pub(crate) struct SlashCommandCompletionProvider {
|
||||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||||
@@ -126,7 +127,6 @@ impl SlashCommandCompletionProvider {
|
|||||||
&command_name,
|
&command_name,
|
||||||
&[],
|
&[],
|
||||||
true,
|
true,
|
||||||
false,
|
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -211,7 +211,6 @@ impl SlashCommandCompletionProvider {
|
|||||||
&command_name,
|
&command_name,
|
||||||
&completed_arguments,
|
&completed_arguments,
|
||||||
true,
|
true,
|
||||||
false,
|
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use super::create_label_for_command;
|
|
||||||
use super::{SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use feature_flags::FeatureFlag;
|
use feature_flags::FeatureFlag;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
|
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
|
||||||
@@ -13,10 +14,12 @@ use language_model::{
|
|||||||
use semantic_index::{FileSummary, SemanticDb};
|
use semantic_index::{FileSummary, SemanticDb};
|
||||||
use smol::channel;
|
use smol::channel;
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use ui::{BorrowAppContext, WindowContext};
|
use ui::{prelude::*, BorrowAppContext, WindowContext};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::slash_command::create_label_for_command;
|
||||||
|
|
||||||
pub struct AutoSlashCommandFeatureFlag;
|
pub struct AutoSlashCommandFeatureFlag;
|
||||||
|
|
||||||
impl FeatureFlag for AutoSlashCommandFeatureFlag {
|
impl FeatureFlag for AutoSlashCommandFeatureFlag {
|
||||||
@@ -34,6 +37,10 @@ impl SlashCommand for AutoCommand {
|
|||||||
"Automatically infer what context to add".into()
|
"Automatically infer what context to add".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::Wand
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -92,7 +99,7 @@ impl SlashCommand for AutoCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||||
};
|
};
|
||||||
@@ -144,7 +151,8 @@ impl SlashCommand for AutoCommand {
|
|||||||
text: prompt,
|
text: prompt,
|
||||||
sections: Vec::new(),
|
sections: Vec::new(),
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{AppContext, Model, Task, WeakView};
|
use gpui::{AppContext, Model, Task, WeakView};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
@@ -123,7 +125,7 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let output = workspace.update(cx, |workspace, cx| {
|
let output = workspace.update(cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let fs = workspace.project().read(cx).fs().clone();
|
let fs = workspace.project().read(cx).fs().clone();
|
||||||
@@ -145,7 +147,8 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
|
|||||||
metadata: None,
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use super::create_label_for_command;
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
|
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
|
||||||
SlashCommandOutputSection,
|
SlashCommandOutputSection, SlashCommandResult,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_servers::{
|
use context_servers::{
|
||||||
@@ -17,6 +16,8 @@ use text::LineEnding;
|
|||||||
use ui::{IconName, SharedString};
|
use ui::{IconName, SharedString};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::slash_command::create_label_for_command;
|
||||||
|
|
||||||
pub struct ContextServerSlashCommand {
|
pub struct ContextServerSlashCommand {
|
||||||
server_id: String,
|
server_id: String,
|
||||||
prompt: Prompt,
|
prompt: Prompt,
|
||||||
@@ -128,7 +129,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let server_id = self.server_id.clone();
|
let server_id = self.server_id.clone();
|
||||||
let prompt_name = self.prompt.name.clone();
|
let prompt_name = self.prompt.name.clone();
|
||||||
|
|
||||||
@@ -145,7 +146,28 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
return Err(anyhow!("Context server not initialized"));
|
return Err(anyhow!("Context server not initialized"));
|
||||||
};
|
};
|
||||||
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
||||||
let mut prompt = result.prompt;
|
|
||||||
|
// Check that there are only user roles
|
||||||
|
if result
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.any(|msg| !matches!(msg.role, context_servers::types::SamplingRole::User))
|
||||||
|
{
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Prompt contains non-user roles, which is not supported"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract text from user messages into a single prompt string
|
||||||
|
let mut prompt = result
|
||||||
|
.messages
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|msg| match msg.content {
|
||||||
|
context_servers::types::SamplingContent::Text { text } => Some(text),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n\n");
|
||||||
|
|
||||||
// We must normalize the line endings here, since servers might return CR characters.
|
// We must normalize the line endings here, since servers might return CR characters.
|
||||||
LineEnding::normalize(&mut prompt);
|
LineEnding::normalize(&mut prompt);
|
||||||
@@ -163,7 +185,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
}],
|
}],
|
||||||
text: prompt,
|
text: prompt,
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Err(anyhow!("Context server not found")))
|
Task::ready(Err(anyhow!("Context server not found")))
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
|
||||||
use crate::prompt_library::PromptStore;
|
use crate::prompt_library::PromptStore;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -48,7 +50,7 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let store = PromptStore::global(cx);
|
let store = PromptStore::global(cx);
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
let store = store.await?;
|
let store = store.await?;
|
||||||
@@ -76,7 +78,8 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
}],
|
}],
|
||||||
text,
|
text,
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
|
use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
@@ -9,6 +10,7 @@ use gpui::{Task, WeakView, WindowContext};
|
|||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use text::OffsetRangeExt;
|
use text::OffsetRangeExt;
|
||||||
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct DeltaSlashCommand;
|
pub(crate) struct DeltaSlashCommand;
|
||||||
@@ -26,6 +28,10 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::Diff
|
||||||
|
}
|
||||||
|
|
||||||
fn requires_argument(&self) -> bool {
|
fn requires_argument(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -37,7 +43,7 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
unimplemented!()
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@@ -48,10 +54,11 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let mut paths = HashSet::default();
|
let mut paths = HashSet::default();
|
||||||
let mut file_command_old_outputs = Vec::new();
|
let mut file_command_old_outputs = Vec::new();
|
||||||
let mut file_command_new_outputs = Vec::new();
|
let mut file_command_new_outputs = Vec::new();
|
||||||
|
|
||||||
for section in context_slash_command_output_sections.iter().rev() {
|
for section in context_slash_command_output_sections.iter().rev() {
|
||||||
if let Some(metadata) = section
|
if let Some(metadata) = section
|
||||||
.metadata
|
.metadata
|
||||||
@@ -78,6 +85,7 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
|
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
let mut output = SlashCommandOutput::default();
|
let mut output = SlashCommandOutput::default();
|
||||||
|
let mut changes_detected = false;
|
||||||
|
|
||||||
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
|
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
|
||||||
for (old_text, new_output) in file_command_old_outputs
|
for (old_text, new_output) in file_command_old_outputs
|
||||||
@@ -85,25 +93,33 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
.zip(file_command_new_outputs)
|
.zip(file_command_new_outputs)
|
||||||
{
|
{
|
||||||
if let Ok(new_output) = new_output {
|
if let Ok(new_output) = new_output {
|
||||||
if let Some(file_command_range) = new_output.sections.first() {
|
if let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await
|
||||||
let new_text = &new_output.text[file_command_range.range.clone()];
|
{
|
||||||
if old_text.chars().ne(new_text.chars()) {
|
if let Some(file_command_range) = new_output.sections.first() {
|
||||||
output.sections.extend(new_output.sections.into_iter().map(
|
let new_text = &new_output.text[file_command_range.range.clone()];
|
||||||
|section| SlashCommandOutputSection {
|
if old_text.chars().ne(new_text.chars()) {
|
||||||
range: output.text.len() + section.range.start
|
changes_detected = true;
|
||||||
..output.text.len() + section.range.end,
|
output.sections.extend(new_output.sections.into_iter().map(
|
||||||
icon: section.icon,
|
|section| SlashCommandOutputSection {
|
||||||
label: section.label,
|
range: output.text.len() + section.range.start
|
||||||
metadata: section.metadata,
|
..output.text.len() + section.range.end,
|
||||||
},
|
icon: section.icon,
|
||||||
));
|
label: section.label,
|
||||||
output.text.push_str(&new_output.text);
|
metadata: section.metadata,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
output.text.push_str(&new_output.text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output)
|
if !changes_detected {
|
||||||
|
return Err(anyhow!("no new changes detected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use fuzzy::{PathMatch, StringMatchCandidate};
|
use fuzzy::{PathMatch, StringMatchCandidate};
|
||||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{
|
use language::{
|
||||||
@@ -19,6 +21,8 @@ use util::paths::PathMatcher;
|
|||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::slash_command::create_label_for_command;
|
||||||
|
|
||||||
pub(crate) struct DiagnosticsSlashCommand;
|
pub(crate) struct DiagnosticsSlashCommand;
|
||||||
|
|
||||||
impl DiagnosticsSlashCommand {
|
impl DiagnosticsSlashCommand {
|
||||||
@@ -94,6 +98,10 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
"Insert diagnostics".into()
|
"Insert diagnostics".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::XCircle
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -167,7 +175,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||||
};
|
};
|
||||||
@@ -176,7 +184,11 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
|
|
||||||
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
||||||
|
|
||||||
cx.spawn(move |_| async move { task.await?.ok_or_else(|| anyhow!("No diagnostics found")) })
|
cx.spawn(move |_| async move {
|
||||||
|
task.await?
|
||||||
|
.map(|output| output.to_event_stream())
|
||||||
|
.ok_or_else(|| anyhow!("No diagnostics found"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::time::Duration;
|
|||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
|
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
|
||||||
use indexed_docs::{
|
use indexed_docs::{
|
||||||
@@ -274,7 +275,7 @@ impl SlashCommand for DocsSlashCommand {
|
|||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
if arguments.is_empty() {
|
if arguments.is_empty() {
|
||||||
return Task::ready(Err(anyhow!("missing an argument")));
|
return Task::ready(Err(anyhow!("missing an argument")));
|
||||||
};
|
};
|
||||||
@@ -355,7 +356,8 @@ impl SlashCommand for DocsSlashCommand {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::sync::Arc;
|
|||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
@@ -133,7 +134,7 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(argument) = arguments.first() else {
|
let Some(argument) = arguments.first() else {
|
||||||
return Task::ready(Err(anyhow!("missing URL")));
|
return Task::ready(Err(anyhow!("missing URL")));
|
||||||
};
|
};
|
||||||
@@ -166,7 +167,8 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
metadata: None,
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
use super::{diagnostics_command::collect_buffer_diagnostics, SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
|
||||||
|
SlashCommandOutput, SlashCommandOutputSection, SlashCommandResult,
|
||||||
|
};
|
||||||
|
use futures::channel::mpsc;
|
||||||
|
use futures::Stream;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project};
|
use project::{PathMatchCandidateSet, Project};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use smol::stream::StreamExt;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
ops::{Range, RangeInclusive},
|
ops::{Range, RangeInclusive},
|
||||||
@@ -110,7 +115,7 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
"Insert file".into()
|
"Insert file and/or directory".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
@@ -121,6 +126,10 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::File
|
||||||
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
@@ -181,7 +190,7 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||||
};
|
};
|
||||||
@@ -190,7 +199,12 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
return Task::ready(Err(anyhow!("missing path")));
|
return Task::ready(Err(anyhow!("missing path")));
|
||||||
};
|
};
|
||||||
|
|
||||||
collect_files(workspace.read(cx).project().clone(), arguments, cx)
|
Task::ready(Ok(collect_files(
|
||||||
|
workspace.read(cx).project().clone(),
|
||||||
|
arguments,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.boxed()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +212,7 @@ fn collect_files(
|
|||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
glob_inputs: &[String],
|
glob_inputs: &[String],
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> impl Stream<Item = Result<SlashCommandEvent>> {
|
||||||
let Ok(matchers) = glob_inputs
|
let Ok(matchers) = glob_inputs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|glob_input| {
|
.map(|glob_input| {
|
||||||
@@ -207,7 +221,7 @@ fn collect_files(
|
|||||||
})
|
})
|
||||||
.collect::<anyhow::Result<Vec<custom_path_matcher::PathMatcher>>>()
|
.collect::<anyhow::Result<Vec<custom_path_matcher::PathMatcher>>>()
|
||||||
else {
|
else {
|
||||||
return Task::ready(Err(anyhow!("invalid path")));
|
return futures::stream::once(async { Err(anyhow!("invalid path")) }).boxed();
|
||||||
};
|
};
|
||||||
|
|
||||||
let project_handle = project.downgrade();
|
let project_handle = project.downgrade();
|
||||||
@@ -217,11 +231,11 @@ fn collect_files(
|
|||||||
.map(|worktree| worktree.read(cx).snapshot())
|
.map(|worktree| worktree.read(cx).snapshot())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let (events_tx, events_rx) = mpsc::unbounded();
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let mut output = SlashCommandOutput::default();
|
|
||||||
for snapshot in snapshots {
|
for snapshot in snapshots {
|
||||||
let worktree_id = snapshot.id();
|
let worktree_id = snapshot.id();
|
||||||
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
let mut directory_stack: Vec<Arc<Path>> = Vec::new();
|
||||||
let mut folded_directory_names_stack = Vec::new();
|
let mut folded_directory_names_stack = Vec::new();
|
||||||
let mut is_top_level_directory = true;
|
let mut is_top_level_directory = true;
|
||||||
|
|
||||||
@@ -237,17 +251,19 @@ fn collect_files(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some((dir, _, _)) = directory_stack.last() {
|
while let Some(dir) = directory_stack.last() {
|
||||||
if entry.path.starts_with(dir) {
|
if entry.path.starts_with(dir) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let (_, entry_name, start) = directory_stack.pop().unwrap();
|
directory_stack.pop().unwrap();
|
||||||
output.sections.push(build_entry_output_section(
|
events_tx
|
||||||
start..output.text.len().saturating_sub(1),
|
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||||
Some(&PathBuf::from(entry_name)),
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
true,
|
SlashCommandContent::Text {
|
||||||
None,
|
text: "\n".into(),
|
||||||
));
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename = entry
|
let filename = entry
|
||||||
@@ -279,23 +295,46 @@ fn collect_files(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
||||||
let entry_start = output.text.len();
|
|
||||||
if prefix_paths.is_empty() {
|
if prefix_paths.is_empty() {
|
||||||
if is_top_level_directory {
|
let label = if is_top_level_directory {
|
||||||
output
|
|
||||||
.text
|
|
||||||
.push_str(&path_including_worktree_name.to_string_lossy());
|
|
||||||
is_top_level_directory = false;
|
is_top_level_directory = false;
|
||||||
|
path_including_worktree_name.to_string_lossy().to_string()
|
||||||
} else {
|
} else {
|
||||||
output.text.push_str(&filename);
|
filename
|
||||||
}
|
};
|
||||||
directory_stack.push((entry.path.clone(), filename, entry_start));
|
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::Folder,
|
||||||
|
label: label.clone().into(),
|
||||||
|
metadata: None,
|
||||||
|
}))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
|
SlashCommandContent::Text {
|
||||||
|
text: label,
|
||||||
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
|
directory_stack.push(entry.path.clone());
|
||||||
} else {
|
} else {
|
||||||
let entry_name = format!("{}/{}", prefix_paths, &filename);
|
let entry_name = format!("{}/{}", prefix_paths, &filename);
|
||||||
output.text.push_str(&entry_name);
|
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||||
directory_stack.push((entry.path.clone(), entry_name, entry_start));
|
icon: IconName::Folder,
|
||||||
|
label: entry_name.clone().into(),
|
||||||
|
metadata: None,
|
||||||
|
}))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
|
SlashCommandContent::Text {
|
||||||
|
text: entry_name,
|
||||||
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
|
directory_stack.push(entry.path.clone());
|
||||||
}
|
}
|
||||||
output.text.push('\n');
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
|
SlashCommandContent::Text {
|
||||||
|
text: "\n".into(),
|
||||||
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
} else if entry.is_file() {
|
} else if entry.is_file() {
|
||||||
let Some(open_buffer_task) = project_handle
|
let Some(open_buffer_task) = project_handle
|
||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
@@ -306,6 +345,7 @@ fn collect_files(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(buffer) = open_buffer_task.await.log_err() {
|
if let Some(buffer) = open_buffer_task.await.log_err() {
|
||||||
|
let mut output = SlashCommandOutput::default();
|
||||||
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
|
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
|
||||||
append_buffer_to_output(
|
append_buffer_to_output(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
@@ -313,33 +353,24 @@ fn collect_files(
|
|||||||
&mut output,
|
&mut output,
|
||||||
)
|
)
|
||||||
.log_err();
|
.log_err();
|
||||||
|
let mut buffer_events = output.to_event_stream();
|
||||||
|
while let Some(event) = buffer_events.next().await {
|
||||||
|
events_tx.unbounded_send(event)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some((dir, entry, start)) = directory_stack.pop() {
|
while let Some(_) = directory_stack.pop() {
|
||||||
if directory_stack.is_empty() {
|
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||||
let mut root_path = PathBuf::new();
|
|
||||||
root_path.push(snapshot.root_name());
|
|
||||||
root_path.push(&dir);
|
|
||||||
output.sections.push(build_entry_output_section(
|
|
||||||
start..output.text.len(),
|
|
||||||
Some(&root_path),
|
|
||||||
true,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
output.sections.push(build_entry_output_section(
|
|
||||||
start..output.text.len(),
|
|
||||||
Some(&PathBuf::from(entry.as_str())),
|
|
||||||
true,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(output)
|
|
||||||
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
|
events_rx.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn codeblock_fence_for_path(
|
pub fn codeblock_fence_for_path(
|
||||||
@@ -510,8 +541,6 @@ pub fn append_buffer_to_output(
|
|||||||
output.text.push('\n');
|
output.text.push('\n');
|
||||||
|
|
||||||
let section_ix = output.sections.len();
|
let section_ix = output.sections.len();
|
||||||
collect_buffer_diagnostics(output, buffer, false);
|
|
||||||
|
|
||||||
output.sections.insert(
|
output.sections.insert(
|
||||||
section_ix,
|
section_ix,
|
||||||
build_entry_output_section(prev_len..output.text.len(), path, false, None),
|
build_entry_output_section(prev_len..output.text.len(), path, false, None),
|
||||||
@@ -524,11 +553,14 @@ pub fn append_buffer_to_output(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use assistant_slash_command::SlashCommandOutput;
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
use smol::stream::StreamExt;
|
||||||
|
|
||||||
use crate::slash_command::file_command::collect_files;
|
use crate::slash_command::file_command::collect_files;
|
||||||
|
|
||||||
@@ -569,8 +601,9 @@ mod test {
|
|||||||
|
|
||||||
let project = Project::test(fs, ["/root".as_ref()], cx).await;
|
let project = Project::test(fs, ["/root".as_ref()], cx).await;
|
||||||
|
|
||||||
let result_1 = cx
|
let result_1 =
|
||||||
.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx))
|
cx.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx));
|
||||||
|
let result_1 = SlashCommandOutput::from_event_stream(result_1.boxed())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -578,17 +611,17 @@ mod test {
|
|||||||
// 4 files + 2 directories
|
// 4 files + 2 directories
|
||||||
assert_eq!(result_1.sections.len(), 6);
|
assert_eq!(result_1.sections.len(), 6);
|
||||||
|
|
||||||
let result_2 = cx
|
let result_2 =
|
||||||
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
|
cx.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx));
|
||||||
|
let result_2 = SlashCommandOutput::from_event_stream(result_2.boxed())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result_1, result_2);
|
assert_eq!(result_1, result_2);
|
||||||
|
|
||||||
let result = cx
|
let result =
|
||||||
.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx))
|
cx.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx).boxed());
|
||||||
.await
|
let result = SlashCommandOutput::from_event_stream(result).await.unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(result.text.starts_with("root/dir"));
|
assert!(result.text.starts_with("root/dir"));
|
||||||
// 5 files + 2 directories
|
// 5 files + 2 directories
|
||||||
@@ -631,8 +664,9 @@ mod test {
|
|||||||
|
|
||||||
let project = Project::test(fs, ["/zed".as_ref()], cx).await;
|
let project = Project::test(fs, ["/zed".as_ref()], cx).await;
|
||||||
|
|
||||||
let result = cx
|
let result =
|
||||||
.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx))
|
cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx));
|
||||||
|
let result = SlashCommandOutput::from_event_stream(result.boxed())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -692,8 +726,9 @@ mod test {
|
|||||||
|
|
||||||
let project = Project::test(fs, ["/zed".as_ref()], cx).await;
|
let project = Project::test(fs, ["/zed".as_ref()], cx).await;
|
||||||
|
|
||||||
let result = cx
|
let result =
|
||||||
.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx))
|
cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx));
|
||||||
|
let result = SlashCommandOutput::from_event_stream(result.boxed())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -716,6 +751,8 @@ mod test {
|
|||||||
assert_eq!(result.sections[6].label, "summercamp");
|
assert_eq!(result.sections[6].label, "summercamp");
|
||||||
assert_eq!(result.sections[7].label, "zed/assets/themes");
|
assert_eq!(result.sections[7].label, "zed/assets/themes");
|
||||||
|
|
||||||
|
assert_eq!(result.text, "zed/assets/themes\n```zed/assets/themes/LICENSE\n1\n```\n\nsummercamp\n```zed/assets/themes/summercamp/LICENSE\n1\n```\n\nsubdir\n```zed/assets/themes/summercamp/subdir/LICENSE\n1\n```\n\nsubsubdir\n```zed/assets/themes/summercamp/subdir/subsubdir/LICENSE\n3\n```\n\n");
|
||||||
|
|
||||||
// Ensure that the project lasts until after the last await
|
// Ensure that the project lasts until after the last await
|
||||||
drop(project);
|
drop(project);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
@@ -48,7 +49,7 @@ impl SlashCommand for NowSlashCommand {
|
|||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let text = format!("Today is {now}.", now = now.to_rfc2822());
|
let text = format!("Today is {now}.", now = now.to_rfc2822());
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
@@ -62,6 +63,7 @@ impl SlashCommand for NowSlashCommand {
|
|||||||
metadata: None,
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}
|
||||||
|
.to_event_stream()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::PromptBuilder;
|
use crate::PromptBuilder;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
|
||||||
use feature_flags::FeatureFlag;
|
use feature_flags::FeatureFlag;
|
||||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||||
use language::{Anchor, CodeLabel, LspAdapterDelegate};
|
use language::{Anchor, CodeLabel, LspAdapterDelegate};
|
||||||
@@ -24,7 +24,8 @@ use std::{
|
|||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{BorrowAppContext as _, IconName};
|
|
||||||
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub struct ProjectSlashCommand {
|
pub struct ProjectSlashCommand {
|
||||||
@@ -50,6 +51,10 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
"Generate a semantic search based on context".into()
|
"Generate a semantic search based on context".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::Folder
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -76,7 +81,7 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
let current_model = model_registry.active_model();
|
let current_model = model_registry.active_model();
|
||||||
let prompt_builder = self.prompt_builder.clone();
|
let prompt_builder = self.prompt_builder.clone();
|
||||||
@@ -162,7 +167,8 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
text: output,
|
text: output,
|
||||||
sections,
|
sections,
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
|
||||||
use crate::prompt_library::PromptStore;
|
use crate::prompt_library::PromptStore;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
@@ -19,6 +21,10 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
"Insert prompt from library".into()
|
"Insert prompt from library".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::Library
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -61,7 +67,7 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let title = arguments.to_owned().join(" ");
|
let title = arguments.to_owned().join(" ");
|
||||||
if title.trim().is_empty() {
|
if title.trim().is_empty() {
|
||||||
return Task::ready(Err(anyhow!("missing prompt name")));
|
return Task::ready(Err(anyhow!("missing prompt name")));
|
||||||
@@ -100,7 +106,8 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
metadata: None,
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use super::{
|
|
||||||
create_label_for_command,
|
|
||||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
|
||||||
SlashCommand, SlashCommandOutput,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use feature_flags::FeatureFlag;
|
use feature_flags::FeatureFlag;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
@@ -16,10 +14,17 @@ use std::{
|
|||||||
use ui::{prelude::*, IconName};
|
use ui::{prelude::*, IconName};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::slash_command::create_label_for_command;
|
||||||
|
use crate::slash_command::file_command::{build_entry_output_section, codeblock_fence_for_path};
|
||||||
|
|
||||||
pub(crate) struct SearchSlashCommandFeatureFlag;
|
pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||||
|
|
||||||
impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
||||||
const NAME: &'static str = "search-slash-command";
|
const NAME: &'static str = "search-slash-command";
|
||||||
|
|
||||||
|
fn enabled_for_staff() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct SearchSlashCommand;
|
pub(crate) struct SearchSlashCommand;
|
||||||
@@ -37,6 +42,10 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
"Search your project semantically".into()
|
"Search your project semantically".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::SearchCode
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -63,7 +72,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||||
};
|
};
|
||||||
@@ -129,6 +138,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
sections,
|
sections,
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}
|
}
|
||||||
|
.to_event_stream()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
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 { metadata: None }));
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
118
crates/assistant/src/slash_command/streaming_example_command.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
|
||||||
|
SlashCommandOutputSection, SlashCommandResult,
|
||||||
|
};
|
||||||
|
use feature_flags::FeatureFlag;
|
||||||
|
use futures::channel::mpsc;
|
||||||
|
use gpui::{Task, WeakView};
|
||||||
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
|
use smol::stream::StreamExt;
|
||||||
|
use smol::Timer;
|
||||||
|
use ui::prelude::*;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
pub struct StreamingExampleSlashCommandFeatureFlag;
|
||||||
|
|
||||||
|
impl FeatureFlag for StreamingExampleSlashCommandFeatureFlag {
|
||||||
|
const NAME: &'static str = "streaming-example-slash-command";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct StreamingExampleSlashCommand;
|
||||||
|
|
||||||
|
impl SlashCommand for StreamingExampleSlashCommand {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"streaming-example".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"An example slash command that showcases streaming.".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu_text(&self) -> String {
|
||||||
|
self.description()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_argument(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_argument(
|
||||||
|
self: Arc<Self>,
|
||||||
|
_arguments: &[String],
|
||||||
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
|
_cx: &mut WindowContext,
|
||||||
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
|
Task::ready(Ok(Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (events_tx, events_rx) = mpsc::unbounded();
|
||||||
|
cx.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::FileRust,
|
||||||
|
label: "Section 1".into(),
|
||||||
|
metadata: None,
|
||||||
|
}))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
|
SlashCommandContent::Text {
|
||||||
|
text: "Hello".into(),
|
||||||
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||||
|
|
||||||
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::FileRust,
|
||||||
|
label: "Section 2".into(),
|
||||||
|
metadata: None,
|
||||||
|
}))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
|
SlashCommandContent::Text {
|
||||||
|
text: "World".into(),
|
||||||
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||||
|
|
||||||
|
for n in 1..=10 {
|
||||||
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::StarFilled,
|
||||||
|
label: format!("Section {n}").into(),
|
||||||
|
metadata: None,
|
||||||
|
}))?;
|
||||||
|
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||||
|
SlashCommandContent::Text {
|
||||||
|
text: "lorem ipsum ".repeat(n).trim().into(),
|
||||||
|
run_commands_in_text: false,
|
||||||
|
},
|
||||||
|
)))?;
|
||||||
|
events_tx
|
||||||
|
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
|
Task::ready(Ok(events_rx.boxed()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
@@ -20,6 +22,10 @@ impl SlashCommand for OutlineSlashCommand {
|
|||||||
"Insert symbols for active tab".into()
|
"Insert symbols for active tab".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::ListTree
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -46,7 +52,7 @@ impl SlashCommand for OutlineSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let output = workspace.update(cx, |workspace, cx| {
|
let output = workspace.update(cx, |workspace, cx| {
|
||||||
let Some(active_item) = workspace.active_item(cx) else {
|
let Some(active_item) = workspace.active_item(cx) else {
|
||||||
return Task::ready(Err(anyhow!("no active tab")));
|
return Task::ready(Err(anyhow!("no active tab")));
|
||||||
@@ -83,7 +89,8 @@ impl SlashCommand for OutlineSlashCommand {
|
|||||||
}],
|
}],
|
||||||
text: outline_text,
|
text: outline_text,
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
}
|
||||||
|
.to_event_stream())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{
|
||||||
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
|
};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
@@ -10,10 +12,12 @@ use std::{
|
|||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{ActiveTheme, WindowContext};
|
use ui::{prelude::*, ActiveTheme, WindowContext};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::slash_command::file_command::append_buffer_to_output;
|
||||||
|
|
||||||
pub(crate) struct TabSlashCommand;
|
pub(crate) struct TabSlashCommand;
|
||||||
|
|
||||||
const ALL_TABS_COMPLETION_ITEM: &str = "all";
|
const ALL_TABS_COMPLETION_ITEM: &str = "all";
|
||||||
@@ -27,6 +31,10 @@ impl SlashCommand for TabSlashCommand {
|
|||||||
"Insert open tabs (active tab by default)".to_owned()
|
"Insert open tabs (active tab by default)".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::FileTree
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -132,7 +140,7 @@ impl SlashCommand for TabSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let tab_items_search = tab_items_for_queries(
|
let tab_items_search = tab_items_for_queries(
|
||||||
Some(workspace),
|
Some(workspace),
|
||||||
arguments,
|
arguments,
|
||||||
@@ -146,7 +154,7 @@ impl SlashCommand for TabSlashCommand {
|
|||||||
for (full_path, buffer, _) in tab_items_search.await? {
|
for (full_path, buffer, _) in tab_items_search.await? {
|
||||||
append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
|
append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
|
||||||
}
|
}
|
||||||
Ok(output)
|
Ok(output.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use gpui::{AppContext, Task, View, WeakView};
|
use gpui::{AppContext, Task, View, WeakView};
|
||||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||||
@@ -32,6 +33,10 @@ impl SlashCommand for TerminalSlashCommand {
|
|||||||
"Insert terminal output".into()
|
"Insert terminal output".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::Terminal
|
||||||
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
@@ -62,7 +67,7 @@ impl SlashCommand for TerminalSlashCommand {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||||
};
|
};
|
||||||
@@ -96,7 +101,8 @@ impl SlashCommand for TerminalSlashCommand {
|
|||||||
metadata: None,
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}
|
||||||
|
.to_event_stream()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
use crate::prompts::PromptBuilder;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assistant_slash_command::{
|
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
|
||||||
};
|
|
||||||
use gpui::{Task, WeakView};
|
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
|
||||||
use ui::prelude::*;
|
|
||||||
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
pub(crate) struct WorkflowSlashCommand {
|
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkflowSlashCommand {
|
|
||||||
pub const NAME: &'static str = "workflow";
|
|
||||||
|
|
||||||
pub fn new(prompt_builder: Arc<PromptBuilder>) -> Self {
|
|
||||||
Self { prompt_builder }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SlashCommand for WorkflowSlashCommand {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
Self::NAME.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> String {
|
|
||||||
"Insert prompt to opt into the edit workflow".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
|
||||||
self.description()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn requires_argument(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_argument(
|
|
||||||
self: Arc<Self>,
|
|
||||||
_arguments: &[String],
|
|
||||||
_cancel: Arc<AtomicBool>,
|
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
|
||||||
_cx: &mut WindowContext,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
|
||||||
Task::ready(Ok(Vec::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Result<SlashCommandOutput>> {
|
|
||||||
let prompt_builder = self.prompt_builder.clone();
|
|
||||||
cx.spawn(|_cx| async move {
|
|
||||||
let text = prompt_builder.generate_workflow_prompt()?;
|
|
||||||
let range = 0..text.len();
|
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
|
||||||
text,
|
|
||||||
sections: vec![SlashCommandOutputSection {
|
|
||||||
range,
|
|
||||||
icon: IconName::Route,
|
|
||||||
label: "Workflow".into(),
|
|
||||||
metadata: None,
|
|
||||||
}],
|
|
||||||
run_commands_in_text: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
use gpui::AnyElement;
|
|
||||||
use gpui::DismissEvent;
|
|
||||||
use gpui::WeakView;
|
|
||||||
use picker::PickerEditorPosition;
|
|
||||||
|
|
||||||
use ui::ListItemSpacing;
|
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
|
||||||
|
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||||
use gpui::SharedString;
|
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||||
use gpui::Task;
|
|
||||||
use picker::{Picker, PickerDelegate};
|
|
||||||
use ui::{prelude::*, ListItem, PopoverMenu, PopoverTrigger};
|
|
||||||
|
|
||||||
use crate::assistant_panel::ContextEditor;
|
use crate::assistant_panel::ContextEditor;
|
||||||
|
|
||||||
@@ -27,6 +20,7 @@ struct SlashCommandInfo {
|
|||||||
name: SharedString,
|
name: SharedString,
|
||||||
description: SharedString,
|
description: SharedString,
|
||||||
args: Option<SharedString>,
|
args: Option<SharedString>,
|
||||||
|
icon: IconName,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -145,16 +139,20 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
||||||
if let SlashCommandEntry::Info(info) = command {
|
match command {
|
||||||
self.active_context_editor
|
SlashCommandEntry::Info(info) => {
|
||||||
.update(cx, |context_editor, cx| {
|
self.active_context_editor
|
||||||
context_editor.insert_command(&info.name, cx)
|
.update(cx, |context_editor, cx| {
|
||||||
})
|
context_editor.insert_command(&info.name, cx)
|
||||||
.ok();
|
})
|
||||||
} else if let SlashCommandEntry::Advert { on_confirm, .. } = command {
|
.ok();
|
||||||
on_confirm(cx);
|
}
|
||||||
|
SlashCommandEntry::Advert { on_confirm, .. } => {
|
||||||
|
on_confirm(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
@@ -178,53 +176,51 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
SlashCommandEntry::Info(info) => Some(
|
SlashCommandEntry::Info(info) => Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Dense)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
v_flex()
|
||||||
.group(format!("command-entry-label-{ix}"))
|
.group(format!("command-entry-label-{ix}"))
|
||||||
.w_full()
|
.w_full()
|
||||||
.min_w(px(250.))
|
.min_w(px(250.))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
h_flex()
|
||||||
.child(
|
.gap_1p5()
|
||||||
h_flex()
|
.child(Icon::new(info.icon).size(IconSize::XSmall))
|
||||||
.child(div().font_buffer(cx).child({
|
.child(div().font_buffer(cx).child({
|
||||||
let mut label = format!("/{}", info.name);
|
let mut label = format!("{}", info.name);
|
||||||
if let Some(args) =
|
if let Some(args) = info.args.as_ref().filter(|_| selected)
|
||||||
info.args.as_ref().filter(|_| selected)
|
{
|
||||||
{
|
label.push_str(&args);
|
||||||
label.push_str(&args);
|
}
|
||||||
}
|
Label::new(label).size(LabelSize::Small)
|
||||||
Label::new(label).size(LabelSize::Small)
|
}))
|
||||||
}))
|
.children(info.args.clone().filter(|_| !selected).map(
|
||||||
.children(info.args.clone().filter(|_| !selected).map(
|
|args| {
|
||||||
|args| {
|
div()
|
||||||
div()
|
.font_buffer(cx)
|
||||||
.font_buffer(cx)
|
.child(
|
||||||
.child(
|
Label::new(args)
|
||||||
Label::new(args)
|
.size(LabelSize::Small)
|
||||||
.size(LabelSize::Small)
|
.color(Color::Muted),
|
||||||
.color(Color::Muted),
|
)
|
||||||
)
|
.visible_on_hover(format!(
|
||||||
.visible_on_hover(format!(
|
"command-entry-label-{ix}"
|
||||||
"command-entry-label-{ix}"
|
))
|
||||||
))
|
},
|
||||||
},
|
)),
|
||||||
)),
|
)
|
||||||
)
|
.child(
|
||||||
.child(
|
Label::new(info.description.clone())
|
||||||
Label::new(info.description.clone())
|
.size(LabelSize::Small)
|
||||||
.size(LabelSize::Small)
|
.color(Color::Muted),
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SlashCommandEntry::Advert { renderer, .. } => Some(
|
SlashCommandEntry::Advert { renderer, .. } => Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Dense)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.child(renderer(cx)),
|
.child(renderer(cx)),
|
||||||
),
|
),
|
||||||
@@ -251,24 +247,40 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
|||||||
name: command_name.into(),
|
name: command_name.into(),
|
||||||
description: menu_text,
|
description: menu_text,
|
||||||
args,
|
args,
|
||||||
|
icon: command.icon(),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.chain([SlashCommandEntry::Advert {
|
.chain([SlashCommandEntry::Advert {
|
||||||
name: "create-your-command".into(),
|
name: "create-your-command".into(),
|
||||||
renderer: |cx| {
|
renderer: |cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.w_full()
|
||||||
.font_buffer(cx)
|
.font_buffer(cx)
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.justify_between()
|
||||||
.child(div().font_buffer(cx).child(
|
.child(
|
||||||
Label::new("create-your-command").size(LabelSize::Small),
|
h_flex()
|
||||||
))
|
.items_center()
|
||||||
.child(Icon::new(IconName::ArrowUpRight).size(IconSize::XSmall)),
|
.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(
|
.child(
|
||||||
Label::new("Learn how to create a custom command")
|
Label::new("Create your custom command")
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,13 +28,36 @@ impl Matrix {
|
|||||||
self.cols = cols;
|
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 {
|
fn get(&self, row: usize, col: usize) -> f64 {
|
||||||
if row >= self.rows {
|
if row >= self.rows {
|
||||||
panic!("row out of bounds")
|
panic!("row out of bounds")
|
||||||
}
|
}
|
||||||
|
|
||||||
if col >= self.cols {
|
if col >= self.cols {
|
||||||
panic!("col out of bounds")
|
panic!("column out of bounds")
|
||||||
}
|
}
|
||||||
self.cells[col * self.rows + row]
|
self.cells[col * self.rows + row]
|
||||||
}
|
}
|
||||||
@@ -45,7 +68,7 @@ impl Matrix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if col >= self.cols {
|
if col >= self.cols {
|
||||||
panic!("col out of bounds")
|
panic!("column out of bounds")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cells[col * self.rows + row] = value;
|
self.cells[col * self.rows + row] = value;
|
||||||
@@ -106,26 +129,32 @@ impl StreamingDiff {
|
|||||||
|
|
||||||
pub fn push_new(&mut self, text: &str) -> Vec<CharOperation> {
|
pub fn push_new(&mut self, text: &str) -> Vec<CharOperation> {
|
||||||
self.new.extend(text.chars());
|
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() {
|
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() {
|
for i in 1..=self.old.len() {
|
||||||
let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
|
let insertion_score = self.scores.get(i, relative_j - 1) + Self::INSERTION_SCORE;
|
||||||
let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_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 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);
|
let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
|
||||||
equal_run += 1;
|
equal_run += 1;
|
||||||
self.equal_runs.insert((i, j), equal_run);
|
self.equal_runs.insert((i, j), equal_run);
|
||||||
|
|
||||||
let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
|
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 {
|
} else {
|
||||||
f64::NEG_INFINITY
|
f64::NEG_INFINITY
|
||||||
};
|
};
|
||||||
|
|
||||||
let score = insertion_score.max(deletion_score).max(equality_score);
|
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 mut next_old_text_ix = self.old_text_ix;
|
||||||
let next_new_text_ix = self.new.len();
|
let next_new_text_ix = self.new.len();
|
||||||
for i in self.old_text_ix..=self.old.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 {
|
if score > max_score {
|
||||||
max_score = score;
|
max_score = score;
|
||||||
next_old_text_ix = i;
|
next_old_text_ix = i;
|
||||||
@@ -174,7 +203,9 @@ impl StreamingDiff {
|
|||||||
|
|
||||||
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
|
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
|
||||||
.iter()
|
.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()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent,
|
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent,
|
||||||
ModelSelector, DEFAULT_CONTEXT_LINES,
|
ModelSelector, RequestType, DEFAULT_CONTEXT_LINES,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
@@ -17,7 +17,8 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
logging::report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||||
|
LanguageModelRequestMessage, Role,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -251,7 +252,7 @@ impl TerminalInlineAssistant {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.active_context(cx)?
|
.active_context(cx)?
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.to_completion_request(cx),
|
.to_completion_request(RequestType::Chat, cx),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -306,6 +307,33 @@ impl TerminalInlineAssistant {
|
|||||||
this.focus_handle(cx).focus(cx);
|
this.focus_handle(cx).focus(cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
|
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||||
|
let codegen = assist.codegen.read(cx);
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
report_assistant_event(
|
||||||
|
AssistantEvent {
|
||||||
|
conversation_id: None,
|
||||||
|
kind: AssistantKind::InlineTerminal,
|
||||||
|
message_id: codegen.message_id.clone(),
|
||||||
|
phase: if undo {
|
||||||
|
AssistantPhase::Rejected
|
||||||
|
} else {
|
||||||
|
AssistantPhase::Accepted
|
||||||
|
},
|
||||||
|
model: model.telemetry_id(),
|
||||||
|
model_provider: model.provider_id().to_string(),
|
||||||
|
response_latency: None,
|
||||||
|
error_message: None,
|
||||||
|
language_name: None,
|
||||||
|
},
|
||||||
|
codegen.telemetry.clone(),
|
||||||
|
cx.http_client(),
|
||||||
|
model.api_key(cx),
|
||||||
|
&executor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assist.codegen.update(cx, |codegen, cx| {
|
assist.codegen.update(cx, |codegen, cx| {
|
||||||
if undo {
|
if undo {
|
||||||
codegen.undo(cx);
|
codegen.undo(cx);
|
||||||
@@ -1016,6 +1044,7 @@ pub struct Codegen {
|
|||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
terminal: Model<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
generation: Task<()>,
|
generation: Task<()>,
|
||||||
|
message_id: Option<String>,
|
||||||
transaction: Option<TerminalTransaction>,
|
transaction: Option<TerminalTransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,6 +1055,7 @@ impl Codegen {
|
|||||||
telemetry,
|
telemetry,
|
||||||
status: CodegenStatus::Idle,
|
status: CodegenStatus::Idle,
|
||||||
generation: Task::ready(()),
|
generation: Task::ready(()),
|
||||||
|
message_id: None,
|
||||||
transaction: None,
|
transaction: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1035,6 +1065,8 @@ impl Codegen {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let model_api_key = model.api_key(cx);
|
||||||
|
let http_client = cx.http_client();
|
||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
self.status = CodegenStatus::Pending;
|
self.status = CodegenStatus::Pending;
|
||||||
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
||||||
@@ -1043,44 +1075,62 @@ impl Codegen {
|
|||||||
let model_provider_id = model.provider_id();
|
let model_provider_id = model.provider_id();
|
||||||
let response = model.stream_completion_text(prompt, &cx).await;
|
let response = model.stream_completion_text(prompt, &cx).await;
|
||||||
let generate = async {
|
let generate = async {
|
||||||
|
let message_id = response
|
||||||
|
.as_ref()
|
||||||
|
.ok()
|
||||||
|
.and_then(|response| response.message_id.clone());
|
||||||
|
|
||||||
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
|
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
|
||||||
|
|
||||||
let task = cx.background_executor().spawn(async move {
|
let task = cx.background_executor().spawn({
|
||||||
let mut response_latency = None;
|
let message_id = message_id.clone();
|
||||||
let request_start = Instant::now();
|
let executor = cx.background_executor().clone();
|
||||||
let task = async {
|
async move {
|
||||||
let mut chunks = response?;
|
let mut response_latency = None;
|
||||||
while let Some(chunk) = chunks.next().await {
|
let request_start = Instant::now();
|
||||||
if response_latency.is_none() {
|
let task = async {
|
||||||
response_latency = Some(request_start.elapsed());
|
let mut chunks = response?.stream;
|
||||||
|
while let Some(chunk) = chunks.next().await {
|
||||||
|
if response_latency.is_none() {
|
||||||
|
response_latency = Some(request_start.elapsed());
|
||||||
|
}
|
||||||
|
let chunk = chunk?;
|
||||||
|
hunks_tx.send(chunk).await?;
|
||||||
}
|
}
|
||||||
let chunk = chunk?;
|
|
||||||
hunks_tx.send(chunk).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = task.await;
|
||||||
|
|
||||||
|
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||||
|
report_assistant_event(
|
||||||
|
AssistantEvent {
|
||||||
|
conversation_id: None,
|
||||||
|
kind: AssistantKind::InlineTerminal,
|
||||||
|
message_id,
|
||||||
|
phase: AssistantPhase::Response,
|
||||||
|
model: model_telemetry_id,
|
||||||
|
model_provider: model_provider_id.to_string(),
|
||||||
|
response_latency,
|
||||||
|
error_message,
|
||||||
|
language_name: None,
|
||||||
|
},
|
||||||
|
telemetry,
|
||||||
|
http_client,
|
||||||
|
model_api_key,
|
||||||
|
&executor,
|
||||||
|
);
|
||||||
|
|
||||||
|
result?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
};
|
|
||||||
|
|
||||||
let result = task.await;
|
|
||||||
|
|
||||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
|
||||||
if let Some(telemetry) = telemetry {
|
|
||||||
telemetry.report_assistant_event(AssistantEvent {
|
|
||||||
conversation_id: None,
|
|
||||||
kind: AssistantKind::Inline,
|
|
||||||
phase: AssistantPhase::Response,
|
|
||||||
model: model_telemetry_id,
|
|
||||||
model_provider: model_provider_id.to_string(),
|
|
||||||
response_latency,
|
|
||||||
error_message,
|
|
||||||
language_name: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
this.message_id = message_id;
|
||||||
|
})?;
|
||||||
|
|
||||||
while let Some(hunk) = hunks_rx.next().await {
|
while let Some(hunk) = hunks_rx.next().await {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(transaction) = &mut this.transaction {
|
if let Some(transaction) = &mut this.transaction {
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
pub mod context_server_tool;
|
||||||
pub mod now_tool;
|
pub mod now_tool;
|
||||||
|
|||||||
82
crates/assistant/src/tools/context_server_tool.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
use assistant_tool::Tool;
|
||||||
|
use context_servers::manager::ContextServerManager;
|
||||||
|
use context_servers::types;
|
||||||
|
use gpui::Task;
|
||||||
|
|
||||||
|
pub struct ContextServerTool {
|
||||||
|
server_id: String,
|
||||||
|
tool: types::Tool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextServerTool {
|
||||||
|
pub fn new(server_id: impl Into<String>, tool: types::Tool) -> Self {
|
||||||
|
Self {
|
||||||
|
server_id: server_id.into(),
|
||||||
|
tool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tool for ContextServerTool {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.tool.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
self.tool.description.clone().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_schema(&self) -> serde_json::Value {
|
||||||
|
match &self.tool.input_schema {
|
||||||
|
serde_json::Value::Null => {
|
||||||
|
serde_json::json!({ "type": "object", "properties": [] })
|
||||||
|
}
|
||||||
|
serde_json::Value::Object(map) if map.is_empty() => {
|
||||||
|
serde_json::json!({ "type": "object", "properties": [] })
|
||||||
|
}
|
||||||
|
_ => self.tool.input_schema.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self: std::sync::Arc<Self>,
|
||||||
|
input: serde_json::Value,
|
||||||
|
_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) {
|
||||||
|
cx.foreground_executor().spawn({
|
||||||
|
let tool_name = self.tool.name.clone();
|
||||||
|
async move {
|
||||||
|
let Some(protocol) = server.client.read().clone() else {
|
||||||
|
bail!("Context server not initialized");
|
||||||
|
};
|
||||||
|
|
||||||
|
let arguments = if let serde_json::Value::Object(map) = input {
|
||||||
|
Some(map.into_iter().collect())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
"Running tool: {} with arguments: {:?}",
|
||||||
|
tool_name,
|
||||||
|
arguments
|
||||||
|
);
|
||||||
|
let response = protocol.run_tool(tool_name, arguments).await?;
|
||||||
|
|
||||||
|
let tool_result = match response.tool_result {
|
||||||
|
serde_json::Value::String(s) => s,
|
||||||
|
_ => serde_json::to_string(&response.tool_result)?,
|
||||||
|
};
|
||||||
|
Ok(tool_result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Task::ready(Err(anyhow!("Context server not found")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,16 @@ path = "src/assistant_slash_command.rs"
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
language_model.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
pretty_assertions.workspace = true
|
||||||
|
workspace = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
mod slash_command_registry;
|
mod slash_command_registry;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use futures::stream::{self, BoxStream};
|
||||||
|
use futures::StreamExt;
|
||||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||||
|
pub use language_model::Role;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use slash_command_registry::*;
|
pub use slash_command_registry::*;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -56,8 +59,13 @@ pub struct ArgumentCompletion {
|
|||||||
pub replace_previous_arguments: bool,
|
pub replace_previous_arguments: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type SlashCommandResult = Result<BoxStream<'static, Result<SlashCommandEvent>>>;
|
||||||
|
|
||||||
pub trait SlashCommand: 'static + Send + Sync {
|
pub trait SlashCommand: 'static + Send + Sync {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
|
fn icon(&self) -> IconName {
|
||||||
|
IconName::Slash
|
||||||
|
}
|
||||||
fn label(&self, _cx: &AppContext) -> CodeLabel {
|
fn label(&self, _cx: &AppContext) -> CodeLabel {
|
||||||
CodeLabel::plain(self.name(), None)
|
CodeLabel::plain(self.name(), None)
|
||||||
}
|
}
|
||||||
@@ -87,7 +95,7 @@ pub trait SlashCommand: 'static + Send + Sync {
|
|||||||
// perhaps another kind of delegate is needed here.
|
// perhaps another kind of delegate is needed here.
|
||||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>>;
|
) -> Task<SlashCommandResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type RenderFoldPlaceholder = Arc<
|
pub type RenderFoldPlaceholder = Arc<
|
||||||
@@ -96,13 +104,160 @@ pub type RenderFoldPlaceholder = Arc<
|
|||||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum SlashCommandContent {
|
||||||
|
Text {
|
||||||
|
text: String,
|
||||||
|
run_commands_in_text: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Clone)]
|
||||||
pub struct SlashCommandOutput {
|
pub struct SlashCommandOutput {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
||||||
pub run_commands_in_text: bool,
|
pub run_commands_in_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SlashCommandOutput {
|
||||||
|
pub fn ensure_valid_section_ranges(&mut self) {
|
||||||
|
for section in &mut self.sections {
|
||||||
|
section.range.start = section.range.start.min(self.text.len());
|
||||||
|
section.range.end = section.range.end.min(self.text.len());
|
||||||
|
while !self.text.is_char_boundary(section.range.start) {
|
||||||
|
section.range.start -= 1;
|
||||||
|
}
|
||||||
|
while !self.text.is_char_boundary(section.range.end) {
|
||||||
|
section.range.end += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this [`SlashCommandOutput`] as a stream of [`SlashCommandEvent`]s.
|
||||||
|
pub fn to_event_stream(mut self) -> BoxStream<'static, Result<SlashCommandEvent>> {
|
||||||
|
self.ensure_valid_section_ranges();
|
||||||
|
|
||||||
|
let mut events = Vec::new();
|
||||||
|
let mut last_section_end = 0;
|
||||||
|
|
||||||
|
for section in self.sections {
|
||||||
|
if last_section_end < section.range.start {
|
||||||
|
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: self
|
||||||
|
.text
|
||||||
|
.get(last_section_end..section.range.start)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string(),
|
||||||
|
run_commands_in_text: self.run_commands_in_text,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_section_end < self.text.len() {
|
||||||
|
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: self.text[last_section_end..].to_string(),
|
||||||
|
run_commands_in_text: self.run_commands_in_text,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream::iter(events).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_event_stream(
|
||||||
|
mut events: BoxStream<'static, Result<SlashCommandEvent>>,
|
||||||
|
) -> Result<SlashCommandOutput> {
|
||||||
|
let mut output = SlashCommandOutput::default();
|
||||||
|
let mut section_stack = Vec::new();
|
||||||
|
|
||||||
|
while let Some(event) = events.next().await {
|
||||||
|
match event? {
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
metadata,
|
||||||
|
} => {
|
||||||
|
let start = output.text.len();
|
||||||
|
section_stack.push(SlashCommandOutputSection {
|
||||||
|
range: start..start,
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text,
|
||||||
|
run_commands_in_text,
|
||||||
|
}) => {
|
||||||
|
output.text.push_str(&text);
|
||||||
|
output.run_commands_in_text = run_commands_in_text;
|
||||||
|
|
||||||
|
if let Some(section) = section_stack.last_mut() {
|
||||||
|
section.range.end = output.text.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlashCommandEvent::EndSection { metadata } => {
|
||||||
|
if let Some(mut section) = section_stack.pop() {
|
||||||
|
section.metadata = metadata;
|
||||||
|
output.sections.push(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlashCommandEvent::StartMessage { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(section) = section_stack.pop() {
|
||||||
|
output.sections.push(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SlashCommandOutputSection<T> {
|
pub struct SlashCommandOutputSection<T> {
|
||||||
pub range: Range<T>,
|
pub range: Range<T>,
|
||||||
@@ -116,3 +271,243 @@ impl SlashCommandOutputSection<language::Anchor> {
|
|||||||
self.range.start.is_valid(buffer) && !self.range.to_offset(buffer).is_empty()
|
self.range.start.is_valid(buffer) && !self.range.to_offset(buffer).is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_slash_command_output_to_events_round_trip() {
|
||||||
|
// Test basic output consisting of a single section.
|
||||||
|
{
|
||||||
|
let text = "Hello, world!".to_string();
|
||||||
|
let range = 0..text.len();
|
||||||
|
let output = SlashCommandOutput {
|
||||||
|
text,
|
||||||
|
sections: vec![SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon: IconName::Code,
|
||||||
|
label: "Section 1".into(),
|
||||||
|
metadata: None,
|
||||||
|
}],
|
||||||
|
run_commands_in_text: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let events = output.clone().to_event_stream().collect::<Vec<_>>().await;
|
||||||
|
let events = events
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|event| event.ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::Code,
|
||||||
|
label: "Section 1".into(),
|
||||||
|
metadata: None
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Hello, world!".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection { metadata: None }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_output =
|
||||||
|
SlashCommandOutput::from_event_stream(output.clone().to_event_stream())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(new_output, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test output where the sections do not comprise all of the text.
|
||||||
|
{
|
||||||
|
let text = "Apple\nCucumber\nBanana\n".to_string();
|
||||||
|
let output = SlashCommandOutput {
|
||||||
|
text,
|
||||||
|
sections: vec![
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: 0..6,
|
||||||
|
icon: IconName::Check,
|
||||||
|
label: "Fruit".into(),
|
||||||
|
metadata: None,
|
||||||
|
},
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: 15..22,
|
||||||
|
icon: IconName::Check,
|
||||||
|
label: "Fruit".into(),
|
||||||
|
metadata: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
run_commands_in_text: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let events = output.clone().to_event_stream().collect::<Vec<_>>().await;
|
||||||
|
let events = events
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|event| event.ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::Check,
|
||||||
|
label: "Fruit".into(),
|
||||||
|
metadata: None
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Apple\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection { metadata: None },
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Cucumber\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::Check,
|
||||||
|
label: "Fruit".into(),
|
||||||
|
metadata: None
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Banana\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection { metadata: None }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_output =
|
||||||
|
SlashCommandOutput::from_event_stream(output.clone().to_event_stream())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(new_output, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test output consisting of multiple sections.
|
||||||
|
{
|
||||||
|
let text = "Line 1\nLine 2\nLine 3\nLine 4\n".to_string();
|
||||||
|
let output = SlashCommandOutput {
|
||||||
|
text,
|
||||||
|
sections: vec![
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: 0..6,
|
||||||
|
icon: IconName::FileCode,
|
||||||
|
label: "Section 1".into(),
|
||||||
|
metadata: Some(json!({ "a": true })),
|
||||||
|
},
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: 7..13,
|
||||||
|
icon: IconName::FileDoc,
|
||||||
|
label: "Section 2".into(),
|
||||||
|
metadata: Some(json!({ "b": true })),
|
||||||
|
},
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: 14..20,
|
||||||
|
icon: IconName::FileGit,
|
||||||
|
label: "Section 3".into(),
|
||||||
|
metadata: Some(json!({ "c": true })),
|
||||||
|
},
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: 21..27,
|
||||||
|
icon: IconName::FileToml,
|
||||||
|
label: "Section 4".into(),
|
||||||
|
metadata: Some(json!({ "d": true })),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
run_commands_in_text: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let events = output.clone().to_event_stream().collect::<Vec<_>>().await;
|
||||||
|
let events = events
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|event| event.ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::FileCode,
|
||||||
|
label: "Section 1".into(),
|
||||||
|
metadata: Some(json!({ "a": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Line 1".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection {
|
||||||
|
metadata: Some(json!({ "a": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::FileDoc,
|
||||||
|
label: "Section 2".into(),
|
||||||
|
metadata: Some(json!({ "b": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Line 2".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection {
|
||||||
|
metadata: Some(json!({ "b": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::FileGit,
|
||||||
|
label: "Section 3".into(),
|
||||||
|
metadata: Some(json!({ "c": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Line 3".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection {
|
||||||
|
metadata: Some(json!({ "c": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::StartSection {
|
||||||
|
icon: IconName::FileToml,
|
||||||
|
label: "Section 4".into(),
|
||||||
|
metadata: Some(json!({ "d": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "Line 4".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
SlashCommandEvent::EndSection {
|
||||||
|
metadata: Some(json!({ "d": true }))
|
||||||
|
},
|
||||||
|
SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||||
|
text: "\n".into(),
|
||||||
|
run_commands_in_text: false
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_output =
|
||||||
|
SlashCommandOutput::from_event_stream(output.clone().to_event_stream())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(new_output, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,4 +32,5 @@ settings.workspace = true
|
|||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
which.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||||
|
use paths::remote_servers_dir;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
@@ -33,6 +34,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use update_notification::UpdateNotification;
|
use update_notification::UpdateNotification;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
use which::which;
|
||||||
use workspace::notifications::NotificationId;
|
use workspace::notifications::NotificationId;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -82,9 +84,9 @@ pub struct AutoUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct JsonRelease {
|
pub struct JsonRelease {
|
||||||
version: String,
|
pub version: String,
|
||||||
url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MacOsUnmounter {
|
struct MacOsUnmounter {
|
||||||
@@ -430,10 +432,14 @@ impl AutoUpdater {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_latest_remote_server_release(
|
// 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,
|
os: &str,
|
||||||
arch: &str,
|
arch: &str,
|
||||||
mut release_channel: ReleaseChannel,
|
release_channel: ReleaseChannel,
|
||||||
|
version: Option<SemanticVersion>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<PathBuf> {
|
) -> Result<PathBuf> {
|
||||||
let this = cx.update(|cx| {
|
let this = cx.update(|cx| {
|
||||||
@@ -443,15 +449,12 @@ impl AutoUpdater {
|
|||||||
.ok_or_else(|| anyhow!("auto-update not initialized"))
|
.ok_or_else(|| anyhow!("auto-update not initialized"))
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
if release_channel == ReleaseChannel::Dev {
|
let release = Self::get_release(
|
||||||
release_channel = ReleaseChannel::Nightly;
|
|
||||||
}
|
|
||||||
|
|
||||||
let release = Self::get_latest_release(
|
|
||||||
&this,
|
&this,
|
||||||
"zed-remote-server",
|
"zed-remote-server",
|
||||||
os,
|
os,
|
||||||
arch,
|
arch,
|
||||||
|
version,
|
||||||
Some(release_channel),
|
Some(release_channel),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -466,13 +469,97 @@ impl AutoUpdater {
|
|||||||
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
|
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
|
||||||
|
|
||||||
if smol::fs::metadata(&version_path).await.is_err() {
|
if smol::fs::metadata(&version_path).await.is_err() {
|
||||||
log::info!("downloading zed-remote-server {os} {arch}");
|
log::info!(
|
||||||
|
"downloading zed-remote-server {os} {arch} version {}",
|
||||||
|
release.version
|
||||||
|
);
|
||||||
download_remote_server_binary(&version_path, release, client, cx).await?;
|
download_remote_server_binary(&version_path, release, client, cx).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(version_path)
|
Ok(version_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_remote_server_release_url(
|
||||||
|
os: &str,
|
||||||
|
arch: &str,
|
||||||
|
release_channel: ReleaseChannel,
|
||||||
|
version: Option<SemanticVersion>,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Result<Option<(String, String)>> {
|
||||||
|
let this = cx.update(|cx| {
|
||||||
|
cx.default_global::<GlobalAutoUpdate>()
|
||||||
|
.0
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("auto-update not initialized"))
|
||||||
|
})??;
|
||||||
|
|
||||||
|
let release = Self::get_release(
|
||||||
|
&this,
|
||||||
|
"zed-remote-server",
|
||||||
|
os,
|
||||||
|
arch,
|
||||||
|
version,
|
||||||
|
Some(release_channel),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let update_request_body = build_remote_server_update_request_body(cx)?;
|
||||||
|
let body = serde_json::to_string(&update_request_body)?;
|
||||||
|
|
||||||
|
Ok(Some((release.url, body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_release(
|
||||||
|
this: &Model<Self>,
|
||||||
|
asset: &str,
|
||||||
|
os: &str,
|
||||||
|
arch: &str,
|
||||||
|
version: Option<SemanticVersion>,
|
||||||
|
release_channel: Option<ReleaseChannel>,
|
||||||
|
cx: &mut AsyncAppContext,
|
||||||
|
) -> Result<JsonRelease> {
|
||||||
|
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
|
||||||
|
|
||||||
|
if let Some(version) = version {
|
||||||
|
let channel = release_channel.map(|c| c.dev_name()).unwrap_or("stable");
|
||||||
|
|
||||||
|
let url = format!("/api/releases/{channel}/{version}/{asset}-{os}-{arch}.gz?update=1",);
|
||||||
|
|
||||||
|
Ok(JsonRelease {
|
||||||
|
version: version.to_string(),
|
||||||
|
url: client.build_url(&url),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let mut url_string = client.build_url(&format!(
|
||||||
|
"/api/releases/latest?asset={}&os={}&arch={}",
|
||||||
|
asset, os, arch
|
||||||
|
));
|
||||||
|
if let Some(param) = release_channel.and_then(|c| c.release_query_param()) {
|
||||||
|
url_string += "&";
|
||||||
|
url_string += param;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = client.get(&url_string, Default::default(), true).await?;
|
||||||
|
let mut body = Vec::new();
|
||||||
|
response.body_mut().read_to_end(&mut body).await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"failed to fetch release: {:?}",
|
||||||
|
String::from_utf8_lossy(&body),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_slice(body.as_slice()).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"error deserializing release {:?}",
|
||||||
|
String::from_utf8_lossy(&body),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_latest_release(
|
async fn get_latest_release(
|
||||||
this: &Model<Self>,
|
this: &Model<Self>,
|
||||||
asset: &str,
|
asset: &str,
|
||||||
@@ -481,38 +568,7 @@ impl AutoUpdater {
|
|||||||
release_channel: Option<ReleaseChannel>,
|
release_channel: Option<ReleaseChannel>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<JsonRelease> {
|
) -> Result<JsonRelease> {
|
||||||
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
|
Self::get_release(this, asset, os, arch, None, release_channel, cx).await
|
||||||
let mut url_string = client.build_url(&format!(
|
|
||||||
"/api/releases/latest?asset={}&os={}&arch={}",
|
|
||||||
asset, os, arch
|
|
||||||
));
|
|
||||||
if let Some(param) = release_channel.and_then(|c| c.release_query_param()) {
|
|
||||||
url_string += "&";
|
|
||||||
url_string += param;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut response = client.get(&url_string, Default::default(), true).await?;
|
|
||||||
|
|
||||||
let mut body = Vec::new();
|
|
||||||
response
|
|
||||||
.body_mut()
|
|
||||||
.read_to_end(&mut body)
|
|
||||||
.await
|
|
||||||
.context("error reading release")?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
Err(anyhow!(
|
|
||||||
"failed to fetch release: {:?}",
|
|
||||||
String::from_utf8_lossy(&body),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
serde_json::from_slice(body.as_slice()).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"error deserializing release {:?}",
|
|
||||||
String::from_utf8_lossy(&body),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
|
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
|
||||||
@@ -560,6 +616,12 @@ impl AutoUpdater {
|
|||||||
"linux" => Ok("zed.tar.gz"),
|
"linux" => Ok("zed.tar.gz"),
|
||||||
_ => Err(anyhow!("not supported: {:?}", OS)),
|
_ => Err(anyhow!("not supported: {:?}", OS)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
|
anyhow::ensure!(
|
||||||
|
which("rsync").is_ok(),
|
||||||
|
"Aborting. Could not find rsync which is required for auto-updates."
|
||||||
|
);
|
||||||
|
|
||||||
let downloaded_asset = temp_dir.path().join(filename);
|
let downloaded_asset = temp_dir.path().join(filename);
|
||||||
download_release(&downloaded_asset, release, client, &cx).await?;
|
download_release(&downloaded_asset, release, client, &cx).await?;
|
||||||
|
|
||||||
@@ -621,7 +683,25 @@ async fn download_remote_server_binary(
|
|||||||
client: Arc<HttpClientWithUrl>,
|
client: Arc<HttpClientWithUrl>,
|
||||||
cx: &AsyncAppContext,
|
cx: &AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut target_file = File::create(&target_path).await?;
|
let temp = tempfile::Builder::new().tempfile_in(remote_servers_dir())?;
|
||||||
|
let mut temp_file = File::create(&temp).await?;
|
||||||
|
let update_request_body = build_remote_server_update_request_body(cx)?;
|
||||||
|
let request_body = AsyncBody::from(serde_json::to_string(&update_request_body)?);
|
||||||
|
|
||||||
|
let mut response = client.get(&release.url, request_body, true).await?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"failed to download remote server release: {:?}",
|
||||||
|
response.status()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
smol::io::copy(response.body_mut(), &mut temp_file).await?;
|
||||||
|
smol::fs::rename(&temp, &target_path).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_remote_server_update_request_body(cx: &AsyncAppContext) -> Result<UpdateRequestBody> {
|
||||||
let (installation_id, release_channel, telemetry_enabled, is_staff) = cx.update(|cx| {
|
let (installation_id, release_channel, telemetry_enabled, is_staff) = cx.update(|cx| {
|
||||||
let telemetry = Client::global(cx).telemetry().clone();
|
let telemetry = Client::global(cx).telemetry().clone();
|
||||||
let is_staff = telemetry.is_staff();
|
let is_staff = telemetry.is_staff();
|
||||||
@@ -637,17 +717,14 @@ async fn download_remote_server_binary(
|
|||||||
is_staff,
|
is_staff,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
|
|
||||||
|
Ok(UpdateRequestBody {
|
||||||
installation_id,
|
installation_id,
|
||||||
release_channel,
|
release_channel,
|
||||||
telemetry: telemetry_enabled,
|
telemetry: telemetry_enabled,
|
||||||
is_staff,
|
is_staff,
|
||||||
destination: "remote",
|
destination: "remote",
|
||||||
})?);
|
})
|
||||||
|
|
||||||
let mut response = client.get(&release.url, request_body, true).await?;
|
|
||||||
smol::io::copy(response.body_mut(), &mut target_file).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_release(
|
async fn download_release(
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ path = "src/call.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
no-webrtc = ["live_kit_client/no-webrtc"]
|
|
||||||
test-support = [
|
test-support = [
|
||||||
"client/test-support",
|
"client/test-support",
|
||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
|
|||||||
@@ -1194,26 +1194,15 @@ impl Room {
|
|||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<u64>> {
|
) -> Task<Result<u64>> {
|
||||||
let request = if let Some(dev_server_project_id) = project.read(cx).dev_server_project_id()
|
if let Some(project_id) = project.read(cx).remote_id() {
|
||||||
{
|
return Task::ready(Ok(project_id));
|
||||||
self.client.request(proto::ShareProject {
|
}
|
||||||
room_id: self.id(),
|
|
||||||
worktrees: vec![],
|
|
||||||
dev_server_project_id: Some(dev_server_project_id.0),
|
|
||||||
is_ssh_project: false,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if let Some(project_id) = project.read(cx).remote_id() {
|
|
||||||
return Task::ready(Ok(project_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.client.request(proto::ShareProject {
|
let request = self.client.request(proto::ShareProject {
|
||||||
room_id: self.id(),
|
room_id: self.id(),
|
||||||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||||
dev_server_project_id: None,
|
is_ssh_project: project.read(cx).is_via_ssh(),
|
||||||
is_ssh_project: project.read(cx).is_via_ssh(),
|
});
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let response = request.await?;
|
let response = request.await?;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ mod channel_index;
|
|||||||
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
|
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use channel_index::ChannelIndex;
|
use channel_index::ChannelIndex;
|
||||||
use client::{ChannelId, Client, ClientSettings, ProjectId, Subscription, User, UserId, UserStore};
|
use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore};
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -33,30 +33,11 @@ struct NotesVersion {
|
|||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct HostedProject {
|
|
||||||
project_id: ProjectId,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
name: SharedString,
|
|
||||||
_visibility: proto::ChannelVisibility,
|
|
||||||
}
|
|
||||||
impl From<proto::HostedProject> for HostedProject {
|
|
||||||
fn from(project: proto::HostedProject) -> Self {
|
|
||||||
Self {
|
|
||||||
project_id: ProjectId(project.project_id),
|
|
||||||
channel_id: ChannelId(project.channel_id),
|
|
||||||
_visibility: project.visibility(),
|
|
||||||
name: project.name.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct ChannelStore {
|
pub struct ChannelStore {
|
||||||
pub channel_index: ChannelIndex,
|
pub channel_index: ChannelIndex,
|
||||||
channel_invitations: Vec<Arc<Channel>>,
|
channel_invitations: Vec<Arc<Channel>>,
|
||||||
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
|
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
|
||||||
channel_states: HashMap<ChannelId, ChannelState>,
|
channel_states: HashMap<ChannelId, ChannelState>,
|
||||||
hosted_projects: HashMap<ProjectId, HostedProject>,
|
|
||||||
|
|
||||||
outgoing_invites: HashSet<(ChannelId, UserId)>,
|
outgoing_invites: HashSet<(ChannelId, UserId)>,
|
||||||
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
|
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
|
||||||
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
||||||
@@ -85,7 +66,6 @@ pub struct ChannelState {
|
|||||||
observed_notes_version: NotesVersion,
|
observed_notes_version: NotesVersion,
|
||||||
observed_chat_message: Option<u64>,
|
observed_chat_message: Option<u64>,
|
||||||
role: Option<ChannelRole>,
|
role: Option<ChannelRole>,
|
||||||
projects: HashSet<ProjectId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
@@ -216,7 +196,6 @@ impl ChannelStore {
|
|||||||
channel_invitations: Vec::default(),
|
channel_invitations: Vec::default(),
|
||||||
channel_index: ChannelIndex::default(),
|
channel_index: ChannelIndex::default(),
|
||||||
channel_participants: Default::default(),
|
channel_participants: Default::default(),
|
||||||
hosted_projects: Default::default(),
|
|
||||||
outgoing_invites: Default::default(),
|
outgoing_invites: Default::default(),
|
||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
opened_chats: Default::default(),
|
opened_chats: Default::default(),
|
||||||
@@ -316,19 +295,6 @@ impl ChannelStore {
|
|||||||
self.channel_index.by_id().get(&channel_id)
|
self.channel_index.by_id().get(&channel_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, ProjectId)> {
|
|
||||||
let mut projects: Vec<(SharedString, ProjectId)> = self
|
|
||||||
.channel_states
|
|
||||||
.get(&channel_id)
|
|
||||||
.map(|state| state.projects.clone())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
|
|
||||||
.collect();
|
|
||||||
projects.sort();
|
|
||||||
projects
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
|
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
|
||||||
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
|
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
|
||||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||||
@@ -1102,9 +1068,7 @@ impl ChannelStore {
|
|||||||
let channels_changed = !payload.channels.is_empty()
|
let channels_changed = !payload.channels.is_empty()
|
||||||
|| !payload.delete_channels.is_empty()
|
|| !payload.delete_channels.is_empty()
|
||||||
|| !payload.latest_channel_message_ids.is_empty()
|
|| !payload.latest_channel_message_ids.is_empty()
|
||||||
|| !payload.latest_channel_buffer_versions.is_empty()
|
|| !payload.latest_channel_buffer_versions.is_empty();
|
||||||
|| !payload.hosted_projects.is_empty()
|
|
||||||
|| !payload.deleted_hosted_projects.is_empty();
|
|
||||||
|
|
||||||
if channels_changed {
|
if channels_changed {
|
||||||
if !payload.delete_channels.is_empty() {
|
if !payload.delete_channels.is_empty() {
|
||||||
@@ -1161,34 +1125,6 @@ impl ChannelStore {
|
|||||||
.or_default()
|
.or_default()
|
||||||
.update_latest_message_id(latest_channel_message.message_id);
|
.update_latest_message_id(latest_channel_message.message_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for hosted_project in payload.hosted_projects {
|
|
||||||
let hosted_project: HostedProject = hosted_project.into();
|
|
||||||
if let Some(old_project) = self
|
|
||||||
.hosted_projects
|
|
||||||
.insert(hosted_project.project_id, hosted_project.clone())
|
|
||||||
{
|
|
||||||
self.channel_states
|
|
||||||
.entry(old_project.channel_id)
|
|
||||||
.or_default()
|
|
||||||
.remove_hosted_project(old_project.project_id);
|
|
||||||
}
|
|
||||||
self.channel_states
|
|
||||||
.entry(hosted_project.channel_id)
|
|
||||||
.or_default()
|
|
||||||
.add_hosted_project(hosted_project.project_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for hosted_project_id in payload.deleted_hosted_projects {
|
|
||||||
let hosted_project_id = ProjectId(hosted_project_id);
|
|
||||||
|
|
||||||
if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
|
|
||||||
self.channel_states
|
|
||||||
.entry(old_project.channel_id)
|
|
||||||
.or_default()
|
|
||||||
.remove_hosted_project(old_project.project_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -1295,12 +1231,4 @@ impl ChannelState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_hosted_project(&mut self, project_id: ProjectId) {
|
|
||||||
self.projects.insert(project_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_hosted_project(&mut self, project_id: ProjectId) {
|
|
||||||
self.projects.remove(&project_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ pub enum CliRequest {
|
|||||||
urls: Vec<String>,
|
urls: Vec<String>,
|
||||||
wait: bool,
|
wait: bool,
|
||||||
open_new_workspace: Option<bool>,
|
open_new_workspace: Option<bool>,
|
||||||
dev_server_token: Option<String>,
|
|
||||||
env: Option<HashMap<String, String>>,
|
env: Option<HashMap<String, String>>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,12 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(_) = args.dev_server_token {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development"
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
||||||
let exit_status = exit_status.clone();
|
let exit_status = exit_status.clone();
|
||||||
move || {
|
move || {
|
||||||
@@ -162,7 +168,6 @@ fn main() -> Result<()> {
|
|||||||
urls,
|
urls,
|
||||||
wait: args.wait,
|
wait: args.wait,
|
||||||
open_new_workspace,
|
open_new_workspace,
|
||||||
dev_server_token: args.dev_server_token,
|
|
||||||
env,
|
env,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ sha2.workspace = true
|
|||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
sysinfo.workspace = true
|
sysinfo.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
tempfile.workspace = true
|
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ use schemars::JsonSchema;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsSources};
|
use settings::{Settings, SettingsSources};
|
||||||
use socks::connect_socks_proxy_stream;
|
use socks::connect_socks_proxy_stream;
|
||||||
use std::fmt;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
@@ -54,15 +53,6 @@ pub use rpc::*;
|
|||||||
pub use telemetry_events::Event;
|
pub use telemetry_events::Event;
|
||||||
pub use user::*;
|
pub use user::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub struct DevServerToken(pub String);
|
|
||||||
|
|
||||||
impl fmt::Display for DevServerToken {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static ZED_SERVER_URL: LazyLock<Option<String>> =
|
static ZED_SERVER_URL: LazyLock<Option<String>> =
|
||||||
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
|
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
|
||||||
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
|
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
|
||||||
@@ -304,20 +294,14 @@ struct ClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Credentials {
|
pub struct Credentials {
|
||||||
DevServer { token: DevServerToken },
|
pub user_id: u64,
|
||||||
User { user_id: u64, access_token: String },
|
pub access_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Credentials {
|
impl Credentials {
|
||||||
pub fn authorization_header(&self) -> String {
|
pub fn authorization_header(&self) -> String {
|
||||||
match self {
|
format!("{} {}", self.user_id, self.access_token)
|
||||||
Credentials::DevServer { token } => format!("dev-server-token {}", token),
|
|
||||||
Credentials::User {
|
|
||||||
user_id,
|
|
||||||
access_token,
|
|
||||||
} => format!("{} {}", user_id, access_token),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,11 +584,11 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_id(&self) -> Option<u64> {
|
pub fn user_id(&self) -> Option<u64> {
|
||||||
if let Some(Credentials::User { user_id, .. }) = self.state.read().credentials.as_ref() {
|
self.state
|
||||||
Some(*user_id)
|
.read()
|
||||||
} else {
|
.credentials
|
||||||
None
|
.as_ref()
|
||||||
}
|
.map(|credentials| credentials.user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn peer_id(&self) -> Option<PeerId> {
|
pub fn peer_id(&self) -> Option<PeerId> {
|
||||||
@@ -793,11 +777,6 @@ impl Client {
|
|||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dev_server_token(&self, token: DevServerToken) -> &Self {
|
|
||||||
self.state.write().credentials = Some(Credentials::DevServer { token });
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_recursion(?Send)]
|
#[async_recursion(?Send)]
|
||||||
pub async fn authenticate_and_connect(
|
pub async fn authenticate_and_connect(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
@@ -848,9 +827,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let credentials = credentials.unwrap();
|
let credentials = credentials.unwrap();
|
||||||
if let Credentials::User { user_id, .. } = &credentials {
|
self.set_id(credentials.user_id);
|
||||||
self.set_id(*user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if was_disconnected {
|
if was_disconnected {
|
||||||
self.set_status(Status::Connecting, cx);
|
self.set_status(Status::Connecting, cx);
|
||||||
@@ -866,9 +843,8 @@ impl Client {
|
|||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
self.state.write().credentials = Some(credentials.clone());
|
self.state.write().credentials = Some(credentials.clone());
|
||||||
if !read_from_provider && IMPERSONATE_LOGIN.is_none() {
|
if !read_from_provider && IMPERSONATE_LOGIN.is_none() {
|
||||||
if let Credentials::User{user_id, access_token} = credentials {
|
self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err();
|
||||||
self.credentials_provider.write_credentials(user_id, access_token, cx).await.log_err();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
@@ -1301,7 +1277,7 @@ impl Client {
|
|||||||
.decrypt_string(&access_token)
|
.decrypt_string(&access_token)
|
||||||
.context("failed to decrypt access token")?;
|
.context("failed to decrypt access token")?;
|
||||||
|
|
||||||
Ok(Credentials::User {
|
Ok(Credentials {
|
||||||
user_id: user_id.parse()?,
|
user_id: user_id.parse()?,
|
||||||
access_token,
|
access_token,
|
||||||
})
|
})
|
||||||
@@ -1422,7 +1398,7 @@ impl Client {
|
|||||||
|
|
||||||
// Use the admin API token to authenticate as the impersonated user.
|
// Use the admin API token to authenticate as the impersonated user.
|
||||||
api_token.insert_str(0, "ADMIN_TOKEN:");
|
api_token.insert_str(0, "ADMIN_TOKEN:");
|
||||||
Ok(Credentials::User {
|
Ok(Credentials {
|
||||||
user_id: response.user.id,
|
user_id: response.user.id,
|
||||||
access_token: api_token,
|
access_token: api_token,
|
||||||
})
|
})
|
||||||
@@ -1667,7 +1643,7 @@ impl CredentialsProvider for DevelopmentCredentialsProvider {
|
|||||||
|
|
||||||
let credentials: DevelopmentCredentials = serde_json::from_slice(&json).log_err()?;
|
let credentials: DevelopmentCredentials = serde_json::from_slice(&json).log_err()?;
|
||||||
|
|
||||||
Some(Credentials::User {
|
Some(Credentials {
|
||||||
user_id: credentials.user_id,
|
user_id: credentials.user_id,
|
||||||
access_token: credentials.access_token,
|
access_token: credentials.access_token,
|
||||||
})
|
})
|
||||||
@@ -1721,7 +1697,7 @@ impl CredentialsProvider for KeychainCredentialsProvider {
|
|||||||
.await
|
.await
|
||||||
.log_err()??;
|
.log_err()??;
|
||||||
|
|
||||||
Some(Credentials::User {
|
Some(Credentials {
|
||||||
user_id: user_id.parse().ok()?,
|
user_id: user_id.parse().ok()?,
|
||||||
access_token: String::from_utf8(access_token).ok()?,
|
access_token: String::from_utf8(access_token).ok()?,
|
||||||
})
|
})
|
||||||
@@ -1855,7 +1831,7 @@ mod tests {
|
|||||||
// Time out when client tries to connect.
|
// Time out when client tries to connect.
|
||||||
client.override_authenticate(move |cx| {
|
client.override_authenticate(move |cx| {
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
Ok(Credentials::User {
|
Ok(Credentials {
|
||||||
user_id,
|
user_id,
|
||||||
access_token: "token".into(),
|
access_token: "token".into(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use parking_lot::Mutex;
|
|||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||||
@@ -21,10 +22,7 @@ use telemetry_events::{
|
|||||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
|
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
|
||||||
SettingEvent,
|
SettingEvent,
|
||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
use util::{ResultExt, TryFutureExt};
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
use util::ResultExt;
|
|
||||||
use util::TryFutureExt;
|
|
||||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||||
|
|
||||||
use self::event_coalescer::EventCoalescer;
|
use self::event_coalescer::EventCoalescer;
|
||||||
@@ -46,7 +44,7 @@ struct TelemetryState {
|
|||||||
architecture: &'static str,
|
architecture: &'static str,
|
||||||
events_queue: Vec<EventWrapper>,
|
events_queue: Vec<EventWrapper>,
|
||||||
flush_events_task: Option<Task<()>>,
|
flush_events_task: Option<Task<()>>,
|
||||||
log_file: Option<NamedTempFile>,
|
log_file: Option<File>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
first_event_date_time: Option<DateTime<Utc>>,
|
first_event_date_time: Option<DateTime<Utc>>,
|
||||||
event_coalescer: EventCoalescer,
|
event_coalescer: EventCoalescer,
|
||||||
@@ -223,15 +221,13 @@ impl Telemetry {
|
|||||||
os_name: os_name(),
|
os_name: os_name(),
|
||||||
app_version: release_channel::AppVersion::global(cx).to_string(),
|
app_version: release_channel::AppVersion::global(cx).to_string(),
|
||||||
}));
|
}));
|
||||||
|
Self::log_file_path();
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.spawn({
|
.spawn({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(tempfile) =
|
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
|
||||||
NamedTempFile::new_in(paths::logs_dir().as_path()).log_err()
|
|
||||||
{
|
|
||||||
state.lock().log_file = Some(tempfile);
|
state.lock().log_file = Some(tempfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,8 +276,8 @@ impl Telemetry {
|
|||||||
Task::ready(())
|
Task::ready(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_file_path(&self) -> Option<PathBuf> {
|
pub fn log_file_path() -> PathBuf {
|
||||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
paths::logs_dir().join("telemetry.log")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(
|
pub fn start(
|
||||||
@@ -341,6 +337,13 @@ impl Telemetry {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn metrics_enabled(self: &Arc<Self>) -> bool {
|
||||||
|
let state = self.state.lock();
|
||||||
|
let enabled = state.settings.metrics;
|
||||||
|
drop(state);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_authenticated_user_info(
|
pub fn set_authenticated_user_info(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
metrics_id: Option<String>,
|
metrics_id: Option<String>,
|
||||||
@@ -638,7 +641,6 @@ impl Telemetry {
|
|||||||
let mut json_bytes = Vec::new();
|
let mut json_bytes = Vec::new();
|
||||||
|
|
||||||
if let Some(file) = &mut this.state.lock().log_file {
|
if let Some(file) = &mut this.state.lock().log_file {
|
||||||
let file = file.as_file_mut();
|
|
||||||
for event in &mut events {
|
for event in &mut events {
|
||||||
json_bytes.clear();
|
json_bytes.clear();
|
||||||
serde_json::to_writer(&mut json_bytes, event)?;
|
serde_json::to_writer(&mut json_bytes, event)?;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl FakeServer {
|
|||||||
let mut state = state.lock();
|
let mut state = state.lock();
|
||||||
state.auth_count += 1;
|
state.auth_count += 1;
|
||||||
let access_token = state.access_token.to_string();
|
let access_token = state.access_token.to_string();
|
||||||
Ok(Credentials::User {
|
Ok(Credentials {
|
||||||
user_id: client_user_id,
|
user_id: client_user_id,
|
||||||
access_token,
|
access_token,
|
||||||
})
|
})
|
||||||
@@ -73,7 +73,7 @@ impl FakeServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if credentials
|
if credentials
|
||||||
!= (Credentials::User {
|
!= (Credentials {
|
||||||
user_id: client_user_id,
|
user_id: client_user_id,
|
||||||
access_token: state.lock().access_token.to_string(),
|
access_token: state.lock().access_token.to_string(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ impl std::fmt::Display for ChannelId {
|
|||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||||
pub struct ProjectId(pub u64);
|
pub struct ProjectId(pub u64);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
pub struct DevServerId(pub u64);
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
||||||
)]
|
)]
|
||||||
@@ -51,6 +48,7 @@ pub struct Collaborator {
|
|||||||
pub peer_id: proto::PeerId,
|
pub peer_id: proto::PeerId,
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
|
pub is_host: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for User {
|
impl PartialOrd for User {
|
||||||
@@ -827,6 +825,7 @@ impl Collaborator {
|
|||||||
peer_id: message.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?,
|
peer_id: message.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?,
|
||||||
replica_id: message.replica_id as ReplicaId,
|
replica_id: message.replica_id as ReplicaId,
|
||||||
user_id: message.user_id as UserId,
|
user_id: message.user_id as UserId,
|
||||||
|
is_host: message.is_host,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ client = { workspace = true, features = ["test-support"] }
|
|||||||
collab_ui = { workspace = true, features = ["test-support"] }
|
collab_ui = { workspace = true, features = ["test-support"] }
|
||||||
collections = { workspace = true, features = ["test-support"] }
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
dev_server_projects.workspace = true
|
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
file_finder.workspace = true
|
file_finder.workspace = true
|
||||||
@@ -94,7 +93,6 @@ fs = { workspace = true, features = ["test-support"] }
|
|||||||
git = { workspace = true, features = ["test-support"] }
|
git = { workspace = true, features = ["test-support"] }
|
||||||
git_hosting_providers.workspace = true
|
git_hosting_providers.workspace = true
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
headless.workspace = true
|
|
||||||
hyper.workspace = true
|
hyper.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ CREATE TABLE "users" (
|
|||||||
"metrics_id" TEXT,
|
"metrics_id" TEXT,
|
||||||
"github_user_id" INTEGER NOT NULL,
|
"github_user_id" INTEGER NOT NULL,
|
||||||
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE,
|
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE,
|
||||||
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE
|
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE,
|
||||||
|
"custom_llm_monthly_allowance_in_cents" INTEGER
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
|
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
|
||||||
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
|
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
|
||||||
@@ -51,9 +52,7 @@ CREATE TABLE "projects" (
|
|||||||
"host_user_id" INTEGER REFERENCES users (id),
|
"host_user_id" INTEGER REFERENCES users (id),
|
||||||
"host_connection_id" INTEGER,
|
"host_connection_id" INTEGER,
|
||||||
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
|
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
|
||||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
|
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
"hosted_project_id" INTEGER REFERENCES hosted_projects (id),
|
|
||||||
"dev_server_project_id" INTEGER REFERENCES dev_server_projects(id)
|
|
||||||
);
|
);
|
||||||
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
|
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
|
||||||
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
|
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
|
||||||
@@ -398,30 +397,6 @@ CREATE TABLE rate_buckets (
|
|||||||
);
|
);
|
||||||
CREATE INDEX idx_user_id_rate_limit ON rate_buckets (user_id, rate_limit_name);
|
CREATE INDEX idx_user_id_rate_limit ON rate_buckets (user_id, rate_limit_name);
|
||||||
|
|
||||||
CREATE TABLE hosted_projects (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
channel_id INTEGER NOT NULL REFERENCES channels(id),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
visibility TEXT NOT NULL,
|
|
||||||
deleted_at TIMESTAMP NULL
|
|
||||||
);
|
|
||||||
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
|
|
||||||
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
|
|
||||||
|
|
||||||
CREATE TABLE dev_servers (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
ssh_connection_string TEXT,
|
|
||||||
hashed_token TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE dev_server_projects (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
dev_server_id INTEGER NOT NULL REFERENCES dev_servers(id),
|
|
||||||
paths TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS billing_preferences (
|
CREATE TABLE IF NOT EXISTS billing_preferences (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table users add column custom_llm_monthly_allowance_in_cents integer;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE projects DROP COLUMN dev_server_project_id;
|
||||||
|
ALTER TABLE projects DROP COLUMN hosted_project_id;
|
||||||
|
|
||||||
|
DROP TABLE hosted_projects;
|
||||||
|
DROP TABLE dev_server_projects;
|
||||||
|
DROP TABLE dev_servers;
|
||||||
@@ -34,7 +34,7 @@ use crate::{
|
|||||||
db::{billing_subscription::StripeSubscriptionStatus, UserId},
|
db::{billing_subscription::StripeSubscriptionStatus, UserId},
|
||||||
llm::db::LlmDatabase,
|
llm::db::LlmDatabase,
|
||||||
};
|
};
|
||||||
use crate::{AppState, Error, Result};
|
use crate::{AppState, Cents, Error, Result};
|
||||||
|
|
||||||
pub fn router() -> Router {
|
pub fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
@@ -226,6 +226,13 @@ async fn create_billing_subscription(
|
|||||||
))?
|
))?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if app.db.has_active_billing_subscription(user.id).await? {
|
||||||
|
return Err(Error::http(
|
||||||
|
StatusCode::CONFLICT,
|
||||||
|
"user already has an active subscription".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let customer_id =
|
let customer_id =
|
||||||
if let Some(existing_customer) = app.db.get_billing_customer_by_user_id(user.id).await? {
|
if let Some(existing_customer) = app.db.get_billing_customer_by_user_id(user.id).await? {
|
||||||
CustomerId::from_str(&existing_customer.stripe_customer_id)
|
CustomerId::from_str(&existing_customer.stripe_customer_id)
|
||||||
@@ -245,7 +252,10 @@ async fn create_billing_subscription(
|
|||||||
|
|
||||||
let default_model = llm_db.model(rpc::LanguageModelProvider::Anthropic, "claude-3-5-sonnet")?;
|
let default_model = llm_db.model(rpc::LanguageModelProvider::Anthropic, "claude-3-5-sonnet")?;
|
||||||
let stripe_model = stripe_billing.register_model(default_model).await?;
|
let stripe_model = stripe_billing.register_model(default_model).await?;
|
||||||
let success_url = format!("{}/account", app.config.zed_dot_dev_url());
|
let success_url = format!(
|
||||||
|
"{}/account?checkout_complete=1",
|
||||||
|
app.config.zed_dot_dev_url()
|
||||||
|
);
|
||||||
let checkout_session_url = stripe_billing
|
let checkout_session_url = stripe_billing
|
||||||
.checkout(customer_id, &user.github_login, &stripe_model, &success_url)
|
.checkout(customer_id, &user.github_login, &stripe_model, &success_url)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -655,6 +665,33 @@ async fn handle_customer_subscription_event(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
|
// If the user already has an active billing subscription, ignore the
|
||||||
|
// event and return an `Ok` to signal that it was processed
|
||||||
|
// successfully.
|
||||||
|
//
|
||||||
|
// There is the possibility that this could cause us to not create a
|
||||||
|
// subscription in the following scenario:
|
||||||
|
//
|
||||||
|
// 1. User has an active subscription A
|
||||||
|
// 2. User cancels subscription A
|
||||||
|
// 3. User creates a new subscription B
|
||||||
|
// 4. We process the new subscription B before the cancellation of subscription A
|
||||||
|
// 5. User ends up with no subscriptions
|
||||||
|
//
|
||||||
|
// In theory this situation shouldn't arise as we try to process the events in the order they occur.
|
||||||
|
if app
|
||||||
|
.db
|
||||||
|
.has_active_billing_subscription(billing_customer.user_id)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
log::info!(
|
||||||
|
"user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}",
|
||||||
|
user_id = billing_customer.user_id,
|
||||||
|
subscription_id = subscription.id
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
app.db
|
app.db
|
||||||
.create_billing_subscription(&CreateBillingSubscriptionParams {
|
.create_billing_subscription(&CreateBillingSubscriptionParams {
|
||||||
billing_customer_id: billing_customer.id,
|
billing_customer_id: billing_customer.id,
|
||||||
@@ -680,7 +717,9 @@ struct GetMonthlySpendParams {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct GetMonthlySpendResponse {
|
struct GetMonthlySpendResponse {
|
||||||
monthly_spend_in_cents: i32,
|
monthly_free_tier_spend_in_cents: u32,
|
||||||
|
monthly_free_tier_allowance_in_cents: u32,
|
||||||
|
monthly_spend_in_cents: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_monthly_spend(
|
async fn get_monthly_spend(
|
||||||
@@ -700,13 +739,22 @@ async fn get_monthly_spend(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let monthly_spend = llm_db
|
let free_tier = user
|
||||||
|
.custom_llm_monthly_allowance_in_cents
|
||||||
|
.map(|allowance| Cents(allowance as u32))
|
||||||
|
.unwrap_or(FREE_TIER_MONTHLY_SPENDING_LIMIT);
|
||||||
|
|
||||||
|
let spending_for_month = llm_db
|
||||||
.get_user_spending_for_month(user.id, Utc::now())
|
.get_user_spending_for_month(user.id, Utc::now())
|
||||||
.await?
|
.await?;
|
||||||
.saturating_sub(FREE_TIER_MONTHLY_SPENDING_LIMIT);
|
|
||||||
|
let free_tier_spend = Cents::min(spending_for_month, free_tier);
|
||||||
|
let monthly_spend = spending_for_month.saturating_sub(free_tier);
|
||||||
|
|
||||||
Ok(Json(GetMonthlySpendResponse {
|
Ok(Json(GetMonthlySpendResponse {
|
||||||
monthly_spend_in_cents: monthly_spend.0 as i32,
|
monthly_free_tier_spend_in_cents: free_tier_spend.0,
|
||||||
|
monthly_free_tier_allowance_in_cents: free_tier.0,
|
||||||
|
monthly_spend_in_cents: monthly_spend.0,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||