Compare commits
297 Commits
performanc
...
evals3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8d44ce267 | ||
|
|
08d89254d1 | ||
|
|
d7cc949e61 | ||
|
|
a3f9dffb04 | ||
|
|
78b32840c3 | ||
|
|
a636b59d72 | ||
|
|
983c3a02a8 | ||
|
|
5bc7b11a6a | ||
|
|
2a5e8c62a7 | ||
|
|
4efeef564c | ||
|
|
514cb933d1 | ||
|
|
06f65b29ab | ||
|
|
cd9242c544 | ||
|
|
65b4e9b10a | ||
|
|
98dec9246e | ||
|
|
39536cae83 | ||
|
|
b4e1d86a16 | ||
|
|
8a12ecf849 | ||
|
|
22bf449b9e | ||
|
|
bcf9142bbc | ||
|
|
a2d57fc7b6 | ||
|
|
96a917091a | ||
|
|
a2ddb0f1cb | ||
|
|
23e5477a4c | ||
|
|
4e043cd56b | ||
|
|
d283338885 | ||
|
|
b1af02ca71 | ||
|
|
59b5de5532 | ||
|
|
efa98a12fd | ||
|
|
7bea1ba555 | ||
|
|
7c95834b7b | ||
|
|
3d58738548 | ||
|
|
2db237aa52 | ||
|
|
305e73ebbb | ||
|
|
ec6e7b84b8 | ||
|
|
4f5cc0a24b | ||
|
|
a2f69cd5bd | ||
|
|
6a097298b0 | ||
|
|
0df86e406a | ||
|
|
a74aac88c9 | ||
|
|
e5105ccdbe | ||
|
|
876b258088 | ||
|
|
19aba43f3e | ||
|
|
8d09610748 | ||
|
|
5b6663ef97 | ||
|
|
f445f22fe6 | ||
|
|
6216af9b5a | ||
|
|
464c0be2b7 | ||
|
|
2df5993eb0 | ||
|
|
04e92fb2d2 | ||
|
|
e27590432f | ||
|
|
a675eb1667 | ||
|
|
b27ad98520 | ||
|
|
9c4e16088c | ||
|
|
34a2bfd6b7 | ||
|
|
99d8d34d48 | ||
|
|
bd79edee71 | ||
|
|
0bb1c6ad3e | ||
|
|
fd146757cf | ||
|
|
6eb9f9add7 | ||
|
|
3d3d124e01 | ||
|
|
de392cda39 | ||
|
|
33513292af | ||
|
|
1535e95066 | ||
|
|
26f77032a2 | ||
|
|
efff602909 | ||
|
|
58c9cbae40 | ||
|
|
7881551dda | ||
|
|
ff6bd7d82e | ||
|
|
bfb876c782 | ||
|
|
64b432e4ac | ||
|
|
33ecb0a68f | ||
|
|
7b7ddbd1e8 | ||
|
|
ed81ef0442 | ||
|
|
88fffae9dd | ||
|
|
61ae59708d | ||
|
|
c8166abbcb | ||
|
|
628c52a96a | ||
|
|
465536644c | ||
|
|
da3bab18fe | ||
|
|
89841d034d | ||
|
|
7aa610e24f | ||
|
|
26ef93ffeb | ||
|
|
60312a3b04 | ||
|
|
d9b3523459 | ||
|
|
1106f77246 | ||
|
|
8561943095 | ||
|
|
a996fa4320 | ||
|
|
1d6b5e72a1 | ||
|
|
5b0f936041 | ||
|
|
b3478e87f1 | ||
|
|
de6855fa6e | ||
|
|
776c853756 | ||
|
|
95f512af9b | ||
|
|
9af6e82e65 | ||
|
|
3969109aa3 | ||
|
|
05c2028068 | ||
|
|
747dc23138 | ||
|
|
aa0e19feca | ||
|
|
bb859a85d5 | ||
|
|
b9c5900fb0 | ||
|
|
4e3aa0b1b6 | ||
|
|
05764e8af7 | ||
|
|
db86febc0c | ||
|
|
d1d419b209 | ||
|
|
2e00f40c54 | ||
|
|
ca6e64d451 | ||
|
|
0079044653 | ||
|
|
450cd3d42b | ||
|
|
dd9cc90de9 | ||
|
|
8abf1d35be | ||
|
|
e75e137a49 | ||
|
|
9c593f32c8 | ||
|
|
e5ce7cb19a | ||
|
|
f326854495 | ||
|
|
200a4a5c78 | ||
|
|
be8605bb10 | ||
|
|
63eb3ea7e0 | ||
|
|
34b453cee6 | ||
|
|
b11f22bb9a | ||
|
|
6040c0c00a | ||
|
|
e8a3368226 | ||
|
|
d4c0b87fb2 | ||
|
|
6404939427 | ||
|
|
557d39332c | ||
|
|
1cc3a4cc9c | ||
|
|
f6ab73033a | ||
|
|
6c98003ffb | ||
|
|
7b4e050dd8 | ||
|
|
7c967b8d1a | ||
|
|
478bcea6d3 | ||
|
|
c18481ed13 | ||
|
|
87e3d6e014 | ||
|
|
d877e562ab | ||
|
|
b89bcbead6 | ||
|
|
8f8a92ccf0 | ||
|
|
fc213f1c26 | ||
|
|
f856a3ca89 | ||
|
|
65e224c551 | ||
|
|
b17b903c57 | ||
|
|
77656a4091 | ||
|
|
05b999bb7c | ||
|
|
d099ea048e | ||
|
|
518ea716ee | ||
|
|
20b584398e | ||
|
|
28dde14a33 | ||
|
|
f10afd1059 | ||
|
|
45285ee345 | ||
|
|
fa070c50e5 | ||
|
|
d97d4f3949 | ||
|
|
64633bade4 | ||
|
|
d82be97963 | ||
|
|
aa899f6d78 | ||
|
|
5f0212de5f | ||
|
|
c2281779af | ||
|
|
02fbafcda6 | ||
|
|
007d648f5e | ||
|
|
bbdbfe3430 | ||
|
|
ab96155d6a | ||
|
|
8e04706c4d | ||
|
|
99d7b2fa1d | ||
|
|
91400e7489 | ||
|
|
82b768258f | ||
|
|
8c355b5eee | ||
|
|
54309f4a48 | ||
|
|
958f1098b7 | ||
|
|
c366627642 | ||
|
|
ae649c66ed | ||
|
|
36a3b41f53 | ||
|
|
f89e5308e3 | ||
|
|
61a414df77 | ||
|
|
233b976441 | ||
|
|
6b92c1a47b | ||
|
|
1a23115773 | ||
|
|
757c043171 | ||
|
|
57e1bb8106 | ||
|
|
5403e74bbd | ||
|
|
0713ddcabc | ||
|
|
6fbbc89904 | ||
|
|
8aa53612fd | ||
|
|
6a311cad11 | ||
|
|
51e97d343d | ||
|
|
c36b12f3b2 | ||
|
|
7c724c0f10 | ||
|
|
1e6a05d0d8 | ||
|
|
b9af6645e3 | ||
|
|
c2cb76b026 | ||
|
|
684a58fc84 | ||
|
|
9150346a43 | ||
|
|
425d4c73f3 | ||
|
|
00e93bfa11 | ||
|
|
9d8b5077b4 | ||
|
|
3072133e59 | ||
|
|
56a2f9cfcf | ||
|
|
88ef5b137f | ||
|
|
e13e93063c | ||
|
|
98e369285b | ||
|
|
6548eb74f1 | ||
|
|
53eb35f5b2 | ||
|
|
877763b960 | ||
|
|
d490443286 | ||
|
|
1f9d5ef684 | ||
|
|
83f0a3fd13 | ||
|
|
7ecbf8cf60 | ||
|
|
fb0fcd86fd | ||
|
|
36708c910a | ||
|
|
388fda2292 | ||
|
|
94f9b85859 | ||
|
|
1c072017a4 | ||
|
|
8a992703a7 | ||
|
|
2053fea0a7 | ||
|
|
552bc02783 | ||
|
|
fafe1afa61 | ||
|
|
ab80ef1845 | ||
|
|
9cae39449a | ||
|
|
f58de21068 | ||
|
|
1cbb49864c | ||
|
|
f8965317c3 | ||
|
|
a359a5a1f2 | ||
|
|
7651854bbd | ||
|
|
5139cc2bfb | ||
|
|
c0e85481b0 | ||
|
|
e6fe95b4f2 | ||
|
|
303c23cf1e | ||
|
|
0e2041dd41 | ||
|
|
9122dd2d70 | ||
|
|
17d7988ad4 | ||
|
|
8fd2e2164c | ||
|
|
e499f157dd | ||
|
|
f75e7582e6 | ||
|
|
9e69ac889c | ||
|
|
769464762a | ||
|
|
342eba6f22 | ||
|
|
bd2c1027fa | ||
|
|
d295ff4f04 | ||
|
|
7ce4f2ae62 | ||
|
|
092250b4fa | ||
|
|
b577f8a5ea | ||
|
|
4329a817aa | ||
|
|
6631d8be4e | ||
|
|
a7fff59136 | ||
|
|
4a36f67f94 | ||
|
|
47e8946581 | ||
|
|
ea7568ceb3 | ||
|
|
e6b42a2be2 | ||
|
|
7bbc65ea71 | ||
|
|
d6c550c838 | ||
|
|
eff592c447 | ||
|
|
138286f3b1 | ||
|
|
f6f8fc1229 | ||
|
|
2d55c088cc | ||
|
|
a0fa5d57c1 | ||
|
|
f8729f6ea0 | ||
|
|
f7772af197 | ||
|
|
2f46e6a43c | ||
|
|
d333535e76 | ||
|
|
4b04be6020 | ||
|
|
fc11ecfa2b | ||
|
|
3281b9077f | ||
|
|
194f6c9f95 | ||
|
|
99277a427f | ||
|
|
48e113a90e | ||
|
|
06f8e35597 | ||
|
|
07b6686411 | ||
|
|
dbcfb48198 | ||
|
|
34a2e1d56b | ||
|
|
da143c5527 | ||
|
|
1f03fc62db | ||
|
|
06e03a41aa | ||
|
|
41c61900d1 | ||
|
|
f57f4cd360 | ||
|
|
d9498b4b55 | ||
|
|
7a5851e155 | ||
|
|
5b23a4ad7b | ||
|
|
10eba0bd5f | ||
|
|
ab0527b390 | ||
|
|
bfe141ea79 | ||
|
|
4376eb8217 | ||
|
|
8e2c0c3a0c | ||
|
|
de58a496ef | ||
|
|
d07193cdf2 | ||
|
|
279b76d440 | ||
|
|
dfa102c5ae | ||
|
|
a04b3d80c8 | ||
|
|
e76b485de3 | ||
|
|
0492255d7b | ||
|
|
6b9c2b0363 | ||
|
|
f0820ae8e4 | ||
|
|
5a9b810aef | ||
|
|
4fb671f4eb | ||
|
|
a3cbe1a554 | ||
|
|
9b823616dd | ||
|
|
3c69e5c46b | ||
|
|
2ac13b9489 | ||
|
|
a8d7f06b47 | ||
|
|
28e1c15e90 | ||
|
|
0ee7271e48 |
70
.github/ISSUE_TEMPLATE/1.bug-report.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: Report an issue
|
||||
description: Report an issue with Zed.
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Feature requests should be opened in [discussions](https://github.com/zed-industries/zed/discussions/new/choose).
|
||||
|
||||
Before opening a new issue, please do a [search](https://github.com/zed-industries/zed/issues) of existing issues and :+1: upvote the existing issue instead. This will help us maintain a proper signal-to-noise ratio.
|
||||
|
||||
If you need help with your own project, you can ask a question in our [Discord Support Forums](https://discord.com/invite/zedindustries).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: A step-by-step description of how to reproduce the issue from a **clean Zed install**. Any code must be sufficient to reproduce (make sure to include context!). Include code as text, not just as a screenshot. **Issues with insufficient detail may be summarily closed**.
|
||||
placeholder: |
|
||||
1. Start Zed
|
||||
2. Click X
|
||||
3. Y will happen
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current vs. Expected behavior
|
||||
description: |
|
||||
A clear and concise description of what is the current behavior (screenshots, videos), vs. what you expected the behavior to be.
|
||||
|
||||
**Skipping this/failure to provide complete information will result in the issue being closed.**
|
||||
placeholder: "Based on my reproduction steps above, when I click X, I expect this to happen, but instead Y happens."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your Zed log file to this issue.
|
||||
description: |
|
||||
Open the command palette in Zed, then type `zed: open log` to see the last 1000 lines. Or type `zed: reveal log in file manager` in the command palette to reveal the log file itself.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Paste your log inside the code block. -->
|
||||
```log
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, provide details about your model provider
|
||||
placeholder: |
|
||||
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc.)
|
||||
- Model Name: (Claude Sonnet 4.5, Gemini 3 Pro, GPT-5)
|
||||
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
|
||||
- Other details (ACPs, MCPs, other settings, etc.):
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Zed version and system specs
|
||||
description: |
|
||||
Open the command palette in Zed, then type “zed: copy system specs into clipboard”. **Skipping this/failure to provide complete information will result in the issue being closed**.
|
||||
placeholder: |
|
||||
Zed: v0.215.0 (Zed Nightly bfe141ea79aa4984028934067ba75c48d99136ae)
|
||||
OS: macOS 15.1
|
||||
Memory: 36 GiB
|
||||
Architecture: aarch64
|
||||
validations:
|
||||
required: true
|
||||
99
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Report a bug
|
||||
description: Report a problem with Zed.
|
||||
type: Bug
|
||||
labels: "state:needs triage"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Is this bug already reported? Upvote to get it noticed faster. [Here's the search](https://github.com/zed-industries/zed/issues). Upvote means giving it a :+1: reaction.
|
||||
|
||||
Feature request? Please open in [discussions](https://github.com/zed-industries/zed/discussions/new/choose) instead.
|
||||
|
||||
Just have a question or need support? Welcome to [Discord Support Forums](https://discord.com/invite/zedindustries).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: A step-by-step description of how to reproduce the bug from a **clean Zed install**. The more context you provide, the easier it is to find and fix the problem fast.
|
||||
placeholder: |
|
||||
1. Start Zed
|
||||
2. Click X
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current vs. Expected behavior
|
||||
description: |
|
||||
Current behavior (screenshots, videos, etc. are appreciated), vs. what you expected the behavior to be.
|
||||
|
||||
placeholder: |
|
||||
Current behavior: <screenshot with an arrow> The icon is blue. Expected behavior: The icon should be red because this is what the setting is documented to do.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed version and system specs
|
||||
description: |
|
||||
Open the command palette in Zed, then type “zed: copy system specs into clipboard”.
|
||||
placeholder: |
|
||||
Zed: v0.215.0 (Zed Nightly bfe141ea79aa4984028934067ba75c48d99136ae)
|
||||
OS: macOS 15.1
|
||||
Memory: 36 GiB
|
||||
Architecture: aarch64
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Attach Zed log file
|
||||
description: |
|
||||
Open the command palette in Zed, then type `zed: open log` to see the last 1000 lines. Or type `zed: reveal log in file manager` in the command palette to reveal the log file itself.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Paste your log inside the code block. -->
|
||||
```log
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant Zed settings
|
||||
description: |
|
||||
Open the command palette in Zed, then type “zed: open settings file” and copy/paste any relevant (e.g., LSP-specific) settings.
|
||||
value: |
|
||||
<details><summary>settings.json</summary>
|
||||
|
||||
<!-- Paste your settings inside the code block. -->
|
||||
```json
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: (for AI issues) Model provider details
|
||||
placeholder: |
|
||||
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc.)
|
||||
- Model Name: (Claude Sonnet 4.5, Gemini 3 Pro, GPT-5)
|
||||
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
|
||||
- Other details (ACPs, MCPs, other settings, etc.):
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: If you are using WSL on Windows, what flavor of Linux are you using?
|
||||
multiple: false
|
||||
options:
|
||||
- Arch Linux
|
||||
- Ubuntu
|
||||
- Fedora
|
||||
- Mint
|
||||
- Pop!_OS
|
||||
- NixOS
|
||||
- Other
|
||||
@@ -1,32 +1,23 @@
|
||||
name: Report a crash
|
||||
description: Zed is crashing or freezing or hanging.
|
||||
type: Crash
|
||||
labels: "state:needs triage"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: A step-by-step description of how to reproduce the crash from a **clean Zed install**. **Be verbose**. **Issues with insufficient detail may be summarily closed**.
|
||||
description: A step-by-step description of how to reproduce the crash from a **clean Zed install**. The more context you provide, the easier it is to find and fix the problem fast.
|
||||
placeholder: |
|
||||
1. Start Zed
|
||||
2. Perform an action
|
||||
3. Zed crashes
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current vs. Expected behavior
|
||||
description: |
|
||||
Go into depth about what actions you’re performing in Zed to trigger the crash. If Zed crashes before it loads any windows, make sure to mention that. Again, **be verbose**.
|
||||
|
||||
**Skipping this/failure to provide complete information will result in the issue being closed.**
|
||||
placeholder: "Based on my reproduction steps above, when I perform said action, I expect this to happen, but instead Zed crashes."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Zed version and system specs
|
||||
description: |
|
||||
Open the command palette in Zed, then type “zed: copy system specs into clipboard”. **Skipping this/failure to provide complete information will result in the issue being closed**.
|
||||
Open the command palette in Zed, then type “zed: copy system specs into clipboard”.
|
||||
placeholder: |
|
||||
Zed: v0.215.0 (Zed Nightly bfe141ea79aa4984028934067ba75c48d99136ae)
|
||||
OS: macOS 15.1
|
||||
@@ -36,7 +27,7 @@ body:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your Zed log file to this issue
|
||||
label: Attach Zed log file
|
||||
description: |
|
||||
Open the command palette in Zed, then type `zed: open log` to see the last 1000 lines. Or type `zed: reveal log in file manager` in the command palette to reveal the log file itself.
|
||||
value: |
|
||||
19
.github/ISSUE_TEMPLATE/99_other.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Other [Staff Only]
|
||||
description: Zed Staff Only
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
|
||||
IF YOU DO NOT WORK FOR ZED INDUSTRIES DO NOT CREATE ISSUES WITH THIS TEMPLATE.
|
||||
THEY WILL BE AUTO-CLOSED AND MAY RESULT IN YOU BEING BANNED FROM THE ZED ISSUE TRACKER.
|
||||
|
||||
FEATURE REQUESTS / SUPPORT REQUESTS SHOULD BE OPENED AS DISCUSSIONS:
|
||||
https://github.com/zed-industries/zed/discussions/new/choose
|
||||
validations:
|
||||
required: true
|
||||
@@ -23,11 +23,13 @@ jobs:
|
||||
AidanV
|
||||
akbxr
|
||||
AlvaroParker
|
||||
amtoaer
|
||||
artemevsevev
|
||||
bajrangCoder
|
||||
bcomnes
|
||||
Be-ing
|
||||
blopker
|
||||
bnjjj
|
||||
bobbymannino
|
||||
CharlesChen0823
|
||||
chbk
|
||||
@@ -35,8 +37,10 @@ jobs:
|
||||
davewa
|
||||
ddoemonn
|
||||
djsauble
|
||||
errmayank
|
||||
fantacell
|
||||
findrakecil
|
||||
FloppyDisco
|
||||
gko
|
||||
huacnlee
|
||||
imumesh18
|
||||
@@ -51,6 +55,7 @@ jobs:
|
||||
marius851000
|
||||
mikebronner
|
||||
ognevny
|
||||
playdohface
|
||||
RemcoSmitsDev
|
||||
romaninsh
|
||||
Simek
|
||||
@@ -58,12 +63,14 @@ jobs:
|
||||
sourcefrog
|
||||
suxiaoshao
|
||||
Takk8IS
|
||||
thedadams
|
||||
tidely
|
||||
timvermeulen
|
||||
valentinegb
|
||||
versecafe
|
||||
vitallium
|
||||
warrenjokinen
|
||||
WhySoBad
|
||||
ya7010
|
||||
Zertsov
|
||||
with:
|
||||
|
||||
176
.github/workflows/extension_bump.yml
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
# Generated from xtask::workflows::extension_bump
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: extension_bump
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: '1'
|
||||
CARGO_INCREMENTAL: '0'
|
||||
ZED_EXTENSION_CLI_SHA: 7cfce605704d41ca247e3f84804bf323f6c6caaf
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
bump-type:
|
||||
description: bump-type
|
||||
type: string
|
||||
default: patch
|
||||
force-bump:
|
||||
description: force-bump
|
||||
required: true
|
||||
type: boolean
|
||||
secrets:
|
||||
app-id:
|
||||
description: The app ID used to create the PR
|
||||
required: true
|
||||
app-secret:
|
||||
description: The app secret for the corresponding app ID
|
||||
required: true
|
||||
jobs:
|
||||
check_extension:
|
||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- id: cache-zed-extension-cli
|
||||
name: extension_tests::cache_zed_extension_cli
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
with:
|
||||
path: zed-extension
|
||||
key: zed-extension-${{ env.ZED_EXTENSION_CLI_SHA }}
|
||||
- name: extension_tests::download_zed_extension_cli
|
||||
if: steps.cache-zed-extension-cli.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension"
|
||||
chmod +x zed-extension
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: extension_tests::check
|
||||
run: |
|
||||
mkdir -p /tmp/ext-scratch
|
||||
mkdir -p /tmp/ext-output
|
||||
./zed-extension --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
|
||||
shell: bash -euxo pipefail {0}
|
||||
timeout-minutes: 2
|
||||
check_bump_needed:
|
||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
fetch-depth: 0
|
||||
- id: compare-versions-check
|
||||
name: extension_bump::compare_versions
|
||||
run: |
|
||||
CURRENT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
|
||||
PR_PARENT_SHA="${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
if [[ -n "$PR_PARENT_SHA" ]]; then
|
||||
git checkout "$PR_PARENT_SHA"
|
||||
elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then
|
||||
git checkout "$BRANCH_PARENT_SHA"
|
||||
else
|
||||
git checkout "$(git log -1 --format=%H)"~1
|
||||
fi
|
||||
|
||||
PARENT_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
|
||||
|
||||
[[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
|
||||
echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
|
||||
echo "needs_bump=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
shell: bash -euxo pipefail {0}
|
||||
outputs:
|
||||
needs_bump: ${{ steps.compare-versions-check.outputs.needs_bump }}
|
||||
current_version: ${{ steps.compare-versions-check.outputs.current_version }}
|
||||
timeout-minutes: 1
|
||||
bump_extension_version:
|
||||
needs:
|
||||
- check_extension
|
||||
- check_bump_needed
|
||||
if: |-
|
||||
(github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') &&
|
||||
(inputs.force-bump == 'true' || needs.check_bump_needed.outputs.needs_bump == 'true')
|
||||
runs-on: namespace-profile-8x16-ubuntu-2204
|
||||
steps:
|
||||
- id: generate-token
|
||||
name: extension_bump::generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.app-id }}
|
||||
private-key: ${{ secrets.app-secret }}
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: extension_bump::install_bump_2_version
|
||||
run: pip install bump2version
|
||||
shell: bash -euxo pipefail {0}
|
||||
- id: bump-version
|
||||
name: extension_bump::bump_version
|
||||
run: |
|
||||
OLD_VERSION="${{ needs.check_bump_needed.outputs.current_version }}"
|
||||
|
||||
BUMP_FILES=("extension.toml")
|
||||
if [[ -f "Cargo.toml" ]]; then
|
||||
BUMP_FILES+=("Cargo.toml")
|
||||
fi
|
||||
|
||||
bump2version --verbose --current-version "$OLD_VERSION" --no-configured-files ${{ inputs.bump-type }} "${BUMP_FILES[@]}"
|
||||
|
||||
if [[ -f "Cargo.toml" ]]; then
|
||||
cargo update --workspace
|
||||
fi
|
||||
|
||||
NEW_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
|
||||
|
||||
echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: extension_bump::create_pull_request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
title: Bump version to ${{ steps.bump-version.outputs.new_version }}
|
||||
body: This PR bumps the version of this extension to v${{ steps.bump-version.outputs.new_version }}
|
||||
commit-message: Bump version to v${{ steps.bump-version.outputs.new_version }}
|
||||
branch: zed-zippy-autobump
|
||||
committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
|
||||
base: main
|
||||
delete-branch: true
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
sign-commits: true
|
||||
timeout-minutes: 1
|
||||
create_version_label:
|
||||
needs:
|
||||
- check_extension
|
||||
- check_bump_needed
|
||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.check_bump_needed.outputs.needs_bump == 'false'
|
||||
runs-on: namespace-profile-8x16-ubuntu-2204
|
||||
steps:
|
||||
- id: generate-token
|
||||
name: extension_bump::generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.app-id }}
|
||||
private-key: ${{ secrets.app-secret }}
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: extension_bump::create_version_tag
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |-
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/v${{ needs.check_bump_needed.outputs.current_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
timeout-minutes: 1
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
|
||||
cancel-in-progress: true
|
||||
43
.github/workflows/extension_release.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Generated from xtask::workflows::extension_release
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: extension_release
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
app-id:
|
||||
description: The app ID used to create the PR
|
||||
required: true
|
||||
app-secret:
|
||||
description: The app secret for the corresponding app ID
|
||||
required: true
|
||||
jobs:
|
||||
create_release:
|
||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
||||
runs-on: namespace-profile-8x16-ubuntu-2204
|
||||
steps:
|
||||
- id: generate-token
|
||||
name: extension_bump::generate_token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.app-id }}
|
||||
private-key: ${{ secrets.app-secret }}
|
||||
owner: zed-industries
|
||||
repositories: extensions
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- id: get-extension-id
|
||||
name: extension_release::get_extension_id
|
||||
run: |
|
||||
EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"
|
||||
|
||||
echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: extension_release::release_action
|
||||
uses: huacnlee/zed-extension-action@v2
|
||||
with:
|
||||
extension-name: ${{ steps.get-extension-id.outputs.extension_id }}
|
||||
push-to: zed-industries/extensions
|
||||
env:
|
||||
COMMITTER_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
13
.github/workflows/extension_tests.yml
vendored
@@ -7,12 +7,7 @@ env:
|
||||
CARGO_INCREMENTAL: '0'
|
||||
ZED_EXTENSION_CLI_SHA: 7cfce605704d41ca247e3f84804bf323f6c6caaf
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_tests:
|
||||
description: Whether the workflow should run rust tests
|
||||
required: true
|
||||
type: boolean
|
||||
workflow_call: {}
|
||||
jobs:
|
||||
orchestrate:
|
||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
||||
@@ -73,12 +68,12 @@ jobs:
|
||||
run: cargo clippy --release --all-targets --all-features -- --deny warnings
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::cargo_install_nextest
|
||||
if: inputs.run_tests
|
||||
uses: taiki-e/install-action@nextest
|
||||
- name: steps::cargo_nextest
|
||||
if: inputs.run_tests
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
shell: bash -euxo pipefail {0}
|
||||
env:
|
||||
NEXTEST_NO_TESTS: warn
|
||||
timeout-minutes: 3
|
||||
check_extension:
|
||||
needs:
|
||||
@@ -108,7 +103,7 @@ jobs:
|
||||
mkdir -p /tmp/ext-output
|
||||
./zed-extension --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
|
||||
shell: bash -euxo pipefail {0}
|
||||
timeout-minutes: 1
|
||||
timeout-minutes: 2
|
||||
tests_pass:
|
||||
needs:
|
||||
- orchestrate
|
||||
|
||||
12
.github/workflows/run_bundling.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
bundle_linux_aarch64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
bundle_linux_x86_64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: namespace-profile-32x64-ubuntu-2004
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
bundle_mac_aarch64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
bundle_mac_x86_64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
bundle_windows_aarch64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -229,7 +229,7 @@ jobs:
|
||||
bundle_windows_x86_64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
9
.github/workflows/run_cron_unit_evals.yml
vendored
@@ -13,6 +13,14 @@ on:
|
||||
jobs:
|
||||
cron_unit_evals:
|
||||
runs-on: namespace-profile-16x32-ubuntu-2204
|
||||
strategy:
|
||||
matrix:
|
||||
model:
|
||||
- anthropic/claude-sonnet-4-5-latest
|
||||
- anthropic/claude-opus-4-5-latest
|
||||
- google/gemini-3-pro
|
||||
- openai/gpt-5
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
@@ -49,6 +57,7 @@ jobs:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
|
||||
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
|
||||
ZED_AGENT_MODEL: ${{ matrix.model }}
|
||||
- name: steps::cleanup_cargo_config
|
||||
if: always()
|
||||
run: |
|
||||
|
||||
3
.github/workflows/run_tests.yml
vendored
@@ -84,7 +84,7 @@ jobs:
|
||||
run: ./script/check-keymaps
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: run_tests::check_style::check_for_typos
|
||||
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1
|
||||
uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06
|
||||
with:
|
||||
config: ./typos.toml
|
||||
- name: steps::cargo_fmt
|
||||
@@ -520,6 +520,7 @@ jobs:
|
||||
uses: bufbuild/buf-setup-action@v1
|
||||
with:
|
||||
version: v1.29.0
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_breaking_action
|
||||
uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
|
||||
126
Cargo.lock
generated
@@ -159,6 +159,7 @@ dependencies = [
|
||||
"derive_more 0.99.20",
|
||||
"editor",
|
||||
"env_logger 0.11.8",
|
||||
"eval_utils",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
@@ -328,6 +329,7 @@ dependencies = [
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
"cloud_llm_client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
@@ -335,6 +337,7 @@ dependencies = [
|
||||
"context_server",
|
||||
"db",
|
||||
"editor",
|
||||
"eval_utils",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"feature_flags",
|
||||
@@ -343,6 +346,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
"image",
|
||||
@@ -370,6 +374,7 @@ dependencies = [
|
||||
"proto",
|
||||
"rand 0.9.2",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"rope",
|
||||
"rules_library",
|
||||
"schemars",
|
||||
@@ -1380,6 +1385,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"markdown_preview",
|
||||
"release_channel",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
@@ -2422,6 +2428,7 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"rope",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"sum_tree",
|
||||
"text",
|
||||
"unindent",
|
||||
@@ -4182,6 +4189,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"smol",
|
||||
"system_specs",
|
||||
"windows 0.61.3",
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
@@ -5298,6 +5306,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
"paths",
|
||||
"project",
|
||||
"regex",
|
||||
@@ -5307,6 +5316,8 @@ dependencies = [
|
||||
"telemetry",
|
||||
"theme",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zeta",
|
||||
@@ -5365,6 +5376,7 @@ dependencies = [
|
||||
"db",
|
||||
"edit_prediction",
|
||||
"emojis",
|
||||
"feature_flags",
|
||||
"file_icons",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
@@ -5769,6 +5781,15 @@ dependencies = [
|
||||
"watch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eval_utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"serde",
|
||||
"smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -6406,6 +6427,7 @@ dependencies = [
|
||||
"git",
|
||||
"gpui",
|
||||
"ignore",
|
||||
"is_executable",
|
||||
"libc",
|
||||
"log",
|
||||
"notify 8.2.0",
|
||||
@@ -6965,7 +6987,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gh-workflow"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435"
|
||||
source = "git+https://github.com/zed-industries/gh-workflow?rev=e5f883040530b4df36437f140084ee5cc7c1c9be#e5f883040530b4df36437f140084ee5cc7c1c9be"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"derive_more 2.0.1",
|
||||
@@ -6982,7 +7004,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gh-workflow-macros"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435"
|
||||
source = "git+https://github.com/zed-industries/gh-workflow?rev=e5f883040530b4df36437f140084ee5cc7c1c9be#e5f883040530b4df36437f140084ee5cc7c1c9be"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"quote",
|
||||
@@ -7105,6 +7127,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
@@ -7126,6 +7149,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"strum 0.27.2",
|
||||
"telemetry",
|
||||
"theme",
|
||||
@@ -8436,6 +8460,15 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@@ -9002,6 +9035,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"collections",
|
||||
"futures 0.3.31",
|
||||
"globset",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"itertools 0.14.0",
|
||||
@@ -9027,7 +9061,9 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"snippet",
|
||||
"task",
|
||||
"terminal",
|
||||
"text",
|
||||
@@ -11530,7 +11566,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger 0.10.2",
|
||||
@@ -11555,6 +11591,7 @@ dependencies = [
|
||||
"pet-python-utils",
|
||||
"pet-reporter",
|
||||
"pet-telemetry",
|
||||
"pet-uv",
|
||||
"pet-venv",
|
||||
"pet-virtualenv",
|
||||
"pet-virtualenvwrapper",
|
||||
@@ -11567,7 +11604,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-conda"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -11586,7 +11623,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
@@ -11601,7 +11638,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-env-var-path"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11617,7 +11654,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-fs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11626,7 +11663,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-global-virtualenvs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11639,7 +11676,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-homebrew"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11657,7 +11694,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-jsonrpc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
@@ -11670,7 +11707,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-linux-global-python"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11683,7 +11720,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-commandlinetools"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11696,7 +11733,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-python-org"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11709,7 +11746,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-xcode"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11722,7 +11759,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pipenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11735,7 +11772,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pixi"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11747,7 +11784,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-poetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"lazy_static",
|
||||
@@ -11768,7 +11805,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pyenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11786,7 +11823,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-python-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -11803,7 +11840,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-reporter"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
@@ -11817,7 +11854,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-telemetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -11829,10 +11866,22 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-uv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pet-core",
|
||||
"pet-python-utils",
|
||||
"serde",
|
||||
"toml 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pet-venv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11844,7 +11893,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-virtualenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11856,7 +11905,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-virtualenvwrapper"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11869,7 +11918,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-windows-registry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11887,7 +11936,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-windows-store"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -15088,6 +15137,7 @@ dependencies = [
|
||||
"editor",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
@@ -17313,8 +17363,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiktoken-rs"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=30c32a4522751699adeda0d5840c71c3b75ae73d#30c32a4522751699adeda0d5840c71c3b75ae73d"
|
||||
version = "0.9.1"
|
||||
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=7249f999c5fdf9bf3cc5c288c964454e4dac0c00#7249f999c5fdf9bf3cc5c288c964454e4dac0c00"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -17971,9 +18021,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-bash"
|
||||
version = "0.25.0"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "871b0606e667e98a1237ebdc1b0d7056e0aebfdc3141d12b399865d4cb6ed8a6"
|
||||
checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -20985,6 +21035,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
@@ -21169,7 +21220,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.215.0"
|
||||
version = "0.216.0"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
@@ -21459,6 +21510,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0729d50b4ca0a7e28e590bbe32e3ca0194d97ef654961451a424c661a366fca0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -21467,9 +21520,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0729d50b4ca0a7e28e590bbe32e3ca0194d97ef654961451a424c661a366fca0"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -21487,12 +21538,12 @@ dependencies = [
|
||||
name = "zed_html"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zed_extension_api 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_proto"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -21501,7 +21552,7 @@ dependencies = [
|
||||
name = "zed_test_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.7.0",
|
||||
"zed_extension_api 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -21645,6 +21696,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"copilot",
|
||||
"credentials_provider",
|
||||
"ctor",
|
||||
"db",
|
||||
"edit_prediction",
|
||||
|
||||
29
Cargo.toml
@@ -59,6 +59,7 @@ members = [
|
||||
"crates/zeta2_tools",
|
||||
"crates/editor",
|
||||
"crates/eval",
|
||||
"crates/eval_utils",
|
||||
"crates/explorer_command_injector",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
@@ -288,6 +289,7 @@ deepseek = { path = "crates/deepseek" }
|
||||
derive_refineable = { path = "crates/refineable/derive_refineable" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
eval_utils = { path = "crates/eval_utils" }
|
||||
extension = { path = "crates/extension" }
|
||||
extension_host = { path = "crates/extension_host" }
|
||||
extensions_ui = { path = "crates/extensions_ui" }
|
||||
@@ -508,7 +510,7 @@ fork = "0.4.0"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "3eaa84abca0778eb54272f45a312cb24f9a0b435" }
|
||||
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "e5f883040530b4df36437f140084ee5cc7c1c9be" }
|
||||
git2 = { version = "0.20.1", default-features = false }
|
||||
globset = "0.4"
|
||||
handlebars = "4.3"
|
||||
@@ -583,14 +585,14 @@ partial-json-fixer = "0.5.3"
|
||||
parse_int = "0.9"
|
||||
pciid-parser = "0.8.0"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||
portable-pty = "0.9.0"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||
@@ -639,6 +641,7 @@ serde_urlencoded = "0.7"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
similar = "2.6"
|
||||
simplelog = "0.12.2"
|
||||
slotmap = "1.0.6"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
@@ -655,7 +658,7 @@ sysinfo = "0.37.0"
|
||||
take-until = "0.2.0"
|
||||
tempfile = "3.20.0"
|
||||
thiserror = "2.0.12"
|
||||
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "30c32a4522751699adeda0d5840c71c3b75ae73d" }
|
||||
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "7249f999c5fdf9bf3cc5c288c964454e4dac0c00" }
|
||||
time = { version = "0.3", features = [
|
||||
"macros",
|
||||
"parsing",
|
||||
@@ -671,7 +674,7 @@ toml = "0.8"
|
||||
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
|
||||
tower-http = "0.4.4"
|
||||
tree-sitter = { version = "0.25.10", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.25.0"
|
||||
tree-sitter-bash = "0.25.1"
|
||||
tree-sitter-c = "0.23"
|
||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
|
||||
tree-sitter-css = "0.23"
|
||||
@@ -866,10 +869,6 @@ debug = "full"
|
||||
lto = false
|
||||
codegen-units = 16
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = "full"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "allow" }
|
||||
|
||||
|
||||
@@ -43,8 +43,9 @@ design
|
||||
= @danilo-leal
|
||||
|
||||
docs
|
||||
= @probably-neb
|
||||
= @miguelraz
|
||||
= @probably-neb
|
||||
= @yeskunall
|
||||
|
||||
extension
|
||||
= @kubkon
|
||||
@@ -52,6 +53,10 @@ extension
|
||||
git
|
||||
= @cole-miller
|
||||
= @danilo-leal
|
||||
= @dvdsk
|
||||
= @kubkon
|
||||
= @Anthony-Eid
|
||||
= @cameron1024
|
||||
|
||||
gpui
|
||||
= @Anthony-Eid
|
||||
@@ -99,6 +104,9 @@ settings_ui
|
||||
= @danilo-leal
|
||||
= @probably-neb
|
||||
|
||||
sum_tree
|
||||
= @Veykril
|
||||
|
||||
support
|
||||
= @miguelraz
|
||||
|
||||
@@ -110,6 +118,9 @@ terminal
|
||||
= @kubkon
|
||||
= @Veykril
|
||||
|
||||
text
|
||||
= @Veykril
|
||||
|
||||
vim
|
||||
= @ConradIrwin
|
||||
= @dinocosta
|
||||
@@ -119,3 +130,4 @@ vim
|
||||
windows
|
||||
= @localcc
|
||||
= @reflectronic
|
||||
= @Veykril
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M14 11.333A6 6 0 0 0 4 6.867l-1 .9"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M2 4.667v4h4"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12a.667.667 0 1 0 0-1.333A.667.667 0 0 0 8 12Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 467 B |
@@ -1 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.333 10 8 14.667 12.667 10M8 5.333v9.334"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 2.667a.667.667 0 1 0 0-1.334.667.667 0 0 0 0 1.334Z"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 13H5" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 13H14" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5 8.5L8 12M8 12L4.5 8.5M8 12L8 3" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 443 B |
@@ -1 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.333 6 8 1.333 12.667 6M8 10.667V1.333"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 13.333a.667.667 0 1 1 0 1.334.667.667 0 0 1 0-1.334Z"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 6.5L8 3M8 3L11.5 6.5M8 3V12" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 13H5" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 13H14" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 439 B |
@@ -1 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2 11.333a6 6 0 0 1 10-4.466l1 .9"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M14 4.667v4h-4"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12a.667.667 0 1 1 0-1.333A.667.667 0 0 1 8 12Z"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 11.333C2.00118 10.1752 2.33729 9.04258 2.96777 8.07159C3.59826 7.10059 4.49621 6.33274 5.55331 5.86064C6.61041 5.38853 7.78152 5.23235 8.9254 5.41091C10.0693 5.58947 11.1371 6.09516 12 6.86698L13 7.76698" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 4.66699V8.66699H10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 13H10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 627 B |
10
assets/icons/file_icons/odin.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_2)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58747 12.9359C4.35741 12.778 4.17558 12.625 4.17558 12.625L10.092 2.37749C10.092 2.37749 10.3355 2.46782 10.5367 2.56426C10.7903 2.6858 11.0003 2.80429 11.0003 2.80429C13.8681 4.46005 14.8523 8.13267 13.1965 11.0005C11.5407 13.8684 7.8681 14.8525 5.00023 13.1967C5.00023 13.1967 4.79936 13.0812 4.58747 12.9359ZM10.5003 3.67032L5.50023 12.3307C7.89013 13.7105 10.9506 12.8904 12.3305 10.5006C13.7102 8.1106 12.8902 5.05015 10.5003 3.67032ZM3.07664 11.4314C2.87558 11.1403 2.804 11.0006 2.804 11.0006C1.77036 9.20524 1.69456 6.92215 2.80404 5.00046C3.91353 3.07877 5.92859 2.00291 8.0003 2.00036C8.0003 2.00036 8.28 1.99964 8.51289 2.02194C8.86375 2.05556 9.09702 2.10083 9.09702 2.10083L3.43905 11.9007C3.43905 11.9007 3.30482 11.7618 3.07664 11.4314ZM7.40178 3.03702C5.89399 3.22027 4.48727 4.08506 3.67008 5.50052C2.85288 6.9159 2.80733 8.56653 3.40252 9.96401L7.40178 3.03702Z" fill="black" stroke="black" stroke-width="0.1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_2">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1279,6 +1279,7 @@
|
||||
"escape": "workspace::CloseWindow",
|
||||
"ctrl-m": "settings_editor::Minimize",
|
||||
"ctrl-f": "search::FocusSearch",
|
||||
"ctrl-,": "settings_editor::OpenCurrentFile",
|
||||
"left": "settings_editor::ToggleFocusNav",
|
||||
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
|
||||
// todo(settings_ui): cut this down based on the max files and overflow UI
|
||||
@@ -1334,5 +1335,12 @@
|
||||
"alt-left": "dev::Zeta2ContextGoBack",
|
||||
"alt-right": "dev::Zeta2ContextGoForward"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "branch_picker::DeleteBranch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
||||
"ctrl-cmd-z": "edit_prediction::RateCompletions",
|
||||
"ctrl-cmd-i": "edit_prediction::ToggleMenu",
|
||||
"ctrl-cmd-l": "lsp_tool::ToggleMenu"
|
||||
"ctrl-cmd-l": "lsp_tool::ToggleMenu",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -589,8 +590,7 @@
|
||||
"cmd-.": "editor::ToggleCodeActions",
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
"cmd-k p": "editor::CopyPath",
|
||||
"cmd-\\": "pane::SplitRight",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
"cmd-\\": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -730,7 +730,8 @@
|
||||
"context": "Workspace && debugger_running",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"f5": "zed::NoAction"
|
||||
"f5": "zed::NoAction",
|
||||
"f11": "debugger::StepInto"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1382,6 +1383,7 @@
|
||||
"escape": "workspace::CloseWindow",
|
||||
"cmd-m": "settings_editor::Minimize",
|
||||
"cmd-f": "search::FocusSearch",
|
||||
"cmd-,": "settings_editor::OpenCurrentFile",
|
||||
"left": "settings_editor::ToggleFocusNav",
|
||||
"cmd-shift-e": "settings_editor::ToggleFocusNav",
|
||||
// todo(settings_ui): cut this down based on the max files and overflow UI
|
||||
@@ -1438,5 +1440,12 @@
|
||||
"alt-left": "dev::Zeta2ContextGoBack",
|
||||
"alt-right": "dev::Zeta2ContextGoForward"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "branch_picker::DeleteBranch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
"shift-f5": "debugger::Stop",
|
||||
"ctrl-shift-f5": "debugger::RerunSession",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"ctrl-f11": "debugger::StepInto",
|
||||
"f10": "debugger::StepOver",
|
||||
"shift-f11": "debugger::StepOut",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-shift-i": "edit_prediction::ToggleMenu",
|
||||
"shift-alt-l": "lsp_tool::ToggleMenu"
|
||||
"shift-alt-l": "lsp_tool::ToggleMenu",
|
||||
"ctrl-shift-alt-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -117,7 +117,7 @@
|
||||
"alt-g m": "git::OpenModifiedFiles",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
"shift-f10": "editor::OpenContextMenu",
|
||||
"ctrl-shift-e": "editor::ToggleEditPrediction",
|
||||
"ctrl-alt-e": "editor::ToggleEditPrediction",
|
||||
"f9": "editor::ToggleBreakpoint",
|
||||
"shift-f9": "editor::EditLogBreakpoint"
|
||||
}
|
||||
@@ -215,7 +215,7 @@
|
||||
"context": "ContextEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-enter": "assistant::Assist",
|
||||
"ctrl-i": "assistant::Assist",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"ctrl-shift-,": "assistant::InsertIntoEditor",
|
||||
"shift-enter": "assistant::Split",
|
||||
@@ -240,18 +240,18 @@
|
||||
"shift-alt-p": "agent::ManageProfiles",
|
||||
"ctrl-i": "agent::ToggleProfileSelector",
|
||||
"shift-alt-/": "agent::ToggleModelSelector",
|
||||
"ctrl-shift-j": "agent::ToggleNavigationMenu",
|
||||
"ctrl-alt-i": "agent::ToggleOptionsMenu",
|
||||
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-j": "agent::ToggleNavigationMenu",
|
||||
"shift-alt-i": "agent::ToggleOptionsMenu",
|
||||
"ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"ctrl-shift-.": "agent::AddSelectionToThread",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
"super-ctrl-b": "agent::ToggleBurnMode",
|
||||
"alt-enter": "agent::ContinueWithBurnMode",
|
||||
"ctrl-y": "agent::AllowOnce",
|
||||
"shift-alt-a": "agent::AllowOnce",
|
||||
"ctrl-alt-y": "agent::AllowAlways",
|
||||
"ctrl-alt-z": "agent::RejectOnce"
|
||||
"shift-alt-z": "agent::RejectOnce"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -500,10 +500,7 @@
|
||||
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
||||
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
|
||||
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||
"ctrl-shift-down": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch
|
||||
"ctrl-shift-up": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch
|
||||
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip
|
||||
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
|
||||
"ctrl-k ctrl-i": "editor::Hover",
|
||||
"ctrl-k ctrl-b": "editor::BlameHover",
|
||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
@@ -512,12 +509,8 @@
|
||||
"f2": "editor::Rename",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"alt-f12": "editor::GoToDefinitionSplit",
|
||||
"ctrl-shift-f10": "editor::GoToDefinitionSplit",
|
||||
"ctrl-f12": "editor::GoToImplementation",
|
||||
"shift-f12": "editor::GoToTypeDefinition",
|
||||
"ctrl-alt-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"shift-alt-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains
|
||||
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-shift-[": "editor::Fold",
|
||||
"ctrl-shift-]": "editor::UnfoldLines",
|
||||
@@ -541,7 +534,6 @@
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-shift-alt-c": "editor::DisplayCursorNames",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPreviousHunk"
|
||||
}
|
||||
@@ -1124,7 +1116,7 @@
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-v": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
@@ -1305,6 +1297,7 @@
|
||||
"escape": "workspace::CloseWindow",
|
||||
"ctrl-m": "settings_editor::Minimize",
|
||||
"ctrl-f": "search::FocusSearch",
|
||||
"ctrl-,": "settings_editor::OpenCurrentFile",
|
||||
"left": "settings_editor::ToggleFocusNav",
|
||||
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
|
||||
// todo(settings_ui): cut this down based on the max files and overflow UI
|
||||
@@ -1361,5 +1354,12 @@
|
||||
"alt-left": "dev::Zeta2ContextGoBack",
|
||||
"alt-right": "dev::Zeta2ContextGoForward"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "branch_picker::DeleteBranch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -857,6 +857,8 @@
|
||||
"ctrl-w shift-right": "workspace::SwapPaneRight",
|
||||
"ctrl-w shift-up": "workspace::SwapPaneUp",
|
||||
"ctrl-w shift-down": "workspace::SwapPaneDown",
|
||||
"ctrl-w x": "workspace::SwapPaneAdjacent",
|
||||
"ctrl-w ctrl-x": "workspace::SwapPaneAdjacent",
|
||||
"ctrl-w shift-h": "workspace::MovePaneLeft",
|
||||
"ctrl-w shift-l": "workspace::MovePaneRight",
|
||||
"ctrl-w shift-k": "workspace::MovePaneUp",
|
||||
|
||||
42
assets/prompts/content_prompt_v2.hbs
Normal file
@@ -0,0 +1,42 @@
|
||||
{{#if language_name}}
|
||||
Here's a file of {{language_name}} that the user is going to ask you to make an edit to.
|
||||
{{else}}
|
||||
Here's a file of text that the user is going to ask you to make an edit to.
|
||||
{{/if}}
|
||||
|
||||
The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.
|
||||
|
||||
<document>
|
||||
{{{document_content}}}
|
||||
</document>
|
||||
|
||||
{{#if is_truncated}}
|
||||
The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.
|
||||
{{/if}}
|
||||
|
||||
{{#if rewrite_section}}
|
||||
And here's the section to rewrite based on that prompt again for reference:
|
||||
|
||||
<rewrite_this>
|
||||
{{{rewrite_section}}}
|
||||
</rewrite_this>
|
||||
|
||||
{{#if diagnostic_errors}}
|
||||
Below are the diagnostic errors visible to the user. If the user requests problems to be fixed, use this information, but do not try to fix these errors if the user hasn't asked you to.
|
||||
|
||||
{{#each diagnostic_errors}}
|
||||
<diagnostic_error>
|
||||
<line_number>{{line_number}}</line_number>
|
||||
<error_message>{{error_message}}</error_message>
|
||||
<code_content>{{code_content}}</code_content>
|
||||
</diagnostic_error>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
||||
|
||||
Start at the indentation level in the original file in the rewritten {{content_type}}.
|
||||
|
||||
You must use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled.
|
||||
@@ -1209,6 +1209,13 @@
|
||||
"tab_size": 4,
|
||||
// What debuggers are preferred by default for all languages.
|
||||
"debuggers": [],
|
||||
// Whether to enable word diff highlighting in the editor.
|
||||
//
|
||||
// When enabled, changed words within modified lines are highlighted
|
||||
// to show exactly what changed.
|
||||
//
|
||||
// Default: true
|
||||
"word_diff_enabled": true,
|
||||
// Control what info is collected by Zed.
|
||||
"telemetry": {
|
||||
// Send debug info like crash reports.
|
||||
@@ -1350,6 +1357,8 @@
|
||||
// "load_direnv": "direct"
|
||||
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
|
||||
// "load_direnv": "shell_hook"
|
||||
// 3. Don't load direnv configuration at all.
|
||||
// "load_direnv": "disabled"
|
||||
"load_direnv": "direct",
|
||||
"edit_predictions": {
|
||||
// A list of globs representing files that edit predictions should be disabled for.
|
||||
|
||||
@@ -98,6 +98,8 @@
|
||||
"link_text.hover": "#74ade8ff",
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
"version_control.word_added": "#2EA04859",
|
||||
"version_control.word_deleted": "#78081BCC",
|
||||
"version_control.deleted": "#e06c76ff",
|
||||
"version_control.conflict_marker.ours": "#a1c1811a",
|
||||
"version_control.conflict_marker.theirs": "#74ade81a",
|
||||
@@ -499,6 +501,8 @@
|
||||
"link_text.hover": "#5c78e2ff",
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
"version_control.word_added": "#2EA04859",
|
||||
"version_control.word_deleted": "#F85149CC",
|
||||
"version_control.deleted": "#e06c76ff",
|
||||
"conflict": "#a48819ff",
|
||||
"conflict.background": "#faf2e6ff",
|
||||
|
||||
@@ -347,13 +347,13 @@ impl ToolCall {
|
||||
let buffer = buffer.await.log_err()?;
|
||||
let position = buffer
|
||||
.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
if let Some(row) = location.line {
|
||||
let snapshot = buffer.snapshot();
|
||||
let column = snapshot.indent_size_for_line(row).len;
|
||||
let point = snapshot.clip_point(Point::new(row, column), Bias::Left);
|
||||
snapshot.anchor_before(point)
|
||||
} else {
|
||||
Anchor::MIN
|
||||
Anchor::min_for_buffer(snapshot.remote_id())
|
||||
}
|
||||
})
|
||||
.ok()?;
|
||||
@@ -2120,7 +2120,7 @@ impl AcpThread {
|
||||
position: edits
|
||||
.last()
|
||||
.map(|(range, _)| range.end)
|
||||
.unwrap_or(Anchor::MIN),
|
||||
.unwrap_or(Anchor::min_for_buffer(buffer.read(cx).remote_id())),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -50,9 +50,14 @@ impl Diff {
|
||||
let hunk_ranges = {
|
||||
let buffer = buffer.read(cx);
|
||||
let diff = diff.read(cx);
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
|
||||
.collect::<Vec<_>>()
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_for_buffer(buffer.remote_id())
|
||||
..Anchor::max_for_buffer(buffer.remote_id()),
|
||||
buffer,
|
||||
cx,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
multibuffer.set_excerpts_for_path(
|
||||
@@ -316,7 +321,12 @@ impl PendingDiff {
|
||||
let buffer = self.new_buffer.read(cx);
|
||||
let diff = self.diff.read(cx);
|
||||
let mut ranges = diff
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
|
||||
.hunks_intersecting_range(
|
||||
Anchor::min_for_buffer(buffer.remote_id())
|
||||
..Anchor::max_for_buffer(buffer.remote_id()),
|
||||
buffer,
|
||||
cx,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
|
||||
.collect::<Vec<_>>();
|
||||
ranges.extend(
|
||||
|
||||
@@ -528,7 +528,7 @@ impl Render for AcpTools {
|
||||
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
|
||||
.size_full(),
|
||||
)
|
||||
.vertical_scrollbar_for(connection.list_state.clone(), window, cx)
|
||||
.vertical_scrollbar_for(&connection.list_state, window, cx)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,9 +409,11 @@ impl ActionLog {
|
||||
let new_diff_base = new_diff_base.clone();
|
||||
async move {
|
||||
let mut unreviewed_edits = Patch::default();
|
||||
for hunk in diff_snapshot
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer_snapshot)
|
||||
{
|
||||
for hunk in diff_snapshot.hunks_intersecting_range(
|
||||
Anchor::min_for_buffer(buffer_snapshot.remote_id())
|
||||
..Anchor::max_for_buffer(buffer_snapshot.remote_id()),
|
||||
&buffer_snapshot,
|
||||
) {
|
||||
let old_range = new_diff_base
|
||||
.offset_to_point(hunk.diff_base_byte_range.start)
|
||||
..new_diff_base.offset_to_point(hunk.diff_base_byte_range.end);
|
||||
@@ -732,12 +734,10 @@ impl ActionLog {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<()> {
|
||||
let futures = self.changed_buffers(cx).into_keys().map(|buffer| {
|
||||
let reject = self.reject_edits_in_ranges(
|
||||
buffer,
|
||||
vec![Anchor::MIN..Anchor::MAX],
|
||||
telemetry.clone(),
|
||||
cx,
|
||||
);
|
||||
let buffer_ranges = vec![Anchor::min_max_range_for_buffer(
|
||||
buffer.read(cx).remote_id(),
|
||||
)];
|
||||
let reject = self.reject_edits_in_ranges(buffer, buffer_ranges, telemetry.clone(), cx);
|
||||
|
||||
async move {
|
||||
reject.await.log_err();
|
||||
@@ -2010,7 +2010,8 @@ mod tests {
|
||||
|
||||
// User accepts the single hunk
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.keep_edits_in_range(buffer.clone(), Anchor::MIN..Anchor::MAX, None, cx)
|
||||
let buffer_range = Anchor::min_max_range_for_buffer(buffer.read(cx).remote_id());
|
||||
log.keep_edits_in_range(buffer.clone(), buffer_range, None, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
|
||||
@@ -2031,7 +2032,14 @@ mod tests {
|
||||
// User rejects the hunk
|
||||
action_log
|
||||
.update(cx, |log, cx| {
|
||||
log.reject_edits_in_ranges(buffer.clone(), vec![Anchor::MIN..Anchor::MAX], None, cx)
|
||||
log.reject_edits_in_ranges(
|
||||
buffer.clone(),
|
||||
vec![Anchor::min_max_range_for_buffer(
|
||||
buffer.read(cx).remote_id(),
|
||||
)],
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -83,6 +83,7 @@ ctor.workspace = true
|
||||
db = { workspace = true, "features" = ["test-support"] }
|
||||
editor = { workspace = true, "features" = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
eval_utils.workspace = true
|
||||
fs = { workspace = true, "features" = ["test-support"] }
|
||||
git = { workspace = true, "features" = ["test-support"] }
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
|
||||
@@ -424,4 +424,20 @@ impl ThreadsDatabase {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_threads(&self) -> Task<Result<()>> {
|
||||
let connection = self.connection.clone();
|
||||
|
||||
self.executor.spawn(async move {
|
||||
let connection = connection.lock();
|
||||
|
||||
let mut delete = connection.exec_bound::<()>(indoc! {"
|
||||
DELETE FROM threads
|
||||
"})?;
|
||||
|
||||
delete(())?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,14 +172,14 @@ impl EditAgent {
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX,
|
||||
position: language::Anchor::max_for_buffer(buffer.read(cx).remote_id()),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
output_events_tx
|
||||
.unbounded_send(EditAgentOutputEvent::Edited(
|
||||
language::Anchor::MIN..language::Anchor::MAX,
|
||||
Anchor::min_max_range_for_buffer(buffer.read(cx).remote_id()),
|
||||
))
|
||||
.ok();
|
||||
})?;
|
||||
@@ -187,7 +187,7 @@ impl EditAgent {
|
||||
while let Some(event) = parse_rx.next().await {
|
||||
match event? {
|
||||
CreateFileParserEvent::NewTextChunk { chunk } => {
|
||||
cx.update(|cx| {
|
||||
let buffer_id = cx.update(|cx| {
|
||||
buffer.update(cx, |buffer, cx| buffer.append(chunk, cx));
|
||||
self.action_log
|
||||
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
|
||||
@@ -195,15 +195,18 @@ impl EditAgent {
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX,
|
||||
position: language::Anchor::max_for_buffer(
|
||||
buffer.read(cx).remote_id(),
|
||||
),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
buffer.read(cx).remote_id()
|
||||
})?;
|
||||
output_events_tx
|
||||
.unbounded_send(EditAgentOutputEvent::Edited(
|
||||
language::Anchor::MIN..language::Anchor::MAX,
|
||||
Anchor::min_max_range_for_buffer(buffer_id),
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
@@ -1200,7 +1203,9 @@ mod tests {
|
||||
project.read_with(cx, |project, _| project.agent_location()),
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX
|
||||
position: language::Anchor::max_for_buffer(
|
||||
cx.update(|cx| buffer.read(cx).remote_id())
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1218,7 +1223,9 @@ mod tests {
|
||||
project.read_with(cx, |project, _| project.agent_location()),
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX
|
||||
position: language::Anchor::max_for_buffer(
|
||||
cx.update(|cx| buffer.read(cx).remote_id())
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1236,7 +1243,9 @@ mod tests {
|
||||
project.read_with(cx, |project, _| project.agent_location()),
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX
|
||||
position: language::Anchor::max_for_buffer(
|
||||
cx.update(|cx| buffer.read(cx).remote_id())
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1254,7 +1263,9 @@ mod tests {
|
||||
project.read_with(cx, |project, _| project.agent_location()),
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX
|
||||
position: language::Anchor::max_for_buffer(
|
||||
cx.update(|cx| buffer.read(cx).remote_id())
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1269,7 +1280,9 @@ mod tests {
|
||||
project.read_with(cx, |project, _| project.agent_location()),
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: language::Anchor::MAX
|
||||
position: language::Anchor::max_for_buffer(
|
||||
cx.update(|cx| buffer.read(cx).remote_id())
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::{
|
||||
};
|
||||
use Role::*;
|
||||
use client::{Client, UserStore};
|
||||
use collections::HashMap;
|
||||
use fs::FakeFs;
|
||||
use futures::{FutureExt, future::LocalBoxFuture};
|
||||
use gpui::{AppContext, TestAppContext, Timer};
|
||||
@@ -20,9 +19,7 @@ use rand::prelude::*;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
fmt::{self, Display},
|
||||
io::Write as _,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
@@ -1316,101 +1313,41 @@ fn eval(
|
||||
iterations: usize,
|
||||
expected_pass_ratio: f32,
|
||||
mismatched_tag_threshold: f32,
|
||||
mut eval: EvalInput,
|
||||
eval: EvalInput,
|
||||
) {
|
||||
let mut evaluated_count = 0;
|
||||
let mut failed_count = 0;
|
||||
report_progress(evaluated_count, failed_count, iterations);
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// Cache the last message in the conversation, and run one instance of the eval so that
|
||||
// all the next ones are cached.
|
||||
eval.conversation.last_mut().unwrap().cache = true;
|
||||
run_eval(eval.clone(), tx.clone());
|
||||
|
||||
let executor = gpui::background_executor();
|
||||
let semaphore = Arc::new(smol::lock::Semaphore::new(32));
|
||||
for _ in 1..iterations {
|
||||
let eval = eval.clone();
|
||||
let tx = tx.clone();
|
||||
let semaphore = semaphore.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let _guard = semaphore.acquire().await;
|
||||
run_eval(eval, tx)
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
drop(tx);
|
||||
|
||||
let mut failed_evals = HashMap::default();
|
||||
let mut errored_evals = HashMap::default();
|
||||
let mut eval_outputs = Vec::new();
|
||||
let mut cumulative_parser_metrics = EditParserMetrics::default();
|
||||
while let Ok(output) = rx.recv() {
|
||||
match output {
|
||||
Ok(output) => {
|
||||
cumulative_parser_metrics += output.sample.edit_output.parser_metrics.clone();
|
||||
eval_outputs.push(output.clone());
|
||||
if output.assertion.score < 80 {
|
||||
failed_count += 1;
|
||||
failed_evals
|
||||
.entry(output.sample.text_after.clone())
|
||||
.or_insert(Vec::new())
|
||||
.push(output);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
failed_count += 1;
|
||||
*errored_evals.entry(format!("{:?}", error)).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
evaluated_count += 1;
|
||||
report_progress(evaluated_count, failed_count, iterations);
|
||||
}
|
||||
|
||||
let actual_pass_ratio = (iterations - failed_count) as f32 / iterations as f32;
|
||||
println!("Actual pass ratio: {}\n", actual_pass_ratio);
|
||||
if actual_pass_ratio < expected_pass_ratio {
|
||||
let mut errored_evals = errored_evals.into_iter().collect::<Vec<_>>();
|
||||
errored_evals.sort_by_key(|(_, count)| Reverse(*count));
|
||||
for (error, count) in errored_evals {
|
||||
println!("Eval errored {} times. Error: {}", count, error);
|
||||
}
|
||||
|
||||
let mut failed_evals = failed_evals.into_iter().collect::<Vec<_>>();
|
||||
failed_evals.sort_by_key(|(_, evals)| Reverse(evals.len()));
|
||||
for (_buffer_output, failed_evals) in failed_evals {
|
||||
let eval_output = failed_evals.first().unwrap();
|
||||
println!("Eval failed {} times", failed_evals.len());
|
||||
println!("{}", eval_output);
|
||||
}
|
||||
|
||||
panic!(
|
||||
"Actual pass ratio: {}\nExpected pass ratio: {}",
|
||||
actual_pass_ratio, expected_pass_ratio
|
||||
);
|
||||
}
|
||||
|
||||
let mismatched_tag_ratio =
|
||||
cumulative_parser_metrics.mismatched_tags as f32 / cumulative_parser_metrics.tags as f32;
|
||||
if mismatched_tag_ratio > mismatched_tag_threshold {
|
||||
for eval_output in eval_outputs {
|
||||
println!("{}", eval_output);
|
||||
}
|
||||
panic!("Too many mismatched tags: {:?}", cumulative_parser_metrics);
|
||||
}
|
||||
eval_utils::eval(
|
||||
iterations,
|
||||
expected_pass_ratio,
|
||||
mismatched_tag_threshold,
|
||||
Arc::new(move |tx| run_eval(eval.clone(), tx)),
|
||||
);
|
||||
}
|
||||
|
||||
fn run_eval(eval: EvalInput, tx: mpsc::Sender<Result<EvalOutput>>) {
|
||||
fn run_eval(eval: EvalInput, tx: mpsc::Sender<eval_utils::EvalOutput>) {
|
||||
let dispatcher = gpui::TestDispatcher::new(StdRng::from_os_rng());
|
||||
let mut cx = TestAppContext::build(dispatcher, None);
|
||||
let output = cx.executor().block_test(async {
|
||||
let result = cx.executor().block_test(async {
|
||||
let test = EditAgentTest::new(&mut cx).await;
|
||||
test.eval(eval, &mut cx).await
|
||||
});
|
||||
let output = match result {
|
||||
Ok(output) => eval_utils::EvalOutput {
|
||||
data: output.to_string(),
|
||||
mismatched_tags: output.sample.edit_output.parser_metrics.mismatched_tags,
|
||||
tags: output.sample.edit_output.parser_metrics.tags,
|
||||
outcome_kind: if output.assertion.score < 80 {
|
||||
eval_utils::OutcomeKind::Failed
|
||||
} else {
|
||||
eval_utils::OutcomeKind::Passed
|
||||
},
|
||||
},
|
||||
Err(e) => eval_utils::EvalOutput {
|
||||
data: format!("{e:?}"),
|
||||
mismatched_tags: 0,
|
||||
tags: 0,
|
||||
outcome_kind: eval_utils::OutcomeKind::Error,
|
||||
},
|
||||
};
|
||||
tx.send(output).unwrap();
|
||||
}
|
||||
|
||||
@@ -1439,22 +1376,6 @@ impl Display for EvalOutput {
|
||||
}
|
||||
}
|
||||
|
||||
fn report_progress(evaluated_count: usize, failed_count: usize, iterations: usize) {
|
||||
let passed_count = evaluated_count - failed_count;
|
||||
let passed_ratio = if evaluated_count == 0 {
|
||||
0.0
|
||||
} else {
|
||||
passed_count as f64 / evaluated_count as f64
|
||||
};
|
||||
print!(
|
||||
"\r\x1b[KEvaluated {}/{} ({:.2}% passed)",
|
||||
evaluated_count,
|
||||
iterations,
|
||||
passed_ratio * 100.0
|
||||
);
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
|
||||
struct EditAgentTest {
|
||||
agent: EditAgent,
|
||||
project: Entity<Project>,
|
||||
@@ -1550,7 +1471,10 @@ impl EditAgentTest {
|
||||
})
|
||||
}
|
||||
|
||||
async fn eval(&self, eval: EvalInput, cx: &mut TestAppContext) -> Result<EvalOutput> {
|
||||
async fn eval(&self, mut eval: EvalInput, cx: &mut TestAppContext) -> Result<EvalOutput> {
|
||||
// Make sure the last message in the conversation is cached.
|
||||
eval.conversation.last_mut().unwrap().cache = true;
|
||||
|
||||
let path = self
|
||||
.project
|
||||
.read_with(cx, |project, cx| {
|
||||
|
||||
@@ -188,6 +188,15 @@ impl HistoryStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_threads(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let database_future = ThreadsDatabase::connect(cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||
database.delete_threads().await?;
|
||||
this.update(cx, |this, cx| this.reload(cx))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_text_thread(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
|
||||
@@ -66,11 +66,9 @@ pub async fn get_buffer_content_or_outline(
|
||||
let outline_text = render_outline(outline_items, None, 0, usize::MAX).await?;
|
||||
|
||||
let text = if let Some(path) = path {
|
||||
format!(
|
||||
"# File outline for {path} (file too large to show full content)\n\n{outline_text}",
|
||||
)
|
||||
format!("# File outline for {path}\n\n{outline_text}",)
|
||||
} else {
|
||||
format!("# File outline (file too large to show full content)\n\n{outline_text}",)
|
||||
format!("# File outline\n\n{outline_text}",)
|
||||
};
|
||||
Ok(BufferContent {
|
||||
text,
|
||||
|
||||
@@ -2553,7 +2553,7 @@ fn setup_context_server(
|
||||
let mut settings = ProjectSettings::get_global(cx).clone();
|
||||
settings.context_servers.insert(
|
||||
name.into(),
|
||||
project::project_settings::ContextServerSettings::Custom {
|
||||
project::project_settings::ContextServerSettings::Stdio {
|
||||
enabled: true,
|
||||
command: ContextServerCommand {
|
||||
path: "somebinary".into(),
|
||||
|
||||
@@ -4,6 +4,7 @@ mod create_directory_tool;
|
||||
mod delete_path_tool;
|
||||
mod diagnostics_tool;
|
||||
mod edit_file_tool;
|
||||
mod failure_message_tool;
|
||||
mod fetch_tool;
|
||||
mod find_path_tool;
|
||||
mod grep_tool;
|
||||
@@ -12,6 +13,7 @@ mod move_path_tool;
|
||||
mod now_tool;
|
||||
mod open_tool;
|
||||
mod read_file_tool;
|
||||
mod rewrite_section_tool;
|
||||
mod terminal_tool;
|
||||
mod thinking_tool;
|
||||
mod web_search_tool;
|
||||
@@ -25,6 +27,7 @@ pub use create_directory_tool::*;
|
||||
pub use delete_path_tool::*;
|
||||
pub use diagnostics_tool::*;
|
||||
pub use edit_file_tool::*;
|
||||
pub use failure_message_tool::*;
|
||||
pub use fetch_tool::*;
|
||||
pub use find_path_tool::*;
|
||||
pub use grep_tool::*;
|
||||
@@ -33,6 +36,7 @@ pub use move_path_tool::*;
|
||||
pub use now_tool::*;
|
||||
pub use open_tool::*;
|
||||
pub use read_file_tool::*;
|
||||
pub use rewrite_section_tool::*;
|
||||
pub use terminal_tool::*;
|
||||
pub use thinking_tool::*;
|
||||
pub use web_search_tool::*;
|
||||
|
||||
50
crates/agent/src/tools/failure_message_tool.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
//! This tool is intended for use with the inline assistant, not the agent panel.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AgentTool, ToolCallEventStream};
|
||||
|
||||
/// Use this tool to provide a message to the user when you're unable to complete a task.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FailureMessageInput {
|
||||
/// A brief message to the user explaining why you're unable to fulfill the request.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub struct FailureMessageTool;
|
||||
|
||||
impl AgentTool for FailureMessageTool {
|
||||
type Input = FailureMessageInput;
|
||||
type Output = String;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"failure_message"
|
||||
}
|
||||
|
||||
fn kind() -> acp::ToolKind {
|
||||
acp::ToolKind::Think
|
||||
}
|
||||
|
||||
fn initial_title(
|
||||
&self,
|
||||
_input: Result<Self::Input, serde_json::Value>,
|
||||
_cx: &mut App,
|
||||
) -> SharedString {
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: Self::Input,
|
||||
_event_stream: ToolCallEventStream,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Resu
|
||||
let mut results = Vec::new();
|
||||
for snapshot in snapshots {
|
||||
for entry in snapshot.entries(false, 0) {
|
||||
if path_matcher.is_match(snapshot.root_name().join(&entry.path).as_std_path()) {
|
||||
if path_matcher.is_match(&snapshot.root_name().join(&entry.path)) {
|
||||
results.push(snapshot.absolutize(&entry.path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,21 @@ pub struct GrepToolInput {
|
||||
/// Do NOT specify a path here! This will only be matched against the code **content**.
|
||||
pub regex: String,
|
||||
/// A glob pattern for the paths of files to include in the search.
|
||||
/// Supports standard glob patterns like "**/*.rs" or "src/**/*.ts".
|
||||
/// Supports standard glob patterns like "**/*.rs" or "frontend/src/**/*.ts".
|
||||
/// If omitted, all files in the project will be searched.
|
||||
///
|
||||
/// The glob pattern is matched against the full path including the project root directory.
|
||||
///
|
||||
/// <example>
|
||||
/// If the project has the following root directories:
|
||||
///
|
||||
/// - /a/b/backend
|
||||
/// - /c/d/frontend
|
||||
///
|
||||
/// Use "backend/**/*.rs" to search only Rust files in the backend root directory.
|
||||
/// Use "frontend/src/**/*.ts" to search TypeScript files only in the frontend root directory (sub-directory "src").
|
||||
/// Use "**/*.rs" to search Rust files across all root directories.
|
||||
/// </example>
|
||||
pub include_pattern: Option<String>,
|
||||
/// Optional starting position for paginated results (0-based).
|
||||
/// When not provided, starts from the beginning.
|
||||
@@ -132,8 +145,7 @@ impl AgentTool for GrepTool {
|
||||
let exclude_patterns = global_settings
|
||||
.file_scan_exclusions
|
||||
.sources()
|
||||
.iter()
|
||||
.chain(global_settings.private_files.sources().iter());
|
||||
.chain(global_settings.private_files.sources());
|
||||
|
||||
match PathMatcher::new(exclude_patterns, path_style) {
|
||||
Ok(matcher) => matcher,
|
||||
|
||||
@@ -17,6 +17,9 @@ use crate::{AgentTool, Thread, ToolCallEventStream, outline};
|
||||
/// Reads the content of the given file in the project.
|
||||
///
|
||||
/// - Never attempt to read a path that hasn't been previously mentioned.
|
||||
/// - For large files, this tool returns a file outline with symbol names and line numbers instead of the full content.
|
||||
/// This outline IS a successful response - use the line numbers to read specific sections with start_line/end_line.
|
||||
/// Do NOT retry reading the same file without line numbers if you receive an outline.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ReadFileToolInput {
|
||||
/// The relative path of the file to read.
|
||||
@@ -254,16 +257,15 @@ impl AgentTool for ReadFileTool {
|
||||
|
||||
if buffer_content.is_outline {
|
||||
Ok(formatdoc! {"
|
||||
This file was too big to read all at once.
|
||||
SUCCESS: File outline retrieved. This file is too large to read all at once, so the outline below shows the file's structure with line numbers.
|
||||
|
||||
IMPORTANT: Do NOT retry this call without line numbers - you will get the same outline.
|
||||
Instead, use the line numbers below to read specific sections by calling this tool again with start_line and end_line parameters.
|
||||
|
||||
{}
|
||||
|
||||
Using the line numbers in this outline, you can call this tool again
|
||||
while specifying the start_line and end_line fields to see the
|
||||
implementations of symbols in the outline.
|
||||
|
||||
Alternatively, you can fall back to the `grep` tool (if available)
|
||||
to search the file for specific content.", buffer_content.text
|
||||
NEXT STEPS: To read a specific symbol's implementation, call read_file with the same path plus start_line and end_line from the outline above.
|
||||
For example, to read a function shown as [L100-150], use start_line: 100 and end_line: 150.", buffer_content.text
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
@@ -275,7 +277,9 @@ impl AgentTool for ReadFileTool {
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: anchor.unwrap_or(text::Anchor::MIN),
|
||||
position: anchor.unwrap_or_else(|| {
|
||||
text::Anchor::min_for_buffer(buffer.read(cx).remote_id())
|
||||
}),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
@@ -438,7 +442,7 @@ mod test {
|
||||
let content = result.to_str().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
content.lines().skip(4).take(6).collect::<Vec<_>>(),
|
||||
content.lines().skip(7).take(6).collect::<Vec<_>>(),
|
||||
vec![
|
||||
"struct Test0 [L1-4]",
|
||||
" a [L2]",
|
||||
@@ -473,7 +477,7 @@ mod test {
|
||||
pretty_assertions::assert_eq!(
|
||||
content
|
||||
.lines()
|
||||
.skip(4)
|
||||
.skip(7)
|
||||
.take(expected_content.len())
|
||||
.collect::<Vec<_>>(),
|
||||
expected_content
|
||||
|
||||
55
crates/agent/src/tools/rewrite_section_tool.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
//! This tool is intended for use with the inline assistant, not the agent panel.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AgentTool, ToolCallEventStream};
|
||||
|
||||
/// Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RewriteSectionInput {
|
||||
/// A brief description of the edit you have made.
|
||||
///
|
||||
/// This is optional - if the edit is simple or obvious, you should leave it empty.
|
||||
pub description: String,
|
||||
|
||||
/// The text to replace the section with.
|
||||
pub replacement_text: String,
|
||||
}
|
||||
|
||||
pub struct RewriteSectionTool;
|
||||
|
||||
impl AgentTool for RewriteSectionTool {
|
||||
type Input = RewriteSectionInput;
|
||||
type Output = String;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"rewrite_section"
|
||||
}
|
||||
|
||||
fn kind() -> acp::ToolKind {
|
||||
acp::ToolKind::Edit
|
||||
}
|
||||
|
||||
fn initial_title(
|
||||
&self,
|
||||
_input: Result<Self::Input, serde_json::Value>,
|
||||
_cx: &mut App,
|
||||
) -> SharedString {
|
||||
"".into()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: Self::Input,
|
||||
_event_stream: ToolCallEventStream,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ path = "src/agent_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["gpui/test-support", "language/test-support"]
|
||||
test-support = ["gpui/test-support", "language/test-support", "reqwest_client"]
|
||||
unit-eval = []
|
||||
|
||||
[dependencies]
|
||||
acp_thread.workspace = true
|
||||
@@ -47,6 +48,7 @@ fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
gpui_tokio.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
indoc.workspace = true
|
||||
@@ -98,14 +100,17 @@ workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
image.workspace = true
|
||||
async-fs.workspace = true
|
||||
reqwest_client = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
acp_thread = { workspace = true, features = ["test-support"] }
|
||||
agent = { workspace = true, features = ["test-support"] }
|
||||
assistant_text_thread = { workspace = true, features = ["test-support"] }
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
clock.workspace = true
|
||||
db = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
eval_utils.workspace = true
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
@@ -115,5 +120,6 @@ pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
semver.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -1423,7 +1423,7 @@ mod tests {
|
||||
rel_path("b/eight.txt"),
|
||||
];
|
||||
|
||||
let slash = PathStyle::local().separator();
|
||||
let slash = PathStyle::local().primary_separator();
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
for path in paths {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::acp::AcpThreadView;
|
||||
use crate::{AgentPanel, RemoveSelectedThread};
|
||||
use crate::{AgentPanel, RemoveHistory, RemoveSelectedThread};
|
||||
use agent::{HistoryEntry, HistoryStore};
|
||||
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
|
||||
use editor::{Editor, EditorEvent};
|
||||
@@ -12,7 +12,7 @@ use std::{fmt::Display, ops::Range};
|
||||
use text::Bias;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tooltip, WithScrollbar,
|
||||
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip, WithScrollbar,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ pub struct AcpThreadHistory {
|
||||
search_query: SharedString,
|
||||
visible_items: Vec<ListItemType>,
|
||||
local_timezone: UtcOffset,
|
||||
confirming_delete_history: bool,
|
||||
_update_task: Task<()>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
}
|
||||
@@ -98,6 +99,7 @@ impl AcpThreadHistory {
|
||||
)
|
||||
.unwrap(),
|
||||
search_query: SharedString::default(),
|
||||
confirming_delete_history: false,
|
||||
_subscriptions: vec![search_editor_subscription, history_store_subscription],
|
||||
_update_task: Task::ready(()),
|
||||
};
|
||||
@@ -331,6 +333,24 @@ impl AcpThreadHistory {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn remove_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.history_store.update(cx, |store, cx| {
|
||||
store.delete_threads(cx).detach_and_log_err(cx)
|
||||
});
|
||||
self.confirming_delete_history = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn prompt_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.confirming_delete_history = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn cancel_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.confirming_delete_history = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_list_items(
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
@@ -426,9 +446,10 @@ impl AcpThreadHistory {
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
|
||||
),
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.remove_thread(ix, cx);
|
||||
cx.stop_propagation()
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -447,6 +468,8 @@ impl Focusable for AcpThreadHistory {
|
||||
|
||||
impl Render for AcpThreadHistory {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let has_no_history = self.history_store.read(cx).is_empty(cx);
|
||||
|
||||
v_flex()
|
||||
.key_context("ThreadHistory")
|
||||
.size_full()
|
||||
@@ -457,9 +480,12 @@ impl Render for AcpThreadHistory {
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::remove_selected_thread))
|
||||
.on_action(cx.listener(|this, _: &RemoveHistory, window, cx| {
|
||||
this.remove_history(window, cx);
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.h(px(41.)) // Match the toolbar perfectly
|
||||
.h(Tab::container_height(cx))
|
||||
.w_full()
|
||||
.py_1()
|
||||
.px_2()
|
||||
@@ -481,7 +507,7 @@ impl Render for AcpThreadHistory {
|
||||
.overflow_hidden()
|
||||
.flex_grow();
|
||||
|
||||
if self.history_store.read(cx).is_empty(cx) {
|
||||
if has_no_history {
|
||||
view.justify_center().items_center().child(
|
||||
Label::new("You don't have any past threads yet.")
|
||||
.size(LabelSize::Small)
|
||||
@@ -502,16 +528,74 @@ impl Render for AcpThreadHistory {
|
||||
)
|
||||
.p_1()
|
||||
.pr_4()
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.flex_grow(),
|
||||
)
|
||||
.vertical_scrollbar_for(
|
||||
self.scroll_handle.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
|
||||
}
|
||||
})
|
||||
.when(!has_no_history, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.when(!self.confirming_delete_history, |this| {
|
||||
this.child(
|
||||
Button::new("delete_history", "Delete All History")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.prompt_delete_history(window, cx);
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(self.confirming_delete_history, |this| {
|
||||
this.w_full()
|
||||
.gap_2()
|
||||
.flex_wrap()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new("Delete all threads?")
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new("You won't be able to recover them later.")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Button::new("cancel_delete", "Cancel")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.cancel_delete_history(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("confirm_delete", "Delete")
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Error))
|
||||
.color(Color::Error)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(RemoveHistory),
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -498,7 +498,17 @@ impl AcpThreadView {
|
||||
Some(new_version_available_tx),
|
||||
);
|
||||
|
||||
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
|
||||
let agent_name = agent.name();
|
||||
let timeout = cx.background_executor().timer(Duration::from_secs(30));
|
||||
let connect_task = smol::future::or(
|
||||
agent.connect(root_dir.as_deref(), delegate, cx),
|
||||
async move {
|
||||
timeout.await;
|
||||
Err(anyhow::Error::new(LoadError::Other(
|
||||
format!("{agent_name} is unable to initialize after 30 seconds.").into(),
|
||||
)))
|
||||
},
|
||||
);
|
||||
let load_task = cx.spawn_in(window, async move |this, cx| {
|
||||
let connection = match connect_task.await {
|
||||
Ok((connection, login)) => {
|
||||
@@ -665,6 +675,8 @@ impl AcpThreadView {
|
||||
})
|
||||
});
|
||||
|
||||
this.message_editor.focus_handle(cx).focus(window);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -999,6 +1011,10 @@ impl AcpThreadView {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_loading(&self) -> bool {
|
||||
matches!(self.thread_state, ThreadState::Loading { .. })
|
||||
}
|
||||
|
||||
fn resume_chat(&mut self, cx: &mut Context<Self>) {
|
||||
self.thread_error.take();
|
||||
let Some(thread) = self.thread() else {
|
||||
@@ -3802,48 +3818,62 @@ impl AcpThreadView {
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_plan_entries(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
|
||||
v_flex().children(plan.entries.iter().enumerate().flat_map(|(index, entry)| {
|
||||
let element = h_flex()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.when(index < plan.entries.len() - 1, |parent| {
|
||||
parent.border_color(cx.theme().colors().border).border_b_1()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id(("plan_entry", index))
|
||||
.gap_1p5()
|
||||
.max_w_full()
|
||||
.overflow_x_scroll()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(match entry.status {
|
||||
acp::PlanEntryStatus::Pending => Icon::new(IconName::TodoPending)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
acp::PlanEntryStatus::InProgress => Icon::new(IconName::TodoProgress)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Accent)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
acp::PlanEntryStatus::Completed => Icon::new(IconName::TodoComplete)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success)
|
||||
.into_any_element(),
|
||||
})
|
||||
.child(MarkdownElement::new(
|
||||
entry.content.clone(),
|
||||
plan_label_markdown_style(&entry.status, window, cx),
|
||||
)),
|
||||
);
|
||||
fn render_plan_entries(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
window: &mut Window,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("plan_items_list")
|
||||
.max_h_40()
|
||||
.overflow_y_scroll()
|
||||
.children(plan.entries.iter().enumerate().flat_map(|(index, entry)| {
|
||||
let element = h_flex()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.when(index < plan.entries.len() - 1, |parent| {
|
||||
parent.border_color(cx.theme().colors().border).border_b_1()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id(("plan_entry", index))
|
||||
.gap_1p5()
|
||||
.max_w_full()
|
||||
.overflow_x_scroll()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(match entry.status {
|
||||
acp::PlanEntryStatus::Pending => Icon::new(IconName::TodoPending)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
acp::PlanEntryStatus::InProgress => {
|
||||
Icon::new(IconName::TodoProgress)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Accent)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element()
|
||||
}
|
||||
acp::PlanEntryStatus::Completed => {
|
||||
Icon::new(IconName::TodoComplete)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success)
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
.child(MarkdownElement::new(
|
||||
entry.content.clone(),
|
||||
plan_label_markdown_style(&entry.status, window, cx),
|
||||
)),
|
||||
);
|
||||
|
||||
Some(element)
|
||||
}))
|
||||
Some(element)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_edits_summary(
|
||||
@@ -3981,162 +4011,177 @@ impl AcpThreadView {
|
||||
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
|
||||
pending_edits: bool,
|
||||
cx: &Context<Self>,
|
||||
) -> Div {
|
||||
) -> impl IntoElement {
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
|
||||
v_flex().children(changed_buffers.iter().enumerate().flat_map(
|
||||
|(index, (buffer, _diff))| {
|
||||
let file = buffer.read(cx).file()?;
|
||||
let path = file.path();
|
||||
let path_style = file.path_style(cx);
|
||||
let separator = file.path_style(cx).separator();
|
||||
v_flex()
|
||||
.id("edited_files_list")
|
||||
.max_h_40()
|
||||
.overflow_y_scroll()
|
||||
.children(
|
||||
changed_buffers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, (buffer, _diff))| {
|
||||
let file = buffer.read(cx).file()?;
|
||||
let path = file.path();
|
||||
let path_style = file.path_style(cx);
|
||||
let separator = file.path_style(cx).primary_separator();
|
||||
|
||||
let file_path = path.parent().and_then(|parent| {
|
||||
if parent.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Label::new(format!("{}{separator}", parent.display(path_style)))
|
||||
.color(Color::Muted)
|
||||
let file_path = path.parent().and_then(|parent| {
|
||||
if parent.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Label::new(format!(
|
||||
"{}{separator}",
|
||||
parent.display(path_style)
|
||||
))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let file_name = path.file_name().map(|name| {
|
||||
Label::new(name.to_string())
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
}
|
||||
});
|
||||
.buffer_font(cx)
|
||||
.ml_1p5()
|
||||
});
|
||||
|
||||
let file_name = path.file_name().map(|name| {
|
||||
Label::new(name.to_string())
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx)
|
||||
.ml_1p5()
|
||||
});
|
||||
let file_icon = FileIcons::get_icon(path.as_std_path(), cx)
|
||||
.map(Icon::from_path)
|
||||
.map(|icon| icon.color(Color::Muted).size(IconSize::Small))
|
||||
.unwrap_or_else(|| {
|
||||
Icon::new(IconName::File)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small)
|
||||
});
|
||||
|
||||
let file_icon = FileIcons::get_icon(path.as_std_path(), cx)
|
||||
.map(Icon::from_path)
|
||||
.map(|icon| icon.color(Color::Muted).size(IconSize::Small))
|
||||
.unwrap_or_else(|| {
|
||||
Icon::new(IconName::File)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small)
|
||||
});
|
||||
let overlay_gradient = linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(editor_bg_color, 1.),
|
||||
linear_color_stop(editor_bg_color.opacity(0.2), 0.),
|
||||
);
|
||||
|
||||
let overlay_gradient = linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(editor_bg_color, 1.),
|
||||
linear_color_stop(editor_bg_color.opacity(0.2), 0.),
|
||||
);
|
||||
|
||||
let element = h_flex()
|
||||
.group("edited-code")
|
||||
.id(("file-container", index))
|
||||
.py_1()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.bg(editor_bg_color)
|
||||
.when(index < changed_buffers.len() - 1, |parent| {
|
||||
parent.border_color(cx.theme().colors().border).border_b_1()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id(("file-name-row", index))
|
||||
.relative()
|
||||
.pr_8()
|
||||
.w_full()
|
||||
.overflow_x_scroll()
|
||||
let element = h_flex()
|
||||
.group("edited-code")
|
||||
.id(("file-container", index))
|
||||
.py_1()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.bg(editor_bg_color)
|
||||
.when(index < changed_buffers.len() - 1, |parent| {
|
||||
parent.border_color(cx.theme().colors().border).border_b_1()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id(("file-name-path", index))
|
||||
.cursor_pointer()
|
||||
.pr_0p5()
|
||||
.gap_0p5()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.rounded_xs()
|
||||
.child(file_icon)
|
||||
.children(file_name)
|
||||
.children(file_path)
|
||||
.tooltip(Tooltip::text("Go to File"))
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.open_edited_buffer(&buffer, window, cx);
|
||||
})
|
||||
}),
|
||||
.id(("file-name-row", index))
|
||||
.relative()
|
||||
.pr_8()
|
||||
.w_full()
|
||||
.overflow_x_scroll()
|
||||
.child(
|
||||
h_flex()
|
||||
.id(("file-name-path", index))
|
||||
.cursor_pointer()
|
||||
.pr_0p5()
|
||||
.gap_0p5()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.rounded_xs()
|
||||
.child(file_icon)
|
||||
.children(file_name)
|
||||
.children(file_path)
|
||||
.tooltip(Tooltip::text("Go to File"))
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.open_edited_buffer(&buffer, window, cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.h_full()
|
||||
.w_12()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.right_0()
|
||||
.bg(overlay_gradient),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.h_full()
|
||||
.w_12()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.right_0()
|
||||
.bg(overlay_gradient),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.visible_on_hover("edited-code")
|
||||
.child(
|
||||
Button::new("review", "Review")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.open_edited_buffer(&buffer, window, cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical().color(DividerColor::BorderVariant))
|
||||
.child(
|
||||
Button::new("reject-file", "Reject")
|
||||
.label_size(LabelSize::Small)
|
||||
.disabled(pending_edits)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
let action_log = action_log.clone();
|
||||
let telemetry = telemetry.clone();
|
||||
move |_, _, cx| {
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.visible_on_hover("edited-code")
|
||||
.child(
|
||||
Button::new("review", "Review")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.open_edited_buffer(&buffer, window, cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical().color(DividerColor::BorderVariant))
|
||||
.child(
|
||||
Button::new("reject-file", "Reject")
|
||||
.label_size(LabelSize::Small)
|
||||
.disabled(pending_edits)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
let action_log = action_log.clone();
|
||||
let telemetry = telemetry.clone();
|
||||
move |_, _, cx| {
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log
|
||||
.reject_edits_in_ranges(
|
||||
buffer.clone(),
|
||||
vec![Anchor::MIN..Anchor::MAX],
|
||||
vec![Anchor::min_max_range_for_buffer(
|
||||
buffer.read(cx).remote_id(),
|
||||
)],
|
||||
Some(telemetry.clone()),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("keep-file", "Keep")
|
||||
.label_size(LabelSize::Small)
|
||||
.disabled(pending_edits)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
let action_log = action_log.clone();
|
||||
let telemetry = telemetry.clone();
|
||||
move |_, _, cx| {
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.keep_edits_in_range(
|
||||
buffer.clone(),
|
||||
Anchor::MIN..Anchor::MAX,
|
||||
Some(telemetry.clone()),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("keep-file", "Keep")
|
||||
.label_size(LabelSize::Small)
|
||||
.disabled(pending_edits)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
let action_log = action_log.clone();
|
||||
let telemetry = telemetry.clone();
|
||||
move |_, _, cx| {
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.keep_edits_in_range(
|
||||
buffer.clone(),
|
||||
Anchor::min_max_range_for_buffer(
|
||||
buffer.read(cx).remote_id(),
|
||||
),
|
||||
Some(telemetry.clone()),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
Some(element)
|
||||
},
|
||||
))
|
||||
Some(element)
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_message_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
@@ -4157,8 +4202,10 @@ impl AcpThreadView {
|
||||
.block_mouse_except_scroll();
|
||||
|
||||
let enable_editor = match self.thread_state {
|
||||
ThreadState::Loading { .. } | ThreadState::Ready { .. } => true,
|
||||
ThreadState::Unauthenticated { .. } | ThreadState::LoadError(..) => false,
|
||||
ThreadState::Ready { .. } => true,
|
||||
ThreadState::Loading { .. }
|
||||
| ThreadState::Unauthenticated { .. }
|
||||
| ThreadState::LoadError(..) => false,
|
||||
};
|
||||
|
||||
v_flex()
|
||||
@@ -4743,11 +4790,8 @@ impl AcpThreadView {
|
||||
let buffer = multibuffer.as_singleton();
|
||||
if agent_location.buffer.upgrade() == buffer {
|
||||
let excerpt_id = multibuffer.excerpt_ids().first().cloned();
|
||||
let anchor = editor::Anchor::in_buffer(
|
||||
excerpt_id.unwrap(),
|
||||
buffer.unwrap().read(cx).remote_id(),
|
||||
agent_location.position,
|
||||
);
|
||||
let anchor =
|
||||
editor::Anchor::in_buffer(excerpt_id.unwrap(), agent_location.position);
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([anchor..anchor]);
|
||||
})
|
||||
@@ -5822,12 +5866,10 @@ fn placeholder_text(agent_name: &str, has_commands: bool) -> String {
|
||||
impl Focusable for AcpThreadView {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match self.thread_state {
|
||||
ThreadState::Loading { .. } | ThreadState::Ready { .. } => {
|
||||
self.active_editor(cx).focus_handle(cx)
|
||||
}
|
||||
ThreadState::LoadError(_) | ThreadState::Unauthenticated { .. } => {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
ThreadState::Ready { .. } => self.active_editor(cx).focus_handle(cx),
|
||||
ThreadState::Loading { .. }
|
||||
| ThreadState::LoadError(_)
|
||||
| ThreadState::Unauthenticated { .. } => self.focus_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5895,7 +5937,7 @@ impl Render for AcpThreadView {
|
||||
.flex_grow()
|
||||
.into_any(),
|
||||
)
|
||||
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
|
||||
.vertical_scrollbar_for(&self.list_state, window, cx)
|
||||
.into_any()
|
||||
} else {
|
||||
this.child(self.render_recent_history(cx)).into_any()
|
||||
@@ -7357,4 +7399,54 @@ pub(crate) mod tests {
|
||||
assert_eq!(text, expected_txt);
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_initialize_timeout(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
struct InfiniteInitialize;
|
||||
|
||||
impl AgentServer for InfiniteInitialize {
|
||||
fn telemetry_id(&self) -> &'static str {
|
||||
"test"
|
||||
}
|
||||
|
||||
fn logo(&self) -> ui::IconName {
|
||||
ui::IconName::Ai
|
||||
}
|
||||
|
||||
fn name(&self) -> SharedString {
|
||||
"Test".into()
|
||||
}
|
||||
|
||||
fn connect(
|
||||
&self,
|
||||
_root_dir: Option<&Path>,
|
||||
_delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>
|
||||
{
|
||||
cx.spawn(async |_| futures::future::pending().await)
|
||||
}
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(InfiniteInitialize, cx).await;
|
||||
|
||||
cx.executor().advance_clock(Duration::from_secs(31));
|
||||
cx.run_until_parked();
|
||||
|
||||
let error = thread_view.read_with(cx, |thread_view, _| match &thread_view.thread_state {
|
||||
ThreadState::LoadError(err) => err.clone(),
|
||||
_ => panic!("Incorrect thread state"),
|
||||
});
|
||||
|
||||
match error {
|
||||
LoadError::Other(str) => assert!(str.contains("initialize")),
|
||||
_ => panic!("Unexpected load error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,7 +1209,7 @@ impl Render for AgentConfiguration {
|
||||
.child(self.render_context_servers_section(window, cx))
|
||||
.child(self.render_provider_configuration_section(cx)),
|
||||
)
|
||||
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ impl Render for AddLlmProviderModal {
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
|
||||
.child(
|
||||
v_flex()
|
||||
.id("modal_content")
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
@@ -182,7 +179,7 @@ impl ConfigurationSource {
|
||||
parse_input(&editor.read(cx).text(cx)).map(|(id, command)| {
|
||||
(
|
||||
id,
|
||||
ContextServerSettings::Custom {
|
||||
ContextServerSettings::Stdio {
|
||||
enabled: true,
|
||||
command,
|
||||
},
|
||||
@@ -224,11 +221,12 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
|
||||
Some((id, cmd)) => {
|
||||
let args = serde_json::to_string(&cmd.args).unwrap();
|
||||
let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap();
|
||||
(id.0.to_string(), cmd.path, args, env)
|
||||
let cmd_path = serde_json::to_string(&cmd.path).unwrap();
|
||||
(id.0.to_string(), cmd_path, args, env)
|
||||
}
|
||||
None => (
|
||||
"some-mcp-server".to_string(),
|
||||
PathBuf::new(),
|
||||
"".to_string(),
|
||||
"[]".to_string(),
|
||||
"{}".to_string(),
|
||||
),
|
||||
@@ -239,14 +237,13 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
|
||||
/// The name of your MCP server
|
||||
"{name}": {{
|
||||
/// The command which runs the MCP server
|
||||
"command": "{}",
|
||||
"command": {command},
|
||||
/// The arguments to pass to the MCP server
|
||||
"args": {args},
|
||||
/// The environment variables to set
|
||||
"env": {env}
|
||||
}}
|
||||
}}"#,
|
||||
command.display()
|
||||
}}"#
|
||||
)
|
||||
}
|
||||
|
||||
@@ -403,7 +400,7 @@ impl ConfigureContextServerModal {
|
||||
|
||||
window.spawn(cx, async move |cx| {
|
||||
let target = match settings {
|
||||
ContextServerSettings::Custom {
|
||||
ContextServerSettings::Stdio {
|
||||
enabled: _,
|
||||
command,
|
||||
} => Some(ConfigurationTarget::Existing {
|
||||
@@ -635,7 +632,6 @@ impl ConfigureContextServerModal {
|
||||
}
|
||||
|
||||
fn render_modal_content(&self, cx: &App) -> AnyElement {
|
||||
// All variants now use single editor approach
|
||||
let editor = match &self.source {
|
||||
ConfigurationSource::New { editor, .. } => editor,
|
||||
ConfigurationSource::Existing { editor, .. } => editor,
|
||||
@@ -712,12 +708,12 @@ impl ConfigureContextServerModal {
|
||||
)
|
||||
} else if let ConfigurationSource::New { is_http, .. } = &self.source {
|
||||
let label = if *is_http {
|
||||
"Run command"
|
||||
"Configure Local"
|
||||
} else {
|
||||
"Connect via HTTP"
|
||||
"Configure Remote"
|
||||
};
|
||||
let tooltip = if *is_http {
|
||||
"Configure an MCP serevr that runs on stdin/stdout."
|
||||
"Configure an MCP server that runs on stdin/stdout."
|
||||
} else {
|
||||
"Configure an MCP server that you connect to over HTTP"
|
||||
};
|
||||
@@ -822,7 +818,6 @@ impl ConfigureContextServerModal {
|
||||
|
||||
impl Render for ConfigureContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
@@ -850,7 +845,7 @@ impl Render for ConfigureContextServerModal {
|
||||
.id("modal-content")
|
||||
.max_h(vh(0.7, window))
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&scroll_handle)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.child(self.render_modal_description(window, cx))
|
||||
.child(self.render_modal_content(cx))
|
||||
.child(match &self.state {
|
||||
@@ -863,7 +858,7 @@ impl Render for ConfigureContextServerModal {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.vertical_scrollbar_for(scroll_handle, window, cx),
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
|
||||
),
|
||||
)
|
||||
.footer(self.render_modal_footer(cx)),
|
||||
|
||||
@@ -138,7 +138,7 @@ impl ConfigureContextServerToolsModal {
|
||||
items
|
||||
})),
|
||||
)
|
||||
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ impl AgentDiffPane {
|
||||
|
||||
let diff_hunk_ranges = diff
|
||||
.hunks_intersecting_range(
|
||||
language::Anchor::MIN..language::Anchor::MAX,
|
||||
language::Anchor::min_max_range_for_buffer(snapshot.remote_id()),
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
@@ -493,7 +493,7 @@ impl Item for AgentDiffPane {
|
||||
Some("Assistant Diff Opened")
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Range, path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
use acp_thread::AcpThread;
|
||||
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
|
||||
@@ -20,10 +17,9 @@ use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
|
||||
use crate::ManageProfiles;
|
||||
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
|
||||
use crate::{
|
||||
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
|
||||
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory,
|
||||
ResetTrialEndUpsell, ResetTrialUpsell, ToggleNavigationMenu, ToggleNewThreadMenu,
|
||||
ToggleOptionsMenu,
|
||||
AddContextServer, AgentDiffPane, Follow, InlineAssistant, NewTextThread, NewThread,
|
||||
OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
|
||||
ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
|
||||
acp::AcpThreadView,
|
||||
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
||||
slash_command::SlashCommandCompletionProvider,
|
||||
@@ -47,9 +43,9 @@ use extension::ExtensionEvents;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
|
||||
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
|
||||
WeakEntity, prelude::*,
|
||||
Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, Corner, DismissEvent,
|
||||
Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription,
|
||||
Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{ConfigurationError, LanguageModelRegistry};
|
||||
@@ -59,10 +55,9 @@ use rules_library::{RulesLibrary, open_rules_library};
|
||||
use search::{BufferSearchBar, buffer_search};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{
|
||||
Callout, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||
ProgressBar, Tab, Tooltip, prelude::*,
|
||||
ProgressBar, Tab, Tooltip, prelude::*, utils::WithRemSize,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
@@ -614,11 +609,14 @@ impl AgentPanel {
|
||||
if let Some(panel) = panel.upgrade() {
|
||||
menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
|
||||
}
|
||||
menu.action("View All", Box::new(OpenHistory))
|
||||
.end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
|
||||
|
||||
menu = menu
|
||||
.action("View All", Box::new(OpenHistory))
|
||||
.fixed_width(px(320.).into())
|
||||
.keep_open_on_confirm(false)
|
||||
.key_context("NavigationMenu")
|
||||
.key_context("NavigationMenu");
|
||||
|
||||
menu
|
||||
});
|
||||
weak_panel
|
||||
.update(cx, |panel, cx| {
|
||||
@@ -2157,28 +2155,41 @@ impl AgentPanel {
|
||||
|
||||
let selected_agent_label = self.selected_agent.label();
|
||||
|
||||
let is_thread_loading = self
|
||||
.active_thread_view()
|
||||
.map(|thread| thread.read(cx).is_loading())
|
||||
.unwrap_or(false);
|
||||
|
||||
let has_custom_icon = selected_agent_custom_icon.is_some();
|
||||
|
||||
let selected_agent = div()
|
||||
.id("selected_agent_icon")
|
||||
.when_some(selected_agent_custom_icon, |this, icon_path| {
|
||||
let label = selected_agent_label.clone();
|
||||
this.px_1()
|
||||
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
|
||||
})
|
||||
})
|
||||
.when(!has_custom_icon, |this| {
|
||||
this.when_some(self.selected_agent.icon(), |this, icon| {
|
||||
let label = selected_agent_label.clone();
|
||||
this.px_1()
|
||||
.child(Icon::new(icon).color(Color::Muted))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
|
||||
})
|
||||
this.px_1().child(Icon::new(icon).color(Color::Muted))
|
||||
})
|
||||
})
|
||||
.into_any_element();
|
||||
.tooltip(move |_, cx| {
|
||||
Tooltip::with_meta(selected_agent_label.clone(), None, "Selected Agent", cx)
|
||||
});
|
||||
|
||||
let selected_agent = if is_thread_loading {
|
||||
selected_agent
|
||||
.with_animation(
|
||||
"pulsating-icon",
|
||||
Animation::new(Duration::from_secs(1))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.2, 0.6)),
|
||||
|icon, delta| icon.opacity(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
selected_agent.into_any_element()
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("agent-panel-toolbar")
|
||||
@@ -2674,16 +2685,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
return;
|
||||
};
|
||||
let project = workspace.read(cx).project().downgrade();
|
||||
let thread_store = panel.read(cx).thread_store().clone();
|
||||
assistant.assist(
|
||||
prompt_editor,
|
||||
self.workspace.clone(),
|
||||
project,
|
||||
panel.read(cx).thread_store().clone(),
|
||||
thread_store,
|
||||
None,
|
||||
initial_prompt,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ mod buffer_codegen;
|
||||
mod completion_provider;
|
||||
mod context;
|
||||
mod context_server_configuration;
|
||||
#[cfg(test)]
|
||||
mod evals;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
@@ -69,6 +71,8 @@ actions!(
|
||||
CycleModeSelector,
|
||||
/// Expands the message editor to full size.
|
||||
ExpandMessageEditor,
|
||||
/// Removes all thread history.
|
||||
RemoveHistory,
|
||||
/// Opens the conversation history view.
|
||||
OpenHistory,
|
||||
/// Adds a context server to the configuration.
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use crate::{context::LoadedContext, inline_prompt_editor::CodegenStatus};
|
||||
use agent::{
|
||||
AgentTool as _, FailureMessageInput, FailureMessageTool, RewriteSectionInput,
|
||||
RewriteSectionTool, SystemPromptTemplate, Template, Templates,
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use cloud_llm_client::CompletionIntent;
|
||||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
use feature_flags::{FeatureFlagAppExt as _, InlineAssistantV2FeatureFlag};
|
||||
use futures::{
|
||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||
channel::mpsc,
|
||||
@@ -14,12 +19,14 @@ use futures::{
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role, report_assistant_event,
|
||||
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, LanguageModelTextStream, LanguageModelToolUse, Role,
|
||||
report_assistant_event,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use prompt_store::PromptBuilder;
|
||||
use prompt_store::{ProjectContext, PromptBuilder};
|
||||
use rope::Rope;
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
@@ -214,6 +221,13 @@ impl BufferCodegen {
|
||||
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
|
||||
self.active_alternative().read(cx).last_equal_ranges()
|
||||
}
|
||||
|
||||
pub fn tool_description<'a>(&self, cx: &'a App) -> Option<&'a str> {
|
||||
self.active_alternative()
|
||||
.read(cx)
|
||||
.tool_description
|
||||
.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||
@@ -238,6 +252,7 @@ pub struct CodegenAlternative {
|
||||
elapsed_time: Option<f64>,
|
||||
completion: Option<String>,
|
||||
pub message_id: Option<String>,
|
||||
pub tool_description: Option<String>,
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
||||
@@ -271,7 +286,7 @@ impl CodegenAlternative {
|
||||
let mut buffer = Buffer::local_normalized(text, line_ending, cx);
|
||||
buffer.set_language(language, cx);
|
||||
if let Some(language_registry) = language_registry {
|
||||
buffer.set_language_registry(language_registry)
|
||||
buffer.set_language_registry(language_registry);
|
||||
}
|
||||
buffer
|
||||
});
|
||||
@@ -288,14 +303,15 @@ impl CodegenAlternative {
|
||||
generation: Task::ready(()),
|
||||
diff: Diff::default(),
|
||||
telemetry,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
builder,
|
||||
active,
|
||||
active: false,
|
||||
edits: Vec::new(),
|
||||
line_operations: Vec::new(),
|
||||
range,
|
||||
elapsed_time: None,
|
||||
completion: None,
|
||||
tool_description: None,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,20 +374,135 @@ impl CodegenAlternative {
|
||||
let api_key = model.api_key(cx);
|
||||
let telemetry_id = model.telemetry_id();
|
||||
let provider_id = model.provider_id();
|
||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||
|
||||
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||
// if false {
|
||||
// let request = self.build_request_v2(&model, user_prompt, context_task, cx)?;
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
let tool_use = cx
|
||||
.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_tool(request.await, cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
};
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
// .boxed_local();
|
||||
;
|
||||
self.handle_tool_use(telemetry_id, provider_id.to_string(), api_key, tool_use, cx);
|
||||
} else {
|
||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
};
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_request_v2(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let language = buffer.language_at(self.range.start);
|
||||
let language_name = if let Some(language) = language.as_ref() {
|
||||
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
|
||||
None
|
||||
} else {
|
||||
Some(language.name())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let language_name = language_name.as_ref();
|
||||
let start = buffer.point_to_buffer_offset(self.range.start);
|
||||
let end = buffer.point_to_buffer_offset(self.range.end);
|
||||
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
|
||||
let (start_buffer, start_buffer_offset) = start;
|
||||
let (end_buffer, end_buffer_offset) = end;
|
||||
if start_buffer.remote_id() == end_buffer.remote_id() {
|
||||
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
|
||||
} else {
|
||||
anyhow::bail!("invalid transformation range");
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("invalid transformation range");
|
||||
};
|
||||
|
||||
let system_prompt = self
|
||||
.builder
|
||||
.generate_inline_transformation_prompt_v2(
|
||||
language_name,
|
||||
buffer,
|
||||
range.start.0..range.end.0,
|
||||
)
|
||||
.context("generating content prompt")?;
|
||||
|
||||
let temperature = AgentSettings::temperature_for_model(model, cx);
|
||||
|
||||
let tool_input_format = model.tool_input_format();
|
||||
|
||||
Ok(cx.spawn(async move |_cx| {
|
||||
let mut messages = vec![LanguageModelRequestMessage {
|
||||
role: Role::System,
|
||||
content: vec![system_prompt.into()],
|
||||
cache: false,
|
||||
reasoning_details: None,
|
||||
}];
|
||||
|
||||
let mut user_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
reasoning_details: None,
|
||||
};
|
||||
|
||||
if let Some(context) = context_task.await {
|
||||
context.add_to_request_message(&mut user_message);
|
||||
}
|
||||
|
||||
user_message.content.push(user_prompt.into());
|
||||
messages.push(user_message);
|
||||
dbg!(&messages);
|
||||
|
||||
let tools = vec![
|
||||
LanguageModelRequestTool {
|
||||
name: RewriteSectionTool::name().to_string(),
|
||||
description: RewriteSectionTool::description().to_string(),
|
||||
input_schema: RewriteSectionTool::input_schema(tool_input_format).to_value(),
|
||||
},
|
||||
LanguageModelRequestTool {
|
||||
name: FailureMessageTool::name().to_string(),
|
||||
description: FailureMessageTool::description().to_string(),
|
||||
input_schema: FailureMessageTool::input_schema(tool_input_format).to_value(),
|
||||
},
|
||||
];
|
||||
|
||||
let req = LanguageModelRequest {
|
||||
thread_id: None,
|
||||
prompt_id: None,
|
||||
intent: Some(CompletionIntent::InlineAssist),
|
||||
mode: None,
|
||||
tools,
|
||||
tool_choice: None,
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
messages,
|
||||
thinking_allowed: false,
|
||||
};
|
||||
dbg!(&req);
|
||||
req
|
||||
}))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
@@ -379,6 +510,10 @@ impl CodegenAlternative {
|
||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||
return self.build_request_v2(model, user_prompt, context_task, cx);
|
||||
}
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let language = buffer.language_at(self.range.start);
|
||||
let language_name = if let Some(language) = language.as_ref() {
|
||||
@@ -415,6 +550,7 @@ impl CodegenAlternative {
|
||||
range.start.0..range.end.0,
|
||||
)
|
||||
.context("generating content prompt")?;
|
||||
dbg!(&prompt);
|
||||
|
||||
let temperature = AgentSettings::temperature_for_model(model, cx);
|
||||
|
||||
@@ -508,8 +644,16 @@ impl CodegenAlternative {
|
||||
let completion = Arc::new(Mutex::new(String::new()));
|
||||
let completion_clone = completion.clone();
|
||||
|
||||
dbg!("AAA 0");
|
||||
self.generation = cx.spawn(async move |codegen, cx| {
|
||||
dbg!("AAA 1");
|
||||
let stream = stream.await;
|
||||
dbg!("AAA 2");
|
||||
|
||||
// use futures::stream::StreamExt;
|
||||
// let all_chunks: Vec<Result<String, _>> = stream.unwrap().collect().await;
|
||||
// dbg!(&all_chunks);
|
||||
|
||||
let token_usage = stream
|
||||
.as_ref()
|
||||
.ok()
|
||||
@@ -518,16 +662,19 @@ impl CodegenAlternative {
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|stream| stream.message_id.clone());
|
||||
dbg!("AAA 3");
|
||||
let generate = async {
|
||||
let model_telemetry_id = model_telemetry_id.clone();
|
||||
let model_provider_id = model_provider_id.clone();
|
||||
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
||||
let executor = cx.background_executor().clone();
|
||||
let message_id = message_id.clone();
|
||||
dbg!("AAA 4");
|
||||
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
||||
cx.background_spawn(async move {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
dbg!("AAA 5");
|
||||
let diff = async {
|
||||
let chunks = StripInvalidSpans::new(
|
||||
stream?.stream.map_err(|error| error.into()),
|
||||
@@ -541,11 +688,14 @@ impl CodegenAlternative {
|
||||
let mut line_indent = None;
|
||||
let mut first_line = true;
|
||||
|
||||
dbg!("AAA 6");
|
||||
while let Some(chunk) = chunks.next().await {
|
||||
dbg!("AAA 7");
|
||||
if response_latency.is_none() {
|
||||
response_latency = Some(request_start.elapsed());
|
||||
}
|
||||
let chunk = chunk?;
|
||||
dbg!(&chunk);
|
||||
completion_clone.lock().push_str(&chunk);
|
||||
|
||||
let mut lines = chunk.split('\n').peekable();
|
||||
@@ -719,6 +869,7 @@ impl CodegenAlternative {
|
||||
output_tokens = usage.output_tokens,
|
||||
)
|
||||
}
|
||||
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -898,6 +1049,126 @@ impl CodegenAlternative {
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_tool_use(
|
||||
&mut self,
|
||||
_telemetry_id: String,
|
||||
_provider_id: String,
|
||||
_api_key: Option<String>,
|
||||
tool_use: impl 'static
|
||||
+ Future<
|
||||
Output = Result<language_model::LanguageModelToolUse, LanguageModelCompletionError>,
|
||||
>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.diff = Diff::default();
|
||||
self.status = CodegenStatus::Pending;
|
||||
|
||||
self.generation = cx.spawn(async move |codegen, cx| {
|
||||
let tool_use = tool_use.await;
|
||||
dbg!(&tool_use);
|
||||
|
||||
match tool_use {
|
||||
Ok(tool_use) if tool_use.name.as_ref() == "rewrite_section" => {
|
||||
eprintln!("Received tool use: {:?}", tool_use);
|
||||
|
||||
// Parse the input JSON into RewriteSectionInput
|
||||
match serde_json::from_value::<RewriteSectionInput>(tool_use.input) {
|
||||
Ok(input) => {
|
||||
eprintln!("Description: {}", input.description);
|
||||
eprintln!("Replacement text length: {}", input.replacement_text.len());
|
||||
|
||||
// Store the description if non-empty
|
||||
let description = if !input.description.trim().is_empty() {
|
||||
Some(input.description.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Apply the replacement text to the buffer and compute diff
|
||||
let batch_diff_task = codegen
|
||||
.update(cx, |this, cx| {
|
||||
this.tool_description = description;
|
||||
let range = this.range.clone();
|
||||
this.apply_edits(
|
||||
std::iter::once((range, input.replacement_text)),
|
||||
cx,
|
||||
);
|
||||
this.reapply_batch_diff(cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Wait for the diff computation to complete
|
||||
if let Some(diff_task) = batch_diff_task {
|
||||
diff_task.await;
|
||||
}
|
||||
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = CodegenStatus::Done;
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to parse RewriteSectionInput: {:?}", e);
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = CodegenStatus::Error(e.into());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(tool_use) if tool_use.name.as_ref() == "failure_message" => {
|
||||
// Handle failure message tool use
|
||||
match serde_json::from_value::<FailureMessageInput>(tool_use.input) {
|
||||
Ok(input) => {
|
||||
eprintln!("Failure message: {}", input.message);
|
||||
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
// Store the failure message as the tool description
|
||||
this.tool_description = Some(input.message);
|
||||
this.status = CodegenStatus::Done;
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to parse FailureMessageInput: {:?}", e);
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = CodegenStatus::Error(e.into());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(tool_use) => {
|
||||
eprintln!("Unexpected tool {}", tool_use.name);
|
||||
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = CodegenStatus::Done;
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get tool use: {:?}", e);
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = CodegenStatus::Error(e.into());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
||||
@@ -7,7 +7,9 @@ use std::sync::atomic::AtomicBool;
|
||||
use acp_thread::MentionUri;
|
||||
use agent::{HistoryEntry, HistoryStore};
|
||||
use anyhow::Result;
|
||||
use editor::{CompletionProvider, Editor, ExcerptId};
|
||||
use editor::{
|
||||
CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH,
|
||||
};
|
||||
use fuzzy::{PathMatch, StringMatch, StringMatchCandidate};
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
|
||||
@@ -25,6 +27,7 @@ use ui::prelude::*;
|
||||
use util::ResultExt as _;
|
||||
use util::paths::PathStyle;
|
||||
use util::rel_path::RelPath;
|
||||
use util::truncate_and_remove_front;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::AgentPanel;
|
||||
@@ -336,14 +339,20 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
|
||||
mention_set: WeakEntity<MentionSet>,
|
||||
workspace: Entity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
label_max_chars: usize,
|
||||
cx: &mut App,
|
||||
) -> Option<Completion> {
|
||||
let path_style = project.read(cx).path_style(cx);
|
||||
let (file_name, directory) =
|
||||
extract_file_name_and_directory(&project_path.path, path_prefix, path_style);
|
||||
|
||||
let label =
|
||||
build_code_label_for_path(&file_name, directory.as_ref().map(|s| s.as_ref()), None, cx);
|
||||
let label = build_code_label_for_path(
|
||||
&file_name,
|
||||
directory.as_ref().map(|s| s.as_ref()),
|
||||
None,
|
||||
label_max_chars,
|
||||
cx,
|
||||
);
|
||||
|
||||
let abs_path = project.read(cx).absolute_path(&project_path, cx)?;
|
||||
|
||||
@@ -392,6 +401,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
|
||||
editor: WeakEntity<Editor>,
|
||||
mention_set: WeakEntity<MentionSet>,
|
||||
workspace: Entity<Workspace>,
|
||||
label_max_chars: usize,
|
||||
cx: &mut App,
|
||||
) -> Option<Completion> {
|
||||
let project = workspace.read(cx).project().clone();
|
||||
@@ -414,6 +424,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
|
||||
&symbol.name,
|
||||
Some(&file_name),
|
||||
Some(symbol.range.start.0.row + 1),
|
||||
label_max_chars,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -852,7 +863,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_position: Anchor,
|
||||
_trigger: CompletionContext,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Task<Result<Vec<CompletionResponse>>> {
|
||||
let state = buffer.update(cx, |buffer, cx| {
|
||||
@@ -861,7 +872,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
let offset_to_line = buffer.point_to_offset(line_start);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
let line = lines.next()?;
|
||||
ContextCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
|
||||
PromptCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
|
||||
});
|
||||
let Some(state) = state else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
@@ -880,7 +891,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
let editor = self.editor.clone();
|
||||
let mention_set = self.mention_set.downgrade();
|
||||
match state {
|
||||
ContextCompletion::SlashCommand(SlashCommandCompletion {
|
||||
PromptCompletion::SlashCommand(SlashCommandCompletion {
|
||||
command, argument, ..
|
||||
}) => {
|
||||
let search_task = self.search_slash_commands(command.unwrap_or_default(), cx);
|
||||
@@ -943,11 +954,36 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
}])
|
||||
})
|
||||
}
|
||||
ContextCompletion::Mention(MentionCompletion { mode, argument, .. }) => {
|
||||
PromptCompletion::Mention(MentionCompletion { mode, argument, .. }) => {
|
||||
let query = argument.unwrap_or_default();
|
||||
let search_task =
|
||||
self.search_mentions(mode, query, Arc::<AtomicBool>::default(), cx);
|
||||
|
||||
// Calculate maximum characters available for the full label (file_name + space + directory)
|
||||
// based on maximum menu width after accounting for padding, spacing, and icon width
|
||||
let label_max_chars = {
|
||||
// Base06 left padding + Base06 gap + Base06 right padding + icon width
|
||||
let used_pixels = DynamicSpacing::Base06.px(cx) * 3.0
|
||||
+ IconSize::XSmall.rems() * window.rem_size();
|
||||
|
||||
let style = window.text_style();
|
||||
let font_id = window.text_system().resolve_font(&style.font());
|
||||
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
|
||||
|
||||
// Fallback em_width of 10px matches file_finder.rs fallback for TextSize::Small
|
||||
let em_width = cx
|
||||
.text_system()
|
||||
.em_width(font_id, font_size)
|
||||
.unwrap_or(px(10.0));
|
||||
|
||||
// Calculate available pixels for text (file_name + directory)
|
||||
// Using max width since dynamic_width allows the menu to expand up to this
|
||||
let available_pixels = COMPLETION_MENU_MAX_WIDTH - used_pixels;
|
||||
|
||||
// Convert to character count (total available for file_name + directory)
|
||||
(f32::from(available_pixels) / f32::from(em_width)) as usize
|
||||
};
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
let matches = search_task.await;
|
||||
|
||||
@@ -984,6 +1020,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
mention_set.clone(),
|
||||
workspace.clone(),
|
||||
project.clone(),
|
||||
label_max_chars,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -996,6 +1033,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
editor.clone(),
|
||||
mention_set.clone(),
|
||||
workspace.clone(),
|
||||
label_max_chars,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1076,7 +1114,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
position: language::Anchor,
|
||||
_text: &str,
|
||||
_trigger_in_words: bool,
|
||||
_menu_is_open: bool,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
let buffer = buffer.read(cx);
|
||||
@@ -1085,12 +1122,12 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
|
||||
let offset_to_line = buffer.point_to_offset(line_start);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
if let Some(line) = lines.next() {
|
||||
ContextCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
|
||||
PromptCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
|
||||
.filter(|completion| {
|
||||
// Right now we don't support completing arguments of slash commands
|
||||
let is_slash_command_with_argument = matches!(
|
||||
completion,
|
||||
ContextCompletion::SlashCommand(SlashCommandCompletion {
|
||||
PromptCompletion::SlashCommand(SlashCommandCompletion {
|
||||
argument: Some(_),
|
||||
..
|
||||
})
|
||||
@@ -1160,12 +1197,13 @@ fn confirm_completion_callback<T: PromptCompletionProviderDelegate>(
|
||||
})
|
||||
}
|
||||
|
||||
enum ContextCompletion {
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum PromptCompletion {
|
||||
SlashCommand(SlashCommandCompletion),
|
||||
Mention(MentionCompletion),
|
||||
}
|
||||
|
||||
impl ContextCompletion {
|
||||
impl PromptCompletion {
|
||||
fn source_range(&self) -> Range<usize> {
|
||||
match self {
|
||||
Self::SlashCommand(completion) => completion.source_range.clone(),
|
||||
@@ -1178,15 +1216,14 @@ impl ContextCompletion {
|
||||
offset_to_line: usize,
|
||||
supported_modes: &[PromptContextType],
|
||||
) -> Option<Self> {
|
||||
if let Some(command) = SlashCommandCompletion::try_parse(line, offset_to_line) {
|
||||
Some(Self::SlashCommand(command))
|
||||
} else if let Some(mention) =
|
||||
MentionCompletion::try_parse(line, offset_to_line, supported_modes)
|
||||
{
|
||||
Some(Self::Mention(mention))
|
||||
} else {
|
||||
None
|
||||
if line.contains('@') {
|
||||
if let Some(mention) =
|
||||
MentionCompletion::try_parse(line, offset_to_line, supported_modes)
|
||||
{
|
||||
return Some(Self::Mention(mention));
|
||||
}
|
||||
}
|
||||
SlashCommandCompletion::try_parse(line, offset_to_line).map(Self::SlashCommand)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1595,6 +1632,7 @@ fn build_code_label_for_path(
|
||||
file: &str,
|
||||
directory: Option<&str>,
|
||||
line_number: Option<u32>,
|
||||
label_max_chars: usize,
|
||||
cx: &App,
|
||||
) -> CodeLabel {
|
||||
let variable_highlight_id = cx
|
||||
@@ -1608,7 +1646,13 @@ fn build_code_label_for_path(
|
||||
label.push_str(" ", None);
|
||||
|
||||
if let Some(directory) = directory {
|
||||
label.push_str(directory, variable_highlight_id);
|
||||
let file_name_chars = file.chars().count();
|
||||
// Account for: file_name + space (ellipsis is handled by truncate_and_remove_front)
|
||||
let directory_max_chars = label_max_chars
|
||||
.saturating_sub(file_name_chars)
|
||||
.saturating_sub(1);
|
||||
let truncated_directory = truncate_and_remove_front(directory, directory_max_chars.max(5));
|
||||
label.push_str(&truncated_directory, variable_highlight_id);
|
||||
}
|
||||
if let Some(line_number) = line_number {
|
||||
label.push_str(&format!(" L{}", line_number), variable_highlight_id);
|
||||
@@ -1653,6 +1697,38 @@ fn selection_ranges(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_prompt_completion_parse() {
|
||||
let supported_modes = vec![PromptContextType::File, PromptContextType::Symbol];
|
||||
|
||||
assert_eq!(
|
||||
PromptCompletion::try_parse("/", 0, &supported_modes),
|
||||
Some(PromptCompletion::SlashCommand(SlashCommandCompletion {
|
||||
source_range: 0..1,
|
||||
command: None,
|
||||
argument: None,
|
||||
}))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PromptCompletion::try_parse("@", 0, &supported_modes),
|
||||
Some(PromptCompletion::Mention(MentionCompletion {
|
||||
source_range: 0..1,
|
||||
mode: None,
|
||||
argument: None,
|
||||
}))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PromptCompletion::try_parse("/test @file", 0, &supported_modes),
|
||||
Some(PromptCompletion::Mention(MentionCompletion {
|
||||
source_range: 6..11,
|
||||
mode: Some(PromptContextType::File),
|
||||
argument: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slash_command_completion_parse() {
|
||||
assert_eq!(
|
||||
|
||||
88
crates/agent_ui/src/evals.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
// use std::sync::Arc;
|
||||
|
||||
use crate::inline_assistant::test::run_inline_assistant_test;
|
||||
|
||||
use eval_utils::EvalOutput;
|
||||
use gpui::TestAppContext;
|
||||
use language_model::{LanguageModelRegistry, SelectedModel};
|
||||
use rand::{SeedableRng as _, rngs::StdRng};
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
fn eval_single_cursor_edit() {
|
||||
eval_utils::eval(
|
||||
1,
|
||||
1.0,
|
||||
0.0,
|
||||
Arc::new(|tx| {
|
||||
run_eval(
|
||||
&EvalInput {
|
||||
prompt: "Rename this variable to buffer_text".to_string(),
|
||||
text: indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
text: Strˇing,
|
||||
prompt: String,
|
||||
}
|
||||
"}
|
||||
.to_string(),
|
||||
},
|
||||
tx,
|
||||
&|_, output| {
|
||||
EvalOutput::assert(
|
||||
format!("Failed to rename variable, output: {}", output),
|
||||
output
|
||||
== indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
buffer_text: String,
|
||||
prompt: String,
|
||||
}
|
||||
"},
|
||||
)
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
struct EvalInput {
|
||||
text: String,
|
||||
prompt: String,
|
||||
}
|
||||
|
||||
fn run_eval(
|
||||
input: &EvalInput,
|
||||
tx: std::sync::mpsc::Sender<eval_utils::EvalOutput>,
|
||||
judge: &dyn Fn(&EvalInput, &str) -> eval_utils::EvalOutput,
|
||||
) {
|
||||
let dispatcher = gpui::TestDispatcher::new(StdRng::from_os_rng());
|
||||
let mut cx = TestAppContext::build(dispatcher, None);
|
||||
|
||||
let buffer_text = run_inline_assistant_test(
|
||||
input.text.clone(),
|
||||
input.prompt.clone(),
|
||||
|cx| {
|
||||
// Reconfigure to use a real model instead of the fake one
|
||||
let model_name = std::env::var("ZED_AGENT_MODEL")
|
||||
.unwrap_or("anthropic/claude-sonnet-4-latest".into());
|
||||
|
||||
let selected_model = SelectedModel::from_str(&model_name)
|
||||
.expect("Invalid model format. Use 'provider/model-id'");
|
||||
|
||||
log::info!("Selected model: {selected_model:?}");
|
||||
|
||||
cx.update(|_, cx| {
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_inline_assistant_model(Some(&selected_model), cx);
|
||||
});
|
||||
});
|
||||
},
|
||||
|_cx| {
|
||||
log::info!("Waiting for actual response from the LLM...");
|
||||
},
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
let output = judge(input, &buffer_text);
|
||||
tx.send(output).ok();
|
||||
}
|
||||
@@ -32,7 +32,7 @@ use editor::{
|
||||
},
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, channel::mpsc};
|
||||
use gpui::{
|
||||
App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal,
|
||||
WeakEntity, Window, point,
|
||||
@@ -102,6 +102,7 @@ pub struct InlineAssistant {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
_inline_assistant_completions: Option<mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>>,
|
||||
}
|
||||
|
||||
impl Global for InlineAssistant {}
|
||||
@@ -123,9 +124,18 @@ impl InlineAssistant {
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
fs,
|
||||
_inline_assistant_completions: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_completion_receiver(
|
||||
&mut self,
|
||||
sender: mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>,
|
||||
) {
|
||||
self._inline_assistant_completions = Some(sender);
|
||||
}
|
||||
|
||||
pub fn register_workspace(
|
||||
&mut self,
|
||||
workspace: &Entity<Workspace>,
|
||||
@@ -287,7 +297,7 @@ impl InlineAssistant {
|
||||
action.prompt.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
InlineAssistTarget::Terminal(active_terminal) => {
|
||||
@@ -301,8 +311,8 @@ impl InlineAssistant {
|
||||
action.prompt.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -377,17 +387,9 @@ impl InlineAssistant {
|
||||
let mut selections = Vec::<Selection<Point>>::new();
|
||||
let mut newest_selection = None;
|
||||
for mut selection in initial_selections {
|
||||
if selection.end > selection.start {
|
||||
selection.start.column = 0;
|
||||
// If the selection ends at the start of the line, we don't want to include it.
|
||||
if selection.end.column == 0 {
|
||||
selection.end.row -= 1;
|
||||
}
|
||||
selection.end.column = snapshot
|
||||
.buffer_snapshot()
|
||||
.line_len(MultiBufferRow(selection.end.row));
|
||||
} else if let Some(fold) =
|
||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||
if selection.end == selection.start
|
||||
&& let Some(fold) =
|
||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||
{
|
||||
selection.start = fold.range().start;
|
||||
selection.end = fold.range().end;
|
||||
@@ -414,6 +416,15 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selection.start.column = 0;
|
||||
// If the selection ends at the start of the line, we don't want to include it.
|
||||
if selection.end.column == 0 && selection.start.row != selection.end.row {
|
||||
selection.end.row -= 1;
|
||||
}
|
||||
selection.end.column = snapshot
|
||||
.buffer_snapshot()
|
||||
.line_len(MultiBufferRow(selection.end.row));
|
||||
}
|
||||
|
||||
if let Some(prev_selection) = selections.last_mut()
|
||||
@@ -440,7 +451,6 @@ impl InlineAssistant {
|
||||
{
|
||||
let anchor_range = Anchor::range_in_buffer(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end),
|
||||
);
|
||||
|
||||
@@ -535,14 +545,15 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
let [prompt_block_id, end_block_id] =
|
||||
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
|
||||
let [prompt_block_id, tool_description_block_id, end_block_id] =
|
||||
self.insert_assist_blocks(&editor, &range, &prompt_editor, cx);
|
||||
|
||||
assists.push((
|
||||
assist_id,
|
||||
range.clone(),
|
||||
prompt_editor,
|
||||
prompt_block_id,
|
||||
tool_description_block_id,
|
||||
end_block_id,
|
||||
));
|
||||
}
|
||||
@@ -561,7 +572,15 @@ impl InlineAssistant {
|
||||
};
|
||||
|
||||
let mut assist_group = InlineAssistGroup::new();
|
||||
for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists {
|
||||
for (
|
||||
assist_id,
|
||||
range,
|
||||
prompt_editor,
|
||||
prompt_block_id,
|
||||
tool_description_block_id,
|
||||
end_block_id,
|
||||
) in assists
|
||||
{
|
||||
let codegen = prompt_editor.read(cx).codegen().clone();
|
||||
|
||||
self.assists.insert(
|
||||
@@ -572,6 +591,7 @@ impl InlineAssistant {
|
||||
editor,
|
||||
&prompt_editor,
|
||||
prompt_block_id,
|
||||
tool_description_block_id,
|
||||
end_block_id,
|
||||
range,
|
||||
codegen,
|
||||
@@ -599,13 +619,13 @@ impl InlineAssistant {
|
||||
initial_prompt: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
) -> Option<InlineAssistId> {
|
||||
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||
|
||||
let Some((codegen_ranges, newest_selection)) =
|
||||
self.codegen_ranges(editor, &snapshot, window, cx)
|
||||
else {
|
||||
return;
|
||||
return None;
|
||||
};
|
||||
|
||||
let assist_to_focus = self.batch_assist(
|
||||
@@ -625,6 +645,8 @@ impl InlineAssistant {
|
||||
if let Some(assist_id) = assist_to_focus {
|
||||
self.focus_assist(assist_id, window, cx);
|
||||
}
|
||||
|
||||
assist_to_focus
|
||||
}
|
||||
|
||||
pub fn suggest_assist(
|
||||
@@ -678,7 +700,7 @@ impl InlineAssistant {
|
||||
range: &Range<Anchor>,
|
||||
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
||||
cx: &mut App,
|
||||
) -> [CustomBlockId; 2] {
|
||||
) -> [CustomBlockId; 3] {
|
||||
let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| {
|
||||
prompt_editor
|
||||
.editor
|
||||
@@ -692,6 +714,14 @@ impl InlineAssistant {
|
||||
render: build_assist_editor_renderer(prompt_editor),
|
||||
priority: 0,
|
||||
},
|
||||
// Placeholder for tool description - will be updated dynamically
|
||||
BlockProperties {
|
||||
style: BlockStyle::Flex,
|
||||
placement: BlockPlacement::Below(range.end),
|
||||
height: Some(0),
|
||||
render: Arc::new(|_cx| div().into_any_element()),
|
||||
priority: 0,
|
||||
},
|
||||
BlockProperties {
|
||||
style: BlockStyle::Sticky,
|
||||
placement: BlockPlacement::Below(range.end),
|
||||
@@ -710,7 +740,7 @@ impl InlineAssistant {
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let block_ids = editor.insert_blocks(assist_blocks, None, cx);
|
||||
[block_ids[0], block_ids[1]]
|
||||
[block_ids[0], block_ids[1], block_ids[2]]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1102,6 +1132,9 @@ impl InlineAssistant {
|
||||
let mut to_remove = decorations.removed_line_block_ids;
|
||||
to_remove.insert(decorations.prompt_block_id);
|
||||
to_remove.insert(decorations.end_block_id);
|
||||
if let Some(tool_description_block_id) = decorations.tool_description_block_id {
|
||||
to_remove.insert(tool_description_block_id);
|
||||
}
|
||||
editor.remove_blocks(to_remove, None, cx);
|
||||
});
|
||||
|
||||
@@ -1422,8 +1455,60 @@ impl InlineAssistant {
|
||||
let old_snapshot = codegen.snapshot(cx);
|
||||
let old_buffer = codegen.old_buffer(cx);
|
||||
let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
|
||||
let tool_description = codegen.tool_description(cx).map(|s| s.to_string());
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Update tool description block
|
||||
if let Some(description) = tool_description {
|
||||
if let Some(block_id) = decorations.tool_description_block_id {
|
||||
editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
let new_block_id = editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
style: BlockStyle::Flex,
|
||||
placement: BlockPlacement::Below(assist.range.end),
|
||||
height: Some(1),
|
||||
render: Arc::new({
|
||||
let description = description.clone();
|
||||
move |cx| {
|
||||
div()
|
||||
.w_full()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.child(
|
||||
Label::new(description.clone())
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
decorations.tool_description_block_id = new_block_id.into_iter().next();
|
||||
}
|
||||
} else if let Some(block_id) = decorations.tool_description_block_id {
|
||||
// Hide the block if there's no description
|
||||
editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
let new_block_id = editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
style: BlockStyle::Flex,
|
||||
placement: BlockPlacement::Below(assist.range.end),
|
||||
height: Some(0),
|
||||
render: Arc::new(|_cx| div().into_any_element()),
|
||||
priority: 0,
|
||||
}],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
decorations.tool_description_block_id = new_block_id.into_iter().next();
|
||||
}
|
||||
|
||||
let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
|
||||
editor.remove_blocks(old_blocks, None, cx);
|
||||
|
||||
@@ -1457,6 +1542,7 @@ impl InlineAssistant {
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_offset_content(false, cx);
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_edit_predictions(Some(false), window, cx);
|
||||
@@ -1674,6 +1760,7 @@ impl InlineAssist {
|
||||
editor: &Entity<Editor>,
|
||||
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
||||
prompt_block_id: CustomBlockId,
|
||||
tool_description_block_id: CustomBlockId,
|
||||
end_block_id: CustomBlockId,
|
||||
range: Range<Anchor>,
|
||||
codegen: Entity<BufferCodegen>,
|
||||
@@ -1688,7 +1775,8 @@ impl InlineAssist {
|
||||
decorations: Some(InlineAssistDecorations {
|
||||
prompt_block_id,
|
||||
prompt_editor: prompt_editor.clone(),
|
||||
removed_line_block_ids: HashSet::default(),
|
||||
removed_line_block_ids: Default::default(),
|
||||
tool_description_block_id: Some(tool_description_block_id),
|
||||
end_block_id,
|
||||
}),
|
||||
range,
|
||||
@@ -1740,6 +1828,16 @@ impl InlineAssist {
|
||||
&& assist.decorations.is_none()
|
||||
&& let Some(workspace) = assist.workspace.upgrade()
|
||||
{
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(sender) = &mut this._inline_assistant_completions {
|
||||
sender
|
||||
.unbounded_send(Err(anyhow::anyhow!(
|
||||
"Inline assistant error: {}",
|
||||
error
|
||||
)))
|
||||
.ok();
|
||||
}
|
||||
|
||||
let error = format!("Inline assistant error: {}", error);
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
struct InlineAssistantError;
|
||||
@@ -1750,6 +1848,11 @@ impl InlineAssist {
|
||||
|
||||
workspace.show_toast(Toast::new(id, error), cx);
|
||||
})
|
||||
} else {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(sender) = &mut this._inline_assistant_completions {
|
||||
sender.unbounded_send(Ok(assist_id)).ok();
|
||||
}
|
||||
}
|
||||
|
||||
if assist.decorations.is_none() {
|
||||
@@ -1777,6 +1880,7 @@ struct InlineAssistDecorations {
|
||||
prompt_block_id: CustomBlockId,
|
||||
prompt_editor: Entity<PromptEditor<BufferCodegen>>,
|
||||
removed_line_block_ids: HashSet<CustomBlockId>,
|
||||
tool_description_block_id: Option<CustomBlockId>,
|
||||
end_block_id: CustomBlockId,
|
||||
}
|
||||
|
||||
@@ -1943,3 +2047,159 @@ fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::HistoryStore;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use client::{Client, UserStore};
|
||||
use editor::{Editor, MultiBuffer, MultiBufferOffset};
|
||||
use fs::FakeFs;
|
||||
use futures::channel::mpsc;
|
||||
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
|
||||
use language::Buffer;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use smol::stream::StreamExt as _;
|
||||
use util::test::marked_text_ranges;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::InlineAssistant;
|
||||
|
||||
pub fn run_inline_assistant_test<SetupF, TestF>(
|
||||
base_buffer: String,
|
||||
prompt: String,
|
||||
setup: SetupF,
|
||||
test: TestF,
|
||||
cx: &mut TestAppContext,
|
||||
) -> String
|
||||
where
|
||||
SetupF: FnOnce(&mut gpui::VisualTestContext),
|
||||
TestF: FnOnce(&mut gpui::VisualTestContext),
|
||||
{
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let app_state = cx.update(|cx| workspace::AppState::test(cx));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let http = Arc::new(reqwest_client::ReqwestClient::user_agent("agent tests").unwrap());
|
||||
let client = cx.update(|cx| {
|
||||
cx.set_http_client(http);
|
||||
Client::production(cx)
|
||||
});
|
||||
let mut inline_assistant =
|
||||
InlineAssistant::new(fs.clone(), prompt_builder, client.telemetry().clone());
|
||||
|
||||
let (tx, mut completion_rx) = mpsc::unbounded();
|
||||
inline_assistant.set_completion_receiver(tx);
|
||||
|
||||
// Initialize settings and client
|
||||
cx.update(|cx| {
|
||||
gpui_tokio::init(cx);
|
||||
settings::init(cx);
|
||||
client::init(&client, cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
|
||||
language_model::init(client.clone(), cx);
|
||||
language_models::init(user_store, client.clone(), cx);
|
||||
|
||||
cx.set_global(inline_assistant);
|
||||
});
|
||||
|
||||
let project = cx
|
||||
.executor()
|
||||
.block_test(async { Project::test(fs.clone(), [], cx).await });
|
||||
|
||||
// Create workspace with window
|
||||
let (workspace, cx) = cx.add_window_view(|window, cx| {
|
||||
window.activate_window();
|
||||
Workspace::new(None, project.clone(), app_state.clone(), window, cx)
|
||||
});
|
||||
|
||||
setup(cx);
|
||||
|
||||
let (_editor, buffer) = cx.update(|window, cx| {
|
||||
let buffer = cx.new(|cx| Buffer::local("", cx));
|
||||
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
let editor = cx.new(|cx| Editor::for_multibuffer(multibuffer, None, window, cx));
|
||||
editor.update(cx, |editor, cx| {
|
||||
let (unmarked_text, selection_ranges) = marked_text_ranges(&base_buffer, true);
|
||||
editor.set_text(unmarked_text, window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(
|
||||
selection_ranges.into_iter().map(|range| {
|
||||
MultiBufferOffset(range.start)..MultiBufferOffset(range.end)
|
||||
}),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
|
||||
// Add editor to workspace
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
|
||||
});
|
||||
|
||||
// Call assist method
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
let assist_id = inline_assistant
|
||||
.assist(
|
||||
&editor,
|
||||
workspace.downgrade(),
|
||||
project.downgrade(),
|
||||
history_store, // thread_store
|
||||
None, // prompt_store
|
||||
Some(prompt),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
inline_assistant.start_assist(assist_id, window, cx);
|
||||
});
|
||||
|
||||
(editor, buffer)
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
test(cx);
|
||||
|
||||
cx.executor()
|
||||
.block_test(async { completion_rx.next().await });
|
||||
|
||||
buffer.read_with(cx, |buffer, _| buffer.text())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn test_inline_assistant(
|
||||
base_buffer: &'static str,
|
||||
llm_output: &'static str, // vec<&'static str>
|
||||
cx: &mut TestAppContext,
|
||||
) -> String {
|
||||
run_inline_assistant_test(
|
||||
base_buffer.to_string(),
|
||||
"Prompt doesn't matter because we're using a fake model".to_string(),
|
||||
|cx| {
|
||||
cx.update(|_, cx| LanguageModelRegistry::test(cx));
|
||||
},
|
||||
|cx| {
|
||||
let fake_model = cx.update(|_, cx| {
|
||||
LanguageModelRegistry::global(cx)
|
||||
.update(cx, |registry, _| registry.fake_model())
|
||||
});
|
||||
let fake = fake_model.as_fake();
|
||||
|
||||
// let fake = fake_model;
|
||||
fake.send_last_completion_stream_text_chunk(llm_output.to_string());
|
||||
fake.end_last_completion_stream();
|
||||
|
||||
// Run again to process the model's response
|
||||
cx.run_until_parked();
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +341,6 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
position: language::Anchor,
|
||||
_text: &str,
|
||||
_trigger_in_words: bool,
|
||||
_menu_is_open: bool,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
let buffer = buffer.read(cx);
|
||||
|
||||
@@ -2556,7 +2556,11 @@ impl Item for TextThreadEditor {
|
||||
Some(self.title(cx).to_string().into())
|
||||
}
|
||||
|
||||
fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(
|
||||
&self,
|
||||
handle: &Entity<Self>,
|
||||
_: &App,
|
||||
) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -249,10 +249,15 @@ impl PasswordProxy {
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path).await?;
|
||||
make_file_executable(&askpass_script_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("marking askpass script executable at {askpass_script_path:?}")
|
||||
})?;
|
||||
// todo(shell): There might be no powershell on the system
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_helper = format!(
|
||||
"powershell.exe -ExecutionPolicy Bypass -File {}",
|
||||
"powershell.exe -ExecutionPolicy Bypass -File \"{}\"",
|
||||
askpass_script_path.display()
|
||||
);
|
||||
|
||||
|
||||
@@ -233,18 +233,11 @@ fn collect_diagnostics(
|
||||
options: Options,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Option<SlashCommandOutput>>> {
|
||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let path_style = project.read(cx).path_style(cx);
|
||||
let glob_is_exact_file_match = if let Some(path) = options
|
||||
.path_matcher
|
||||
.as_ref()
|
||||
.and_then(|pm| pm.sources().first())
|
||||
.and_then(|pm| pm.sources().next())
|
||||
{
|
||||
project
|
||||
.read(cx)
|
||||
@@ -266,6 +259,13 @@ fn collect_diagnostics(
|
||||
.collect();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||
debug_assert_eq!(path_matcher.sources().count(), 1);
|
||||
Some(path_matcher.sources().next().unwrap_or_default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut output = SlashCommandOutput::default();
|
||||
|
||||
if let Some(error_source) = error_source.as_ref() {
|
||||
@@ -277,7 +277,7 @@ fn collect_diagnostics(
|
||||
let mut project_summary = DiagnosticSummary::default();
|
||||
for (project_path, path, summary) in diagnostic_summaries {
|
||||
if let Some(path_matcher) = &options.path_matcher
|
||||
&& !path_matcher.is_match(&path.as_std_path())
|
||||
&& !path_matcher.is_match(&path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -797,7 +797,7 @@ impl TextThread {
|
||||
});
|
||||
let message = MessageAnchor {
|
||||
id: first_message_id,
|
||||
start: language::Anchor::MIN,
|
||||
start: language::Anchor::min_for_buffer(this.buffer.read(cx).remote_id()),
|
||||
};
|
||||
this.messages_metadata.insert(
|
||||
first_message_id,
|
||||
@@ -1147,12 +1147,10 @@ impl TextThread {
|
||||
cx: &App,
|
||||
) -> bool {
|
||||
let version = &self.buffer.read(cx).version;
|
||||
let observed_start = range.start == language::Anchor::MIN
|
||||
|| range.start == language::Anchor::MAX
|
||||
|| version.observed(range.start.timestamp);
|
||||
let observed_end = range.end == language::Anchor::MIN
|
||||
|| range.end == language::Anchor::MAX
|
||||
|| version.observed(range.end.timestamp);
|
||||
let observed_start =
|
||||
range.start.is_min() || range.start.is_max() || version.observed(range.start.timestamp);
|
||||
let observed_end =
|
||||
range.end.is_min() || range.end.is_max() || version.observed(range.end.timestamp);
|
||||
observed_start && observed_end
|
||||
}
|
||||
|
||||
@@ -2858,7 +2856,8 @@ impl TextThread {
|
||||
messages.next();
|
||||
}
|
||||
}
|
||||
let message_end_anchor = message_end.unwrap_or(language::Anchor::MAX);
|
||||
let message_end_anchor =
|
||||
message_end.unwrap_or(language::Anchor::max_for_buffer(buffer.remote_id()));
|
||||
let message_end = message_end_anchor.to_offset(buffer);
|
||||
|
||||
return Some(Message {
|
||||
@@ -2934,6 +2933,7 @@ impl TextThread {
|
||||
RenameOptions {
|
||||
overwrite: true,
|
||||
ignore_if_exists: true,
|
||||
create_parents: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -510,7 +510,9 @@ impl AutoUpdater {
|
||||
(None, None, None)
|
||||
};
|
||||
|
||||
let version = if let Some(version) = version {
|
||||
let version = if let Some(mut version) = version {
|
||||
version.pre = semver::Prerelease::EMPTY;
|
||||
version.build = semver::BuildMetadata::EMPTY;
|
||||
version.to_string()
|
||||
} else {
|
||||
"latest".to_string()
|
||||
@@ -637,10 +639,11 @@ impl AutoUpdater {
|
||||
if let AutoUpdateStatus::Updated { version, .. } = status {
|
||||
match version {
|
||||
VersionCheckType::Sha(cached_version) => {
|
||||
let should_download = parsed_fetched_version
|
||||
.as_ref()
|
||||
.ok()
|
||||
.is_none_or(|version| version.build.as_str() != cached_version.full());
|
||||
let should_download =
|
||||
parsed_fetched_version.as_ref().ok().is_none_or(|version| {
|
||||
version.build.as_str().rsplit('.').next()
|
||||
!= Some(&cached_version.full())
|
||||
});
|
||||
let newer_version = should_download
|
||||
.then(|| VersionCheckType::Sha(AppCommitSha::new(fetched_version)));
|
||||
return Ok(newer_version);
|
||||
@@ -660,10 +663,9 @@ impl AutoUpdater {
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|sha| {
|
||||
parsed_fetched_version
|
||||
.as_ref()
|
||||
.ok()
|
||||
.is_none_or(|version| version.build.as_str() != sha)
|
||||
parsed_fetched_version.as_ref().ok().is_none_or(|version| {
|
||||
version.build.as_str().rsplit('.').next() != Some(&sha)
|
||||
})
|
||||
})
|
||||
.unwrap_or(true);
|
||||
let newer_version = should_download
|
||||
@@ -717,9 +719,12 @@ impl AutoUpdater {
|
||||
}
|
||||
|
||||
fn check_if_fetched_version_is_newer_non_nightly(
|
||||
installed_version: Version,
|
||||
mut installed_version: Version,
|
||||
fetched_version: Version,
|
||||
) -> Result<Option<VersionCheckType>> {
|
||||
// For non-nightly releases, ignore build and pre-release fields as they're not provided by our endpoints right now.
|
||||
installed_version.build = semver::BuildMetadata::EMPTY;
|
||||
installed_version.pre = semver::Prerelease::EMPTY;
|
||||
let should_download = fetched_version > installed_version;
|
||||
let newer_version = should_download.then(|| VersionCheckType::Semantic(fetched_version));
|
||||
Ok(newer_version)
|
||||
|
||||
@@ -20,6 +20,7 @@ gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
release_channel.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
@@ -148,7 +148,9 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
|
||||
let should_show_notification = should_show_notification.await?;
|
||||
if should_show_notification {
|
||||
cx.update(|cx| {
|
||||
let version = updater.read(cx).current_version();
|
||||
let mut version = updater.read(cx).current_version();
|
||||
version.build = semver::BuildMetadata::EMPTY;
|
||||
version.pre = semver::Prerelease::EMPTY;
|
||||
let app_name = ReleaseChannel::global(cx).display_name();
|
||||
show_app_notification(
|
||||
NotificationId::unique::<UpdateNotification>(),
|
||||
|
||||
@@ -12,7 +12,7 @@ workspace = true
|
||||
path = "src/buffer_diff.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
test-support = ["settings"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -24,6 +24,7 @@ language.workspace = true
|
||||
log.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
rope.workspace = true
|
||||
settings = { workspace = true, optional = true }
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
util.workspace = true
|
||||
@@ -33,6 +34,7 @@ ctor.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
zlog.workspace = true
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use futures::channel::oneshot;
|
||||
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use language::{
|
||||
BufferRow, DiffOptions, File, Language, LanguageName, LanguageRegistry,
|
||||
language_settings::language_settings, word_diff_ranges,
|
||||
};
|
||||
use rope::Rope;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
@@ -11,14 +14,16 @@ use std::{
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use sum_tree::SumTree;
|
||||
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};
|
||||
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _, ToPoint as _};
|
||||
use util::ResultExt;
|
||||
|
||||
pub static CALCULATE_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
|
||||
pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
|
||||
|
||||
pub struct BufferDiff {
|
||||
pub buffer_id: BufferId,
|
||||
inner: BufferDiffInner,
|
||||
// diff of the index vs head
|
||||
secondary_diff: Option<Entity<BufferDiff>>,
|
||||
}
|
||||
|
||||
@@ -31,6 +36,7 @@ pub struct BufferDiffSnapshot {
|
||||
#[derive(Clone)]
|
||||
struct BufferDiffInner {
|
||||
hunks: SumTree<InternalDiffHunk>,
|
||||
// Used for making staging mo
|
||||
pending_hunks: SumTree<PendingHunk>,
|
||||
base_text: language::BufferSnapshot,
|
||||
base_text_exists: bool,
|
||||
@@ -50,11 +56,18 @@ pub enum DiffHunkStatusKind {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
/// Diff of Working Copy vs Index
|
||||
/// aka 'is this hunk staged or not'
|
||||
pub enum DiffHunkSecondaryStatus {
|
||||
/// Unstaged
|
||||
HasSecondaryHunk,
|
||||
/// Partially staged
|
||||
OverlapsWithSecondaryHunk,
|
||||
/// Staged
|
||||
NoSecondaryHunk,
|
||||
/// We are unstaging
|
||||
SecondaryHunkAdditionPending,
|
||||
/// We are stagind
|
||||
SecondaryHunkRemovalPending,
|
||||
}
|
||||
|
||||
@@ -68,6 +81,10 @@ pub struct DiffHunk {
|
||||
/// The range in the buffer's diff base text to which this hunk corresponds.
|
||||
pub diff_base_byte_range: Range<usize>,
|
||||
pub secondary_status: DiffHunkSecondaryStatus,
|
||||
// Anchors representing the word diff locations in the active buffer
|
||||
pub buffer_word_diffs: Vec<Range<Anchor>>,
|
||||
// Offsets relative to the start of the deleted diff that represent word diff locations
|
||||
pub base_word_diffs: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
|
||||
@@ -75,6 +92,8 @@ pub struct DiffHunk {
|
||||
struct InternalDiffHunk {
|
||||
buffer_range: Range<Anchor>,
|
||||
diff_base_byte_range: Range<usize>,
|
||||
base_word_diffs: Vec<Range<usize>>,
|
||||
buffer_word_diffs: Vec<Range<Anchor>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -88,6 +107,7 @@ struct PendingHunk {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiffHunkSummary {
|
||||
buffer_range: Range<Anchor>,
|
||||
diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl sum_tree::Item for InternalDiffHunk {
|
||||
@@ -96,6 +116,7 @@ impl sum_tree::Item for InternalDiffHunk {
|
||||
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
diff_base_byte_range: self.diff_base_byte_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +127,7 @@ impl sum_tree::Item for PendingHunk {
|
||||
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
|
||||
DiffHunkSummary {
|
||||
buffer_range: self.buffer_range.clone(),
|
||||
diff_base_byte_range: self.diff_base_byte_range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +138,7 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
diff_base_byte_range: 0..0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +148,15 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
.start
|
||||
.min(&other.buffer_range.start, buffer);
|
||||
self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
|
||||
|
||||
self.diff_base_byte_range.start = self
|
||||
.diff_base_byte_range
|
||||
.start
|
||||
.min(other.diff_base_byte_range.start);
|
||||
self.diff_base_byte_range.end = self
|
||||
.diff_base_byte_range
|
||||
.end
|
||||
.max(other.diff_base_byte_range.end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +185,10 @@ impl std::fmt::Debug for BufferDiffInner {
|
||||
}
|
||||
|
||||
impl BufferDiffSnapshot {
|
||||
pub fn buffer_diff_id(&self) -> BufferId {
|
||||
self.inner.base_text.remote_id()
|
||||
}
|
||||
|
||||
fn empty(buffer: &text::BufferSnapshot, cx: &mut App) -> BufferDiffSnapshot {
|
||||
BufferDiffSnapshot {
|
||||
inner: BufferDiffInner {
|
||||
@@ -191,6 +227,13 @@ impl BufferDiffSnapshot {
|
||||
let base_text_pair;
|
||||
let base_text_exists;
|
||||
let base_text_snapshot;
|
||||
let diff_options = build_diff_options(
|
||||
None,
|
||||
language.as_ref().map(|l| l.name()),
|
||||
language.as_ref().map(|l| l.default_scope()),
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Some(text) = &base_text {
|
||||
let base_text_rope = Rope::from(text.as_str());
|
||||
base_text_pair = Some((text.clone(), base_text_rope.clone()));
|
||||
@@ -208,7 +251,7 @@ impl BufferDiffSnapshot {
|
||||
.background_executor()
|
||||
.spawn_labeled(*CALCULATE_DIFF_TASK, {
|
||||
let buffer = buffer.clone();
|
||||
async move { compute_hunks(base_text_pair, buffer) }
|
||||
async move { compute_hunks(base_text_pair, buffer, diff_options) }
|
||||
});
|
||||
|
||||
async move {
|
||||
@@ -231,6 +274,12 @@ impl BufferDiffSnapshot {
|
||||
base_text_snapshot: language::BufferSnapshot,
|
||||
cx: &App,
|
||||
) -> impl Future<Output = Self> + use<> {
|
||||
let diff_options = build_diff_options(
|
||||
base_text_snapshot.file(),
|
||||
base_text_snapshot.language().map(|l| l.name()),
|
||||
base_text_snapshot.language().map(|l| l.default_scope()),
|
||||
cx,
|
||||
);
|
||||
let base_text_exists = base_text.is_some();
|
||||
let base_text_pair = base_text.map(|text| {
|
||||
debug_assert_eq!(&*text, &base_text_snapshot.text());
|
||||
@@ -242,7 +291,7 @@ impl BufferDiffSnapshot {
|
||||
inner: BufferDiffInner {
|
||||
base_text: base_text_snapshot,
|
||||
pending_hunks: SumTree::new(&buffer),
|
||||
hunks: compute_hunks(base_text_pair, buffer),
|
||||
hunks: compute_hunks(base_text_pair, buffer, diff_options),
|
||||
base_text_exists,
|
||||
},
|
||||
secondary_diff: None,
|
||||
@@ -301,6 +350,54 @@ impl BufferDiffSnapshot {
|
||||
let (new_id, new_empty) = (right.remote_id(), right.is_empty());
|
||||
new_id == old_id || (new_empty && old_empty)
|
||||
}
|
||||
|
||||
pub fn row_to_base_text_row(&self, row: BufferRow, buffer: &text::BufferSnapshot) -> u32 {
|
||||
// TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start
|
||||
|
||||
// Find the last hunk that starts before this position.
|
||||
let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);
|
||||
let position = buffer.anchor_before(Point::new(row, 0));
|
||||
cursor.seek(&position, Bias::Left);
|
||||
if cursor
|
||||
.item()
|
||||
.is_none_or(|hunk| hunk.buffer_range.start.cmp(&position, buffer).is_gt())
|
||||
{
|
||||
cursor.prev();
|
||||
}
|
||||
|
||||
let unclipped_point = if let Some(hunk) = cursor.item()
|
||||
&& hunk.buffer_range.start.cmp(&position, buffer).is_le()
|
||||
{
|
||||
let mut unclipped_point = cursor
|
||||
.end()
|
||||
.diff_base_byte_range
|
||||
.end
|
||||
.to_point(self.base_text());
|
||||
if position.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {
|
||||
unclipped_point +=
|
||||
Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);
|
||||
}
|
||||
// Move the cursor so that at the next step we can clip with the start of the next hunk.
|
||||
cursor.next();
|
||||
unclipped_point
|
||||
} else {
|
||||
// Position is before the added region for the first hunk.
|
||||
debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {
|
||||
position.cmp(&first_hunk.buffer_range.start, buffer).is_le()
|
||||
}));
|
||||
Point::new(row, 0)
|
||||
};
|
||||
|
||||
let max_point = if let Some(next_hunk) = cursor.item() {
|
||||
next_hunk
|
||||
.diff_base_byte_range
|
||||
.start
|
||||
.to_point(self.base_text())
|
||||
} else {
|
||||
self.base_text().max_point()
|
||||
};
|
||||
unclipped_point.min(max_point).row
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferDiffInner {
|
||||
@@ -340,7 +437,7 @@ impl BufferDiffInner {
|
||||
};
|
||||
|
||||
let hunk = PendingHunk {
|
||||
buffer_range: Anchor::MIN..Anchor::MAX,
|
||||
buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
|
||||
buffer_version: buffer.version().clone(),
|
||||
new_status,
|
||||
@@ -537,11 +634,15 @@ impl BufferDiffInner {
|
||||
[
|
||||
(
|
||||
&hunk.buffer_range.start,
|
||||
(hunk.buffer_range.start, hunk.diff_base_byte_range.start),
|
||||
(
|
||||
hunk.buffer_range.start,
|
||||
hunk.diff_base_byte_range.start,
|
||||
hunk,
|
||||
),
|
||||
),
|
||||
(
|
||||
&hunk.buffer_range.end,
|
||||
(hunk.buffer_range.end, hunk.diff_base_byte_range.end),
|
||||
(hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
|
||||
),
|
||||
]
|
||||
});
|
||||
@@ -560,8 +661,11 @@ impl BufferDiffInner {
|
||||
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
|
||||
iter::from_fn(move || {
|
||||
loop {
|
||||
let (start_point, (start_anchor, start_base)) = summaries.next()?;
|
||||
let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;
|
||||
let (start_point, (start_anchor, start_base, hunk)) = summaries.next()?;
|
||||
let (mut end_point, (mut end_anchor, end_base, _)) = summaries.next()?;
|
||||
|
||||
let base_word_diffs = hunk.base_word_diffs.clone();
|
||||
let buffer_word_diffs = hunk.buffer_word_diffs.clone();
|
||||
|
||||
if !start_anchor.is_valid(buffer) {
|
||||
continue;
|
||||
@@ -631,6 +735,8 @@ impl BufferDiffInner {
|
||||
range: start_point..end_point,
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
buffer_range: start_anchor..end_anchor,
|
||||
base_word_diffs,
|
||||
buffer_word_diffs,
|
||||
secondary_status,
|
||||
});
|
||||
}
|
||||
@@ -662,6 +768,8 @@ impl BufferDiffInner {
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
// The secondary status is not used by callers of this method.
|
||||
secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
|
||||
base_word_diffs: hunk.base_word_diffs.clone(),
|
||||
buffer_word_diffs: hunk.buffer_word_diffs.clone(),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -730,9 +838,36 @@ impl BufferDiffInner {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_diff_options(
|
||||
file: Option<&Arc<dyn File>>,
|
||||
language: Option<LanguageName>,
|
||||
language_scope: Option<language::LanguageScope>,
|
||||
cx: &App,
|
||||
) -> Option<DiffOptions> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
if !cx.has_global::<settings::SettingsStore>() {
|
||||
return Some(DiffOptions {
|
||||
language_scope,
|
||||
max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
language_settings(language, file, cx)
|
||||
.word_diff_enabled
|
||||
.then_some(DiffOptions {
|
||||
language_scope,
|
||||
max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_hunks(
|
||||
diff_base: Option<(Arc<String>, Rope)>,
|
||||
buffer: text::BufferSnapshot,
|
||||
diff_options: Option<DiffOptions>,
|
||||
) -> SumTree<InternalDiffHunk> {
|
||||
let mut tree = SumTree::new(&buffer);
|
||||
|
||||
@@ -758,6 +893,8 @@ fn compute_hunks(
|
||||
InternalDiffHunk {
|
||||
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
|
||||
diff_base_byte_range: 0..diff_base.len() - 1,
|
||||
base_word_diffs: Vec::default(),
|
||||
buffer_word_diffs: Vec::default(),
|
||||
},
|
||||
&buffer,
|
||||
);
|
||||
@@ -773,6 +910,7 @@ fn compute_hunks(
|
||||
&diff_base_rope,
|
||||
&buffer,
|
||||
&mut divergence,
|
||||
diff_options.as_ref(),
|
||||
);
|
||||
tree.push(hunk, &buffer);
|
||||
}
|
||||
@@ -780,8 +918,10 @@ fn compute_hunks(
|
||||
} else {
|
||||
tree.push(
|
||||
InternalDiffHunk {
|
||||
buffer_range: Anchor::MIN..Anchor::MAX,
|
||||
buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
diff_base_byte_range: 0..0,
|
||||
base_word_diffs: Vec::default(),
|
||||
buffer_word_diffs: Vec::default(),
|
||||
},
|
||||
&buffer,
|
||||
);
|
||||
@@ -796,6 +936,7 @@ fn process_patch_hunk(
|
||||
diff_base: &Rope,
|
||||
buffer: &text::BufferSnapshot,
|
||||
buffer_row_divergence: &mut i64,
|
||||
diff_options: Option<&DiffOptions>,
|
||||
) -> InternalDiffHunk {
|
||||
let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
|
||||
assert!(line_item_count > 0);
|
||||
@@ -860,9 +1001,49 @@ fn process_patch_hunk(
|
||||
let start = Point::new(buffer_row_range.start, 0);
|
||||
let end = Point::new(buffer_row_range.end, 0);
|
||||
let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
|
||||
|
||||
let base_line_count = line_item_count.saturating_sub(buffer_row_range.len());
|
||||
|
||||
let (base_word_diffs, buffer_word_diffs) = if let Some(diff_options) = diff_options
|
||||
&& !buffer_row_range.is_empty()
|
||||
&& base_line_count == buffer_row_range.len()
|
||||
&& diff_options.max_word_diff_line_count >= base_line_count
|
||||
{
|
||||
let base_text: String = diff_base
|
||||
.chunks_in_range(diff_base_byte_range.clone())
|
||||
.collect();
|
||||
|
||||
let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
|
||||
|
||||
let (base_word_diffs, buffer_word_diffs_relative) = word_diff_ranges(
|
||||
&base_text,
|
||||
&buffer_text,
|
||||
DiffOptions {
|
||||
language_scope: diff_options.language_scope.clone(),
|
||||
..*diff_options
|
||||
},
|
||||
);
|
||||
|
||||
let buffer_start_offset = buffer_range.start.to_offset(buffer);
|
||||
let buffer_word_diffs = buffer_word_diffs_relative
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let start = buffer.anchor_after(buffer_start_offset + range.start);
|
||||
let end = buffer.anchor_after(buffer_start_offset + range.end);
|
||||
start..end
|
||||
})
|
||||
.collect();
|
||||
|
||||
(base_word_diffs, buffer_word_diffs)
|
||||
} else {
|
||||
(Vec::default(), Vec::default())
|
||||
};
|
||||
|
||||
InternalDiffHunk {
|
||||
buffer_range,
|
||||
diff_base_byte_range,
|
||||
base_word_diffs,
|
||||
buffer_word_diffs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -941,10 +1122,11 @@ impl BufferDiff {
|
||||
pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
|
||||
if self.secondary_diff.is_some() {
|
||||
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
|
||||
diff_base_byte_range: 0..0,
|
||||
});
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(Anchor::MIN..Anchor::MAX),
|
||||
changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1065,7 +1247,10 @@ impl BufferDiff {
|
||||
{
|
||||
(false, new_state.compare(state, buffer))
|
||||
}
|
||||
_ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),
|
||||
_ => (
|
||||
true,
|
||||
Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
|
||||
),
|
||||
};
|
||||
|
||||
if let Some(secondary_changed_range) = secondary_diff_change
|
||||
@@ -1126,7 +1311,11 @@ impl BufferDiff {
|
||||
buffer_snapshot: &'a text::BufferSnapshot,
|
||||
cx: &'a App,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk> {
|
||||
self.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer_snapshot, cx)
|
||||
self.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()),
|
||||
buffer_snapshot,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range<'a>(
|
||||
@@ -1222,7 +1411,9 @@ impl BufferDiff {
|
||||
|
||||
impl DiffHunk {
|
||||
pub fn is_created_file(&self) -> bool {
|
||||
self.diff_base_byte_range == (0..0) && self.buffer_range == (Anchor::MIN..Anchor::MAX)
|
||||
self.diff_base_byte_range == (0..0)
|
||||
&& self.buffer_range.start.is_min()
|
||||
&& self.buffer_range.end.is_max()
|
||||
}
|
||||
|
||||
pub fn status(&self) -> DiffHunkStatus {
|
||||
@@ -1389,7 +1580,10 @@ mod tests {
|
||||
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
&buffer,
|
||||
),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
|
||||
@@ -1398,7 +1592,10 @@ mod tests {
|
||||
buffer.edit([(0..0, "point five\n")]);
|
||||
diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
&buffer,
|
||||
),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[
|
||||
@@ -1409,7 +1606,10 @@ mod tests {
|
||||
|
||||
diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
|
||||
assert_hunks::<&str, _>(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
&buffer,
|
||||
),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[],
|
||||
@@ -1483,7 +1683,10 @@ mod tests {
|
||||
];
|
||||
|
||||
assert_hunks(
|
||||
uncommitted_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
|
||||
uncommitted_diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
&buffer,
|
||||
),
|
||||
&buffer,
|
||||
&head_text,
|
||||
&expected_hunks,
|
||||
@@ -1542,8 +1745,11 @@ mod tests {
|
||||
})
|
||||
.await;
|
||||
assert_eq!(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer)
|
||||
.count(),
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
&buffer
|
||||
)
|
||||
.count(),
|
||||
8
|
||||
);
|
||||
|
||||
@@ -2155,8 +2361,12 @@ mod tests {
|
||||
|
||||
let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
|
||||
let mut hunks = diff.update(cx, |diff, cx| {
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
|
||||
.collect::<Vec<_>>()
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(diff.buffer_id),
|
||||
&working_copy,
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
if hunks.is_empty() {
|
||||
return;
|
||||
@@ -2185,8 +2395,12 @@ mod tests {
|
||||
|
||||
diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
|
||||
let found_hunks = diff.update(cx, |diff, cx| {
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
|
||||
.collect::<Vec<_>>()
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(diff.buffer_id),
|
||||
&working_copy,
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunks.len(), found_hunks.len());
|
||||
|
||||
@@ -2204,4 +2418,62 @@ mod tests {
|
||||
hunks = found_hunks;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
|
||||
let base_text = "
|
||||
zero
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
"
|
||||
.unindent();
|
||||
let buffer_text = "
|
||||
zero
|
||||
ONE
|
||||
two
|
||||
NINE
|
||||
five
|
||||
seven
|
||||
"
|
||||
.unindent();
|
||||
|
||||
// zero
|
||||
// - one
|
||||
// + ONE
|
||||
// two
|
||||
// - three
|
||||
// - four
|
||||
// + NINE
|
||||
// five
|
||||
// - six
|
||||
// seven
|
||||
// + eight
|
||||
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
|
||||
let expected_results = [
|
||||
// don't format me
|
||||
(0, 0),
|
||||
(1, 2),
|
||||
(2, 2),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(5, 7),
|
||||
(6, 9),
|
||||
];
|
||||
for (buffer_row, expected) in expected_results {
|
||||
assert_eq!(
|
||||
diff.row_to_base_text_row(buffer_row, &buffer_snapshot),
|
||||
expected,
|
||||
"{buffer_row}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,6 +524,16 @@ impl Room {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn room_id(&self) -> impl Future<Output = Option<String>> + 'static {
|
||||
let room = self.live_kit.as_ref().map(|lk| lk.room.clone());
|
||||
async move {
|
||||
let room = room?;
|
||||
let sid = room.sid().await;
|
||||
let name = room.name();
|
||||
Some(format!("{} (sid: {sid})", name))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> RoomStatus {
|
||||
self.status
|
||||
}
|
||||
|
||||
@@ -59,3 +59,11 @@ pub fn agent_server_docs(cx: &App) -> String {
|
||||
server_url = server_url(cx)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the URL to Zed's edit prediction documentation.
|
||||
pub fn edit_prediction_docs(cx: &App) -> String {
|
||||
format!(
|
||||
"{server_url}/docs/ai/edit-prediction",
|
||||
server_url = server_url(cx)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -169,6 +169,17 @@ pub struct PredictEditsBody {
|
||||
/// Info about the git repository state, only present when can_collect_data is true.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub git_info: Option<PredictEditsGitInfo>,
|
||||
/// The trigger for this request.
|
||||
#[serde(default)]
|
||||
pub trigger: PredictEditsRequestTrigger,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum PredictEditsRequestTrigger {
|
||||
Diagnostics,
|
||||
Cli,
|
||||
#[default]
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -195,17 +206,41 @@ pub struct AcceptEditPredictionBody {
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RejectEditPredictionsBody {
|
||||
pub rejections: Vec<EditPredictionRejection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct RejectEditPredictionsBodyRef<'a> {
|
||||
pub rejections: &'a [EditPredictionRejection],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct EditPredictionRejection {
|
||||
pub request_id: String,
|
||||
#[serde(default)]
|
||||
pub reason: EditPredictionRejectReason,
|
||||
pub was_shown: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub enum EditPredictionRejectReason {
|
||||
/// New requests were triggered before this one completed
|
||||
Canceled,
|
||||
/// No edits returned
|
||||
Empty,
|
||||
/// Edits returned, but none remained after interpolation
|
||||
InterpolatedEmpty,
|
||||
/// The new prediction was preferred over the current one
|
||||
Replaced,
|
||||
/// The current prediction was preferred over the new one
|
||||
CurrentPreferred,
|
||||
/// The current prediction was discarded
|
||||
#[default]
|
||||
Discarded,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CompletionMode {
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
use strum::EnumIter;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::PredictEditsGitInfo;
|
||||
use crate::{PredictEditsGitInfo, PredictEditsRequestTrigger};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlanContextRetrievalRequest {
|
||||
@@ -53,6 +53,8 @@ pub struct PredictEditsRequest {
|
||||
pub prompt_max_bytes: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub prompt_format: PromptFormat,
|
||||
#[serde(default)]
|
||||
pub trigger: PredictEditsRequestTrigger,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -64,6 +64,16 @@ async fn check_is_contributor(
|
||||
}));
|
||||
}
|
||||
|
||||
if ZedZippyBot::is_zed_zippy_bot(¶ms) {
|
||||
return Ok(Json(CheckIsContributorResponse {
|
||||
signed_at: Some(
|
||||
ZedZippyBot::created_at()
|
||||
.and_utc()
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(Json(CheckIsContributorResponse {
|
||||
signed_at: app
|
||||
.db
|
||||
@@ -103,6 +113,36 @@ impl RenovateBot {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Zed Zippy bot GitHub user (`zed-zippy[bot]`).
|
||||
///
|
||||
/// https://api.github.com/users/zed-zippy[bot]
|
||||
struct ZedZippyBot;
|
||||
|
||||
impl ZedZippyBot {
|
||||
const LOGIN: &'static str = "zed-zippy[bot]";
|
||||
const USER_ID: i32 = 234243425;
|
||||
|
||||
/// Returns the `created_at` timestamp for the Zed Zippy bot user.
|
||||
fn created_at() -> &'static NaiveDateTime {
|
||||
static CREATED_AT: OnceLock<NaiveDateTime> = OnceLock::new();
|
||||
CREATED_AT.get_or_init(|| {
|
||||
chrono::DateTime::parse_from_rfc3339("2025-09-24T17:00:11Z")
|
||||
.expect("failed to parse 'created_at' for 'zed-zippy[bot]'")
|
||||
.naive_utc()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns whether the given contributor selector corresponds to the Zed Zippy bot user.
|
||||
fn is_zed_zippy_bot(contributor: &ContributorSelector) -> bool {
|
||||
match contributor {
|
||||
ContributorSelector::GitHubLogin { github_login } => github_login == Self::LOGIN,
|
||||
ContributorSelector::GitHubUserId { github_user_id } => {
|
||||
github_user_id == &Self::USER_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddContributorBody {
|
||||
github_user_id: i32,
|
||||
|
||||
@@ -1581,7 +1581,10 @@ async fn test_share_project(
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
buffer
|
||||
.snapshot()
|
||||
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
|
||||
.selections_in_range(
|
||||
text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
false,
|
||||
)
|
||||
.count()
|
||||
== 1
|
||||
});
|
||||
@@ -1622,7 +1625,10 @@ async fn test_share_project(
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
buffer
|
||||
.snapshot()
|
||||
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
|
||||
.selections_in_range(
|
||||
text::Anchor::min_max_range_for_buffer(buffer.remote_id()),
|
||||
false,
|
||||
)
|
||||
.count()
|
||||
== 0
|
||||
});
|
||||
|
||||
@@ -541,7 +541,7 @@ impl Item for ChannelView {
|
||||
})
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ use ui::{
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt, maybe};
|
||||
use workspace::{
|
||||
Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
|
||||
CopyRoomId, Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, NotifyResultExt},
|
||||
};
|
||||
@@ -128,6 +128,32 @@ pub fn init(cx: &mut App) {
|
||||
workspace.register_action(|_, _: &LeaveCall, window, cx| {
|
||||
CollabPanel::leave_call(window, cx);
|
||||
});
|
||||
workspace.register_action(|workspace, _: &CopyRoomId, window, cx| {
|
||||
use workspace::notifications::{NotificationId, NotifyTaskExt as _};
|
||||
|
||||
struct RoomIdCopiedToast;
|
||||
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
|
||||
let romo_id_fut = room.read(cx).room_id();
|
||||
cx.spawn(async move |workspace, cx| {
|
||||
let room_id = romo_id_fut.await.context("Failed to get livekit room")?;
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(room_id));
|
||||
workspace.show_toast(
|
||||
workspace::Toast::new(
|
||||
NotificationId::unique::<RoomIdCopiedToast>(),
|
||||
"Room ID copied to clipboard",
|
||||
)
|
||||
.autohide(),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
})
|
||||
.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
workspace.show_error(&"There’s no active call; join one first.", cx);
|
||||
}
|
||||
});
|
||||
workspace.register_action(|workspace, _: &ShareProject, window, cx| {
|
||||
let project = workspace.project().clone();
|
||||
println!("{project:?}");
|
||||
|
||||
@@ -8,6 +8,9 @@ license = "GPL-3.0-or-later"
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
test-support = ["db/test-support"]
|
||||
|
||||
[lib]
|
||||
path = "src/command_palette.rs"
|
||||
doctest = false
|
||||
|
||||
@@ -294,6 +294,10 @@ pub enum ChatMessage {
|
||||
content: ChatMessageContent,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
tool_calls: Vec<ToolCall>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
reasoning_opaque: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
reasoning_text: Option<String>,
|
||||
},
|
||||
User {
|
||||
content: ChatMessageContent,
|
||||
@@ -386,6 +390,8 @@ pub struct ResponseDelta {
|
||||
pub role: Option<Role>,
|
||||
#[serde(default)]
|
||||
pub tool_calls: Vec<ToolCallChunk>,
|
||||
pub reasoning_opaque: Option<String>,
|
||||
pub reasoning_text: Option<String>,
|
||||
}
|
||||
#[derive(Deserialize, Debug, Eq, PartialEq)]
|
||||
pub struct ToolCallChunk {
|
||||
@@ -786,13 +792,13 @@ async fn stream_completion(
|
||||
is_user_initiated: bool,
|
||||
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
|
||||
let is_vision_request = request.messages.iter().any(|message| match message {
|
||||
ChatMessage::User { content }
|
||||
| ChatMessage::Assistant { content, .. }
|
||||
| ChatMessage::Tool { content, .. } => {
|
||||
matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. })))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
ChatMessage::User { content }
|
||||
| ChatMessage::Assistant { content, .. }
|
||||
| ChatMessage::Tool { content, .. } => {
|
||||
matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. })))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let request_initiator = if is_user_initiated { "user" } else { "agent" };
|
||||
|
||||
|
||||
@@ -313,7 +313,8 @@ pub async fn stream_response(
|
||||
};
|
||||
|
||||
let is_streaming = request.stream;
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let json = serde_json::to_string(&request)?;
|
||||
let request = request_builder.body(AsyncBody::from(json))?;
|
||||
let mut response = client.send(request).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
|
||||
@@ -23,6 +23,9 @@ zstd.workspace = true
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
mach2.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ use log::info;
|
||||
use minidumper::{Client, LoopAction, MinidumpBinary};
|
||||
use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use smol::process::Command;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -70,11 +72,16 @@ pub async fn init(crash_init: InitCrashHandler) {
|
||||
// used by the crash handler isn't destroyed correctly which causes it to stay on the file
|
||||
// system and block further attempts to initialize crash handlers with that socket path.
|
||||
let socket_name = paths::temp_dir().join(format!("zed-crash-handler-{zed_pid}"));
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let _crash_handler = Command::new(exe)
|
||||
.arg("--crash-handler")
|
||||
.arg(&socket_name)
|
||||
.spawn()
|
||||
.expect("unable to spawn server process");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
spawn_crash_handler_windows(&exe, &socket_name);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let server_pid = _crash_handler.id();
|
||||
info!("spawning crash handler process");
|
||||
@@ -342,6 +349,57 @@ pub fn panic_hook(info: &PanicHookInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn spawn_crash_handler_windows(exe: &Path, socket_name: &Path) {
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use windows::Win32::System::Threading::{
|
||||
CreateProcessW, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, STARTF_FORCEOFFFEEDBACK,
|
||||
STARTUPINFOW,
|
||||
};
|
||||
use windows::core::PWSTR;
|
||||
|
||||
let mut command_line: Vec<u16> = OsStr::new(&format!(
|
||||
"\"{}\" --crash-handler \"{}\"",
|
||||
exe.display(),
|
||||
socket_name.display()
|
||||
))
|
||||
.encode_wide()
|
||||
.chain(once(0))
|
||||
.collect();
|
||||
|
||||
let mut startup_info = STARTUPINFOW::default();
|
||||
startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||
|
||||
// By default, Windows enables a "busy" cursor when a GUI application is launched.
|
||||
// This cursor is disabled once the application starts processing window messages.
|
||||
// Since the crash handler process doesn't process messages, this "busy" cursor stays enabled for a long time.
|
||||
// Disable the cursor feedback to prevent this from happening.
|
||||
startup_info.dwFlags = STARTF_FORCEOFFFEEDBACK;
|
||||
|
||||
let mut process_info = PROCESS_INFORMATION::default();
|
||||
|
||||
unsafe {
|
||||
CreateProcessW(
|
||||
None,
|
||||
Some(PWSTR(command_line.as_mut_ptr())),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
PROCESS_CREATION_FLAGS(0),
|
||||
None,
|
||||
None,
|
||||
&startup_info,
|
||||
&mut process_info,
|
||||
)
|
||||
.expect("unable to spawn server process");
|
||||
|
||||
windows::Win32::Foundation::CloseHandle(process_info.hProcess).ok();
|
||||
windows::Win32::Foundation::CloseHandle(process_info.hThread).ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crash_server(socket: &Path) {
|
||||
let Ok(mut server) = minidumper::Server::with_name(socket) else {
|
||||
log::info!("Couldn't create socket, there may already be a running crash server");
|
||||
|
||||
@@ -366,7 +366,7 @@ impl DebugAdapter for GoDebugAdapter {
|
||||
dap::DebugRequest::Attach(attach_config) => {
|
||||
json!({
|
||||
"request": "attach",
|
||||
"mode": "debug",
|
||||
"mode": "local",
|
||||
"processId": attach_config.process_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -998,7 +998,11 @@ impl Item for DapLogView {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(
|
||||
&self,
|
||||
handle: &Entity<Self>,
|
||||
_: &App,
|
||||
) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,7 +740,7 @@ impl DebugPanel {
|
||||
}
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-step-over", IconName::ArrowRight)
|
||||
IconButton::new("step-over", IconName::DebugStepOver)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(window.listener_for(
|
||||
running_state,
|
||||
@@ -762,32 +762,29 @@ impl DebugPanel {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-step-into",
|
||||
IconName::ArrowDownRight,
|
||||
)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(window.listener_for(
|
||||
running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step In",
|
||||
&StepInto,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
IconButton::new("step-into", IconName::DebugStepInto)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(window.listener_for(
|
||||
running_state,
|
||||
|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step In",
|
||||
&StepInto,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-out", IconName::ArrowUpRight)
|
||||
IconButton::new("step-out", IconName::DebugStepOut)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(window.listener_for(
|
||||
running_state,
|
||||
|
||||
@@ -575,7 +575,7 @@ impl BreakpointList {
|
||||
)
|
||||
.with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
|
||||
.with_width_from_item(self.max_width_index)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.flex_1()
|
||||
}
|
||||
|
||||
@@ -776,7 +776,7 @@ impl Render for BreakpointList {
|
||||
.child(self.render_list(cx))
|
||||
.custom_scrollbars(
|
||||
ui::Scrollbars::new(ScrollAxes::Both)
|
||||
.tracked_scroll_handle(self.scroll_handle.clone())
|
||||
.tracked_scroll_handle(&self.scroll_handle)
|
||||
.with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
|
||||
.tracked_entity(cx.entity_id()),
|
||||
window,
|
||||
|
||||
@@ -18,14 +18,14 @@ use gpui::{
|
||||
use language::{Anchor, Buffer, CharScopeContext, CodeLabel, TextBufferSnapshot, ToOffset};
|
||||
use menu::{Confirm, SelectNext, SelectPrevious};
|
||||
use project::{
|
||||
Completion, CompletionDisplayOptions, CompletionResponse,
|
||||
CompletionDisplayOptions, CompletionResponse,
|
||||
debugger::session::{CompletionsQuery, OutputToken, Session},
|
||||
lsp_store::CompletionDocumentation,
|
||||
search_history::{SearchHistory, SearchHistoryCursor},
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::fmt::Write;
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc, usize};
|
||||
use std::{ops::Range, rc::Rc, usize};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
@@ -553,24 +553,12 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_additional_edits_for_completion(
|
||||
&self,
|
||||
_buffer: Entity<Buffer>,
|
||||
_completions: Rc<RefCell<Box<[Completion]>>>,
|
||||
_completion_index: usize,
|
||||
_push_to_history: bool,
|
||||
_cx: &mut Context<Editor>,
|
||||
) -> gpui::Task<anyhow::Result<Option<language::Transaction>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: language::Anchor,
|
||||
text: &str,
|
||||
trigger_in_words: bool,
|
||||
menu_is_open: bool,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
let mut chars = text.chars();
|
||||
@@ -581,9 +569,6 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider {
|
||||
};
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
|
||||
return false;
|
||||
}
|
||||
|
||||
let classifier = snapshot
|
||||
.char_classifier_at(position)
|
||||
|
||||
@@ -229,7 +229,7 @@ impl MemoryView {
|
||||
rows
|
||||
},
|
||||
)
|
||||
.track_scroll(view_state.scroll_handle)
|
||||
.track_scroll(&view_state.scroll_handle)
|
||||
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
|
||||
.on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {
|
||||
let mut view_state = this.view_state();
|
||||
@@ -921,7 +921,7 @@ impl Render for MemoryView {
|
||||
}))
|
||||
.custom_scrollbars(
|
||||
ui::Scrollbars::new(ui::ScrollAxes::Both)
|
||||
.tracked_scroll_handle(self.view_state_handle.clone())
|
||||
.tracked_scroll_handle(&self.view_state_handle)
|
||||
.with_track_along(
|
||||
ui::ScrollAxes::Both,
|
||||
cx.theme().colors().panel_background,
|
||||
|
||||
@@ -253,7 +253,7 @@ impl ModuleList {
|
||||
range.map(|ix| this.render_entry(ix, cx)).collect()
|
||||
}),
|
||||
)
|
||||
.track_scroll(self.scroll_handle.clone())
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.size_full()
|
||||
}
|
||||
}
|
||||
@@ -279,6 +279,6 @@ impl Render for ModuleList {
|
||||
.size_full()
|
||||
.p_1()
|
||||
.child(self.render_list(window, cx))
|
||||
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,7 +913,7 @@ impl Render for StackFrameList {
|
||||
)
|
||||
})
|
||||
.child(self.render_list(window, cx))
|
||||
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
|
||||
.vertical_scrollbar_for(&self.list_state, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1557,7 +1557,7 @@ impl Render for VariableList {
|
||||
this.render_entries(range, window, cx)
|
||||
}),
|
||||
)
|
||||
.track_scroll(self.list_handle.clone())
|
||||
.track_scroll(&self.list_handle)
|
||||
.with_width_from_item(self.max_width_index)
|
||||
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
|
||||
.with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
|
||||
@@ -1574,10 +1574,10 @@ impl Render for VariableList {
|
||||
)
|
||||
.with_priority(1)
|
||||
}))
|
||||
// .vertical_scrollbar_for(self.list_handle.clone(), window, cx)
|
||||
// .vertical_scrollbar_for(&self.list_handle, window, cx)
|
||||
.custom_scrollbars(
|
||||
ui::Scrollbars::new(ScrollAxes::Both)
|
||||
.tracked_scroll_handle(self.list_handle.clone())
|
||||
.tracked_scroll_handle(&self.list_handle)
|
||||
.with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
|
||||
.tracked_entity(cx.entity_id()),
|
||||
window,
|
||||
|
||||
@@ -428,7 +428,7 @@ impl Item for StackTraceView {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ impl DiagnosticBlock {
|
||||
if range.context.overlaps(&diagnostic.range, &snapshot) {
|
||||
Self::jump_to(
|
||||
editor,
|
||||
Anchor::range_in_buffer(excerpt_id, buffer_id, diagnostic.range),
|
||||
Anchor::range_in_buffer(excerpt_id, diagnostic.range),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -308,7 +308,7 @@ impl ProjectDiagnosticsEditor {
|
||||
.selections
|
||||
.all_anchors(&snapshot)
|
||||
.iter()
|
||||
.filter_map(|anchor| anchor.start.buffer_id)
|
||||
.filter_map(|anchor| anchor.start.text_anchor.buffer_id)
|
||||
.collect::<HashSet<_>>()
|
||||
});
|
||||
for buffer_id in buffer_ids {
|
||||
@@ -890,7 +890,7 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ settings.workspace = true
|
||||
supermaven.workspace = true
|
||||
telemetry.workspace = true
|
||||
ui.workspace = true
|
||||
ui_input.workspace = true
|
||||
menu.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zeta.workspace = true
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
mod sweep_api_token_modal;
|
||||
|
||||
pub use sweep_api_token_modal::SweepApiKeyModal;
|
||||
|
||||
use anyhow::Result;
|
||||
use client::{Client, UserStore, zed_urls};
|
||||
use cloud_llm_client::UsageLimit;
|
||||
@@ -11,7 +15,7 @@ use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt, App, AsyncWindowContext, Corner, Entity, FocusHandle,
|
||||
Focusable, IntoElement, ParentElement, Render, Subscription, WeakEntity, actions, div,
|
||||
pulsating_between,
|
||||
ease_in_out, pulsating_between,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
@@ -34,13 +38,13 @@ use ui::{
|
||||
Clickable, ContextMenu, ContextMenuEntry, DocumentationEdge, DocumentationSide, IconButton,
|
||||
IconButtonShape, Indicator, PopoverMenu, PopoverMenuHandle, ProgressBar, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle,
|
||||
notifications::NotificationId,
|
||||
};
|
||||
use zed_actions::OpenBrowser;
|
||||
use zeta::RateCompletions;
|
||||
use zeta::{SweepFeatureFlag, Zeta2FeatureFlag};
|
||||
use zeta::{RateCompletions, SweepFeatureFlag, Zeta2FeatureFlag};
|
||||
|
||||
actions!(
|
||||
edit_prediction,
|
||||
@@ -312,6 +316,10 @@ impl Render for EditPredictionButton {
|
||||
)
|
||||
);
|
||||
|
||||
let sweep_missing_token = is_sweep
|
||||
&& !zeta::Zeta::try_global(cx)
|
||||
.map_or(false, |zeta| zeta.read(cx).has_sweep_api_token());
|
||||
|
||||
let zeta_icon = match (is_sweep, enabled) {
|
||||
(true, _) => IconName::SweepAi,
|
||||
(false, true) => IconName::ZedPredict,
|
||||
@@ -322,7 +330,7 @@ impl Render for EditPredictionButton {
|
||||
let tooltip_meta = if self.user_store.read(cx).current_user().is_some() {
|
||||
"Choose a Plan"
|
||||
} else {
|
||||
"Sign In"
|
||||
"Sign In To Use"
|
||||
};
|
||||
|
||||
return div().child(
|
||||
@@ -357,25 +365,39 @@ impl Render for EditPredictionButton {
|
||||
}
|
||||
|
||||
let show_editor_predictions = self.editor_show_predictions;
|
||||
let user = self.user_store.read(cx).current_user();
|
||||
|
||||
let indicator_color = if sweep_missing_token {
|
||||
Some(Color::Error)
|
||||
} else if enabled && (!show_editor_predictions || over_limit) {
|
||||
Some(if over_limit {
|
||||
Color::Error
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let icon_button = IconButton::new("zed-predict-pending-button", zeta_icon)
|
||||
.shape(IconButtonShape::Square)
|
||||
.when(
|
||||
enabled && (!show_editor_predictions || over_limit),
|
||||
|this| {
|
||||
this.indicator(Indicator::dot().when_else(
|
||||
over_limit,
|
||||
|dot| dot.color(Color::Error),
|
||||
|dot| dot.color(Color::Muted),
|
||||
))
|
||||
.when_some(indicator_color, |this, color| {
|
||||
this.indicator(Indicator::dot().color(color))
|
||||
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
|
||||
},
|
||||
)
|
||||
})
|
||||
.when(!self.popover_menu_handle.is_deployed(), |element| {
|
||||
let user = user.clone();
|
||||
element.tooltip(move |_window, cx| {
|
||||
if enabled {
|
||||
if show_editor_predictions {
|
||||
Tooltip::for_action("Edit Prediction", &ToggleMenu, cx)
|
||||
} else if user.is_none() {
|
||||
Tooltip::with_meta(
|
||||
"Edit Prediction",
|
||||
Some(&ToggleMenu),
|
||||
"Sign In To Use",
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::with_meta(
|
||||
"Edit Prediction",
|
||||
@@ -398,11 +420,25 @@ impl Render for EditPredictionButton {
|
||||
let this = cx.weak_entity();
|
||||
|
||||
let mut popover_menu = PopoverMenu::new("zeta")
|
||||
.menu(move |window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.build_zeta_context_menu(provider, window, cx)
|
||||
.when(user.is_some(), |popover_menu| {
|
||||
let this = this.clone();
|
||||
|
||||
popover_menu.menu(move |window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.build_zeta_context_menu(provider, window, cx)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
})
|
||||
.when(user.is_none(), |popover_menu| {
|
||||
let this = this.clone();
|
||||
|
||||
popover_menu.menu(move |window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.build_zeta_upsell_context_menu(window, cx)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.anchor(Corner::BottomRight)
|
||||
.with_handle(self.popover_menu_handle.clone());
|
||||
@@ -513,23 +549,23 @@ impl EditPredictionButton {
|
||||
|
||||
const ZED_AI_CALLOUT: &str =
|
||||
"Zed's edit prediction is powered by Zeta, an open-source, dataset mode.";
|
||||
const USE_SWEEP_API_TOKEN_CALLOUT: &str =
|
||||
"Set the SWEEP_API_TOKEN environment variable to use Sweep";
|
||||
|
||||
let other_providers: Vec<_> = available_providers
|
||||
let providers: Vec<_> = available_providers
|
||||
.into_iter()
|
||||
.filter(|p| *p != current_provider && *p != EditPredictionProvider::None)
|
||||
.filter(|p| *p != EditPredictionProvider::None)
|
||||
.collect();
|
||||
|
||||
if !other_providers.is_empty() {
|
||||
menu = menu.separator().header("Switch Providers");
|
||||
if !providers.is_empty() {
|
||||
menu = menu.separator().header("Providers");
|
||||
|
||||
for provider in other_providers {
|
||||
for provider in providers {
|
||||
let is_current = provider == current_provider;
|
||||
let fs = self.fs.clone();
|
||||
|
||||
menu = match provider {
|
||||
EditPredictionProvider::Zed => menu.item(
|
||||
ContextMenuEntry::new("Zed AI")
|
||||
.toggleable(IconPosition::Start, is_current)
|
||||
.documentation_aside(
|
||||
DocumentationSide::Left,
|
||||
DocumentationEdge::Bottom,
|
||||
@@ -539,46 +575,77 @@ impl EditPredictionButton {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
}),
|
||||
),
|
||||
EditPredictionProvider::Copilot => {
|
||||
menu.entry("GitHub Copilot", None, move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
})
|
||||
}
|
||||
EditPredictionProvider::Supermaven => {
|
||||
menu.entry("Supermaven", None, move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
})
|
||||
}
|
||||
EditPredictionProvider::Codestral => {
|
||||
menu.entry("Codestral", None, move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
})
|
||||
}
|
||||
EditPredictionProvider::Copilot => menu.item(
|
||||
ContextMenuEntry::new("GitHub Copilot")
|
||||
.toggleable(IconPosition::Start, is_current)
|
||||
.handler(move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
}),
|
||||
),
|
||||
EditPredictionProvider::Supermaven => menu.item(
|
||||
ContextMenuEntry::new("Supermaven")
|
||||
.toggleable(IconPosition::Start, is_current)
|
||||
.handler(move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
}),
|
||||
),
|
||||
EditPredictionProvider::Codestral => menu.item(
|
||||
ContextMenuEntry::new("Codestral")
|
||||
.toggleable(IconPosition::Start, is_current)
|
||||
.handler(move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
}),
|
||||
),
|
||||
EditPredictionProvider::Experimental(
|
||||
EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
|
||||
) => {
|
||||
let has_api_token = zeta::Zeta::try_global(cx)
|
||||
.map_or(false, |zeta| zeta.read(cx).has_sweep_api_token());
|
||||
|
||||
let entry = ContextMenuEntry::new("Sweep")
|
||||
.when(!has_api_token, |this| {
|
||||
this.disabled(true).documentation_aside(
|
||||
let should_open_modal = !has_api_token || is_current;
|
||||
|
||||
let entry = if has_api_token {
|
||||
ContextMenuEntry::new("Sweep")
|
||||
.toggleable(IconPosition::Start, is_current)
|
||||
} else {
|
||||
ContextMenuEntry::new("Sweep")
|
||||
.icon(IconName::XCircle)
|
||||
.icon_color(Color::Error)
|
||||
.documentation_aside(
|
||||
DocumentationSide::Left,
|
||||
DocumentationEdge::Bottom,
|
||||
|_| Label::new(USE_SWEEP_API_TOKEN_CALLOUT).into_any_element(),
|
||||
|_| {
|
||||
Label::new("Click to configure your Sweep API token")
|
||||
.into_any_element()
|
||||
},
|
||||
)
|
||||
})
|
||||
.handler(move |_, cx| {
|
||||
};
|
||||
|
||||
let entry = entry.handler(move |window, cx| {
|
||||
if should_open_modal {
|
||||
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
SweepApiKeyModal::new(window, cx)
|
||||
});
|
||||
});
|
||||
};
|
||||
} else {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
menu.item(entry)
|
||||
}
|
||||
EditPredictionProvider::Experimental(
|
||||
EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME,
|
||||
) => menu.entry("Zeta2", None, move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
}),
|
||||
) => menu.item(
|
||||
ContextMenuEntry::new("Zeta2")
|
||||
.toggleable(IconPosition::Start, is_current)
|
||||
.handler(move |_, cx| {
|
||||
set_completion_provider(fs.clone(), cx, provider);
|
||||
}),
|
||||
),
|
||||
EditPredictionProvider::None | EditPredictionProvider::Experimental(_) => {
|
||||
continue;
|
||||
}
|
||||
@@ -1045,6 +1112,55 @@ impl EditPredictionButton {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_zeta_upsell_context_menu(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<ContextMenu> {
|
||||
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||
menu = menu
|
||||
.custom_row(move |_window, cx| {
|
||||
let description = indoc! {
|
||||
"You get 2,000 accepted suggestions at every keystroke for free, \
|
||||
powered by Zeta, our open-source, open-data model"
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.max_w_64()
|
||||
.h(rems_from_px(148.))
|
||||
.child(render_zeta_tab_animation(cx))
|
||||
.child(Label::new("Edit Prediction"))
|
||||
.child(
|
||||
Label::new(description)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.separator()
|
||||
.entry("Sign In & Start Using", None, |window, cx| {
|
||||
let client = Client::global(cx);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
client
|
||||
.sign_in_with_optional_connect(true, &cx)
|
||||
.await
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.link(
|
||||
"Learn More",
|
||||
OpenBrowser {
|
||||
url: zed_urls::edit_prediction_docs(cx),
|
||||
}
|
||||
.boxed_clone(),
|
||||
);
|
||||
|
||||
menu
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_enabled(&mut self, editor: Entity<Editor>, cx: &mut Context<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
@@ -1248,6 +1364,73 @@ fn toggle_edit_prediction_mode(fs: Arc<dyn Fs>, mode: EditPredictionsMode, cx: &
|
||||
}
|
||||
}
|
||||
|
||||
fn render_zeta_tab_animation(cx: &App) -> impl IntoElement {
|
||||
let tab = |n: u64, inverted: bool| {
|
||||
let text_color = cx.theme().colors().text;
|
||||
|
||||
h_flex().child(
|
||||
h_flex()
|
||||
.text_size(TextSize::XSmall.rems(cx))
|
||||
.text_color(text_color)
|
||||
.child("tab")
|
||||
.with_animation(
|
||||
ElementId::Integer(n),
|
||||
Animation::new(Duration::from_secs(3)).repeat(),
|
||||
move |tab, delta| {
|
||||
let n_f32 = n as f32;
|
||||
|
||||
let offset = if inverted {
|
||||
0.2 * (4.0 - n_f32)
|
||||
} else {
|
||||
0.2 * n_f32
|
||||
};
|
||||
|
||||
let phase = (delta - offset + 1.0) % 1.0;
|
||||
let pulse = if phase < 0.6 {
|
||||
let t = phase / 0.6;
|
||||
1.0 - (0.5 - t).abs() * 2.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let eased = ease_in_out(pulse);
|
||||
let opacity = 0.1 + 0.5 * eased;
|
||||
|
||||
tab.text_color(text_color.opacity(opacity))
|
||||
},
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let tab_sequence = |inverted: bool| {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(tab(0, inverted))
|
||||
.child(tab(1, inverted))
|
||||
.child(tab(2, inverted))
|
||||
.child(tab(3, inverted))
|
||||
.child(tab(4, inverted))
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.my_1p5()
|
||||
.p_4()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.rounded_xs()
|
||||
.border_1()
|
||||
.border_dashed()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(gpui::pattern_slash(
|
||||
cx.theme().colors().border.opacity(0.5),
|
||||
1.,
|
||||
8.,
|
||||
))
|
||||
.child(tab_sequence(true))
|
||||
.child(Icon::new(IconName::ZedPredict))
|
||||
.child(tab_sequence(false))
|
||||
}
|
||||
|
||||
fn copilot_settings_url(enterprise_uri: Option<&str>) -> String {
|
||||
match enterprise_uri {
|
||||
Some(uri) => {
|
||||
|
||||
84
crates/edit_prediction_button/src/sweep_api_token_modal.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use gpui::{
|
||||
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render,
|
||||
};
|
||||
use ui::{Button, ButtonStyle, Clickable, Headline, HeadlineSize, prelude::*};
|
||||
use ui_input::InputField;
|
||||
use workspace::ModalView;
|
||||
use zeta::Zeta;
|
||||
|
||||
pub struct SweepApiKeyModal {
|
||||
api_key_input: Entity<InputField>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl SweepApiKeyModal {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_input = cx.new(|cx| InputField::new(window, cx, "Enter your Sweep API token"));
|
||||
|
||||
Self {
|
||||
api_key_input,
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let api_key = self.api_key_input.read(cx).text(cx);
|
||||
let api_key = (!api_key.trim().is_empty()).then_some(api_key);
|
||||
|
||||
if let Some(zeta) = Zeta::try_global(cx) {
|
||||
zeta.update(cx, |zeta, cx| {
|
||||
zeta.sweep_ai
|
||||
.set_api_token(api_key, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for SweepApiKeyModal {}
|
||||
|
||||
impl ModalView for SweepApiKeyModal {}
|
||||
|
||||
impl Focusable for SweepApiKeyModal {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SweepApiKeyModal {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.key_context("SweepApiKeyModal")
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.elevation_2(cx)
|
||||
.w(px(400.))
|
||||
.p_4()
|
||||
.gap_3()
|
||||
.child(Headline::new("Sweep API Token").size(HeadlineSize::Small))
|
||||
.child(self.api_key_input.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.gap_2()
|
||||
.child(Button::new("cancel", "Cancel").on_click(cx.listener(
|
||||
|_, _, _window, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
},
|
||||
)))
|
||||
.child(
|
||||
Button::new("save", "Save")
|
||||
.style(ButtonStyle::Filled)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.confirm(&menu::Confirm, window, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ dap.workspace = true
|
||||
db.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
emojis.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -45,7 +45,7 @@ fn to_tab_point_benchmark(c: &mut Criterion) {
|
||||
&snapshot,
|
||||
|bench, snapshot| {
|
||||
bench.iter(|| {
|
||||
snapshot.to_tab_point(fold_point);
|
||||
snapshot.fold_point_to_tab_point(fold_point);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -79,7 +79,7 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
|
||||
);
|
||||
|
||||
let (_, snapshot) = TabMap::new(fold_snapshot, NonZeroU32::new(4).unwrap());
|
||||
let tab_point = snapshot.to_tab_point(fold_point);
|
||||
let tab_point = snapshot.fold_point_to_tab_point(fold_point);
|
||||
|
||||
(length, snapshot, tab_point)
|
||||
};
|
||||
@@ -94,7 +94,7 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
|
||||
&snapshot,
|
||||
|bench, snapshot| {
|
||||
bench.iter(|| {
|
||||
snapshot.to_fold_point(tab_point, Bias::Left);
|
||||
snapshot.tab_point_to_fold_point(tab_point, Bias::Left);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::ops::Range;
|
||||
use crate::Editor;
|
||||
use collections::HashMap;
|
||||
use gpui::{Context, HighlightStyle};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings;
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use ui::{ActiveTheme, utils::ensure_minimum_contrast};
|
||||
@@ -26,20 +27,23 @@ impl Editor {
|
||||
let accents_count = cx.theme().accents().0.len();
|
||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let all_excerpts = self.buffer().read(cx).excerpt_ids();
|
||||
let anchor_in_multi_buffer = |current_excerpt: ExcerptId, text_anchor: text::Anchor| {
|
||||
let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
|
||||
text_anchors: [text::Anchor; 4]|
|
||||
-> Option<[Option<_>; 4]> {
|
||||
multi_buffer_snapshot
|
||||
.anchor_in_excerpt(current_excerpt, text_anchor)
|
||||
.anchors_in_excerpt(current_excerpt, text_anchors)
|
||||
.or_else(|| {
|
||||
all_excerpts
|
||||
.iter()
|
||||
.filter(|&&excerpt_id| excerpt_id != current_excerpt)
|
||||
.find_map(|&excerpt_id| {
|
||||
multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)
|
||||
multi_buffer_snapshot.anchors_in_excerpt(excerpt_id, text_anchors)
|
||||
})
|
||||
})
|
||||
})?
|
||||
.collect_array()
|
||||
};
|
||||
|
||||
let bracket_matches_by_accent = self.visible_excerpts(cx).into_iter().fold(
|
||||
let bracket_matches_by_accent = self.visible_excerpts(false, cx).into_iter().fold(
|
||||
HashMap::default(),
|
||||
|mut acc, (excerpt_id, (buffer, buffer_version, buffer_range))| {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
@@ -77,13 +81,24 @@ impl Editor {
|
||||
let buffer_close_range = buffer_snapshot
|
||||
.anchor_before(pair.close_range.start)
|
||||
..buffer_snapshot.anchor_after(pair.close_range.end);
|
||||
let [
|
||||
buffer_open_range_start,
|
||||
buffer_open_range_end,
|
||||
buffer_close_range_start,
|
||||
buffer_close_range_end,
|
||||
] = anchors_in_multi_buffer(
|
||||
excerpt_id,
|
||||
[
|
||||
buffer_open_range.start,
|
||||
buffer_open_range.end,
|
||||
buffer_close_range.start,
|
||||
buffer_close_range.end,
|
||||
],
|
||||
)?;
|
||||
let multi_buffer_open_range =
|
||||
anchor_in_multi_buffer(excerpt_id, buffer_open_range.start)
|
||||
.zip(anchor_in_multi_buffer(excerpt_id, buffer_open_range.end));
|
||||
buffer_open_range_start.zip(buffer_open_range_end);
|
||||
let multi_buffer_close_range =
|
||||
anchor_in_multi_buffer(excerpt_id, buffer_close_range.start).zip(
|
||||
anchor_in_multi_buffer(excerpt_id, buffer_close_range.end),
|
||||
);
|
||||
buffer_close_range_start.zip(buffer_close_range_end);
|
||||
|
||||
let mut ranges = Vec::with_capacity(2);
|
||||
if let Some((open_start, open_end)) = multi_buffer_open_range {
|
||||
@@ -149,7 +164,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
DisplayPoint, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
|
||||
DisplayPoint, EditorMode, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
|
||||
display_map::{DisplayRow, ToDisplayPoint},
|
||||
editor_tests::init_test,
|
||||
test::{
|
||||
@@ -161,7 +176,7 @@ mod tests {
|
||||
use gpui::{AppContext as _, UpdateGlobal as _};
|
||||
use indoc::indoc;
|
||||
use itertools::Itertools;
|
||||
use language::Capability;
|
||||
use language::{Capability, markdown_lang};
|
||||
use languages::rust_lang;
|
||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -261,6 +276,65 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_file_less_file_colorization(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.colorize_brackets = Some(true);
|
||||
});
|
||||
let editor = cx.add_window(|window, cx| {
|
||||
let multi_buffer = MultiBuffer::build_simple("fn main() {}", cx);
|
||||
multi_buffer.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.update(cx, |buffer, cx| {
|
||||
buffer.set_language(Some(rust_lang()), cx);
|
||||
});
|
||||
});
|
||||
Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
"fn main«1()1» «1{}1»
|
||||
1 hsla(207.80, 16.20%, 69.19%, 1.00)
|
||||
",
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor_bracket_colors_markup(&editor.snapshot(window, cx))
|
||||
})
|
||||
.unwrap(),
|
||||
"File-less buffer should still have its brackets colorized"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.colorize_brackets = Some(true);
|
||||
});
|
||||
let mut cx = EditorLspTestContext::new(
|
||||
Arc::into_inner(markdown_lang()).unwrap(),
|
||||
lsp::ServerCapabilities::default(),
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {r#"ˇ[LLM-powered features](./ai/overview.md), [bring and configure your own API keys](./ai/llm-providers.md#use-your-own-keys)"#});
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
r#"«1[LLM-powered features]1»«1(./ai/overview.md)1», «1[bring and configure your own API keys]1»«1(./ai/llm-providers.md#use-your-own-keys)1»
|
||||
1 hsla(207.80, 16.20%, 69.19%, 1.00)
|
||||
"#,
|
||||
&bracket_colors_markup(&mut cx),
|
||||
"All markdown brackets should be colored based on their depth"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |language_settings| {
|
||||
|
||||
@@ -239,6 +239,89 @@ async fn test_fuzzy_over_sort_positions(cx: &mut TestAppContext) {
|
||||
assert_eq!(matches[2].string, "fetch_code_lens");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_semver_label_sort_by_latest_version(cx: &mut TestAppContext) {
|
||||
let mut versions = [
|
||||
"10.4.112",
|
||||
"10.4.22",
|
||||
"10.4.2",
|
||||
"10.4.20",
|
||||
"10.4.21",
|
||||
"10.4.12",
|
||||
// Pre-release versions
|
||||
"10.4.22-alpha",
|
||||
"10.4.22-beta.1",
|
||||
"10.4.22-rc.1",
|
||||
// Build metadata versions
|
||||
"10.4.21+build.123",
|
||||
"10.4.20+20210327",
|
||||
];
|
||||
versions.sort_by(|a, b| {
|
||||
match (
|
||||
semver::Version::parse(a).ok(),
|
||||
semver::Version::parse(b).ok(),
|
||||
) {
|
||||
(Some(a_ver), Some(b_ver)) => b_ver.cmp(&a_ver),
|
||||
_ => std::cmp::Ordering::Equal,
|
||||
}
|
||||
});
|
||||
let completions: Vec<_> = versions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, version)| {
|
||||
// This sort text would come from the LSP
|
||||
let sort_text = format!("{:08}", i);
|
||||
CompletionBuilder::new(version, None, &sort_text, None)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Case 1: User types just the major and minor version
|
||||
let matches =
|
||||
filter_and_sort_matches("10.4.", &completions, SnippetSortOrder::default(), cx).await;
|
||||
// Versions are ordered by recency (latest first)
|
||||
let expected_versions = [
|
||||
"10.4.112",
|
||||
"10.4.22",
|
||||
"10.4.22-rc.1",
|
||||
"10.4.22-beta.1",
|
||||
"10.4.22-alpha",
|
||||
"10.4.21+build.123",
|
||||
"10.4.21",
|
||||
"10.4.20+20210327",
|
||||
"10.4.20",
|
||||
"10.4.12",
|
||||
"10.4.2",
|
||||
];
|
||||
for (match_item, expected) in matches.iter().zip(expected_versions.iter()) {
|
||||
assert_eq!(match_item.string.as_ref() as &str, *expected);
|
||||
}
|
||||
|
||||
// Case 2: User types the major, minor, and patch version
|
||||
let matches =
|
||||
filter_and_sort_matches("10.4.2", &completions, SnippetSortOrder::default(), cx).await;
|
||||
let expected_versions = [
|
||||
// Exact match comes first
|
||||
"10.4.2",
|
||||
// Ordered by recency with exact major, minor, and patch versions
|
||||
"10.4.22",
|
||||
"10.4.22-rc.1",
|
||||
"10.4.22-beta.1",
|
||||
"10.4.22-alpha",
|
||||
"10.4.21+build.123",
|
||||
"10.4.21",
|
||||
"10.4.20+20210327",
|
||||
"10.4.20",
|
||||
// Versions with non-exact patch versions are ordered by fuzzy score
|
||||
// Higher fuzzy score than 112 patch version since "2" appears before "1"
|
||||
// in "12", making it rank higher than "112"
|
||||
"10.4.12",
|
||||
"10.4.112",
|
||||
];
|
||||
for (match_item, expected) in matches.iter().zip(expected_versions.iter()) {
|
||||
assert_eq!(match_item.string.as_ref() as &str, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_for_each_prefix<F>(
|
||||
target: &str,
|
||||
completions: &Vec<Completion>,
|
||||
@@ -259,30 +342,55 @@ struct CompletionBuilder;
|
||||
|
||||
impl CompletionBuilder {
|
||||
fn constant(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||
Self::new(label, filter_text, sort_text, CompletionItemKind::CONSTANT)
|
||||
Self::new(
|
||||
label,
|
||||
filter_text,
|
||||
sort_text,
|
||||
Some(CompletionItemKind::CONSTANT),
|
||||
)
|
||||
}
|
||||
|
||||
fn function(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||
Self::new(label, filter_text, sort_text, CompletionItemKind::FUNCTION)
|
||||
Self::new(
|
||||
label,
|
||||
filter_text,
|
||||
sort_text,
|
||||
Some(CompletionItemKind::FUNCTION),
|
||||
)
|
||||
}
|
||||
|
||||
fn method(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||
Self::new(label, filter_text, sort_text, CompletionItemKind::METHOD)
|
||||
Self::new(
|
||||
label,
|
||||
filter_text,
|
||||
sort_text,
|
||||
Some(CompletionItemKind::METHOD),
|
||||
)
|
||||
}
|
||||
|
||||
fn variable(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||
Self::new(label, filter_text, sort_text, CompletionItemKind::VARIABLE)
|
||||
Self::new(
|
||||
label,
|
||||
filter_text,
|
||||
sort_text,
|
||||
Some(CompletionItemKind::VARIABLE),
|
||||
)
|
||||
}
|
||||
|
||||
fn snippet(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||
Self::new(label, filter_text, sort_text, CompletionItemKind::SNIPPET)
|
||||
Self::new(
|
||||
label,
|
||||
filter_text,
|
||||
sort_text,
|
||||
Some(CompletionItemKind::SNIPPET),
|
||||
)
|
||||
}
|
||||
|
||||
fn new(
|
||||
label: &str,
|
||||
filter_text: Option<&str>,
|
||||
sort_text: &str,
|
||||
kind: CompletionItemKind,
|
||||
kind: Option<CompletionItemKind>,
|
||||
) -> Completion {
|
||||
Completion {
|
||||
replace_range: Anchor::MIN..Anchor::MAX,
|
||||
@@ -294,7 +402,7 @@ impl CompletionBuilder {
|
||||
server_id: LanguageServerId(0),
|
||||
lsp_completion: Box::new(CompletionItem {
|
||||
label: label.to_string(),
|
||||
kind: Some(kind),
|
||||
kind: kind,
|
||||
sort_text: Some(sort_text.to_string()),
|
||||
filter_text: filter_text.map(|text| text.to_string()),
|
||||
..Default::default()
|
||||
|
||||