Compare commits
472 Commits
gpui-butto
...
vim-syntax
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e6f44d87c | ||
|
|
707a4c7f20 | ||
|
|
854076f96d | ||
|
|
cf931247d0 | ||
|
|
b74477d12e | ||
|
|
3077abf9cf | ||
|
|
07dab4e94a | ||
|
|
59686f1f44 | ||
|
|
a60bea8a3d | ||
|
|
b820aa1fcd | ||
|
|
55d91bce53 | ||
|
|
b798392050 | ||
|
|
657c8b1084 | ||
|
|
2bb8aa2f73 | ||
|
|
beeb42da29 | ||
|
|
6d66ff1d95 | ||
|
|
e0b818af62 | ||
|
|
58a400b1ee | ||
|
|
8ab7d44d51 | ||
|
|
56d4c0af9f | ||
|
|
feeda7fa37 | ||
|
|
4a5c55a8f2 | ||
|
|
7c1ae9bcc3 | ||
|
|
6f97da3435 | ||
|
|
63c1033448 | ||
|
|
b16911e756 | ||
|
|
b14401f817 | ||
|
|
17cf865d1e | ||
|
|
b7ec437b13 | ||
|
|
f1aab1120d | ||
|
|
3f90bc81bd | ||
|
|
9d5fb3c3f3 | ||
|
|
864767ad35 | ||
|
|
ec69b68e72 | ||
|
|
9dd18e5ee1 | ||
|
|
2ebe16a52f | ||
|
|
1ed4647203 | ||
|
|
ebed567adb | ||
|
|
a6544c70c5 | ||
|
|
b363e1a482 | ||
|
|
65e3e84cbc | ||
|
|
1e1d4430c2 | ||
|
|
c874f1fa9d | ||
|
|
9a9e96ed5a | ||
|
|
8c46e290df | ||
|
|
aacbb9c2f4 | ||
|
|
f90333f92e | ||
|
|
b24f614ca3 | ||
|
|
cefa0cbed8 | ||
|
|
3fb1023667 | ||
|
|
9c715b470e | ||
|
|
ae219e9e99 | ||
|
|
6d99c12796 | ||
|
|
8fb7fa941a | ||
|
|
22d75b798e | ||
|
|
06a199da4d | ||
|
|
ab6125ddde | ||
|
|
d3bc561f26 | ||
|
|
f13f2dfb70 | ||
|
|
24e4446cd3 | ||
|
|
cc536655a1 | ||
|
|
2a9e73c65d | ||
|
|
4f1728e5ee | ||
|
|
40c91d5df0 | ||
|
|
fe1b36671d | ||
|
|
bb9e2b0403 | ||
|
|
4f8d7f0a6b | ||
|
|
caf3d30bf6 | ||
|
|
df0cf22347 | ||
|
|
a305eda8d1 | ||
|
|
ba7b1db054 | ||
|
|
019c8ded77 | ||
|
|
1704dbea7e | ||
|
|
eefa6c4882 | ||
|
|
1f17df7fb0 | ||
|
|
6d687a2c2c | ||
|
|
32214abb64 | ||
|
|
a78563b80b | ||
|
|
f881cacd8a | ||
|
|
a539a38f13 | ||
|
|
ca6fd101c1 | ||
|
|
f8097c7c98 | ||
|
|
c1427ea802 | ||
|
|
1e83022f03 | ||
|
|
0ee900e8fb | ||
|
|
f9f4be1fc4 | ||
|
|
a00b07371a | ||
|
|
f725b5e248 | ||
|
|
07436b4284 | ||
|
|
8bec4cbecb | ||
|
|
047e7eacec | ||
|
|
1d5d3de85c | ||
|
|
c4dbaa91f0 | ||
|
|
97c01c6720 | ||
|
|
310ea43048 | ||
|
|
6bb4b5fa64 | ||
|
|
e0fa3032ec | ||
|
|
9cf6be2057 | ||
|
|
5462e199fb | ||
|
|
3a60420b41 | ||
|
|
89c184a26f | ||
|
|
d7f0241d7b | ||
|
|
1445af559b | ||
|
|
804de3316e | ||
|
|
a387bf5f54 | ||
|
|
c7047d5f0a | ||
|
|
406d975f39 | ||
|
|
cbed580db0 | ||
|
|
8aef64bbfa | ||
|
|
9086784038 | ||
|
|
2abc5893c1 | ||
|
|
a23ee61a4b | ||
|
|
38e45e828b | ||
|
|
181bf78b7d | ||
|
|
c42d060509 | ||
|
|
6ea9abdc1b | ||
|
|
070eac28e3 | ||
|
|
05692e298a | ||
|
|
ccb049bd97 | ||
|
|
fe57eedb44 | ||
|
|
c57e6bc784 | ||
|
|
83135e98e6 | ||
|
|
703ee29658 | ||
|
|
f792827a01 | ||
|
|
45f9edcbb9 | ||
|
|
e3354543c0 | ||
|
|
cb187b0b4d | ||
|
|
d989b2260b | ||
|
|
ae076fa415 | ||
|
|
b4af61edfe | ||
|
|
ea8a3be91b | ||
|
|
5173a1a968 | ||
|
|
87f097a0ab | ||
|
|
f9407db7d6 | ||
|
|
384b11392a | ||
|
|
f20596c33b | ||
|
|
eb863f8fd6 | ||
|
|
97579662e6 | ||
|
|
53849cf983 | ||
|
|
1e25249055 | ||
|
|
469824c350 | ||
|
|
a1c645e57e | ||
|
|
0791596cda | ||
|
|
9cc1851be7 | ||
|
|
50bd8770bd | ||
|
|
00bdebc89d | ||
|
|
d5134062ac | ||
|
|
0e9f6986cf | ||
|
|
1035c6aab5 | ||
|
|
75e69a5ae9 | ||
|
|
05afe95539 | ||
|
|
a5a116439e | ||
|
|
361ceee72b | ||
|
|
68724ea99e | ||
|
|
e12106e025 | ||
|
|
77aa667bf3 | ||
|
|
8b47b40dc0 | ||
|
|
01990c8375 | ||
|
|
4e7dc37f01 | ||
|
|
00fd045844 | ||
|
|
7443fde4e9 | ||
|
|
d5ab42aeb8 | ||
|
|
07403f0b08 | ||
|
|
00bc154c46 | ||
|
|
f627ac92ee | ||
|
|
218e8d09c5 | ||
|
|
2c4b75ab30 | ||
|
|
aab76208b5 | ||
|
|
f3f0766242 | ||
|
|
148e9adec2 | ||
|
|
e314963f5b | ||
|
|
957e4adc3f | ||
|
|
fee6f13887 | ||
|
|
4f78165ee8 | ||
|
|
94a5fe265d | ||
|
|
c0a5ace8b8 | ||
|
|
15d59fcda9 | ||
|
|
6545c5ebe0 | ||
|
|
506beafe10 | ||
|
|
31d908fc74 | ||
|
|
0731097ee5 | ||
|
|
233b73b385 | ||
|
|
0145e2c101 | ||
|
|
09fc64e0c5 | ||
|
|
fc803ce9d4 | ||
|
|
697c2ba71f | ||
|
|
f54c057001 | ||
|
|
32848e9c8a | ||
|
|
86b75759d1 | ||
|
|
94c006236e | ||
|
|
5b6b911946 | ||
|
|
b9a5d437db | ||
|
|
21bd91a773 | ||
|
|
5db14d315b | ||
|
|
b63cea1f17 | ||
|
|
b7c5540075 | ||
|
|
b01f7c848b | ||
|
|
3476705bbb | ||
|
|
ba6b5a59f9 | ||
|
|
28d6362964 | ||
|
|
b4a03989b1 | ||
|
|
19b6892c8d | ||
|
|
b5c2b25a76 | ||
|
|
8faeb34367 | ||
|
|
61a40e293d | ||
|
|
239ffa49e1 | ||
|
|
a4978ee5ff | ||
|
|
a8ca7e9c04 | ||
|
|
ee6ce78fed | ||
|
|
3ff62ef289 | ||
|
|
f8f36d0c17 | ||
|
|
05763b2fe3 | ||
|
|
7ec61ceec9 | ||
|
|
119beb210a | ||
|
|
0d3fad7764 | ||
|
|
450a10facf | ||
|
|
c208532693 | ||
|
|
4a577fff4a | ||
|
|
03071a9152 | ||
|
|
092be31b2b | ||
|
|
62545b985f | ||
|
|
5e72c2a870 | ||
|
|
2a8242ac90 | ||
|
|
d211f88d23 | ||
|
|
fe0bcd14d2 | ||
|
|
e84463648a | ||
|
|
24809c4219 | ||
|
|
f8365c5375 | ||
|
|
2c8049270a | ||
|
|
6840a4e5bc | ||
|
|
5b320d6714 | ||
|
|
534bb0620d | ||
|
|
5bafb2b160 | ||
|
|
ee415de45f | ||
|
|
4acb4730a5 | ||
|
|
4567360fd9 | ||
|
|
4c396bcc91 | ||
|
|
8a24f9f280 | ||
|
|
f4b361f04d | ||
|
|
649072d140 | ||
|
|
6253b95f82 | ||
|
|
bffde7c6b4 | ||
|
|
7e87916642 | ||
|
|
29f0762b6c | ||
|
|
10af3c7e58 | ||
|
|
c0aa8f63fd | ||
|
|
0c27aaecb3 | ||
|
|
8e5d50b85b | ||
|
|
625bf09830 | ||
|
|
5a0a8ce30a | ||
|
|
d9a5dc2dfe | ||
|
|
d4926626d8 | ||
|
|
2a973109d4 | ||
|
|
2e62f16149 | ||
|
|
f2601ce52c | ||
|
|
a58c48f629 | ||
|
|
ddbcab2b5b | ||
|
|
7497deff7a | ||
|
|
e78b726ed8 | ||
|
|
998542b048 | ||
|
|
6363fdab88 | ||
|
|
e6f51966a1 | ||
|
|
9da9ef860b | ||
|
|
134463f043 | ||
|
|
a47fd1d723 | ||
|
|
ef0e1cb2ba | ||
|
|
c73af0a52f | ||
|
|
e42cf21703 | ||
|
|
2c114f7df6 | ||
|
|
49f3ec7f35 | ||
|
|
748840519c | ||
|
|
8b59776320 | ||
|
|
206be2b348 | ||
|
|
51b25b5c22 | ||
|
|
2f274b2a89 | ||
|
|
88fb623efa | ||
|
|
df98d94a24 | ||
|
|
c7da6283cc | ||
|
|
7ceb792a58 | ||
|
|
83af7b30eb | ||
|
|
3d0147aafc | ||
|
|
1b3f20bdf4 | ||
|
|
4c28d2c2e2 | ||
|
|
a204510cfc | ||
|
|
34be7830a3 | ||
|
|
d312a13f8a | ||
|
|
20a0956fb2 | ||
|
|
6f918ed99b | ||
|
|
7fb9569c15 | ||
|
|
fc8702a8f8 | ||
|
|
ab59982bf7 | ||
|
|
685933b5c8 | ||
|
|
172e0df2d8 | ||
|
|
7341ab3980 | ||
|
|
ca72efe701 | ||
|
|
cb112a4012 | ||
|
|
f3c2e71ca7 | ||
|
|
208f525a11 | ||
|
|
697c838455 | ||
|
|
1683e2f144 | ||
|
|
2f1d9284b7 | ||
|
|
68a46c3627 | ||
|
|
3a1053bf0c | ||
|
|
14d9a4189f | ||
|
|
9c01119b3c | ||
|
|
9dba8e5b0d | ||
|
|
03ac3fb91a | ||
|
|
0201d1e0b4 | ||
|
|
f48b6b583e | ||
|
|
d8fc23a5e9 | ||
|
|
6206150e27 | ||
|
|
e88cad29e5 | ||
|
|
9b7d849879 | ||
|
|
c4677c21a9 | ||
|
|
26318b5b6a | ||
|
|
4266f0da85 | ||
|
|
c50093d68c | ||
|
|
1cad1cbbfc | ||
|
|
fbc922ad46 | ||
|
|
f435304209 | ||
|
|
508ccde363 | ||
|
|
9f7987c532 | ||
|
|
cb52acbf3d | ||
|
|
f8b997b25c | ||
|
|
73a5856fb8 | ||
|
|
e3b6fa2c30 | ||
|
|
ceb5164114 | ||
|
|
24a108d876 | ||
|
|
5c0b161563 | ||
|
|
ad4645c59b | ||
|
|
37047a6fde | ||
|
|
fc78408ee4 | ||
|
|
37f49ce304 | ||
|
|
cc428330a9 | ||
|
|
1475ace6f1 | ||
|
|
dd4e8b9e66 | ||
|
|
b188e5d3aa | ||
|
|
e3d3daec92 | ||
|
|
ced8e4d88e | ||
|
|
fa1abd8201 | ||
|
|
ee4e43f1b6 | ||
|
|
d61e1e24a7 | ||
|
|
3c03d53e3e | ||
|
|
8ab664a52c | ||
|
|
2044426634 | ||
|
|
02fa6f6fc2 | ||
|
|
80a00cd241 | ||
|
|
06f725d51b | ||
|
|
baf6d82cd4 | ||
|
|
28ec7fbb81 | ||
|
|
0415e853d5 | ||
|
|
1c9b818342 | ||
|
|
0d7f4842f3 | ||
|
|
ab017129d8 | ||
|
|
71fb17c507 | ||
|
|
97e437c632 | ||
|
|
66667d1eef | ||
|
|
dce22a965e | ||
|
|
5f452dbca2 | ||
|
|
b2a92097ee | ||
|
|
eb35d25a7d | ||
|
|
8742d4ab90 | ||
|
|
b829f72c17 | ||
|
|
ffa8310d04 | ||
|
|
3fda539c46 | ||
|
|
b444b326cb | ||
|
|
f196288e2d | ||
|
|
e30cc131b4 | ||
|
|
09c8a84935 | ||
|
|
6e5996a815 | ||
|
|
c8f56e38b1 | ||
|
|
cfd3b0ff7b | ||
|
|
afe23cf85a | ||
|
|
f915c24279 | ||
|
|
bdd9e015ab | ||
|
|
6bbab4b55a | ||
|
|
7450b788f3 | ||
|
|
0c03519393 | ||
|
|
636eff2e9a | ||
|
|
6c8f4002d9 | ||
|
|
91bc5aefa4 | ||
|
|
2f3564b85f | ||
|
|
d61a544400 | ||
|
|
8061bacee3 | ||
|
|
77dadfedfe | ||
|
|
0023b37bfc | ||
|
|
4ece4a635f | ||
|
|
77c2aecf93 | ||
|
|
3ee56c196c | ||
|
|
3b1f6eaab8 | ||
|
|
44fbe27d31 | ||
|
|
a824119367 | ||
|
|
16366cf9f2 | ||
|
|
1e51a7ac44 | ||
|
|
d547a86e31 | ||
|
|
4bb04cef9d | ||
|
|
89700c3682 | ||
|
|
7609402200 | ||
|
|
a0ec9cf383 | ||
|
|
eb318c1626 | ||
|
|
5e5a124ae1 | ||
|
|
65e751ca33 | ||
|
|
17cf04558b | ||
|
|
36ae564b61 | ||
|
|
110195cdae | ||
|
|
b7d5e6480a | ||
|
|
0fa9f05313 | ||
|
|
051f49ce9a | ||
|
|
e5670ba081 | ||
|
|
e4262f97af | ||
|
|
944a0df436 | ||
|
|
a1be61949d | ||
|
|
a092e2dc03 | ||
|
|
b1c7fa1dac | ||
|
|
df66237428 | ||
|
|
ca513f52bf | ||
|
|
e9c9a8a269 | ||
|
|
315321bf8c | ||
|
|
c747a57b7e | ||
|
|
f73c8e5841 | ||
|
|
f7a0834f54 | ||
|
|
83d513aef4 | ||
|
|
b440e1a467 | ||
|
|
5c4f9e57d8 | ||
|
|
05f8001ee9 | ||
|
|
b93c67438c | ||
|
|
fdec966226 | ||
|
|
9041f734fd | ||
|
|
844c7ad22e | ||
|
|
926f377c6c | ||
|
|
26a8cac0d8 | ||
|
|
c7aae6bd62 | ||
|
|
851121ffd4 | ||
|
|
e48daa92c0 | ||
|
|
d9f12879e2 | ||
|
|
42dd511fc2 | ||
|
|
571c5e7407 | ||
|
|
c76295251b | ||
|
|
b057b4697f | ||
|
|
57424e4743 | ||
|
|
2b6dab9197 | ||
|
|
70b0c4d63d | ||
|
|
875d1ef263 | ||
|
|
e1a2e8a3aa | ||
|
|
a829281841 | ||
|
|
592568ff87 | ||
|
|
83afe56a61 | ||
|
|
e468f9d2da | ||
|
|
1ce2652a89 | ||
|
|
784d51c40f | ||
|
|
0079c99c2c | ||
|
|
230eb12f72 | ||
|
|
dd3956eaf1 | ||
|
|
122d6c9e4d | ||
|
|
19e89a8b2d | ||
|
|
919ffe7655 | ||
|
|
841a4e35ea | ||
|
|
175ce05fd1 | ||
|
|
e518941445 | ||
|
|
10b8174c1b | ||
|
|
21fd1c8b80 | ||
|
|
c80bd698f8 | ||
|
|
03419da6f1 | ||
|
|
f56960ab5b | ||
|
|
4d827924f0 | ||
|
|
25b4591539 | ||
|
|
afbf527aa2 | ||
|
|
eb9ea20313 | ||
|
|
3d2ab4e58c | ||
|
|
ff0060aa36 | ||
|
|
d791c6cdb1 | ||
|
|
c7725e31d9 |
@@ -13,12 +13,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|||||||
linker = "clang"
|
linker = "clang"
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
[target.aarch64-apple-darwin]
|
|
||||||
rustflags = ["-C", "link-args=-all_load"]
|
|
||||||
|
|
||||||
[target.x86_64-apple-darwin]
|
|
||||||
rustflags = ["-C", "link-args=-all_load"]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")']
|
[target.'cfg(target_os = "windows")']
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"--cfg",
|
"--cfg",
|
||||||
|
|||||||
@@ -30,3 +30,7 @@ ffdda588b41f7d9d270ffe76cab116f828ad545e
|
|||||||
# 2024-07-05 Improved formatting of default keymaps (single line per bind)
|
# 2024-07-05 Improved formatting of default keymaps (single line per bind)
|
||||||
# https://github.com/zed-industries/zed/pull/13887
|
# https://github.com/zed-industries/zed/pull/13887
|
||||||
813cc3f5e537372fc86720b5e71b6e1c815440ab
|
813cc3f5e537372fc86720b5e71b6e1c815440ab
|
||||||
|
|
||||||
|
# 2024-07-24 docs: Format docs
|
||||||
|
# https://github.com/zed-industries/zed/pull/15352
|
||||||
|
3a44a59f8ec114ac1ba22f7da1652717ef7e4e5c
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name: Bug Report (Agent Panel)
|
name: Bug Report (AI Related)
|
||||||
description: Zed Agent Panel Bugs
|
description: Zed Agent Panel Bugs
|
||||||
type: "Bug"
|
type: "Bug"
|
||||||
labels: ["agent", "ai"]
|
labels: ["ai"]
|
||||||
title: "Agent Panel: <a short description of the Agent Panel bug>"
|
title: "AI: <a short description of the AI Related bug>"
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
@@ -14,7 +14,6 @@ body:
|
|||||||
|
|
||||||
### Description
|
### Description
|
||||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||||
<!-- Please include the LLM provider and model name you are using -->
|
|
||||||
Steps to trigger the problem:
|
Steps to trigger the problem:
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
@@ -22,6 +21,13 @@ body:
|
|||||||
|
|
||||||
Actual Behavior:
|
Actual Behavior:
|
||||||
Expected Behavior:
|
Expected Behavior:
|
||||||
|
|
||||||
|
### Model Provider Details
|
||||||
|
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc)
|
||||||
|
- Model Name:
|
||||||
|
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
|
||||||
|
- MCP Servers in-use:
|
||||||
|
- Other Details:
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
55
.github/workflows/ci.yml
vendored
@@ -482,7 +482,9 @@ jobs:
|
|||||||
- macos_tests
|
- macos_tests
|
||||||
- windows_clippy
|
- windows_clippy
|
||||||
- windows_tests
|
- windows_tests
|
||||||
if: always()
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
always()
|
||||||
steps:
|
steps:
|
||||||
- name: Check all tests passed
|
- name: Check all tests passed
|
||||||
run: |
|
run: |
|
||||||
@@ -524,7 +526,6 @@ jobs:
|
|||||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
@@ -611,7 +612,6 @@ jobs:
|
|||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
env:
|
env:
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
@@ -669,7 +669,6 @@ jobs:
|
|||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
env:
|
env:
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
@@ -717,49 +716,13 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
nix-build:
|
nix-build:
|
||||||
timeout-minutes: 60
|
name: Build with Nix
|
||||||
name: Nix Build
|
uses: ./.github/workflows/nix.yml
|
||||||
continue-on-error: true
|
|
||||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||||
strategy:
|
with:
|
||||||
fail-fast: false
|
flake-output: debug
|
||||||
matrix:
|
# excludes the final package to only cache dependencies
|
||||||
system:
|
cachix-filter: "-zed-editor-[0-9.]*-nightly"
|
||||||
- os: x86 Linux
|
|
||||||
runner: buildjet-16vcpu-ubuntu-2204
|
|
||||||
install_nix: true
|
|
||||||
- os: arm Mac
|
|
||||||
runner: [macOS, ARM64, test]
|
|
||||||
install_nix: false
|
|
||||||
runs-on: ${{ matrix.system.runner }}
|
|
||||||
env:
|
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
||||||
with:
|
|
||||||
clean: false
|
|
||||||
- name: Set path
|
|
||||||
if: ${{ ! matrix.system.install_nix }}
|
|
||||||
run: |
|
|
||||||
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
|
||||||
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
|
|
||||||
if: ${{ matrix.system.install_nix }}
|
|
||||||
with:
|
|
||||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
|
||||||
with:
|
|
||||||
name: zed-industries
|
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
skipPush: true
|
|
||||||
- run: nix build .#debug
|
|
||||||
- name: Limit /nix/store to 50GB
|
|
||||||
run: "[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d"
|
|
||||||
|
|
||||||
auto-release-preview:
|
auto-release-preview:
|
||||||
name: Auto release preview
|
name: Auto release preview
|
||||||
|
|||||||
66
.github/workflows/nix.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: "Nix build"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
flake-output:
|
||||||
|
type: string
|
||||||
|
default: "default"
|
||||||
|
cachix-filter:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nix-build:
|
||||||
|
timeout-minutes: 60
|
||||||
|
name: (${{ matrix.system.os }}) Nix Build
|
||||||
|
continue-on-error: true # TODO: remove when we want this to start blocking CI
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
system:
|
||||||
|
- os: x86 Linux
|
||||||
|
runner: buildjet-16vcpu-ubuntu-2204
|
||||||
|
install_nix: true
|
||||||
|
- os: arm Mac
|
||||||
|
runner: [macOS, ARM64, test]
|
||||||
|
install_nix: false
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
runs-on: ${{ matrix.system.runner }}
|
||||||
|
env:
|
||||||
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
|
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
with:
|
||||||
|
clean: false
|
||||||
|
|
||||||
|
# on our macs we manually install nix. for some reason the cachix action is running
|
||||||
|
# under a non-login /bin/bash shell which doesn't source the proper script to add the
|
||||||
|
# nix profile to PATH, so we manually add them here
|
||||||
|
- name: Set path
|
||||||
|
if: ${{ ! matrix.system.install_nix }}
|
||||||
|
run: |
|
||||||
|
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
||||||
|
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
||||||
|
if: ${{ matrix.system.install_nix }}
|
||||||
|
with:
|
||||||
|
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
|
with:
|
||||||
|
name: zed
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
pushFilter: "${{ inputs.cachix-filter }}"
|
||||||
|
cachixArgs: '-v'
|
||||||
|
|
||||||
|
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
|
||||||
|
|
||||||
|
- name: Limit /nix/store to 50GB on macs
|
||||||
|
if: ${{ ! matrix.system.install_nix }}
|
||||||
|
run: |
|
||||||
|
[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d || :
|
||||||
8
.github/workflows/release_nightly.yml
vendored
@@ -68,7 +68,6 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
@@ -104,7 +103,6 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
@@ -144,7 +142,6 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
@@ -170,6 +167,11 @@ jobs:
|
|||||||
- name: Upload Zed Nightly
|
- name: Upload Zed Nightly
|
||||||
run: script/upload-nightly linux-targz
|
run: script/upload-nightly linux-targz
|
||||||
|
|
||||||
|
bundle-nix:
|
||||||
|
name: Build and cache Nix package
|
||||||
|
needs: tests
|
||||||
|
uses: ./.github/workflows/nix.yml
|
||||||
|
|
||||||
update-nightly-tag:
|
update-nightly-tag:
|
||||||
name: Update nightly tag
|
name: Update nightly tag
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
|||||||
20
.mailmap
@@ -19,6 +19,8 @@ amtoaer <amtoaer@gmail.com>
|
|||||||
amtoaer <amtoaer@gmail.com> <amtoaer@outlook.com>
|
amtoaer <amtoaer@gmail.com> <amtoaer@outlook.com>
|
||||||
Andrei Zvonimir Crnković <andrei@0x7f.dev>
|
Andrei Zvonimir Crnković <andrei@0x7f.dev>
|
||||||
Andrei Zvonimir Crnković <andrei@0x7f.dev> <andreicek@0x7f.dev>
|
Andrei Zvonimir Crnković <andrei@0x7f.dev> <andreicek@0x7f.dev>
|
||||||
|
Angelk90 <angelo.k90@hotmail.it>
|
||||||
|
Angelk90 <angelo.k90@hotmail.it> <20476002+Angelk90@users.noreply.github.com>
|
||||||
Antonio Scandurra <me@as-cii.com>
|
Antonio Scandurra <me@as-cii.com>
|
||||||
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||||
Ben Kunkle <ben@zed.dev>
|
Ben Kunkle <ben@zed.dev>
|
||||||
@@ -38,6 +40,8 @@ Dairon Medina <dairon.medina@gmail.com>
|
|||||||
Danilo Leal <danilo@zed.dev>
|
Danilo Leal <danilo@zed.dev>
|
||||||
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
|
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
|
||||||
Edwin Aronsson <75266237+4teapo@users.noreply.github.com>
|
Edwin Aronsson <75266237+4teapo@users.noreply.github.com>
|
||||||
|
Elvis Pranskevichus <elvis@geldata.com>
|
||||||
|
Elvis Pranskevichus <elvis@geldata.com> <elvis@magic.io>
|
||||||
Evren Sen <nervenes@icloud.com>
|
Evren Sen <nervenes@icloud.com>
|
||||||
Evren Sen <nervenes@icloud.com> <146845123+evrensen467@users.noreply.github.com>
|
Evren Sen <nervenes@icloud.com> <146845123+evrensen467@users.noreply.github.com>
|
||||||
Evren Sen <nervenes@icloud.com> <146845123+evrsen@users.noreply.github.com>
|
Evren Sen <nervenes@icloud.com> <146845123+evrsen@users.noreply.github.com>
|
||||||
@@ -69,6 +73,8 @@ Lilith Iris <itslirissama@gmail.com> <83819417+Irilith@users.noreply.github.com>
|
|||||||
LoganDark <contact@logandark.mozmail.com>
|
LoganDark <contact@logandark.mozmail.com>
|
||||||
LoganDark <contact@logandark.mozmail.com> <git@logandark.mozmail.com>
|
LoganDark <contact@logandark.mozmail.com> <git@logandark.mozmail.com>
|
||||||
LoganDark <contact@logandark.mozmail.com> <github@logandark.mozmail.com>
|
LoganDark <contact@logandark.mozmail.com> <github@logandark.mozmail.com>
|
||||||
|
Marko Kungla <marko.kungla@gmail.com>
|
||||||
|
Marko Kungla <marko.kungla@gmail.com> <marko@mkungla.dev>
|
||||||
Marshall Bowers <git@maxdeviant.com>
|
Marshall Bowers <git@maxdeviant.com>
|
||||||
Marshall Bowers <git@maxdeviant.com> <elliott.codes@gmail.com>
|
Marshall Bowers <git@maxdeviant.com> <elliott.codes@gmail.com>
|
||||||
Marshall Bowers <git@maxdeviant.com> <marshall@zed.dev>
|
Marshall Bowers <git@maxdeviant.com> <marshall@zed.dev>
|
||||||
@@ -84,6 +90,7 @@ Michael Sloan <michael@zed.dev> <mgsloan@google.com>
|
|||||||
Mikayla Maki <mikayla@zed.dev>
|
Mikayla Maki <mikayla@zed.dev>
|
||||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
||||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
||||||
|
Morgan Krey <morgan@zed.dev>
|
||||||
Muhammad Talal Anwar <mail@talal.io>
|
Muhammad Talal Anwar <mail@talal.io>
|
||||||
Muhammad Talal Anwar <mail@talal.io> <talalanwar@outlook.com>
|
Muhammad Talal Anwar <mail@talal.io> <talalanwar@outlook.com>
|
||||||
Nate Butler <iamnbutler@gmail.com>
|
Nate Butler <iamnbutler@gmail.com>
|
||||||
@@ -116,11 +123,18 @@ Shish <webmaster@shishnet.org>
|
|||||||
Shish <webmaster@shishnet.org> <shish@shishnet.org>
|
Shish <webmaster@shishnet.org> <shish@shishnet.org>
|
||||||
Smit Barmase <0xtimsb@gmail.com>
|
Smit Barmase <0xtimsb@gmail.com>
|
||||||
Smit Barmase <0xtimsb@gmail.com> <smit@zed.dev>
|
Smit Barmase <0xtimsb@gmail.com> <smit@zed.dev>
|
||||||
|
Thomas <github.thomaub@gmail.com>
|
||||||
|
Thomas <github.thomaub@gmail.com> <thomas.aubry94@gmail.com>
|
||||||
|
Thomas <github.thomaub@gmail.com> <thomas.aubry@paylead.fr>
|
||||||
|
Thomas Heartman <thomasheartman+github@gmail.com>
|
||||||
|
Thomas Heartman <thomasheartman+github@gmail.com> <thomas@getunleash.io>
|
||||||
|
Thomas Mickley-Doyle <tmickleydoyle@gmail.com>
|
||||||
|
Thomas Mickley-Doyle <tmickleydoyle@gmail.com> <thomas@zed.dev>
|
||||||
Thorben Kröger <dev@thorben.net>
|
Thorben Kröger <dev@thorben.net>
|
||||||
Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com>
|
Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com>
|
||||||
Thorsten Ball <thorsten@zed.dev>
|
Thorsten Ball <mrnugget@gmail.com>
|
||||||
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
|
Thorsten Ball <mrnugget@gmail.com> <me@thorstenball.com>
|
||||||
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
|
Thorsten Ball <mrnugget@gmail.com> <thorsten@zed.dev>
|
||||||
Tristan Hume <tris.hume@gmail.com>
|
Tristan Hume <tris.hume@gmail.com>
|
||||||
Tristan Hume <tris.hume@gmail.com> <tristan@anthropic.com>
|
Tristan Hume <tris.hume@gmail.com> <tristan@anthropic.com>
|
||||||
Uladzislau Kaminski <i@uladkaminski.com>
|
Uladzislau Kaminski <i@uladkaminski.com>
|
||||||
|
|||||||
2
.rules
@@ -115,7 +115,7 @@ Other entities can then register a callback to handle these events by doing `cx.
|
|||||||
GPUI has had some changes to its APIs. Always write code using the new APIs:
|
GPUI has had some changes to its APIs. Always write code using the new APIs:
|
||||||
|
|
||||||
* `spawn` methods now take async closures (`AsyncFn`), and so should be called like `cx.spawn(async move |cx| ...)`.
|
* `spawn` methods now take async closures (`AsyncFn`), and so should be called like `cx.spawn(async move |cx| ...)`.
|
||||||
* Use `Entity<T>`. This replaces `Model<T>` and `View<T>` which longer exists and should NEVER be used.
|
* Use `Entity<T>`. This replaces `Model<T>` and `View<T>` which no longer exist and should NEVER be used.
|
||||||
* Use `App` references. This replaces `AppContext` which no longer exists and should NEVER be used.
|
* Use `App` references. This replaces `AppContext` which no longer exists and should NEVER be used.
|
||||||
* Use `Context<T>` references. This replaces `ModelContext<T>` which no longer exists and should NEVER be used.
|
* Use `Context<T>` references. This replaces `ModelContext<T>` which no longer exists and should NEVER be used.
|
||||||
* `Window` is now passed around explicitly. The new interface adds a `Window` reference parameter to some methods, and adds some new "*_in" methods for plumbing `Window`. The old types `WindowContext` and `ViewContext<T>` should NEVER be used.
|
* `Window` is now passed around explicitly. The new interface adds a `Window` reference parameter to some methods, and adds some new "*_in" methods for plumbing `Window`. The old types `WindowContext` and `ViewContext<T>` should NEVER be used.
|
||||||
|
|||||||
@@ -2,16 +2,11 @@
|
|||||||
{
|
{
|
||||||
"label": "Debug Zed (CodeLLDB)",
|
"label": "Debug Zed (CodeLLDB)",
|
||||||
"adapter": "CodeLLDB",
|
"adapter": "CodeLLDB",
|
||||||
"program": "target/debug/zed",
|
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
|
||||||
"request": "launch"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Debug Zed (GDB)",
|
"label": "Debug Zed (GDB)",
|
||||||
"adapter": "GDB",
|
"adapter": "GDB",
|
||||||
"program": "target/debug/zed",
|
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
|
||||||
"request": "launch",
|
|
||||||
"initialize_args": {
|
|
||||||
"stopAtBeginningOfMainSubprogram": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
1740
Cargo.lock
generated
35
Cargo.toml
@@ -3,11 +3,11 @@ resolver = "2"
|
|||||||
members = [
|
members = [
|
||||||
"crates/activity_indicator",
|
"crates/activity_indicator",
|
||||||
"crates/agent",
|
"crates/agent",
|
||||||
|
"crates/agent_settings",
|
||||||
"crates/anthropic",
|
"crates/anthropic",
|
||||||
"crates/askpass",
|
"crates/askpass",
|
||||||
"crates/assets",
|
"crates/assets",
|
||||||
"crates/assistant_context_editor",
|
"crates/assistant_context_editor",
|
||||||
"crates/assistant_settings",
|
|
||||||
"crates/assistant_slash_command",
|
"crates/assistant_slash_command",
|
||||||
"crates/assistant_slash_commands",
|
"crates/assistant_slash_commands",
|
||||||
"crates/assistant_tool",
|
"crates/assistant_tool",
|
||||||
@@ -73,12 +73,14 @@ members = [
|
|||||||
"crates/indexed_docs",
|
"crates/indexed_docs",
|
||||||
"crates/inline_completion",
|
"crates/inline_completion",
|
||||||
"crates/inline_completion_button",
|
"crates/inline_completion_button",
|
||||||
|
"crates/inspector_ui",
|
||||||
"crates/install_cli",
|
"crates/install_cli",
|
||||||
|
"crates/jj",
|
||||||
|
"crates/jj_ui",
|
||||||
"crates/journal",
|
"crates/journal",
|
||||||
"crates/language",
|
"crates/language",
|
||||||
"crates/language_extension",
|
"crates/language_extension",
|
||||||
"crates/language_model",
|
"crates/language_model",
|
||||||
"crates/language_model_selector",
|
|
||||||
"crates/language_models",
|
"crates/language_models",
|
||||||
"crates/language_selector",
|
"crates/language_selector",
|
||||||
"crates/language_tools",
|
"crates/language_tools",
|
||||||
@@ -209,12 +211,12 @@ edition = "2024"
|
|||||||
|
|
||||||
activity_indicator = { path = "crates/activity_indicator" }
|
activity_indicator = { path = "crates/activity_indicator" }
|
||||||
agent = { path = "crates/agent" }
|
agent = { path = "crates/agent" }
|
||||||
|
agent_settings = { path = "crates/agent_settings" }
|
||||||
ai = { path = "crates/ai" }
|
ai = { path = "crates/ai" }
|
||||||
anthropic = { path = "crates/anthropic" }
|
anthropic = { path = "crates/anthropic" }
|
||||||
askpass = { path = "crates/askpass" }
|
askpass = { path = "crates/askpass" }
|
||||||
assets = { path = "crates/assets" }
|
assets = { path = "crates/assets" }
|
||||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||||
assistant_settings = { path = "crates/assistant_settings" }
|
|
||||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||||
assistant_tool = { path = "crates/assistant_tool" }
|
assistant_tool = { path = "crates/assistant_tool" }
|
||||||
@@ -278,12 +280,14 @@ image_viewer = { path = "crates/image_viewer" }
|
|||||||
indexed_docs = { path = "crates/indexed_docs" }
|
indexed_docs = { path = "crates/indexed_docs" }
|
||||||
inline_completion = { path = "crates/inline_completion" }
|
inline_completion = { path = "crates/inline_completion" }
|
||||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||||
|
inspector_ui = { path = "crates/inspector_ui" }
|
||||||
install_cli = { path = "crates/install_cli" }
|
install_cli = { path = "crates/install_cli" }
|
||||||
|
jj = { path = "crates/jj" }
|
||||||
|
jj_ui = { path = "crates/jj_ui" }
|
||||||
journal = { path = "crates/journal" }
|
journal = { path = "crates/journal" }
|
||||||
language = { path = "crates/language" }
|
language = { path = "crates/language" }
|
||||||
language_extension = { path = "crates/language_extension" }
|
language_extension = { path = "crates/language_extension" }
|
||||||
language_model = { path = "crates/language_model" }
|
language_model = { path = "crates/language_model" }
|
||||||
language_model_selector = { path = "crates/language_model_selector" }
|
|
||||||
language_models = { path = "crates/language_models" }
|
language_models = { path = "crates/language_models" }
|
||||||
language_selector = { path = "crates/language_selector" }
|
language_selector = { path = "crates/language_selector" }
|
||||||
language_tools = { path = "crates/language_tools" }
|
language_tools = { path = "crates/language_tools" }
|
||||||
@@ -426,8 +430,9 @@ convert_case = "0.8.0"
|
|||||||
core-foundation = "0.10.0"
|
core-foundation = "0.10.0"
|
||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
core-video = { version = "0.4.3", features = ["metal"] }
|
core-video = { version = "0.4.3", features = ["metal"] }
|
||||||
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
ctor = "0.4.0"
|
ctor = "0.4.0"
|
||||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
|
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "68516de327fa1be15214133a0a2e52a12982ce75" }
|
||||||
dashmap = "6.0"
|
dashmap = "6.0"
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
dirs = "4.0"
|
dirs = "4.0"
|
||||||
@@ -444,6 +449,7 @@ futures-batch = "0.6.1"
|
|||||||
futures-lite = "1.13"
|
futures-lite = "1.13"
|
||||||
git2 = { version = "0.20.1", default-features = false }
|
git2 = { version = "0.20.1", default-features = false }
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
|
hashbrown = "0.15.3"
|
||||||
handlebars = "4.3"
|
handlebars = "4.3"
|
||||||
heck = "0.5"
|
heck = "0.5"
|
||||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||||
@@ -458,6 +464,8 @@ indexmap = { version = "2.7.0", features = ["serde"] }
|
|||||||
indoc = "2"
|
indoc = "2"
|
||||||
inventory = "0.3.19"
|
inventory = "0.3.19"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
jj-lib = { git = "https://github.com/jj-vcs/jj", rev = "e18eb8e05efaa153fad5ef46576af145bba1807f" }
|
||||||
|
json_dotpath = "1.1"
|
||||||
jsonschema = "0.30.0"
|
jsonschema = "0.30.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
@@ -470,6 +478,7 @@ lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189
|
|||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
metal = "0.29"
|
metal = "0.29"
|
||||||
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
||||||
|
moka = { version = "0.12.10", features = ["sync"] }
|
||||||
naga = { version = "25.0", features = ["wgsl-in"] }
|
naga = { version = "25.0", features = ["wgsl-in"] }
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
@@ -544,13 +553,13 @@ streaming-iterator = "0.1"
|
|||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum = { version = "0.27.0", features = ["derive"] }
|
strum = { version = "0.27.0", features = ["derive"] }
|
||||||
subtle = "2.5.0"
|
subtle = "2.5.0"
|
||||||
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
|
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
sysinfo = "0.31.0"
|
sysinfo = "0.31.0"
|
||||||
take-until = "0.2.0"
|
take-until = "0.2.0"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.20.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tiktoken-rs = "0.6.0"
|
tiktoken-rs = "0.7.0"
|
||||||
time = { version = "0.3", features = [
|
time = { version = "0.3", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"parsing",
|
"parsing",
|
||||||
@@ -563,7 +572,7 @@ tokio = { version = "1" }
|
|||||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
tower-http = "0.4.4"
|
tower-http = "0.4.4"
|
||||||
tree-sitter = { version = "0.25.3", features = ["wasm"] }
|
tree-sitter = { version = "0.25.5", features = ["wasm"] }
|
||||||
tree-sitter-bash = "0.23"
|
tree-sitter-bash = "0.23"
|
||||||
tree-sitter-c = "0.23"
|
tree-sitter-c = "0.23"
|
||||||
tree-sitter-cpp = "0.23"
|
tree-sitter-cpp = "0.23"
|
||||||
@@ -593,8 +602,7 @@ unindent = "0.2.0"
|
|||||||
url = "2.2"
|
url = "2.2"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
||||||
walkdir = "2.3"
|
walkdir = "2.5"
|
||||||
wasi-preview1-component-adapter-provider = "29"
|
|
||||||
wasm-encoder = "0.221"
|
wasm-encoder = "0.221"
|
||||||
wasmparser = "0.221"
|
wasmparser = "0.221"
|
||||||
wasmtime = { version = "29", default-features = false, features = [
|
wasmtime = { version = "29", default-features = false, features = [
|
||||||
@@ -603,12 +611,13 @@ wasmtime = { version = "29", default-features = false, features = [
|
|||||||
"runtime",
|
"runtime",
|
||||||
"cranelift",
|
"cranelift",
|
||||||
"component-model",
|
"component-model",
|
||||||
|
"incremental-cache",
|
||||||
|
"parallel-compilation",
|
||||||
] }
|
] }
|
||||||
wasmtime-wasi = "29"
|
wasmtime-wasi = "29"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
wit-component = "0.221"
|
|
||||||
workspace-hack = "0.1.0"
|
workspace-hack = "0.1.0"
|
||||||
zed_llm_client = "0.8.1"
|
zed_llm_client = "0.8.4"
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
|
|
||||||
[workspace.dependencies.async-stripe]
|
[workspace.dependencies.async-stripe]
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ Welcome to Zed, a high-performance, multiplayer code editor from the creators of
|
|||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
<a href="https://repology.org/project/zed-editor/versions">
|
|
||||||
<img src="https://repology.org/badge/vertical-allrepos/zed-editor.svg?minversion=0.143.5" alt="Packaging status" align="right">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
On macOS and Linux you can [download Zed directly](https://zed.dev/download) or [install Zed via your local package manager](https://zed.dev/docs/linux#installing-via-a-package-manager).
|
On macOS and Linux you can [download Zed directly](https://zed.dev/download) or [install Zed via your local package manager](https://zed.dev/docs/linux#installing-via-a-package-manager).
|
||||||
|
|
||||||
Other platforms are not yet available:
|
Other platforms are not yet available:
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
|
<path d="M9.3 1.75L3 7.35H5.8L4.7 12.25L11 6.65H8.2L9.3 1.75Z" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 633 B After Width: | Height: | Size: 227 B |
3
assets/icons/bolt_filled.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
@@ -1,5 +1,4 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M17 20H16C14.9391 20 13.9217 19.6629 13.1716 19.0627C12.4214 18.4626 12 17.6487 12 16.8V7.2C12 6.35131 12.4214 5.53737 13.1716 4.93726C13.9217 4.33714 14.9391 4 16 4H17" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M11 13H10.4C9.76346 13 9.15302 12.7893 8.70296 12.4142C8.25284 12.0391 8 11.5304 8 11V5C8 4.46957 8.25284 3.96086 8.70296 3.58579C9.15302 3.21071 9.76346 3 10.4 3H11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M7 20H8C9.06087 20 10.0783 19.5786 10.8284 18.8284C11.5786 18.0783 12 17.0609 12 16V15" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M5 13H5.6C6.23654 13 6.84698 12.7893 7.29704 12.4142C7.74716 12.0391 8 11.5304 8 11V5C8 4.46957 7.74716 3.96086 7.29704 3.58579C6.84698 3.21071 6.23654 3 5.6 3H5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M7 4H8C9.06087 4 10.0783 4.42143 10.8284 5.17157C11.5786 5.92172 12 6.93913 12 8V9" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 617 B |
3
assets/icons/play_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 3L13 8L4 13V3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 214 B |
8
assets/icons/play_bug.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 12C2.35977 11.85 1 10.575 1 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M1.00875 15.2C1.00875 13.625 0.683456 12.275 4.00001 12.2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 9C7 10.575 5.62857 11.85 4 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 12.2C6.98117 12.2 7 13.625 7 15.2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<rect x="2.5" y="9" width="3" height="6" rx="1.5" fill="black"/>
|
||||||
|
<path d="M9 10L13 8L4 3V7.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 813 B |
@@ -1,3 +1,8 @@
|
|||||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
|
<path d="M2 5H4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M8 5L14 5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M12 11L14 11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M2 11H8" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="6" cy="5" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="10" cy="11" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 657 B |
@@ -1,5 +1,5 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M7 1.75L5.88467 5.14092C5.82759 5.31446 5.73055 5.47218 5.60136 5.60136C5.47218 5.73055 5.31446 5.82759 5.14092 5.88467L1.75 7L5.14092 8.11533C5.31446 8.17241 5.47218 8.26945 5.60136 8.39864C5.73055 8.52782 5.82759 8.68554 5.88467 8.85908L7 12.25L8.11533 8.85908C8.17241 8.68554 8.26945 8.52782 8.39864 8.39864C8.52782 8.26945 8.68554 8.17241 8.85908 8.11533L12.25 7L8.85908 5.88467C8.68554 5.82759 8.52782 5.73055 8.39864 5.60136C8.26945 5.47218 8.17241 5.31446 8.11533 5.14092L7 1.75Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8 2L6.72534 5.87534C6.6601 6.07367 6.5492 6.25392 6.40155 6.40155C6.25392 6.5492 6.07367 6.6601 5.87534 6.72534L2 8L5.87534 9.27466C6.07367 9.3399 6.25392 9.4508 6.40155 9.59845C6.5492 9.74608 6.6601 9.92633 6.72534 10.1247L8 14L9.27466 10.1247C9.3399 9.92633 9.4508 9.74608 9.59845 9.59845C9.74608 9.4508 9.92633 9.3399 10.1247 9.27466L14 8L10.1247 6.72534C9.92633 6.6601 9.74608 6.5492 9.59845 6.40155C9.4508 6.25392 9.3399 6.07367 9.27466 5.87534L8 2Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M2.91667 1.75V4.08333M1.75 2.91667H4.08333" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3.33334 2V4.66666M2 3.33334H4.66666" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M11.0833 9.91667V12.25M9.91667 11.0833H12.25" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12.6665 11.3333V14M11.3333 12.6666H13.9999" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 998 B |
3
assets/icons/zed_burn_mode.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.99207 8.14741C5.37246 8.14741 5.73726 7.9963 6.00623 7.72733C6.27521 7.45836 6.42631 7.09355 6.42631 6.71317C6.42631 5.92147 6.13946 5.56578 5.85262 4.99208C5.23761 3.76265 5.72411 2.66631 7.00001 1.5499C7.28686 2.98414 8.1474 4.36101 9.2948 5.27893C10.4422 6.19684 11.0159 7.28687 11.0159 8.43426C11.0159 8.96163 10.912 9.48384 10.7102 9.97107C10.5084 10.4583 10.2126 10.901 9.83967 11.2739C9.46676 11.6468 9.02405 11.9426 8.53682 12.1444C8.04959 12.3463 7.52738 12.4501 7.00001 12.4501C6.47264 12.4501 5.95043 12.3463 5.4632 12.1444C4.97597 11.9426 4.53326 11.6468 4.16035 11.2739C3.78745 10.901 3.49164 10.4583 3.28982 9.97107C3.088 9.48384 2.98413 8.96163 2.98413 8.43426C2.98413 7.77279 3.23254 7.1182 3.55783 6.71317C3.55783 7.09355 3.70894 7.45836 3.97791 7.72733C4.24688 7.9963 4.61169 8.14741 4.99207 8.14741Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1018 B |
13
assets/icons/zed_burn_mode_on.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2595_5640)">
|
||||||
|
<path d="M4.99207 8.14741C5.37246 8.14741 5.73726 7.9963 6.00623 7.72733C6.27521 7.45836 6.42631 7.09355 6.42631 6.71317C6.42631 5.92147 6.13946 5.56578 5.85262 4.99208C5.23761 3.76265 5.72411 2.66631 7.00001 1.5499C7.28686 2.98414 8.1474 4.36101 9.2948 5.27893C10.4422 6.19684 11.0159 7.28687 11.0159 8.43426C11.0159 8.96163 10.912 9.48384 10.7102 9.97107C10.5084 10.4583 10.2126 10.901 9.83967 11.2739C9.46676 11.6468 9.02405 11.9426 8.53682 12.1444C8.04959 12.3463 7.52738 12.4501 7.00001 12.4501C6.47264 12.4501 5.95043 12.3463 5.4632 12.1444C4.97597 11.9426 4.53326 11.6468 4.16035 11.2739C3.78745 10.901 3.49164 10.4583 3.28982 9.97107C3.088 9.48384 2.98413 8.96163 2.98413 8.43426C2.98413 7.77279 3.23254 7.1182 3.55783 6.71317C3.55783 7.09355 3.70894 7.45836 3.97791 7.72733C4.24688 7.9963 4.61169 8.14741 4.99207 8.14741Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 4C2.55228 4 3 3.55228 3 3C3 2.44772 2.55228 2 2 2C1.44772 2 1 2.44772 1 3C1 3.55228 1.44772 4 2 4Z" fill="black"/>
|
||||||
|
<path d="M10 2C10.5523 2 11 1.55228 11 1C11 0.44772 10.5523 0 10 0C9.44772 0 9 0.44772 9 1C9 1.55228 9.44772 2 10 2Z" fill="black"/>
|
||||||
|
<path d="M13 5C13.5522 5 14 4.55228 14 4C14 3.44772 13.5522 3 13 3C12.4478 3 12 3.44772 12 4C12 4.55228 12.4478 5 13 5Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2595_5640">
|
||||||
|
<rect width="14" height="14" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,14 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_2489_484)">
|
|
||||||
<path d="M11 8.9V11C8.51716 11 7.48284 11 5 11V10.4L11 5.6V5H5V7.1" stroke="black" stroke-width="1.5"/>
|
|
||||||
<path d="M1.5 5.5V1.5H5" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
|
|
||||||
<path d="M14.5 5.5V1.5H11" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
|
|
||||||
<path d="M1.5 10.5V14.5H5" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
|
|
||||||
<path d="M14.5 10.5V14.5H11" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_2489_484">
|
|
||||||
<rect width="16" height="16" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 687 B |
@@ -31,8 +31,8 @@
|
|||||||
"ctrl-,": "zed::OpenSettings",
|
"ctrl-,": "zed::OpenSettings",
|
||||||
"ctrl-q": "zed::Quit",
|
"ctrl-q": "zed::Quit",
|
||||||
"f4": "debugger::Start",
|
"f4": "debugger::Start",
|
||||||
"f5": "debugger::Continue",
|
|
||||||
"shift-f5": "debugger::Stop",
|
"shift-f5": "debugger::Stop",
|
||||||
|
"ctrl-shift-f5": "debugger::Restart",
|
||||||
"f6": "debugger::Pause",
|
"f6": "debugger::Pause",
|
||||||
"f7": "debugger::StepOver",
|
"f7": "debugger::StepOver",
|
||||||
"cmd-f11": "debugger::StepInto",
|
"cmd-f11": "debugger::StepInto",
|
||||||
@@ -125,9 +125,7 @@
|
|||||||
"shift-f10": "editor::OpenContextMenu",
|
"shift-f10": "editor::OpenContextMenu",
|
||||||
"ctrl-shift-e": "editor::ToggleEditPrediction",
|
"ctrl-shift-e": "editor::ToggleEditPrediction",
|
||||||
"f9": "editor::ToggleBreakpoint",
|
"f9": "editor::ToggleBreakpoint",
|
||||||
"shift-f9": "editor::EditLogBreakpoint",
|
"shift-f9": "editor::EditLogBreakpoint"
|
||||||
"ctrl-shift-backspace": "editor::GoToPreviousChange",
|
|
||||||
"ctrl-shift-alt-backspace": "editor::GoToNextChange"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -146,6 +144,8 @@
|
|||||||
"ctrl->": "assistant::QuoteSelection",
|
"ctrl->": "assistant::QuoteSelection",
|
||||||
"ctrl-<": "assistant::InsertIntoEditor",
|
"ctrl-<": "assistant::InsertIntoEditor",
|
||||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
||||||
|
"ctrl-shift-backspace": "editor::GoToPreviousChange",
|
||||||
|
"ctrl-shift-alt-backspace": "editor::GoToNextChange",
|
||||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -242,11 +242,14 @@
|
|||||||
"ctrl-i": "agent::ToggleProfileSelector",
|
"ctrl-i": "agent::ToggleProfileSelector",
|
||||||
"ctrl-alt-/": "agent::ToggleModelSelector",
|
"ctrl-alt-/": "agent::ToggleModelSelector",
|
||||||
"ctrl-shift-a": "agent::ToggleContextPicker",
|
"ctrl-shift-a": "agent::ToggleContextPicker",
|
||||||
"ctrl-shift-o": "agent::ToggleNavigationMenu",
|
"ctrl-shift-j": "agent::ToggleNavigationMenu",
|
||||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||||
"ctrl-shift-e": "project_panel::ToggleFocus"
|
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||||
|
"ctrl-shift-enter": "agent::ContinueThread",
|
||||||
|
"alt-enter": "agent::ContinueWithBurnMode",
|
||||||
|
"ctrl-alt-b": "agent::ToggleBurnMode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -273,6 +276,7 @@
|
|||||||
"context": "MessageEditor > Editor",
|
"context": "MessageEditor > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "agent::Chat",
|
"enter": "agent::Chat",
|
||||||
|
"ctrl-enter": "agent::ChatWithFollow",
|
||||||
"ctrl-i": "agent::ToggleProfileSelector",
|
"ctrl-i": "agent::ToggleProfileSelector",
|
||||||
"shift-ctrl-r": "agent::OpenAgentDiff"
|
"shift-ctrl-r": "agent::OpenAgentDiff"
|
||||||
}
|
}
|
||||||
@@ -512,6 +516,8 @@
|
|||||||
"alt-ctrl-o": "projects::OpenRecent",
|
"alt-ctrl-o": "projects::OpenRecent",
|
||||||
"alt-shift-open": "projects::OpenRemote",
|
"alt-shift-open": "projects::OpenRemote",
|
||||||
"alt-ctrl-shift-o": "projects::OpenRemote",
|
"alt-ctrl-shift-o": "projects::OpenRemote",
|
||||||
|
// Change to open path modal for existing remote connection by setting the parameter
|
||||||
|
// "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]",
|
||||||
"alt-ctrl-shift-b": "branches::OpenRecent",
|
"alt-ctrl-shift-b": "branches::OpenRecent",
|
||||||
"alt-shift-enter": "toast::RunAction",
|
"alt-shift-enter": "toast::RunAction",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
@@ -556,6 +562,7 @@
|
|||||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||||
"ctrl-shift-g": "git_panel::ToggleFocus",
|
"ctrl-shift-g": "git_panel::ToggleFocus",
|
||||||
|
"ctrl-shift-d": "debug_panel::ToggleFocus",
|
||||||
"ctrl-?": "agent::ToggleFocus",
|
"ctrl-?": "agent::ToggleFocus",
|
||||||
"alt-save": "workspace::SaveAll",
|
"alt-save": "workspace::SaveAll",
|
||||||
"ctrl-alt-s": "workspace::SaveAll",
|
"ctrl-alt-s": "workspace::SaveAll",
|
||||||
@@ -574,11 +581,24 @@
|
|||||||
"ctrl-alt-r": "task::Rerun",
|
"ctrl-alt-r": "task::Rerun",
|
||||||
"alt-t": "task::Rerun",
|
"alt-t": "task::Rerun",
|
||||||
"alt-shift-t": "task::Spawn",
|
"alt-shift-t": "task::Spawn",
|
||||||
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }],
|
||||||
// also possible to spawn tasks by name:
|
// also possible to spawn tasks by name:
|
||||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||||
// or by tag:
|
// or by tag:
|
||||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||||
|
"f5": "debugger::RerunLastSession"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Workspace && debugger_running",
|
||||||
|
"bindings": {
|
||||||
|
"f5": "zed::NoAction"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Workspace && debugger_stopped",
|
||||||
|
"bindings": {
|
||||||
|
"f5": "debugger::Continue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -593,7 +613,6 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-d": "editor::DuplicateLineDown",
|
|
||||||
"ctrl-shift-j": "editor::JoinLines",
|
"ctrl-shift-j": "editor::JoinLines",
|
||||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||||
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
|
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
|
||||||
@@ -672,7 +691,8 @@
|
|||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
|
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
|
||||||
"ctrl-alt-i": "zed::DebugElements"
|
// Only available in debug builds: opens an element inspector for development.
|
||||||
|
"ctrl-alt-i": "dev::ToggleInspector"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -860,6 +880,31 @@
|
|||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "DebugPanel",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-t": "debugger::ToggleThreadPicker",
|
||||||
|
"ctrl-i": "debugger::ToggleSessionPicker",
|
||||||
|
"shift-alt-escape": "debugger::ToggleExpandItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "VariableList",
|
||||||
|
"bindings": {
|
||||||
|
"left": "variable_list::CollapseSelectedEntry",
|
||||||
|
"right": "variable_list::ExpandSelectedEntry",
|
||||||
|
"enter": "variable_list::EditVariable",
|
||||||
|
"ctrl-c": "variable_list::CopyVariableValue",
|
||||||
|
"ctrl-alt-c": "variable_list::CopyVariableName"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "BreakpointList",
|
||||||
|
"bindings": {
|
||||||
|
"space": "debugger::ToggleEnableBreakpoint",
|
||||||
|
"backspace": "debugger::UnsetBreakpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -895,6 +940,13 @@
|
|||||||
"tab": "channel_modal::ToggleMode"
|
"tab": "channel_modal::ToggleMode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "FileFinder",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-shift-a": "file_finder::ToggleSplitMenu",
|
||||||
|
"ctrl-shift-i": "file_finder::ToggleFilterMenu"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
|
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -928,6 +980,7 @@
|
|||||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||||
"alt-.": ["terminal::SendText", "\u001b."],
|
"alt-.": ["terminal::SendText", "\u001b."],
|
||||||
|
"ctrl-delete": ["terminal::SendText", "\u001bd"],
|
||||||
// Overrides for conflicting keybindings
|
// Overrides for conflicting keybindings
|
||||||
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
||||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||||
@@ -985,5 +1038,12 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "menu::Confirm"
|
"enter": "menu::Confirm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "RunModal",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-tab": "pane::ActivateNextItem",
|
||||||
|
"ctrl-shift-tab": "pane::ActivatePreviousItem"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
[
|
[
|
||||||
// Moved before Standard macOS bindings so that `cmd-w` is not the last binding for
|
|
||||||
// `workspace::CloseWindow` and displayed/intercepted by macOS
|
|
||||||
{
|
|
||||||
"context": "PromptLibrary",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"cmd-n": "rules_library::NewRule",
|
|
||||||
"cmd-shift-s": "rules_library::ToggleDefaultRule",
|
|
||||||
"cmd-w": "workspace::CloseWindow"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Standard macOS bindings
|
// Standard macOS bindings
|
||||||
{
|
{
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"f4": "debugger::Start",
|
"f4": "debugger::Start",
|
||||||
"f5": "debugger::Continue",
|
|
||||||
"shift-f5": "debugger::Stop",
|
"shift-f5": "debugger::Stop",
|
||||||
|
"shift-cmd-f5": "debugger::Restart",
|
||||||
"f6": "debugger::Pause",
|
"f6": "debugger::Pause",
|
||||||
"f7": "debugger::StepOver",
|
"f7": "debugger::StepOver",
|
||||||
"f11": "debugger::StepInto",
|
"f11": "debugger::StepInto",
|
||||||
@@ -288,11 +277,14 @@
|
|||||||
"cmd-i": "agent::ToggleProfileSelector",
|
"cmd-i": "agent::ToggleProfileSelector",
|
||||||
"cmd-alt-/": "agent::ToggleModelSelector",
|
"cmd-alt-/": "agent::ToggleModelSelector",
|
||||||
"cmd-shift-a": "agent::ToggleContextPicker",
|
"cmd-shift-a": "agent::ToggleContextPicker",
|
||||||
"cmd-shift-o": "agent::ToggleNavigationMenu",
|
"cmd-shift-j": "agent::ToggleNavigationMenu",
|
||||||
"cmd-shift-i": "agent::ToggleOptionsMenu",
|
"cmd-shift-i": "agent::ToggleOptionsMenu",
|
||||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||||
"cmd-alt-e": "agent::RemoveAllContext",
|
"cmd-alt-e": "agent::RemoveAllContext",
|
||||||
"cmd-shift-e": "project_panel::ToggleFocus"
|
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||||
|
"cmd-shift-enter": "agent::ContinueThread",
|
||||||
|
"alt-enter": "agent::ContinueWithBurnMode",
|
||||||
|
"cmd-alt-b": "agent::ToggleBurnMode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -321,6 +313,7 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "agent::Chat",
|
"enter": "agent::Chat",
|
||||||
|
"cmd-enter": "agent::ChatWithFollow",
|
||||||
"cmd-i": "agent::ToggleProfileSelector",
|
"cmd-i": "agent::ToggleProfileSelector",
|
||||||
"shift-ctrl-r": "agent::OpenAgentDiff"
|
"shift-ctrl-r": "agent::OpenAgentDiff"
|
||||||
}
|
}
|
||||||
@@ -367,18 +360,21 @@
|
|||||||
"ctrl--": "pane::GoBack"
|
"ctrl--": "pane::GoBack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "ThreadHistory",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl--": "pane::GoBack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "ThreadHistory > Editor",
|
"context": "ThreadHistory > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"shift-backspace": "agent::RemoveSelectedThread"
|
"shift-backspace": "agent::RemoveSelectedThread"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "PromptLibrary",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-n": "rules_library::NewRule",
|
||||||
|
"cmd-shift-s": "rules_library::ToggleDefaultRule",
|
||||||
|
"cmd-w": "workspace::CloseWindow"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -548,9 +544,7 @@
|
|||||||
"cmd-\\": "pane::SplitRight",
|
"cmd-\\": "pane::SplitRight",
|
||||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||||
"cmd-shift-v": "markdown::OpenPreview",
|
"cmd-shift-v": "markdown::OpenPreview",
|
||||||
"ctrl-cmd-c": "editor::DisplayCursorNames",
|
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||||
"cmd-shift-backspace": "editor::GoToPreviousChange",
|
|
||||||
"cmd-shift-alt-backspace": "editor::GoToNextChange"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -558,7 +552,9 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-o": "outline::Toggle",
|
"cmd-shift-o": "outline::Toggle",
|
||||||
"ctrl-g": "go_to_line::Toggle"
|
"ctrl-g": "go_to_line::Toggle",
|
||||||
|
"cmd-shift-backspace": "editor::GoToPreviousChange",
|
||||||
|
"cmd-shift-alt-backspace": "editor::GoToNextChange"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -588,6 +584,7 @@
|
|||||||
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
||||||
"alt-cmd-o": "projects::OpenRecent",
|
"alt-cmd-o": "projects::OpenRecent",
|
||||||
"ctrl-cmd-o": "projects::OpenRemote",
|
"ctrl-cmd-o": "projects::OpenRemote",
|
||||||
|
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true }],
|
||||||
"alt-cmd-b": "branches::OpenRecent",
|
"alt-cmd-b": "branches::OpenRecent",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
@@ -623,6 +620,7 @@
|
|||||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
"cmd-shift-b": "outline_panel::ToggleFocus",
|
||||||
"ctrl-shift-g": "git_panel::ToggleFocus",
|
"ctrl-shift-g": "git_panel::ToggleFocus",
|
||||||
|
"cmd-shift-d": "debug_panel::ToggleFocus",
|
||||||
"cmd-?": "agent::ToggleFocus",
|
"cmd-?": "agent::ToggleFocus",
|
||||||
"cmd-alt-s": "workspace::SaveAll",
|
"cmd-alt-s": "workspace::SaveAll",
|
||||||
"cmd-k m": "language_selector::Toggle",
|
"cmd-k m": "language_selector::Toggle",
|
||||||
@@ -635,7 +633,8 @@
|
|||||||
"cmd-k shift-right": "workspace::SwapPaneRight",
|
"cmd-k shift-right": "workspace::SwapPaneRight",
|
||||||
"cmd-k shift-up": "workspace::SwapPaneUp",
|
"cmd-k shift-up": "workspace::SwapPaneUp",
|
||||||
"cmd-k shift-down": "workspace::SwapPaneDown",
|
"cmd-k shift-down": "workspace::SwapPaneDown",
|
||||||
"cmd-shift-x": "zed::Extensions"
|
"cmd-shift-x": "zed::Extensions",
|
||||||
|
"f5": "debugger::RerunLastSession"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -652,6 +651,20 @@
|
|||||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Workspace && debugger_running",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"f5": "zed::NoAction"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Workspace && debugger_stopped",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"f5": "debugger::Continue"
|
||||||
|
}
|
||||||
|
},
|
||||||
// Bindings from Sublime Text
|
// Bindings from Sublime Text
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -740,7 +753,8 @@
|
|||||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||||
// TODO: Move this to a dock open action
|
// TODO: Move this to a dock open action
|
||||||
"cmd-shift-c": "collab_panel::ToggleFocus",
|
"cmd-shift-c": "collab_panel::ToggleFocus",
|
||||||
"cmd-alt-i": "zed::DebugElements"
|
// Only available in debug builds: opens an element inspector for development.
|
||||||
|
"cmd-alt-i": "dev::ToggleInspector"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -843,7 +857,10 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"left": "variable_list::CollapseSelectedEntry",
|
"left": "variable_list::CollapseSelectedEntry",
|
||||||
"right": "variable_list::ExpandSelectedEntry"
|
"right": "variable_list::ExpandSelectedEntry",
|
||||||
|
"enter": "variable_list::EditVariable",
|
||||||
|
"cmd-c": "variable_list::CopyVariableValue",
|
||||||
|
"cmd-alt-c": "variable_list::CopyVariableName"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -928,6 +945,21 @@
|
|||||||
"alt-tab": "git::GenerateCommitMessage"
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "DebugPanel",
|
||||||
|
"bindings": {
|
||||||
|
"cmd-t": "debugger::ToggleThreadPicker",
|
||||||
|
"cmd-i": "debugger::ToggleSessionPicker",
|
||||||
|
"shift-alt-escape": "debugger::ToggleExpandItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "BreakpointList",
|
||||||
|
"bindings": {
|
||||||
|
"space": "debugger::ToggleEnableBreakpoint",
|
||||||
|
"backspace": "debugger::UnsetBreakpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -969,6 +1001,14 @@
|
|||||||
"tab": "channel_modal::ToggleMode"
|
"tab": "channel_modal::ToggleMode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "FileFinder",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-shift-a": "file_finder::ToggleSplitMenu",
|
||||||
|
"cmd-shift-i": "file_finder::ToggleFilterMenu"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
|
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -1011,7 +1051,7 @@
|
|||||||
"alt-right": ["terminal::SendText", "\u001bf"],
|
"alt-right": ["terminal::SendText", "\u001bf"],
|
||||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||||
"alt-.": ["terminal::SendText", "\u001b."],
|
"ctrl-delete": ["terminal::SendText", "\u001bd"],
|
||||||
// There are conflicting bindings for these keys in the global context.
|
// There are conflicting bindings for these keys in the global context.
|
||||||
// these bindings override them, remove at your own risk:
|
// these bindings override them, remove at your own risk:
|
||||||
"up": ["terminal::SendKeystroke", "up"],
|
"up": ["terminal::SendKeystroke", "up"],
|
||||||
@@ -1091,5 +1131,13 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "menu::Confirm"
|
"enter": "menu::Confirm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "RunModal",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-tab": "pane::ActivateNextItem",
|
||||||
|
"ctrl-shift-tab": "pane::ActivatePreviousItem"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
85
assets/keymaps/linux/cursor.json
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
[
|
||||||
|
// Cursor for MacOS. See: https://docs.cursor.com/kbd
|
||||||
|
{
|
||||||
|
"context": "Workspace",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-i": "agent::ToggleFocus",
|
||||||
|
"ctrl-shift-i": "agent::ToggleFocus",
|
||||||
|
"ctrl-l": "agent::ToggleFocus",
|
||||||
|
"ctrl-shift-l": "agent::ToggleFocus",
|
||||||
|
"ctrl-alt-b": "agent::ToggleFocus",
|
||||||
|
"ctrl-shift-j": "agent::OpenConfiguration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && mode == full",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-i": "agent::ToggleFocus",
|
||||||
|
"ctrl-shift-i": "agent::ToggleFocus",
|
||||||
|
"ctrl-shift-l": "assistant::QuoteSelection", // In cursor uses "Ask" mode
|
||||||
|
"ctrl-l": "assistant::QuoteSelection", // In cursor uses "Agent" mode
|
||||||
|
"ctrl-k": "assistant::InlineAssist",
|
||||||
|
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "InlineAssistEditor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-shift-backspace": "editor::Cancel"
|
||||||
|
// "alt-enter": // Quick Question
|
||||||
|
// "ctrl-shift-enter": // Full File Context
|
||||||
|
// "ctrl-shift-k": // Toggle input focus (editor <> inline assist)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-i": "workspace::ToggleRightDock",
|
||||||
|
"ctrl-shift-i": "workspace::ToggleRightDock",
|
||||||
|
"ctrl-l": "workspace::ToggleRightDock",
|
||||||
|
"ctrl-shift-l": "workspace::ToggleRightDock",
|
||||||
|
"ctrl-alt-b": "workspace::ToggleRightDock",
|
||||||
|
"ctrl-w": "workspace::ToggleRightDock", // technically should close chat
|
||||||
|
"ctrl-.": "agent::ToggleProfileSelector",
|
||||||
|
"ctrl-/": "agent::ToggleModelSelector",
|
||||||
|
"ctrl-shift-backspace": "editor::Cancel",
|
||||||
|
"ctrl-r": "agent::NewThread",
|
||||||
|
"ctrl-shift-v": "editor::Paste",
|
||||||
|
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||||
|
// "escape": "agent::ToggleFocus"
|
||||||
|
///// Enable when Zed supports multiple thread tabs
|
||||||
|
// "ctrl-t": // new thread tab
|
||||||
|
// "ctrl-[": // next thread tab
|
||||||
|
// "ctrl-]": // next thread tab
|
||||||
|
///// Enable if Zed adds support for keyboard navigation of thread elements
|
||||||
|
// "tab": // cycle to next message
|
||||||
|
// "shift-tab": // cycle to previous message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && editor_agent_diff",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-enter": "agent::KeepAll",
|
||||||
|
"ctrl-backspace": "agent::RejectAll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && mode == full && edit_prediction",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-right": "editor::AcceptPartialEditPrediction"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Terminal",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-k": "assistant::InlineAssist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -72,7 +72,9 @@
|
|||||||
"alt-left": "editor::SelectToPreviousWordStart",
|
"alt-left": "editor::SelectToPreviousWordStart",
|
||||||
"alt-right": "editor::SelectToNextWordEnd",
|
"alt-right": "editor::SelectToNextWordEnd",
|
||||||
"pagedown": "editor::SelectPageDown",
|
"pagedown": "editor::SelectPageDown",
|
||||||
|
"ctrl-v": "editor::SelectPageDown",
|
||||||
"pageup": "editor::SelectPageUp",
|
"pageup": "editor::SelectPageUp",
|
||||||
|
"alt-v": "editor::SelectPageUp",
|
||||||
"ctrl-f": "editor::SelectRight",
|
"ctrl-f": "editor::SelectRight",
|
||||||
"ctrl-b": "editor::SelectLeft",
|
"ctrl-b": "editor::SelectLeft",
|
||||||
"ctrl-n": "editor::SelectDown",
|
"ctrl-n": "editor::SelectDown",
|
||||||
|
|||||||
@@ -52,8 +52,10 @@
|
|||||||
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
||||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||||
"f3": "editor::FindNextMatch",
|
"alt-right": "editor::MoveToNextSubwordEnd",
|
||||||
"shift-f3": "editor::FindPreviousMatch"
|
"alt-left": "editor::MoveToPreviousSubwordStart",
|
||||||
|
"alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||||
|
"alt-shift-left": "editor::SelectToPreviousSubwordStart"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
85
assets/keymaps/macos/cursor.json
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
[
|
||||||
|
// Cursor for MacOS. See: https://docs.cursor.com/kbd
|
||||||
|
{
|
||||||
|
"context": "Workspace",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-i": "agent::ToggleFocus",
|
||||||
|
"cmd-shift-i": "agent::ToggleFocus",
|
||||||
|
"cmd-l": "agent::ToggleFocus",
|
||||||
|
"cmd-shift-l": "agent::ToggleFocus",
|
||||||
|
"cmd-alt-b": "agent::ToggleFocus",
|
||||||
|
"cmd-shift-j": "agent::OpenConfiguration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && mode == full",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-i": "agent::ToggleFocus",
|
||||||
|
"cmd-shift-i": "agent::ToggleFocus",
|
||||||
|
"cmd-shift-l": "assistant::QuoteSelection", // In cursor uses "Ask" mode
|
||||||
|
"cmd-l": "assistant::QuoteSelection", // In cursor uses "Agent" mode
|
||||||
|
"cmd-k": "assistant::InlineAssist",
|
||||||
|
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "InlineAssistEditor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-shift-backspace": "editor::Cancel"
|
||||||
|
// "alt-enter": // Quick Question
|
||||||
|
// "cmd-shift-enter": // Full File Context
|
||||||
|
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-i": "workspace::ToggleRightDock",
|
||||||
|
"cmd-shift-i": "workspace::ToggleRightDock",
|
||||||
|
"cmd-l": "workspace::ToggleRightDock",
|
||||||
|
"cmd-shift-l": "workspace::ToggleRightDock",
|
||||||
|
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||||
|
"cmd-w": "workspace::ToggleRightDock", // technically should close chat
|
||||||
|
"cmd-.": "agent::ToggleProfileSelector",
|
||||||
|
"cmd-/": "agent::ToggleModelSelector",
|
||||||
|
"cmd-shift-backspace": "editor::Cancel",
|
||||||
|
"cmd-r": "agent::NewThread",
|
||||||
|
"cmd-shift-v": "editor::Paste",
|
||||||
|
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||||
|
// "escape": "agent::ToggleFocus"
|
||||||
|
///// Enable when Zed supports multiple thread tabs
|
||||||
|
// "cmd-t": // new thread tab
|
||||||
|
// "cmd-[": // next thread tab
|
||||||
|
// "cmd-]": // next thread tab
|
||||||
|
///// Enable if Zed adds support for keyboard navigation of thread elements
|
||||||
|
// "tab": // cycle to next message
|
||||||
|
// "shift-tab": // cycle to previous message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && editor_agent_diff",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-enter": "agent::KeepAll",
|
||||||
|
"cmd-backspace": "agent::RejectAll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && mode == full && edit_prediction",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-right": "editor::AcceptPartialEditPrediction"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Terminal",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-k": "assistant::InlineAssist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -72,7 +72,9 @@
|
|||||||
"alt-left": "editor::SelectToPreviousWordStart",
|
"alt-left": "editor::SelectToPreviousWordStart",
|
||||||
"alt-right": "editor::SelectToNextWordEnd",
|
"alt-right": "editor::SelectToNextWordEnd",
|
||||||
"pagedown": "editor::SelectPageDown",
|
"pagedown": "editor::SelectPageDown",
|
||||||
|
"ctrl-v": "editor::SelectPageDown",
|
||||||
"pageup": "editor::SelectPageUp",
|
"pageup": "editor::SelectPageUp",
|
||||||
|
"alt-v": "editor::SelectPageUp",
|
||||||
"ctrl-f": "editor::SelectRight",
|
"ctrl-f": "editor::SelectRight",
|
||||||
"ctrl-b": "editor::SelectLeft",
|
"ctrl-b": "editor::SelectLeft",
|
||||||
"ctrl-n": "editor::SelectDown",
|
"ctrl-n": "editor::SelectDown",
|
||||||
|
|||||||
@@ -54,8 +54,10 @@
|
|||||||
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
||||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||||
"cmd-g": "editor::FindNextMatch",
|
"ctrl-right": "editor::MoveToNextSubwordEnd",
|
||||||
"cmd-shift-g": "editor::FindPreviousMatch"
|
"ctrl-left": "editor::MoveToPreviousSubwordStart",
|
||||||
|
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
|
||||||
|
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -152,6 +152,7 @@
|
|||||||
"g end": ["vim::EndOfLine", { "display_lines": true }],
|
"g end": ["vim::EndOfLine", { "display_lines": true }],
|
||||||
"g 0": ["vim::StartOfLine", { "display_lines": true }],
|
"g 0": ["vim::StartOfLine", { "display_lines": true }],
|
||||||
"g home": ["vim::StartOfLine", { "display_lines": true }],
|
"g home": ["vim::StartOfLine", { "display_lines": true }],
|
||||||
|
"g shift-m": ["vim::MiddleOfLine", { "display_lines": true }],
|
||||||
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
|
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
|
||||||
"g v": "vim::RestoreVisualSelection",
|
"g v": "vim::RestoreVisualSelection",
|
||||||
"g ]": "editor::GoToDiagnostic",
|
"g ]": "editor::GoToDiagnostic",
|
||||||
@@ -837,6 +838,19 @@
|
|||||||
"tab": "editor::AcceptEditPrediction"
|
"tab": "editor::AcceptEditPrediction"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "MessageEditor > Editor && VimControl",
|
||||||
|
"bindings": {
|
||||||
|
"enter": "agent::Chat",
|
||||||
|
// TODO: Implement search
|
||||||
|
"/": null,
|
||||||
|
"?": null,
|
||||||
|
"#": null,
|
||||||
|
"*": null,
|
||||||
|
"n": null,
|
||||||
|
"shift-n": null
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "os != macos && Editor && edit_prediction_conflict",
|
"context": "os != macos && Editor && edit_prediction_conflict",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -845,13 +859,5 @@
|
|||||||
// and Windows.
|
// and Windows.
|
||||||
"alt-l": "editor::AcceptEditPrediction"
|
"alt-l": "editor::AcceptEditPrediction"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
// Fixes https://github.com/zed-industries/zed/issues/29095 by ensuring that
|
|
||||||
// the last binding for editor::ToggleComments is not ctrl-c.
|
|
||||||
"context": "hack_to_fix_ctrl-c",
|
|
||||||
"bindings": {
|
|
||||||
"g c": "editor::ToggleComments"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -128,6 +128,8 @@
|
|||||||
//
|
//
|
||||||
// Default: true
|
// Default: true
|
||||||
"restore_on_file_reopen": true,
|
"restore_on_file_reopen": true,
|
||||||
|
// Whether to automatically close files that have been deleted on disk.
|
||||||
|
"close_on_file_delete": false,
|
||||||
// Size of the drop target in the editor.
|
// Size of the drop target in the editor.
|
||||||
"drop_target_size": 0.2,
|
"drop_target_size": 0.2,
|
||||||
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
||||||
@@ -213,6 +215,8 @@
|
|||||||
// Whether to show the signature help after completion or a bracket pair inserted.
|
// Whether to show the signature help after completion or a bracket pair inserted.
|
||||||
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||||
"show_signature_help_after_edits": false,
|
"show_signature_help_after_edits": false,
|
||||||
|
// Whether to show code action button at start of buffer line.
|
||||||
|
"inline_code_actions": true,
|
||||||
// What to do when go to definition yields no results.
|
// What to do when go to definition yields no results.
|
||||||
//
|
//
|
||||||
// 1. Do nothing: `none`
|
// 1. Do nothing: `none`
|
||||||
@@ -230,11 +234,11 @@
|
|||||||
// Possible values:
|
// Possible values:
|
||||||
// - "off" — no diagnostics are allowed
|
// - "off" — no diagnostics are allowed
|
||||||
// - "error"
|
// - "error"
|
||||||
// - "warning" (default)
|
// - "warning"
|
||||||
// - "info"
|
// - "info"
|
||||||
// - "hint"
|
// - "hint"
|
||||||
// - null — allow all diagnostics
|
// - null — allow all diagnostics (default)
|
||||||
"diagnostics_max_severity": "warning",
|
"diagnostics_max_severity": null,
|
||||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||||
@@ -322,7 +326,9 @@
|
|||||||
// Whether to show the Selections menu in the editor toolbar.
|
// Whether to show the Selections menu in the editor toolbar.
|
||||||
"selections_menu": true,
|
"selections_menu": true,
|
||||||
// Whether to show agent review buttons in the editor toolbar.
|
// Whether to show agent review buttons in the editor toolbar.
|
||||||
"agent_review": true
|
"agent_review": true,
|
||||||
|
// Whether to show code action buttons in the editor toolbar.
|
||||||
|
"code_actions": false
|
||||||
},
|
},
|
||||||
// Titlebar related settings
|
// Titlebar related settings
|
||||||
"title_bar": {
|
"title_bar": {
|
||||||
@@ -471,6 +477,10 @@
|
|||||||
// Scroll sensitivity multiplier. This multiplier is applied
|
// Scroll sensitivity multiplier. This multiplier is applied
|
||||||
// to both the horizontal and vertical delta values while scrolling.
|
// to both the horizontal and vertical delta values while scrolling.
|
||||||
"scroll_sensitivity": 1.0,
|
"scroll_sensitivity": 1.0,
|
||||||
|
// Scroll sensitivity multiplier for fast scrolling. This multiplier is applied
|
||||||
|
// to both the horizontal and vertical delta values while scrolling. Fast scrolling
|
||||||
|
// happens when a user holds the alt or option key while scrolling.
|
||||||
|
"fast_scroll_sensitivity": 4.0,
|
||||||
"relative_line_numbers": false,
|
"relative_line_numbers": false,
|
||||||
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
|
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
|
||||||
"search_wrap": true,
|
"search_wrap": true,
|
||||||
@@ -706,7 +716,7 @@
|
|||||||
"version": "2",
|
"version": "2",
|
||||||
// Whether the agent is enabled.
|
// Whether the agent is enabled.
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
/// What completion mode to start new threads in, if available. Can be 'normal' or 'max'.
|
/// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
|
||||||
"preferred_completion_mode": "normal",
|
"preferred_completion_mode": "normal",
|
||||||
// Whether to show the agent panel button in the status bar.
|
// Whether to show the agent panel button in the status bar.
|
||||||
"button": true,
|
"button": true,
|
||||||
@@ -721,14 +731,7 @@
|
|||||||
// The provider to use.
|
// The provider to use.
|
||||||
"provider": "zed.dev",
|
"provider": "zed.dev",
|
||||||
// The model to use.
|
// The model to use.
|
||||||
"model": "claude-3-7-sonnet-latest"
|
"model": "claude-sonnet-4"
|
||||||
},
|
|
||||||
// The model to use when applying edits from the agent.
|
|
||||||
"editor_model": {
|
|
||||||
// The provider to use.
|
|
||||||
"provider": "zed.dev",
|
|
||||||
// The model to use.
|
|
||||||
"model": "claude-3-7-sonnet-latest"
|
|
||||||
},
|
},
|
||||||
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
|
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
|
||||||
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
|
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
|
||||||
@@ -748,7 +751,7 @@
|
|||||||
// To set parameters for a specific provider and model:
|
// To set parameters for a specific provider and model:
|
||||||
// {
|
// {
|
||||||
// "provider": "zed.dev",
|
// "provider": "zed.dev",
|
||||||
// "model": "claude-3-7-sonnet-latest",
|
// "model": "claude-sonnet-4",
|
||||||
// "temperature": 1.0
|
// "temperature": 1.0
|
||||||
// }
|
// }
|
||||||
],
|
],
|
||||||
@@ -814,7 +817,12 @@
|
|||||||
// "primary_screen" - Show the notification only on your primary screen (default)
|
// "primary_screen" - Show the notification only on your primary screen (default)
|
||||||
// "all_screens" - Show these notifications on all screens
|
// "all_screens" - Show these notifications on all screens
|
||||||
// "never" - Never show these notifications
|
// "never" - Never show these notifications
|
||||||
"notify_when_agent_waiting": "primary_screen"
|
"notify_when_agent_waiting": "primary_screen",
|
||||||
|
// Whether to play a sound when the agent has either completed
|
||||||
|
// its response, or needs user input.
|
||||||
|
|
||||||
|
// Default: false
|
||||||
|
"play_sound_when_agent_done": false
|
||||||
},
|
},
|
||||||
// The settings for slash commands.
|
// The settings for slash commands.
|
||||||
"slash_commands": {
|
"slash_commands": {
|
||||||
@@ -946,7 +954,17 @@
|
|||||||
// "skip_focus_for_active_in_search": false
|
// "skip_focus_for_active_in_search": false
|
||||||
//
|
//
|
||||||
// Default: true
|
// Default: true
|
||||||
"skip_focus_for_active_in_search": true
|
"skip_focus_for_active_in_search": true,
|
||||||
|
// Whether to show the git status in the file finder.
|
||||||
|
"git_status": true,
|
||||||
|
// Whether to use gitignored files when searching.
|
||||||
|
// Only the file Zed had indexed will be used, not necessary all the gitignored files.
|
||||||
|
//
|
||||||
|
// Can accept 3 values:
|
||||||
|
// * `true`: Use all gitignored files
|
||||||
|
// * `false`: Use only the files Zed had indexed
|
||||||
|
// * `null`: Be smart and search for ignored when called from a gitignored worktree
|
||||||
|
"include_ignored": null
|
||||||
},
|
},
|
||||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||||
// before saving it.
|
// before saving it.
|
||||||
@@ -1291,7 +1309,17 @@
|
|||||||
// Settings related to running tasks.
|
// Settings related to running tasks.
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"variables": {},
|
"variables": {},
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
// Use LSP tasks over Zed language extension ones.
|
||||||
|
// If no LSP tasks are returned due to error/timeout or regular execution,
|
||||||
|
// Zed language extension tasks will be used instead.
|
||||||
|
//
|
||||||
|
// Other Zed tasks will still be shown:
|
||||||
|
// * Zed task from either of the task config file
|
||||||
|
// * Zed task from history (e.g. one-off task was spawned before)
|
||||||
|
//
|
||||||
|
// Default: true
|
||||||
|
"prefer_lsp": true
|
||||||
},
|
},
|
||||||
// An object whose keys are language names, and whose values
|
// An object whose keys are language names, and whose values
|
||||||
// are arrays of filenames or extensions of files that should
|
// are arrays of filenames or extensions of files that should
|
||||||
@@ -1715,6 +1743,8 @@
|
|||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
"ssh_connections": [],
|
"ssh_connections": [],
|
||||||
|
// Whether to read ~/.ssh/config for ssh connection sources.
|
||||||
|
"read_ssh_config": true,
|
||||||
// Configures context servers for use by the agent.
|
// Configures context servers for use by the agent.
|
||||||
"context_servers": {},
|
"context_servers": {},
|
||||||
"debugger": {
|
"debugger": {
|
||||||
|
|||||||
@@ -1,32 +1,34 @@
|
|||||||
|
// Some example tasks for common languages.
|
||||||
|
//
|
||||||
|
// For more documentation on how to configure debug tasks,
|
||||||
|
// see: https://zed.dev/docs/debugger
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": "Debug active PHP file",
|
"label": "Debug active PHP file",
|
||||||
"adapter": "php",
|
"adapter": "PHP",
|
||||||
"program": "$ZED_FILE",
|
"program": "$ZED_FILE",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Debug active Python file",
|
"label": "Debug active Python file",
|
||||||
"adapter": "python",
|
"adapter": "Debugpy",
|
||||||
"program": "$ZED_FILE",
|
"program": "$ZED_FILE",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Debug active JavaScript file",
|
"label": "Debug active JavaScript file",
|
||||||
"adapter": "javascript",
|
"adapter": "JavaScript",
|
||||||
"program": "$ZED_FILE",
|
"program": "$ZED_FILE",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "JavaScript debug terminal",
|
"label": "JavaScript debug terminal",
|
||||||
"adapter": "javascript",
|
"adapter": "JavaScript",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
"initialize_args": {
|
"console": "integratedTerminal"
|
||||||
"console": "integratedTerminal"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
5
assets/settings/initial_local_debug_tasks.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Project-local debug tasks
|
||||||
|
//
|
||||||
|
// For more documentation on how to configure debug tasks,
|
||||||
|
// see: https://zed.dev/docs/debugger
|
||||||
|
[]
|
||||||
BIN
assets/sounds/agent_done.wav
Executable file
@@ -24,8 +24,9 @@ project.workspace = true
|
|||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
workspace.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
|
release_channel.workspace = true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
|
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage, VersionCheckType};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use extension_host::ExtensionStore;
|
use extension_host::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -60,6 +60,7 @@ struct Content {
|
|||||||
message: String,
|
message: String,
|
||||||
on_click:
|
on_click:
|
||||||
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
|
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
|
||||||
|
tooltip_message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivityIndicator {
|
impl ActivityIndicator {
|
||||||
@@ -262,6 +263,7 @@ impl ActivityIndicator {
|
|||||||
});
|
});
|
||||||
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Show any language server has pending activity.
|
// Show any language server has pending activity.
|
||||||
@@ -305,6 +307,32 @@ impl ActivityIndicator {
|
|||||||
),
|
),
|
||||||
message,
|
message,
|
||||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||||
|
tooltip_message: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(session) = self
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.dap_store()
|
||||||
|
.read(cx)
|
||||||
|
.sessions()
|
||||||
|
.find(|s| !s.read(cx).is_started())
|
||||||
|
{
|
||||||
|
return Some(Content {
|
||||||
|
icon: Some(
|
||||||
|
Icon::new(IconName::ArrowCircle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
message: format!("Debug: {}", session.read(cx).adapter()),
|
||||||
|
tooltip_message: Some(session.read(cx).label().to_string()),
|
||||||
|
on_click: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,6 +360,7 @@ impl ActivityIndicator {
|
|||||||
),
|
),
|
||||||
message: job_info.message.into(),
|
message: job_info.message.into(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,6 +403,7 @@ impl ActivityIndicator {
|
|||||||
.retain(|status| !downloading.contains(&status.name));
|
.retain(|status| !downloading.contains(&status.name));
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +432,7 @@ impl ActivityIndicator {
|
|||||||
.retain(|status| !checking_for_update.contains(&status.name));
|
.retain(|status| !checking_for_update.contains(&status.name));
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,6 +459,7 @@ impl ActivityIndicator {
|
|||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, window, cx| {
|
||||||
this.show_error_message(&Default::default(), window, cx)
|
this.show_error_message(&Default::default(), window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,6 +478,7 @@ impl ActivityIndicator {
|
|||||||
});
|
});
|
||||||
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,8 +495,9 @@ impl ActivityIndicator {
|
|||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, window, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Downloading => Some(Content {
|
AutoUpdateStatus::Downloading { version } => Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
Icon::new(IconName::Download)
|
Icon::new(IconName::Download)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -473,8 +507,9 @@ impl ActivityIndicator {
|
|||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, window, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: Some(Self::version_tooltip_message(&version)),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Installing => Some(Content {
|
AutoUpdateStatus::Installing { version } => Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
Icon::new(IconName::Download)
|
Icon::new(IconName::Download)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -484,8 +519,12 @@ impl ActivityIndicator {
|
|||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, window, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: Some(Self::version_tooltip_message(&version)),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Updated { binary_path } => Some(Content {
|
AutoUpdateStatus::Updated {
|
||||||
|
binary_path,
|
||||||
|
version,
|
||||||
|
} => Some(Content {
|
||||||
icon: None,
|
icon: None,
|
||||||
message: "Click to restart and update Zed".to_string(),
|
message: "Click to restart and update Zed".to_string(),
|
||||||
on_click: Some(Arc::new({
|
on_click: Some(Arc::new({
|
||||||
@@ -494,6 +533,7 @@ impl ActivityIndicator {
|
|||||||
};
|
};
|
||||||
move |_, _, cx| workspace::reload(&reload, cx)
|
move |_, _, cx| workspace::reload(&reload, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: Some(Self::version_tooltip_message(&version)),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Errored => Some(Content {
|
AutoUpdateStatus::Errored => Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
@@ -505,6 +545,7 @@ impl ActivityIndicator {
|
|||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, window, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Idle => None,
|
AutoUpdateStatus::Idle => None,
|
||||||
};
|
};
|
||||||
@@ -524,6 +565,7 @@ impl ActivityIndicator {
|
|||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, window, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||||
})),
|
})),
|
||||||
|
tooltip_message: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -531,6 +573,17 @@ impl ActivityIndicator {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn version_tooltip_message(version: &VersionCheckType) -> String {
|
||||||
|
format!("Version: {}", {
|
||||||
|
match version {
|
||||||
|
auto_update::VersionCheckType::Sha(sha) => format!("{}…", sha.short()),
|
||||||
|
auto_update::VersionCheckType::Semantic(semantic_version) => {
|
||||||
|
semantic_version.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn toggle_language_server_work_context_menu(
|
fn toggle_language_server_work_context_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
@@ -575,7 +628,14 @@ impl Render for ActivityIndicator {
|
|||||||
)
|
)
|
||||||
.tooltip(Tooltip::text(content.message))
|
.tooltip(Tooltip::text(content.message))
|
||||||
} else {
|
} else {
|
||||||
button.child(Label::new(content.message).size(LabelSize::Small))
|
button
|
||||||
|
.child(Label::new(content.message).size(LabelSize::Small))
|
||||||
|
.when_some(
|
||||||
|
content.tooltip_message,
|
||||||
|
|this, tooltip_message| {
|
||||||
|
this.tooltip(Tooltip::text(tooltip_message))
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when_some(content.on_click, |this, handler| {
|
.when_some(content.on_click, |this, handler| {
|
||||||
@@ -655,3 +715,26 @@ impl StatusItemView for ActivityIndicator {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use gpui::SemanticVersion;
|
||||||
|
use release_channel::AppCommitSha;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_version_tooltip_message() {
|
||||||
|
let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Semantic(
|
||||||
|
SemanticVersion::new(1, 0, 0),
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(message, "Version: 1.0.0");
|
||||||
|
|
||||||
|
let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Sha(
|
||||||
|
AppCommitSha::new("14d9a4189f058d8736339b06ff2340101eaea5af".to_string()),
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(message, "Version: 14d9a41…");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
agent_settings.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
assistant_context_editor.workspace = true
|
assistant_context_editor.workspace = true
|
||||||
assistant_settings.workspace = true
|
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
assistant_slash_commands.workspace = true
|
assistant_slash_commands.workspace = true
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
async-watch.workspace = true
|
async-watch.workspace = true
|
||||||
|
audio.workspace = true
|
||||||
buffer_diff.workspace = true
|
buffer_diff.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
@@ -45,6 +46,7 @@ git.workspace = true
|
|||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
heed.workspace = true
|
heed.workspace = true
|
||||||
html_to_markdown.workspace = true
|
html_to_markdown.workspace = true
|
||||||
|
indoc.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
indexed_docs.workspace = true
|
indexed_docs.workspace = true
|
||||||
inventory.workspace = true
|
inventory.workspace = true
|
||||||
@@ -52,7 +54,6 @@ itertools.workspace = true
|
|||||||
jsonschema.workspace = true
|
jsonschema.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
language_model_selector.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp.workspace = true
|
lsp.workspace = true
|
||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
@@ -77,8 +78,8 @@ serde.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_json_lenient.workspace = true
|
serde_json_lenient.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smallvec.workspace = true
|
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
sqlez.workspace = true
|
||||||
streaming_diff.workspace = true
|
streaming_diff.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
@@ -98,6 +99,7 @@ workspace-hack.workspace = true
|
|||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
zed_llm_client.workspace = true
|
zed_llm_client.workspace = true
|
||||||
|
zstd.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ use crate::tool_use::{PendingToolUseStatus, ToolUse};
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
||||||
};
|
};
|
||||||
|
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting};
|
|
||||||
use assistant_tool::ToolUseStatus;
|
use assistant_tool::ToolUseStatus;
|
||||||
|
use audio::{Audio, Sound};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::actions::{MoveUp, Paste};
|
use editor::actions::{MoveUp, Paste};
|
||||||
use editor::scroll::Autoscroll;
|
use editor::scroll::Autoscroll;
|
||||||
@@ -52,8 +53,9 @@ use ui::{
|
|||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use util::markdown::MarkdownCodeBlock;
|
use util::markdown::MarkdownCodeBlock;
|
||||||
use workspace::Workspace;
|
use workspace::{CollaboratorId, Workspace};
|
||||||
use zed_actions::assistant::OpenRulesLibrary;
|
use zed_actions::assistant::OpenRulesLibrary;
|
||||||
|
use zed_llm_client::CompletionIntent;
|
||||||
|
|
||||||
pub struct ActiveThread {
|
pub struct ActiveThread {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
@@ -333,7 +335,6 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
|
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
|
||||||
const MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK: usize = 10;
|
|
||||||
|
|
||||||
fn render_markdown_code_block(
|
fn render_markdown_code_block(
|
||||||
message_id: MessageId,
|
message_id: MessageId,
|
||||||
@@ -346,17 +347,20 @@ fn render_markdown_code_block(
|
|||||||
_window: &Window,
|
_window: &Window,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Div {
|
) -> Div {
|
||||||
|
let label_size = rems(0.8125);
|
||||||
|
|
||||||
let label = match kind {
|
let label = match kind {
|
||||||
CodeBlockKind::Indented => None,
|
CodeBlockKind::Indented => None,
|
||||||
CodeBlockKind::Fenced => Some(
|
CodeBlockKind::Fenced => Some(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.px_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::Code)
|
Icon::new(IconName::Code)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.size(IconSize::XSmall),
|
.size(IconSize::XSmall),
|
||||||
)
|
)
|
||||||
.child(Label::new("untitled").size(LabelSize::Small))
|
.child(div().text_size(label_size).child("Plain Text"))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
CodeBlockKind::FencedLang(raw_language_name) => Some(render_code_language(
|
CodeBlockKind::FencedLang(raw_language_name) => Some(render_code_language(
|
||||||
@@ -393,7 +397,7 @@ fn render_markdown_code_block(
|
|||||||
.id(("code-block-header-label", ix))
|
.id(("code-block-header-label", ix))
|
||||||
.ml_1()
|
.ml_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(Label::new(file_name).size(LabelSize::Small))
|
.child(div().text_size(label_size).child(file_name))
|
||||||
.child(Label::new(path).color(Color::Muted).size(LabelSize::Small))
|
.child(Label::new(path).color(Color::Muted).size(LabelSize::Small))
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |window, cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
@@ -406,9 +410,10 @@ fn render_markdown_code_block(
|
|||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
} else {
|
} else {
|
||||||
Label::new(path_range.path.to_string_lossy().to_string())
|
div()
|
||||||
.size(LabelSize::Small)
|
|
||||||
.ml_1()
|
.ml_1()
|
||||||
|
.text_size(label_size)
|
||||||
|
.child(path_range.path.to_string_lossy().to_string())
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -456,19 +461,13 @@ fn render_markdown_code_block(
|
|||||||
.copied_code_block_ids
|
.copied_code_block_ids
|
||||||
.contains(&(message_id, ix));
|
.contains(&(message_id, ix));
|
||||||
|
|
||||||
let can_expand = metadata.line_count >= MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK;
|
let is_expanded = active_thread.read(cx).is_codeblock_expanded(message_id, ix);
|
||||||
|
|
||||||
let is_expanded = if can_expand {
|
|
||||||
active_thread.read(cx).is_codeblock_expanded(message_id, ix)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let codeblock_header_bg = cx
|
let codeblock_header_bg = cx
|
||||||
.theme()
|
.theme()
|
||||||
.colors()
|
.colors()
|
||||||
.element_background
|
.element_background
|
||||||
.blend(cx.theme().colors().editor_foreground.opacity(0.01));
|
.blend(cx.theme().colors().editor_foreground.opacity(0.025));
|
||||||
|
|
||||||
let control_buttons = h_flex()
|
let control_buttons = h_flex()
|
||||||
.visible_on_hover(CODEBLOCK_CONTAINER_GROUP)
|
.visible_on_hover(CODEBLOCK_CONTAINER_GROUP)
|
||||||
@@ -519,44 +518,48 @@ fn render_markdown_code_block(
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when(can_expand, |header| {
|
.child(
|
||||||
header.child(
|
IconButton::new(
|
||||||
IconButton::new(
|
("expand-collapse-code", ix),
|
||||||
("expand-collapse-code", ix),
|
if is_expanded {
|
||||||
if is_expanded {
|
IconName::ChevronUp
|
||||||
IconName::ChevronUp
|
|
||||||
} else {
|
|
||||||
IconName::ChevronDown
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.icon_color(Color::Muted)
|
|
||||||
.shape(ui::IconButtonShape::Square)
|
|
||||||
.tooltip(Tooltip::text(if is_expanded {
|
|
||||||
"Collapse Code"
|
|
||||||
} else {
|
} else {
|
||||||
"Expand Code"
|
IconName::ChevronDown
|
||||||
}))
|
},
|
||||||
.on_click({
|
|
||||||
let active_thread = active_thread.clone();
|
|
||||||
move |_event, _window, cx| {
|
|
||||||
active_thread.update(cx, |this, cx| {
|
|
||||||
this.toggle_codeblock_expanded(message_id, ix);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
});
|
.icon_color(Color::Muted)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.tooltip(Tooltip::text(if is_expanded {
|
||||||
|
"Collapse Code"
|
||||||
|
} else {
|
||||||
|
"Expand Code"
|
||||||
|
}))
|
||||||
|
.on_click({
|
||||||
|
let active_thread = active_thread.clone();
|
||||||
|
move |_event, _window, cx| {
|
||||||
|
active_thread.update(cx, |this, cx| {
|
||||||
|
this.toggle_codeblock_expanded(message_id, ix);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
let codeblock_header = h_flex()
|
let codeblock_header = h_flex()
|
||||||
.relative()
|
.relative()
|
||||||
.p_1()
|
.p_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.border_b_1()
|
|
||||||
.border_color(cx.theme().colors().border.opacity(0.6))
|
|
||||||
.bg(codeblock_header_bg)
|
.bg(codeblock_header_bg)
|
||||||
.rounded_t_md()
|
.map(|this| {
|
||||||
|
if !is_expanded {
|
||||||
|
this.rounded_md()
|
||||||
|
} else {
|
||||||
|
this.rounded_t_md()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border.opacity(0.6))
|
||||||
|
}
|
||||||
|
})
|
||||||
.children(label)
|
.children(label)
|
||||||
.child(control_buttons);
|
.child(control_buttons);
|
||||||
|
|
||||||
@@ -564,12 +567,12 @@ fn render_markdown_code_block(
|
|||||||
.group(CODEBLOCK_CONTAINER_GROUP)
|
.group(CODEBLOCK_CONTAINER_GROUP)
|
||||||
.my_2()
|
.my_2()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.rounded_lg()
|
.rounded_md()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().colors().border.opacity(0.6))
|
.border_color(cx.theme().colors().border.opacity(0.6))
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.child(codeblock_header)
|
.child(codeblock_header)
|
||||||
.when(can_expand && !is_expanded, |this| this.max_h_80())
|
.when(!is_expanded, |this| this.h(rems_from_px(31.)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_path(
|
fn open_path(
|
||||||
@@ -630,10 +633,13 @@ fn render_code_language(
|
|||||||
.map(|language| language.name().into())
|
.map(|language| language.name().into())
|
||||||
.unwrap_or(name_fallback);
|
.unwrap_or(name_fallback);
|
||||||
|
|
||||||
|
let label_size = rems(0.8125);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.px_1()
|
||||||
.children(icon_path.map(|icon| icon.color(Color::Muted).size(IconSize::Small)))
|
.gap_1p5()
|
||||||
.child(Label::new(language_label).size(LabelSize::Small))
|
.children(icon_path.map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)))
|
||||||
|
.child(div().text_size(label_size).child(language_label))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,7 +973,22 @@ impl ActiveThread {
|
|||||||
ThreadEvent::ShowError(error) => {
|
ThreadEvent::ShowError(error) => {
|
||||||
self.last_error = Some(error.clone());
|
self.last_error = Some(error.clone());
|
||||||
}
|
}
|
||||||
ThreadEvent::NewRequest | ThreadEvent::CompletionCanceled => {
|
ThreadEvent::NewRequest => {
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
ThreadEvent::CompletionCanceled => {
|
||||||
|
self.thread.update(cx, |thread, cx| {
|
||||||
|
thread.project().update(cx, |project, cx| {
|
||||||
|
project.set_agent_location(None, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
self.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
if workspace.is_being_followed(CollaboratorId::Agent) {
|
||||||
|
workspace.unfollow(CollaboratorId::Agent, window, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
ThreadEvent::StreamedCompletion
|
ThreadEvent::StreamedCompletion
|
||||||
@@ -977,9 +998,10 @@ impl ActiveThread {
|
|||||||
}
|
}
|
||||||
ThreadEvent::Stopped(reason) => match reason {
|
ThreadEvent::Stopped(reason) => match reason {
|
||||||
Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
|
Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
|
||||||
let thread = self.thread.read(cx);
|
let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
|
||||||
|
self.play_notification_sound(cx);
|
||||||
self.show_notification(
|
self.show_notification(
|
||||||
if thread.used_tools_since_last_user_message() {
|
if used_tools {
|
||||||
"Finished running tools"
|
"Finished running tools"
|
||||||
} else {
|
} else {
|
||||||
"New message"
|
"New message"
|
||||||
@@ -992,8 +1014,18 @@ impl ActiveThread {
|
|||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
ThreadEvent::ToolConfirmationNeeded => {
|
ThreadEvent::ToolConfirmationNeeded => {
|
||||||
|
self.play_notification_sound(cx);
|
||||||
self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
|
self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
|
||||||
}
|
}
|
||||||
|
ThreadEvent::ToolUseLimitReached => {
|
||||||
|
self.play_notification_sound(cx);
|
||||||
|
self.show_notification(
|
||||||
|
"Consecutive tool use limit reached.",
|
||||||
|
IconName::Warning,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
||||||
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
|
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
|
||||||
rendered_message.append_text(text, cx);
|
rendered_message.append_text(text, cx);
|
||||||
@@ -1027,6 +1059,7 @@ impl ActiveThread {
|
|||||||
self.edited_message(message_id, &message_segments, window, cx);
|
self.edited_message(message_id, &message_segments, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.scroll_to_bottom(cx);
|
||||||
self.save_thread(cx);
|
self.save_thread(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -1127,6 +1160,13 @@ impl ActiveThread {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn play_notification_sound(&self, cx: &mut App) {
|
||||||
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
if settings.play_sound_when_agent_done {
|
||||||
|
Audio::play_sound(Sound::AgentDone, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_notification(
|
fn show_notification(
|
||||||
&mut self,
|
&mut self,
|
||||||
caption: impl Into<SharedString>,
|
caption: impl Into<SharedString>,
|
||||||
@@ -1140,7 +1180,7 @@ impl ActiveThread {
|
|||||||
|
|
||||||
let title = self.thread.read(cx).summary().unwrap_or("Agent Panel");
|
let title = self.thread.read(cx).summary().unwrap_or("Agent Panel");
|
||||||
|
|
||||||
match AssistantSettings::get_global(cx).notify_when_agent_waiting {
|
match AgentSettings::get_global(cx).notify_when_agent_waiting {
|
||||||
NotifyWhenAgentWaiting::PrimaryScreen => {
|
NotifyWhenAgentWaiting::PrimaryScreen => {
|
||||||
if let Some(primary) = cx.primary_display() {
|
if let Some(primary) = cx.primary_display() {
|
||||||
self.pop_up(icon, caption.into(), title.clone(), window, primary, cx);
|
self.pop_up(icon, caption.into(), title.clone(), window, primary, cx);
|
||||||
@@ -1406,12 +1446,13 @@ impl ActiveThread {
|
|||||||
let request = language_model::LanguageModelRequest {
|
let request = language_model::LanguageModelRequest {
|
||||||
thread_id: None,
|
thread_id: None,
|
||||||
prompt_id: None,
|
prompt_id: None,
|
||||||
|
intent: None,
|
||||||
mode: None,
|
mode: None,
|
||||||
messages: vec![request_message],
|
messages: vec![request_message],
|
||||||
tools: vec![],
|
tools: vec![],
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
stop: vec![],
|
stop: vec![],
|
||||||
temperature: AssistantSettings::temperature_for_model(
|
temperature: AgentSettings::temperature_for_model(
|
||||||
&configured_model.model,
|
&configured_model.model,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
@@ -1461,7 +1502,7 @@ impl ActiveThread {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.context_store.update(cx, |store, _cx| store.clear());
|
self.context_store.update(cx, |store, cx| store.clear(cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1503,9 +1544,22 @@ impl ActiveThread {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
fn cancel_editing_message(
|
||||||
|
&mut self,
|
||||||
|
_: &menu::Cancel,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
self.editing_message.take();
|
self.editing_message.take();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
|
if let Some(workspace) = self.workspace.upgrade() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||||
|
panel.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_editing_message(
|
fn confirm_editing_message(
|
||||||
@@ -1540,11 +1594,15 @@ impl ActiveThread {
|
|||||||
let project = self.thread.read(cx).project().clone();
|
let project = self.thread.read(cx).project().clone();
|
||||||
let prompt_store = self.thread_store.read(cx).prompt_store().clone();
|
let prompt_store = self.thread_store.read(cx).prompt_store().clone();
|
||||||
|
|
||||||
|
let git_store = project.read(cx).git_store().clone();
|
||||||
|
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
||||||
|
|
||||||
let load_context_task =
|
let load_context_task =
|
||||||
crate::context::load_context(new_context, &project, &prompt_store, cx);
|
crate::context::load_context(new_context, &project, &prompt_store, cx);
|
||||||
self._load_edited_message_context_task =
|
self._load_edited_message_context_task =
|
||||||
Some(cx.spawn_in(window, async move |this, cx| {
|
Some(cx.spawn_in(window, async move |this, cx| {
|
||||||
let context = load_context_task.await;
|
let (context, checkpoint) =
|
||||||
|
futures::future::join(load_context_task, checkpoint).await;
|
||||||
let _ = this
|
let _ = this
|
||||||
.update_in(cx, |this, window, cx| {
|
.update_in(cx, |this, window, cx| {
|
||||||
this.thread.update(cx, |thread, cx| {
|
this.thread.update(cx, |thread, cx| {
|
||||||
@@ -1553,6 +1611,7 @@ impl ActiveThread {
|
|||||||
Role::User,
|
Role::User,
|
||||||
vec![MessageSegment::Text(edited_text)],
|
vec![MessageSegment::Text(edited_text)],
|
||||||
Some(context.loaded_context),
|
Some(context.loaded_context),
|
||||||
|
checkpoint.ok(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
for message_id in this.messages_after(message_id) {
|
for message_id in this.messages_after(message_id) {
|
||||||
@@ -1562,7 +1621,12 @@ impl ActiveThread {
|
|||||||
|
|
||||||
this.thread.update(cx, |thread, cx| {
|
this.thread.update(cx, |thread, cx| {
|
||||||
thread.advance_prompt_id();
|
thread.advance_prompt_id();
|
||||||
thread.send_to_model(model.model, Some(window.window_handle()), cx);
|
thread.send_to_model(
|
||||||
|
model.model,
|
||||||
|
CompletionIntent::UserPrompt,
|
||||||
|
Some(window.window_handle()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
this._load_edited_message_context_task = None;
|
this._load_edited_message_context_task = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -1743,6 +1807,11 @@ impl ActiveThread {
|
|||||||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||||
return Empty.into_any();
|
return Empty.into_any();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if message.is_hidden {
|
||||||
|
return Empty.into_any();
|
||||||
|
}
|
||||||
|
|
||||||
let message_creases = message.creases.clone();
|
let message_creases = message.creases.clone();
|
||||||
|
|
||||||
let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
|
let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
|
||||||
@@ -1778,6 +1847,7 @@ impl ActiveThread {
|
|||||||
|
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
let editor_bg_color = colors.editor_background;
|
let editor_bg_color = colors.editor_background;
|
||||||
|
let panel_bg = colors.panel_background;
|
||||||
|
|
||||||
let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::DocumentText)
|
let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::DocumentText)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
@@ -1798,7 +1868,6 @@ impl ActiveThread {
|
|||||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||||
|
|
||||||
let show_feedback = thread.is_turn_end(ix);
|
let show_feedback = thread.is_turn_end(ix);
|
||||||
|
|
||||||
let feedback_container = h_flex()
|
let feedback_container = h_flex()
|
||||||
.group("feedback_container")
|
.group("feedback_container")
|
||||||
.mt_1()
|
.mt_1()
|
||||||
@@ -1863,7 +1932,7 @@ impl ActiveThread {
|
|||||||
.child(open_as_markdown),
|
.child(open_as_markdown),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
None if AssistantSettings::get_global(cx).enable_feedback =>
|
None if AgentSettings::get_global(cx).enable_feedback =>
|
||||||
feedback_container
|
feedback_container
|
||||||
.child(
|
.child(
|
||||||
div().visible_on_hover("feedback_container").child(
|
div().visible_on_hover("feedback_container").child(
|
||||||
@@ -1971,65 +2040,89 @@ impl ActiveThread {
|
|||||||
.border_1()
|
.border_1()
|
||||||
.border_color(colors.border)
|
.border_color(colors.border)
|
||||||
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
||||||
.cursor_pointer()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
v_flex()
|
||||||
.p_2p5()
|
.p_2p5()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.items_end()
|
|
||||||
.children(message_content)
|
.children(message_content)
|
||||||
.when_some(editing_message_state, |this, state| {
|
.when_some(editing_message_state, |this, state| {
|
||||||
let focus_handle = state.editor.focus_handle(cx).clone();
|
let focus_handle = state.editor.focus_handle(cx).clone();
|
||||||
this.w_full().justify_between().child(
|
|
||||||
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_0p5()
|
.w_full()
|
||||||
|
.gap_1()
|
||||||
|
.justify_between()
|
||||||
|
.flex_wrap()
|
||||||
.child(
|
.child(
|
||||||
IconButton::new(
|
h_flex()
|
||||||
"cancel-edit-message",
|
.gap_1p5()
|
||||||
IconName::Close,
|
.child(
|
||||||
)
|
div()
|
||||||
.shape(ui::IconButtonShape::Square)
|
.opacity(0.8)
|
||||||
.icon_color(Color::Error)
|
.child(
|
||||||
.icon_size(IconSize::Small)
|
Icon::new(IconName::Warning)
|
||||||
.tooltip({
|
.size(IconSize::Indicator)
|
||||||
let focus_handle = focus_handle.clone();
|
.color(Color::Warning)
|
||||||
move |window, cx| {
|
),
|
||||||
Tooltip::for_action_in(
|
)
|
||||||
"Cancel Edit",
|
.child(
|
||||||
&menu::Cancel,
|
Label::new("Editing will restart the thread from this point.")
|
||||||
&focus_handle,
|
.color(Color::Muted)
|
||||||
window,
|
.size(LabelSize::XSmall),
|
||||||
cx,
|
),
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(Self::handle_cancel_click)),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
IconButton::new(
|
h_flex()
|
||||||
"confirm-edit-message",
|
.gap_0p5()
|
||||||
IconName::Return,
|
.child(
|
||||||
)
|
IconButton::new(
|
||||||
.disabled(state.editor.read(cx).is_empty(cx))
|
"cancel-edit-message",
|
||||||
.shape(ui::IconButtonShape::Square)
|
IconName::Close,
|
||||||
.icon_color(Color::Muted)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Regenerate",
|
|
||||||
&menu::Confirm,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
)
|
||||||
}
|
.shape(ui::IconButtonShape::Square)
|
||||||
})
|
.icon_color(Color::Error)
|
||||||
.on_click(
|
.icon_size(IconSize::Small)
|
||||||
cx.listener(Self::handle_regenerate_click),
|
.tooltip({
|
||||||
),
|
let focus_handle = focus_handle.clone();
|
||||||
),
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Cancel Edit",
|
||||||
|
&menu::Cancel,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(Self::handle_cancel_click)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"confirm-edit-message",
|
||||||
|
IconName::Return,
|
||||||
|
)
|
||||||
|
.disabled(state.editor.read(cx).is_empty(cx))
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.tooltip({
|
||||||
|
let focus_handle = focus_handle.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Regenerate",
|
||||||
|
&menu::Confirm,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click(
|
||||||
|
cx.listener(Self::handle_regenerate_click),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -2071,16 +2164,14 @@ impl ActiveThread {
|
|||||||
message_id > *editing_message_id
|
message_id > *editing_message_id
|
||||||
});
|
});
|
||||||
|
|
||||||
let panel_background = cx.theme().colors().panel_background;
|
|
||||||
|
|
||||||
let backdrop = div()
|
let backdrop = div()
|
||||||
.id("backdrop")
|
.id(("backdrop", ix))
|
||||||
.stop_mouse_events_except_scroll()
|
.size_full()
|
||||||
.absolute()
|
.absolute()
|
||||||
.inset_0()
|
.inset_0()
|
||||||
.size_full()
|
.bg(panel_bg)
|
||||||
.bg(panel_background)
|
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
|
.block_mouse_except_scroll()
|
||||||
.on_click(cx.listener(Self::handle_cancel_click));
|
.on_click(cx.listener(Self::handle_cancel_click));
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -2362,41 +2453,17 @@ impl ActiveThread {
|
|||||||
}),
|
}),
|
||||||
transform: Some(Arc::new({
|
transform: Some(Arc::new({
|
||||||
let active_thread = cx.entity();
|
let active_thread = cx.entity();
|
||||||
let editor_bg = cx.theme().colors().editor_background;
|
|
||||||
|
|
||||||
move |el, range, metadata, _, cx| {
|
|
||||||
let can_expand = metadata.line_count
|
|
||||||
>= MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK;
|
|
||||||
|
|
||||||
if !can_expand {
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
move |element, range, _, _, cx| {
|
||||||
let is_expanded = active_thread
|
let is_expanded = active_thread
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.is_codeblock_expanded(message_id, range.start);
|
.is_codeblock_expanded(message_id, range.start);
|
||||||
|
|
||||||
if is_expanded {
|
if is_expanded {
|
||||||
return el;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
el.child(
|
element
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.bottom_0()
|
|
||||||
.left_0()
|
|
||||||
.w_full()
|
|
||||||
.h_1_4()
|
|
||||||
.rounded_b_lg()
|
|
||||||
.bg(linear_gradient(
|
|
||||||
0.,
|
|
||||||
linear_color_stop(editor_bg, 0.),
|
|
||||||
linear_color_stop(
|
|
||||||
editor_bg.opacity(0.),
|
|
||||||
1.,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -3067,7 +3134,7 @@ impl ActiveThread {
|
|||||||
.on_click(cx.listener(
|
.on_click(cx.listener(
|
||||||
move |this, event, window, cx| {
|
move |this, event, window, cx| {
|
||||||
if let Some(fs) = fs.clone() {
|
if let Some(fs) = fs.clone() {
|
||||||
update_settings_file::<AssistantSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
|settings, _| {
|
|settings, _| {
|
||||||
@@ -3408,6 +3475,11 @@ impl ActiveThread {
|
|||||||
.or_insert(true);
|
.or_insert(true);
|
||||||
*is_expanded = !*is_expanded;
|
*is_expanded = !*is_expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.list_state.reset(self.messages.len());
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ActiveThreadEvent {
|
pub enum ActiveThreadEvent {
|
||||||
@@ -3601,3 +3673,164 @@ fn open_editor_at_position(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use assistant_tool::{ToolRegistry, ToolWorkingSet};
|
||||||
|
use editor::EditorSettings;
|
||||||
|
use fs::FakeFs;
|
||||||
|
use gpui::{AppContext, TestAppContext, VisualTestContext};
|
||||||
|
use language_model::{LanguageModel, fake_provider::FakeLanguageModel};
|
||||||
|
use project::Project;
|
||||||
|
use prompt_store::PromptBuilder;
|
||||||
|
use serde_json::json;
|
||||||
|
use settings::SettingsStore;
|
||||||
|
use util::path;
|
||||||
|
use workspace::CollaboratorId;
|
||||||
|
|
||||||
|
use crate::{ContextLoadResult, thread_store};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_agent_is_unfollowed_after_cancelling_completion(cx: &mut TestAppContext) {
|
||||||
|
init_test_settings(cx);
|
||||||
|
|
||||||
|
let project = create_test_project(
|
||||||
|
cx,
|
||||||
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (cx, _active_thread, workspace, thread, model) =
|
||||||
|
setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
|
// Insert user message without any context (empty context vector)
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.insert_user_message(
|
||||||
|
"What is the best way to learn Rust?",
|
||||||
|
ContextLoadResult::default(),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stream response to user message
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
let request =
|
||||||
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx);
|
||||||
|
thread.stream_completion(request, model, cx.active_window(), cx)
|
||||||
|
});
|
||||||
|
// Follow the agent
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.follow(CollaboratorId::Agent, window, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
assert!(cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
|
||||||
|
|
||||||
|
// Cancel the current completion
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.cancel_last_completion(cx.active_window(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
|
// No longer following the agent
|
||||||
|
assert!(!cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_test_settings(cx: &mut TestAppContext) {
|
||||||
|
cx.update(|cx| {
|
||||||
|
let settings_store = SettingsStore::test(cx);
|
||||||
|
cx.set_global(settings_store);
|
||||||
|
language::init(cx);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
AgentSettings::register(cx);
|
||||||
|
prompt_store::init(cx);
|
||||||
|
thread_store::init(cx);
|
||||||
|
workspace::init_settings(cx);
|
||||||
|
language_model::init_settings(cx);
|
||||||
|
ThemeSettings::register(cx);
|
||||||
|
EditorSettings::register(cx);
|
||||||
|
ToolRegistry::default_global(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to create a test project with test files
|
||||||
|
async fn create_test_project(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
files: serde_json::Value,
|
||||||
|
) -> Entity<Project> {
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(path!("/test"), files).await;
|
||||||
|
Project::test(fs, [path!("/test").as_ref()], cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_test_environment(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
project: Entity<Project>,
|
||||||
|
) -> (
|
||||||
|
&mut VisualTestContext,
|
||||||
|
Entity<ActiveThread>,
|
||||||
|
Entity<Workspace>,
|
||||||
|
Entity<Thread>,
|
||||||
|
Arc<dyn LanguageModel>,
|
||||||
|
) {
|
||||||
|
let (workspace, cx) =
|
||||||
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
|
let thread_store = cx
|
||||||
|
.update(|_, cx| {
|
||||||
|
ThreadStore::load(
|
||||||
|
project.clone(),
|
||||||
|
cx.new(|_| ToolWorkingSet::default()),
|
||||||
|
None,
|
||||||
|
Arc::new(PromptBuilder::new(None).unwrap()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let text_thread_store = cx
|
||||||
|
.update(|_, cx| {
|
||||||
|
TextThreadStore::new(
|
||||||
|
project.clone(),
|
||||||
|
Arc::new(PromptBuilder::new(None).unwrap()),
|
||||||
|
Default::default(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||||
|
let context_store =
|
||||||
|
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
||||||
|
|
||||||
|
let model = FakeLanguageModel::default();
|
||||||
|
let model: Arc<dyn LanguageModel> = Arc::new(model);
|
||||||
|
|
||||||
|
let language_registry = LanguageRegistry::new(cx.executor());
|
||||||
|
let language_registry = Arc::new(language_registry);
|
||||||
|
|
||||||
|
let active_thread = cx.update(|window, cx| {
|
||||||
|
cx.new(|cx| {
|
||||||
|
ActiveThread::new(
|
||||||
|
thread.clone(),
|
||||||
|
thread_store.clone(),
|
||||||
|
text_thread_store,
|
||||||
|
context_store.clone(),
|
||||||
|
language_registry.clone(),
|
||||||
|
workspace.downgrade(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
(cx, active_thread, workspace, thread, model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ mod ui;
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_settings::{AgentProfileId, AssistantSettings, LanguageModelSelection};
|
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use feature_flags::FeatureFlagAppExt as _;
|
use feature_flags::FeatureFlagAppExt as _;
|
||||||
@@ -69,6 +69,7 @@ actions!(
|
|||||||
AddContextServer,
|
AddContextServer,
|
||||||
RemoveSelectedThread,
|
RemoveSelectedThread,
|
||||||
Chat,
|
Chat,
|
||||||
|
ChatWithFollow,
|
||||||
CycleNextInlineAssist,
|
CycleNextInlineAssist,
|
||||||
CyclePreviousInlineAssist,
|
CyclePreviousInlineAssist,
|
||||||
FocusUp,
|
FocusUp,
|
||||||
@@ -85,6 +86,10 @@ actions!(
|
|||||||
KeepAll,
|
KeepAll,
|
||||||
Follow,
|
Follow,
|
||||||
ResetTrialUpsell,
|
ResetTrialUpsell,
|
||||||
|
ResetTrialEndUpsell,
|
||||||
|
ContinueThread,
|
||||||
|
ContinueWithBurnMode,
|
||||||
|
ToggleBurnMode,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,14 +121,19 @@ pub fn init(
|
|||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
is_eval: bool,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
SlashCommandSettings::register(cx);
|
SlashCommandSettings::register(cx);
|
||||||
|
|
||||||
assistant_context_editor::init(client.clone(), cx);
|
assistant_context_editor::init(client.clone(), cx);
|
||||||
rules_library::init(cx);
|
rules_library::init(cx);
|
||||||
init_language_model_settings(cx);
|
if !is_eval {
|
||||||
|
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||||
|
// we're not running inside of the eval.
|
||||||
|
init_language_model_settings(cx);
|
||||||
|
}
|
||||||
assistant_slash_command::init(cx);
|
assistant_slash_command::init(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(cx);
|
||||||
agent_panel::init(cx);
|
agent_panel::init(cx);
|
||||||
@@ -167,7 +177,7 @@ fn init_language_model_settings(cx: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
|
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
|
||||||
language_model::SelectedModel {
|
language_model::SelectedModel {
|
||||||
@@ -216,7 +226,6 @@ fn register_slash_commands(cx: &mut App) {
|
|||||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||||
slash_command_registry.register_command(assistant_slash_commands::TerminalSlashCommand, true);
|
|
||||||
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
||||||
slash_command_registry
|
slash_command_registry
|
||||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ mod tool_picker;
|
|||||||
|
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use assistant_settings::AssistantSettings;
|
use agent_settings::AgentSettings;
|
||||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::ContextServerId;
|
use context_server::ContextServerId;
|
||||||
@@ -249,7 +249,7 @@ impl AgentConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let always_allow_tool_actions = AssistantSettings::get_global(cx).always_allow_tool_actions;
|
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
@@ -277,7 +277,7 @@ impl AgentConfiguration {
|
|||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
move |state, _window, cx| {
|
move |state, _window, cx| {
|
||||||
let allow = state == &ToggleState::Selected;
|
let allow = state == &ToggleState::Selected;
|
||||||
update_settings_file::<AssistantSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
move |settings, _| {
|
move |settings, _| {
|
||||||
@@ -290,7 +290,7 @@ impl AgentConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let single_file_review = AssistantSettings::get_global(cx).single_file_review;
|
let single_file_review = AgentSettings::get_global(cx).single_file_review;
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
@@ -315,7 +315,7 @@ impl AgentConfiguration {
|
|||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
move |state, _window, cx| {
|
move |state, _window, cx| {
|
||||||
let allow = state == &ToggleState::Selected;
|
let allow = state == &ToggleState::Selected;
|
||||||
update_settings_file::<AssistantSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
move |settings, _| {
|
move |settings, _| {
|
||||||
@@ -327,6 +327,44 @@ impl AgentConfiguration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.gap_4()
|
||||||
|
.justify_between()
|
||||||
|
.flex_wrap()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.max_w_5_6()
|
||||||
|
.child(Label::new("Play sound when finished generating"))
|
||||||
|
.child(
|
||||||
|
Label::new(
|
||||||
|
"Hear a notification sound when the agent is done generating changes or needs your input.",
|
||||||
|
)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
|
||||||
|
.color(SwitchColor::Accent)
|
||||||
|
.on_click({
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
move |state, _window, cx| {
|
||||||
|
let allow = state == &ToggleState::Selected;
|
||||||
|
update_settings_file::<AgentSettings>(
|
||||||
|
fs.clone(),
|
||||||
|
cx,
|
||||||
|
move |settings, _| {
|
||||||
|
settings.set_play_sound_when_agent_done(allow);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
.p(DynamicSpacing::Base16.rems(cx))
|
||||||
@@ -337,6 +375,7 @@ impl AgentConfiguration {
|
|||||||
.child(Headline::new("General Settings"))
|
.child(Headline::new("General Settings"))
|
||||||
.child(self.render_command_permission(cx))
|
.child(self.render_command_permission(cx))
|
||||||
.child(self.render_single_file_review(cx))
|
.child(self.render_single_file_review(cx))
|
||||||
|
.child(self.render_sound_notification(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_context_servers_section(
|
fn render_context_servers_section(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ mod profile_modal_header;
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings, builtin_profiles};
|
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
|
||||||
use assistant_tool::ToolWorkingSet;
|
use assistant_tool::ToolWorkingSet;
|
||||||
use convert_case::{Case, Casing as _};
|
use convert_case::{Case, Casing as _};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
@@ -42,7 +42,7 @@ enum Mode {
|
|||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
|
pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
let mut builtin_profiles = Vec::new();
|
let mut builtin_profiles = Vec::new();
|
||||||
let mut custom_profiles = Vec::new();
|
let mut custom_profiles = Vec::new();
|
||||||
@@ -196,7 +196,7 @@ impl ManageProfilesModal {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -234,7 +234,7 @@ impl ManageProfilesModal {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -270,7 +270,7 @@ impl ManageProfilesModal {
|
|||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::ChooseProfile { .. } => {}
|
Mode::ChooseProfile { .. } => {}
|
||||||
Mode::NewProfile(mode) => {
|
Mode::NewProfile(mode) => {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
let base_profile = mode
|
let base_profile = mode
|
||||||
.base_profile_id
|
.base_profile_id
|
||||||
@@ -332,7 +332,7 @@ impl ManageProfilesModal {
|
|||||||
profile: AgentProfile,
|
profile: AgentProfile,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
|
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||||
move |settings, _cx| {
|
move |settings, _cx| {
|
||||||
settings.create_profile(profile_id, profile).log_err();
|
settings.create_profile(profile_id, profile).log_err();
|
||||||
}
|
}
|
||||||
@@ -485,7 +485,7 @@ impl ManageProfilesModal {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
|
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
|
||||||
settings
|
settings
|
||||||
@@ -518,7 +518,7 @@ impl ManageProfilesModal {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
let profile_id = &settings.default_profile;
|
let profile_id = &settings.default_profile;
|
||||||
let profile_name = settings
|
let profile_name = settings
|
||||||
@@ -712,7 +712,7 @@ impl ManageProfilesModal {
|
|||||||
|
|
||||||
impl Render for ManageProfilesModal {
|
impl Render for ManageProfilesModal {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
let go_back_item = div()
|
let go_back_item = div()
|
||||||
.id("cancel-item")
|
.id("cancel-item")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
||||||
use assistant_settings::{
|
use agent_settings::{
|
||||||
AgentProfile, AgentProfileContent, AgentProfileId, AssistantSettings, AssistantSettingsContent,
|
AgentProfile, AgentProfileContent, AgentProfileId, AgentSettings, AgentSettingsContent,
|
||||||
ContextServerPresetContent,
|
ContextServerPresetContent,
|
||||||
};
|
};
|
||||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||||
@@ -259,7 +259,7 @@ impl PickerDelegate for ToolPickerDelegate {
|
|||||||
is_enabled
|
is_enabled
|
||||||
};
|
};
|
||||||
|
|
||||||
let active_profile_id = &AssistantSettings::get_global(cx).default_profile;
|
let active_profile_id = &AgentSettings::get_global(cx).default_profile;
|
||||||
if active_profile_id == &self.profile_id {
|
if active_profile_id == &self.profile_id {
|
||||||
self.thread_store
|
self.thread_store
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
@@ -268,12 +268,12 @@ impl PickerDelegate for ToolPickerDelegate {
|
|||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
|
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||||
let profile_id = self.profile_id.clone();
|
let profile_id = self.profile_id.clone();
|
||||||
let default_profile = self.profile.clone();
|
let default_profile = self.profile.clone();
|
||||||
let server_id = server_id.clone();
|
let server_id = server_id.clone();
|
||||||
let tool_name = tool_name.clone();
|
let tool_name = tool_name.clone();
|
||||||
move |settings: &mut AssistantSettingsContent, _cx| {
|
move |settings: &mut AgentSettingsContent, _cx| {
|
||||||
settings
|
settings
|
||||||
.v2_setting(|v2_settings| {
|
.v2_setting(|v2_settings| {
|
||||||
let profiles = v2_settings.profiles.get_or_insert_default();
|
let profiles = v2_settings.profiles.get_or_insert_default();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent};
|
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent};
|
||||||
|
use agent_settings::AgentSettings;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_settings::AssistantSettings;
|
|
||||||
use buffer_diff::DiffHunkStatus;
|
use buffer_diff::DiffHunkStatus;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
@@ -699,7 +699,7 @@ fn render_diff_hunk_controls(
|
|||||||
.rounded_b_md()
|
.rounded_b_md()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.occlude()
|
.block_mouse_except_scroll()
|
||||||
.shadow_md()
|
.shadow_md()
|
||||||
.children(vec![
|
.children(vec![
|
||||||
Button::new(("reject", row as u64), "Reject")
|
Button::new(("reject", row as u64), "Reject")
|
||||||
@@ -1253,9 +1253,9 @@ impl AgentDiff {
|
|||||||
|
|
||||||
let settings_subscription = cx.observe_global_in::<SettingsStore>(window, {
|
let settings_subscription = cx.observe_global_in::<SettingsStore>(window, {
|
||||||
let workspace = workspace.clone();
|
let workspace = workspace.clone();
|
||||||
let mut was_active = AssistantSettings::get_global(cx).single_file_review;
|
let mut was_active = AgentSettings::get_global(cx).single_file_review;
|
||||||
move |this, window, cx| {
|
move |this, window, cx| {
|
||||||
let is_active = AssistantSettings::get_global(cx).single_file_review;
|
let is_active = AgentSettings::get_global(cx).single_file_review;
|
||||||
if was_active != is_active {
|
if was_active != is_active {
|
||||||
was_active = is_active;
|
was_active = is_active;
|
||||||
this.update_reviewing_editors(&workspace, window, cx);
|
this.update_reviewing_editors(&workspace, window, cx);
|
||||||
@@ -1348,6 +1348,7 @@ impl AgentDiff {
|
|||||||
ThreadEvent::NewRequest
|
ThreadEvent::NewRequest
|
||||||
| ThreadEvent::Stopped(Ok(StopReason::EndTurn))
|
| ThreadEvent::Stopped(Ok(StopReason::EndTurn))
|
||||||
| ThreadEvent::Stopped(Ok(StopReason::MaxTokens))
|
| ThreadEvent::Stopped(Ok(StopReason::MaxTokens))
|
||||||
|
| ThreadEvent::Stopped(Ok(StopReason::Refusal))
|
||||||
| ThreadEvent::Stopped(Err(_))
|
| ThreadEvent::Stopped(Err(_))
|
||||||
| ThreadEvent::ShowError(_)
|
| ThreadEvent::ShowError(_)
|
||||||
| ThreadEvent::CompletionCanceled => {
|
| ThreadEvent::CompletionCanceled => {
|
||||||
@@ -1371,6 +1372,7 @@ impl AgentDiff {
|
|||||||
| ThreadEvent::ToolFinished { .. }
|
| ThreadEvent::ToolFinished { .. }
|
||||||
| ThreadEvent::CheckpointChanged
|
| ThreadEvent::CheckpointChanged
|
||||||
| ThreadEvent::ToolConfirmationNeeded
|
| ThreadEvent::ToolConfirmationNeeded
|
||||||
|
| ThreadEvent::ToolUseLimitReached
|
||||||
| ThreadEvent::CancelEditing => {}
|
| ThreadEvent::CancelEditing => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1460,10 +1462,13 @@ impl AgentDiff {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if !AssistantSettings::get_global(cx).single_file_review {
|
if !AgentSettings::get_global(cx).single_file_review {
|
||||||
for (editor, _) in self.reviewing_editors.drain() {
|
for (editor, _) in self.reviewing_editors.drain() {
|
||||||
editor
|
editor
|
||||||
.update(cx, |editor, cx| editor.end_temporary_diff_override(cx))
|
.update(cx, |editor, cx| {
|
||||||
|
editor.end_temporary_diff_override(cx);
|
||||||
|
editor.unregister_addon::<EditorAgentDiffAddon>();
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1559,7 +1564,10 @@ impl AgentDiff {
|
|||||||
|
|
||||||
if in_workspace {
|
if in_workspace {
|
||||||
editor
|
editor
|
||||||
.update(cx, |editor, cx| editor.end_temporary_diff_override(cx))
|
.update(cx, |editor, cx| {
|
||||||
|
editor.end_temporary_diff_override(cx);
|
||||||
|
editor.unregister_addon::<EditorAgentDiffAddon>();
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
self.reviewing_editors.remove(&editor);
|
self.reviewing_editors.remove(&editor);
|
||||||
}
|
}
|
||||||
@@ -1735,7 +1743,7 @@ impl editor::Addon for EditorAgentDiffAddon {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Keep, ThreadStore, thread_store};
|
use crate::{Keep, ThreadStore, thread_store};
|
||||||
use assistant_settings::AssistantSettings;
|
use agent_settings::AgentSettings;
|
||||||
use assistant_tool::ToolWorkingSet;
|
use assistant_tool::ToolWorkingSet;
|
||||||
use editor::EditorSettings;
|
use editor::EditorSettings;
|
||||||
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
|
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
|
||||||
@@ -1754,7 +1762,7 @@ mod tests {
|
|||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
@@ -1910,7 +1918,7 @@ mod tests {
|
|||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use assistant_settings::AssistantSettings;
|
use agent_settings::AgentSettings;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Entity, FocusHandle, SharedString};
|
use gpui::{Entity, FocusHandle, SharedString};
|
||||||
|
use picker::popover_menu::PickerPopoverMenu;
|
||||||
|
|
||||||
use crate::Thread;
|
use crate::Thread;
|
||||||
use language_model::{ConfiguredModel, LanguageModelRegistry};
|
use assistant_context_editor::language_model_selector::{
|
||||||
use language_model_selector::{
|
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||||
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
|
|
||||||
};
|
};
|
||||||
|
use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
@@ -35,7 +36,7 @@ impl AgentModelSelector {
|
|||||||
Self {
|
Self {
|
||||||
selector: cx.new(move |cx| {
|
selector: cx.new(move |cx| {
|
||||||
let fs = fs.clone();
|
let fs = fs.clone();
|
||||||
LanguageModelSelector::new(
|
language_model_selector(
|
||||||
{
|
{
|
||||||
let model_type = model_type.clone();
|
let model_type = model_type.clone();
|
||||||
move |cx| match &model_type {
|
move |cx| match &model_type {
|
||||||
@@ -63,7 +64,7 @@ impl AgentModelSelector {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
update_settings_file::<AssistantSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
move |settings, _cx| {
|
move |settings, _cx| {
|
||||||
@@ -72,7 +73,7 @@ impl AgentModelSelector {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
ModelType::InlineAssistant => {
|
ModelType::InlineAssistant => {
|
||||||
update_settings_file::<AssistantSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
move |settings, _cx| {
|
move |settings, _cx| {
|
||||||
@@ -100,15 +101,14 @@ impl AgentModelSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentModelSelector {
|
impl Render for AgentModelSelector {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
let model = self.selector.read(cx).active_model(cx);
|
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||||
let model_name = model
|
let model_name = model
|
||||||
.map(|model| model.model.name().0)
|
.map(|model| model.model.name().0)
|
||||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||||
|
PickerPopoverMenu::new(
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
Button::new("active-model", model_name)
|
Button::new("active-model", model_name)
|
||||||
.label_size(LabelSize::Small)
|
.label_size(LabelSize::Small)
|
||||||
@@ -127,7 +127,9 @@ impl Render for AgentModelSelector {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
gpui::Corner::BottomRight,
|
gpui::Corner::BottomRight,
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
.with_handle(self.menu_handle.clone())
|
.with_handle(self.menu_handle.clone())
|
||||||
|
.render(window, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::context::ContextLoadResult;
|
use crate::context::ContextLoadResult;
|
||||||
use crate::inline_prompt_editor::CodegenStatus;
|
use crate::inline_prompt_editor::CodegenStatus;
|
||||||
use crate::{context::load_context, context_store::ContextStore};
|
use crate::{context::load_context, context_store::ContextStore};
|
||||||
use anyhow::Result;
|
use agent_settings::AgentSettings;
|
||||||
use assistant_settings::AssistantSettings;
|
use anyhow::{Context as _, Result};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||||
@@ -34,6 +34,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
|
use zed_llm_client::CompletionIntent;
|
||||||
|
|
||||||
pub struct BufferCodegen {
|
pub struct BufferCodegen {
|
||||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
alternatives: Vec<Entity<CodegenAlternative>>,
|
||||||
@@ -419,16 +420,16 @@ impl CodegenAlternative {
|
|||||||
if start_buffer.remote_id() == end_buffer.remote_id() {
|
if start_buffer.remote_id() == end_buffer.remote_id() {
|
||||||
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
|
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow::anyhow!("invalid transformation range"));
|
anyhow::bail!("invalid transformation range");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow::anyhow!("invalid transformation range"));
|
anyhow::bail!("invalid transformation range");
|
||||||
};
|
};
|
||||||
|
|
||||||
let prompt = self
|
let prompt = self
|
||||||
.builder
|
.builder
|
||||||
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
|
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
|
.context("generating content prompt")?;
|
||||||
|
|
||||||
let context_task = self.context_store.as_ref().map(|context_store| {
|
let context_task = self.context_store.as_ref().map(|context_store| {
|
||||||
if let Some(project) = self.project.upgrade() {
|
if let Some(project) = self.project.upgrade() {
|
||||||
@@ -443,7 +444,7 @@ impl CodegenAlternative {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
let temperature = AgentSettings::temperature_for_model(&model, cx);
|
||||||
|
|
||||||
Ok(cx.spawn(async move |_cx| {
|
Ok(cx.spawn(async move |_cx| {
|
||||||
let mut request_message = LanguageModelRequestMessage {
|
let mut request_message = LanguageModelRequestMessage {
|
||||||
@@ -464,6 +465,7 @@ impl CodegenAlternative {
|
|||||||
LanguageModelRequest {
|
LanguageModelRequest {
|
||||||
thread_id: None,
|
thread_id: None,
|
||||||
prompt_id: None,
|
prompt_id: None,
|
||||||
|
intent: Some(CompletionIntent::InlineAssist),
|
||||||
mode: None,
|
mode: None,
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
|
|||||||
@@ -734,6 +734,7 @@ impl Display for RulesContext {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ImageContext {
|
pub struct ImageContext {
|
||||||
pub project_path: Option<ProjectPath>,
|
pub project_path: Option<ProjectPath>,
|
||||||
|
pub full_path: Option<Arc<Path>>,
|
||||||
pub original_image: Arc<gpui::Image>,
|
pub original_image: Arc<gpui::Image>,
|
||||||
// TODO: handle this elsewhere and remove `ignore-interior-mutability` opt-out in clippy.toml
|
// TODO: handle this elsewhere and remove `ignore-interior-mutability` opt-out in clippy.toml
|
||||||
// needed due to a false positive of `clippy::mutable_key_type`.
|
// needed due to a false positive of `clippy::mutable_key_type`.
|
||||||
|
|||||||
@@ -766,6 +766,7 @@ pub(crate) fn insert_crease_for_mention(
|
|||||||
|
|
||||||
let ids = editor.insert_creases(vec![crease.clone()], cx);
|
let ids = editor.insert_creases(vec![crease.clone()], cx);
|
||||||
editor.fold_creases(vec![crease], false, window, cx);
|
editor.fold_creases(vec![crease], false, window, cx);
|
||||||
|
|
||||||
Some(ids[0])
|
Some(ids[0])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use http_client::HttpClientWithUrl;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{Buffer, CodeLabel, HighlightId};
|
use language::{Buffer, CodeLabel, HighlightId};
|
||||||
use lsp::CompletionContext;
|
use lsp::CompletionContext;
|
||||||
use project::{Completion, CompletionIntent, ProjectPath, Symbol, WorktreeId};
|
use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, Symbol, WorktreeId};
|
||||||
use prompt_store::PromptStore;
|
use prompt_store::PromptStore;
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use text::{Anchor, OffsetRangeExt, ToPoint};
|
use text::{Anchor, OffsetRangeExt, ToPoint};
|
||||||
@@ -322,7 +322,10 @@ impl ContextPickerCompletionProvider {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let new_text = selection_infos.iter().map(|(_, link, _)| link).join(" ");
|
let new_text = format!(
|
||||||
|
"{} ",
|
||||||
|
selection_infos.iter().map(|(_, link, _)| link).join(" ")
|
||||||
|
);
|
||||||
|
|
||||||
let callback = Arc::new({
|
let callback = Arc::new({
|
||||||
let context_store = context_store.clone();
|
let context_store = context_store.clone();
|
||||||
@@ -420,7 +423,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
} else {
|
} else {
|
||||||
IconName::MessageBubbles
|
IconName::MessageBubbles
|
||||||
};
|
};
|
||||||
let new_text = MentionLink::for_thread(&thread_entry);
|
let new_text = format!("{} ", MentionLink::for_thread(&thread_entry));
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
@@ -435,7 +438,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
thread_entry.title().clone(),
|
thread_entry.title().clone(),
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.start,
|
source_range.start,
|
||||||
new_text_len,
|
new_text_len - 1,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
move |window, cx| match &thread_entry {
|
move |window, cx| match &thread_entry {
|
||||||
@@ -489,7 +492,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
) -> Completion {
|
) -> Completion {
|
||||||
let new_text = MentionLink::for_rule(&rules);
|
let new_text = format!("{} ", MentionLink::for_rule(&rules));
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
@@ -504,7 +507,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
rules.title.clone(),
|
rules.title.clone(),
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.start,
|
source_range.start,
|
||||||
new_text_len,
|
new_text_len - 1,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
@@ -526,7 +529,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
http_client: Arc<HttpClientWithUrl>,
|
http_client: Arc<HttpClientWithUrl>,
|
||||||
) -> Completion {
|
) -> Completion {
|
||||||
let new_text = MentionLink::for_fetch(&url_to_fetch);
|
let new_text = format!("{} ", MentionLink::for_fetch(&url_to_fetch));
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
@@ -541,7 +544,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
url_to_fetch.clone(),
|
url_to_fetch.clone(),
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.start,
|
source_range.start,
|
||||||
new_text_len,
|
new_text_len - 1,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
@@ -550,7 +553,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
let url_to_fetch = url_to_fetch.clone();
|
let url_to_fetch = url_to_fetch.clone();
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
if let Some(context) = context_store
|
if let Some(context) = context_store
|
||||||
.update(cx, |context_store, _| {
|
.read_with(cx, |context_store, _| {
|
||||||
context_store.get_url_context(url_to_fetch.clone())
|
context_store.get_url_context(url_to_fetch.clone())
|
||||||
})
|
})
|
||||||
.ok()?
|
.ok()?
|
||||||
@@ -611,7 +614,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
crease_icon_path.clone()
|
crease_icon_path.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_text = MentionLink::for_file(&file_name, &full_path);
|
let new_text = format!("{} ", MentionLink::for_file(&file_name, &full_path));
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Completion {
|
Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
@@ -626,7 +629,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
file_name,
|
file_name,
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.start,
|
source_range.start,
|
||||||
new_text_len,
|
new_text_len - 1,
|
||||||
editor,
|
editor,
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
@@ -682,7 +685,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
label.push_str(" ", None);
|
label.push_str(" ", None);
|
||||||
label.push_str(&file_name, comment_id);
|
label.push_str(&file_name, comment_id);
|
||||||
|
|
||||||
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
|
let new_text = format!("{} ", MentionLink::for_symbol(&symbol.name, &full_path));
|
||||||
let new_text_len = new_text.len();
|
let new_text_len = new_text.len();
|
||||||
Some(Completion {
|
Some(Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
@@ -697,7 +700,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
symbol.name.clone().into(),
|
symbol.name.clone().into(),
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.start,
|
source_range.start,
|
||||||
new_text_len,
|
new_text_len - 1,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
@@ -743,7 +746,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
_trigger: CompletionContext,
|
_trigger: CompletionContext,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Task<Result<Option<Vec<Completion>>>> {
|
) -> Task<Result<Vec<CompletionResponse>>> {
|
||||||
let state = buffer.update(cx, |buffer, _cx| {
|
let state = buffer.update(cx, |buffer, _cx| {
|
||||||
let position = buffer_position.to_point(buffer);
|
let position = buffer_position.to_point(buffer);
|
||||||
let line_start = Point::new(position.row, 0);
|
let line_start = Point::new(position.row, 0);
|
||||||
@@ -753,13 +756,13 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
MentionCompletion::try_parse(line, offset_to_line)
|
MentionCompletion::try_parse(line, offset_to_line)
|
||||||
});
|
});
|
||||||
let Some(state) = state else {
|
let Some(state) = state else {
|
||||||
return Task::ready(Ok(None));
|
return Task::ready(Ok(Vec::new()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some((workspace, context_store)) =
|
let Some((workspace, context_store)) =
|
||||||
self.workspace.upgrade().zip(self.context_store.upgrade())
|
self.workspace.upgrade().zip(self.context_store.upgrade())
|
||||||
else {
|
else {
|
||||||
return Task::ready(Ok(None));
|
return Task::ready(Ok(Vec::new()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
@@ -812,10 +815,10 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
let matches = search_task.await;
|
let matches = search_task.await;
|
||||||
let Some(editor) = editor.upgrade() else {
|
let Some(editor) = editor.upgrade() else {
|
||||||
return Ok(None);
|
return Ok(Vec::new());
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(cx.update(|cx| {
|
let completions = cx.update(|cx| {
|
||||||
matches
|
matches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|mat| match mat {
|
.filter_map(|mat| match mat {
|
||||||
@@ -898,7 +901,14 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})?))
|
})?;
|
||||||
|
|
||||||
|
Ok(vec![CompletionResponse {
|
||||||
|
completions,
|
||||||
|
// Since this does its own filtering (see `filter_completions()` returns false),
|
||||||
|
// there is no benefit to computing whether this set of completions is incomplete.
|
||||||
|
is_incomplete: true,
|
||||||
|
}])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,7 +1223,7 @@ mod tests {
|
|||||||
assert_eq!(worktrees.len(), 1);
|
assert_eq!(worktrees.len(), 1);
|
||||||
worktrees.pop().unwrap()
|
worktrees.pop().unwrap()
|
||||||
});
|
});
|
||||||
let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
|
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||||
|
|
||||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||||
|
|
||||||
@@ -1286,7 +1296,7 @@ mod tests {
|
|||||||
.map(Entity::downgrade)
|
.map(Entity::downgrade)
|
||||||
});
|
});
|
||||||
window.focus(&editor.focus_handle(cx));
|
window.focus(&editor.focus_handle(cx));
|
||||||
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
None,
|
None,
|
||||||
@@ -1353,7 +1363,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt)",);
|
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
@@ -1364,7 +1374,7 @@ mod tests {
|
|||||||
cx.simulate_input(" ");
|
cx.simulate_input(" ");
|
||||||
|
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ",);
|
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
@@ -1377,7 +1387,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
|
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
|
||||||
);
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1391,7 +1401,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
|
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
|
||||||
);
|
);
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1409,14 +1419,14 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt)"
|
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) "
|
||||||
);
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 6)..Point::new(0, 37),
|
Point::new(0, 6)..Point::new(0, 37),
|
||||||
Point::new(0, 44)..Point::new(0, 79)
|
Point::new(0, 45)..Point::new(0, 80)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1426,14 +1436,14 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt)\n@"
|
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@"
|
||||||
);
|
);
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 6)..Point::new(0, 37),
|
Point::new(0, 6)..Point::new(0, 37),
|
||||||
Point::new(0, 44)..Point::new(0, 79)
|
Point::new(0, 45)..Point::new(0, 80)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1447,14 +1457,14 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt)\n[@six.txt](@file:dir/b/six.txt)"
|
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) "
|
||||||
);
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 6)..Point::new(0, 37),
|
Point::new(0, 6)..Point::new(0, 37),
|
||||||
Point::new(0, 44)..Point::new(0, 79),
|
Point::new(0, 45)..Point::new(0, 80),
|
||||||
Point::new(1, 0)..Point::new(1, 31)
|
Point::new(1, 0)..Point::new(1, 31)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use std::ops::Range;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_context_editor::AssistantContext;
|
use assistant_context_editor::AssistantContext;
|
||||||
use collections::{HashSet, IndexSet};
|
use collections::{HashSet, IndexSet};
|
||||||
use futures::{self, FutureExt};
|
use futures::{self, FutureExt};
|
||||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||||
use language::Buffer;
|
use language::{Buffer, File as _};
|
||||||
use language_model::LanguageModelImage;
|
use language_model::LanguageModelImage;
|
||||||
use project::image_store::is_image_file;
|
use project::image_store::is_image_file;
|
||||||
use project::{Project, ProjectItem, ProjectPath, Symbol};
|
use project::{Project, ProjectItem, ProjectPath, Symbol};
|
||||||
@@ -58,9 +58,10 @@ impl ContextStore {
|
|||||||
self.context_set.iter().map(|entry| entry.as_ref())
|
self.context_set.iter().map(|entry| entry.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self, cx: &mut Context<Self>) {
|
||||||
self.context_set.clear();
|
self.context_set.clear();
|
||||||
self.context_thread_ids.clear();
|
self.context_thread_ids.clear();
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_context_for_thread(
|
pub fn new_context_for_thread(
|
||||||
@@ -142,17 +143,12 @@ impl ContextStore {
|
|||||||
remove_if_exists: bool,
|
remove_if_exists: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Result<Option<AgentContextHandle>> {
|
) -> Result<Option<AgentContextHandle>> {
|
||||||
let Some(project) = self.project.upgrade() else {
|
let project = self.project.upgrade().context("failed to read project")?;
|
||||||
return Err(anyhow!("failed to read project"));
|
let entry_id = project
|
||||||
};
|
|
||||||
|
|
||||||
let Some(entry_id) = project
|
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.entry_for_path(project_path, cx)
|
.entry_for_path(project_path, cx)
|
||||||
.map(|entry| entry.id)
|
.map(|entry| entry.id)
|
||||||
else {
|
.context("no entry found for directory context")?;
|
||||||
return Err(anyhow!("no entry found for directory context"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let context_id = self.next_context_id.post_inc();
|
let context_id = self.next_context_id.post_inc();
|
||||||
let context = AgentContextHandle::Directory(DirectoryContextHandle {
|
let context = AgentContextHandle::Directory(DirectoryContextHandle {
|
||||||
@@ -308,11 +304,13 @@ impl ContextStore {
|
|||||||
project.open_image(project_path.clone(), cx)
|
project.open_image(project_path.clone(), cx)
|
||||||
})?;
|
})?;
|
||||||
let image_item = open_image_task.await?;
|
let image_item = open_image_task.await?;
|
||||||
let image = image_item.read_with(cx, |image_item, _| image_item.image.clone())?;
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
let item = image_item.read(cx);
|
||||||
this.insert_image(
|
this.insert_image(
|
||||||
Some(image_item.read(cx).project_path(cx)),
|
Some(item.project_path(cx)),
|
||||||
image,
|
Some(item.file.full_path(cx).into()),
|
||||||
|
item.image.clone(),
|
||||||
remove_if_exists,
|
remove_if_exists,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -321,12 +319,13 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_image_instance(&mut self, image: Arc<Image>, cx: &mut Context<ContextStore>) {
|
pub fn add_image_instance(&mut self, image: Arc<Image>, cx: &mut Context<ContextStore>) {
|
||||||
self.insert_image(None, image, false, cx);
|
self.insert_image(None, None, image, false, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_image(
|
fn insert_image(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: Option<ProjectPath>,
|
project_path: Option<ProjectPath>,
|
||||||
|
full_path: Option<Arc<Path>>,
|
||||||
image: Arc<Image>,
|
image: Arc<Image>,
|
||||||
remove_if_exists: bool,
|
remove_if_exists: bool,
|
||||||
cx: &mut Context<ContextStore>,
|
cx: &mut Context<ContextStore>,
|
||||||
@@ -334,6 +333,7 @@ impl ContextStore {
|
|||||||
let image_task = LanguageModelImage::from_image(image.clone(), cx).shared();
|
let image_task = LanguageModelImage::from_image(image.clone(), cx).shared();
|
||||||
let context = AgentContextHandle::Image(ImageContext {
|
let context = AgentContextHandle::Image(ImageContext {
|
||||||
project_path,
|
project_path,
|
||||||
|
full_path,
|
||||||
original_image: image,
|
original_image: image,
|
||||||
image_task,
|
image_task,
|
||||||
context_id: self.next_context_id.post_inc(),
|
context_id: self.next_context_id.post_inc(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{collections::VecDeque, path::Path, sync::Arc};
|
use std::{collections::VecDeque, path::Path, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{Context as _, anyhow};
|
use anyhow::Context as _;
|
||||||
use assistant_context_editor::{AssistantContext, SavedContextMetadata};
|
use assistant_context_editor::{AssistantContext, SavedContextMetadata};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use futures::future::{TryFutureExt as _, join_all};
|
use futures::future::{TryFutureExt as _, join_all};
|
||||||
@@ -130,7 +130,10 @@ impl HistoryStore {
|
|||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
async { Err(anyhow!("no thread store")) }.boxed()
|
async {
|
||||||
|
anyhow::bail!("no thread store");
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
}),
|
}),
|
||||||
SerializedRecentEntry::Context(id) => context_store
|
SerializedRecentEntry::Context(id) => context_store
|
||||||
.update(cx, |context_store, cx| {
|
.update(cx, |context_store, cx| {
|
||||||
@@ -140,13 +143,16 @@ impl HistoryStore {
|
|||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
async { Err(anyhow!("no context store")) }.boxed()
|
async {
|
||||||
|
anyhow::bail!("no context store");
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
let entries = join_all(entries)
|
let entries = join_all(entries)
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|result| result.log_err())
|
.filter_map(|result| result.log_with_level(log::Level::Debug))
|
||||||
.collect::<VecDeque<_>>();
|
.collect::<VecDeque<_>>();
|
||||||
|
|
||||||
this.update(cx, |this, _| {
|
this.update(cx, |this, _| {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::ops::Range;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use agent_settings::AgentSettings;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use assistant_settings::AssistantSettings;
|
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||||
use editor::display_map::EditorMargins;
|
use editor::display_map::EditorMargins;
|
||||||
@@ -134,7 +134,7 @@ impl InlineAssistant {
|
|||||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let enabled = AssistantSettings::get_global(cx).enabled;
|
let enabled = AgentSettings::get_global(cx).enabled;
|
||||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||||
});
|
});
|
||||||
@@ -219,7 +219,7 @@ impl InlineAssistant {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
if !settings.enabled {
|
if !settings.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1445,7 +1445,7 @@ impl InlineAssistant {
|
|||||||
style: BlockStyle::Flex,
|
style: BlockStyle::Flex,
|
||||||
render: Arc::new(move |cx| {
|
render: Arc::new(move |cx| {
|
||||||
div()
|
div()
|
||||||
.block_mouse_down()
|
.block_mouse_except_scroll()
|
||||||
.bg(cx.theme().status().deleted_background)
|
.bg(cx.theme().status().deleted_background)
|
||||||
.size_full()
|
.size_full()
|
||||||
.h(height as f32 * cx.window.line_height())
|
.h(height as f32 * cx.window.line_height())
|
||||||
@@ -1771,7 +1771,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<Vec<CodeAction>>> {
|
) -> Task<Result<Vec<CodeAction>>> {
|
||||||
if !AssistantSettings::get_global(cx).enabled {
|
if !AgentSettings::get_global(cx).enabled {
|
||||||
return Task::ready(Ok(Vec::new()));
|
return Task::ready(Ok(Vec::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use crate::terminal_codegen::TerminalCodegen;
|
|||||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||||
use crate::{RemoveAllContext, ToggleContextPicker};
|
use crate::{RemoveAllContext, ToggleContextPicker};
|
||||||
|
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||||
use client::ErrorExt;
|
use client::ErrorExt;
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
|
use db::kvp::Dismissable;
|
||||||
use editor::display_map::EditorMargins;
|
use editor::display_map::EditorMargins;
|
||||||
use editor::{
|
use editor::{
|
||||||
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||||
@@ -23,17 +25,16 @@ use gpui::{
|
|||||||
Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window, anchored, deferred, point,
|
Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window, anchored, deferred, point,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||||
use language_model_selector::ToggleModelSelector;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::utils::WithRemSize;
|
use ui::utils::WithRemSize;
|
||||||
use ui::{
|
use ui::{
|
||||||
CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip, prelude::*,
|
CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub struct PromptEditor<T> {
|
pub struct PromptEditor<T> {
|
||||||
@@ -99,7 +100,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.key_context("PromptEditor")
|
.key_context("PromptEditor")
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.block_mouse_down()
|
.block_mouse_except_scroll()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.border_y_1()
|
.border_y_1()
|
||||||
.border_color(cx.theme().status().info_border)
|
.border_color(cx.theme().status().info_border)
|
||||||
@@ -326,9 +327,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
EditorEvent::Edited { .. } => {
|
EditorEvent::Edited { .. } => {
|
||||||
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
let is_via_ssh = workspace
|
let is_via_ssh = workspace.project().read(cx).is_via_ssh();
|
||||||
.project()
|
|
||||||
.update(cx, |project, _| project.is_via_ssh());
|
|
||||||
|
|
||||||
workspace
|
workspace
|
||||||
.client()
|
.client()
|
||||||
@@ -373,7 +372,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.context_store.update(cx, |store, _cx| store.clear());
|
self.context_store.update(cx, |store, cx| store.clear(cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,7 +721,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.child(CheckboxWithLabel::new(
|
.child(CheckboxWithLabel::new(
|
||||||
"dont-show-again",
|
"dont-show-again",
|
||||||
Label::new("Don't show again"),
|
Label::new("Don't show again"),
|
||||||
if dismissed_rate_limit_notice() {
|
if RateLimitNotice::dismissed() {
|
||||||
ui::ToggleState::Selected
|
ui::ToggleState::Selected
|
||||||
} else {
|
} else {
|
||||||
ui::ToggleState::Unselected
|
ui::ToggleState::Unselected
|
||||||
@@ -734,7 +733,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
ui::ToggleState::Selected => true,
|
ui::ToggleState::Selected => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
set_rate_limit_notice_dismissed(is_dismissed, cx)
|
RateLimitNotice::set_dismissed(is_dismissed, cx);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.child(
|
.child(
|
||||||
@@ -892,7 +891,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
|
|
||||||
let prompt_editor_entity = prompt_editor.downgrade();
|
let prompt_editor_entity = prompt_editor.downgrade();
|
||||||
prompt_editor.update(cx, |editor, _| {
|
prompt_editor.update(cx, |editor, _| {
|
||||||
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
@@ -974,7 +973,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
CodegenStatus::Error(error) => {
|
CodegenStatus::Error(error) => {
|
||||||
if cx.has_flag::<ZedProFeatureFlag>()
|
if cx.has_flag::<ZedProFeatureFlag>()
|
||||||
&& error.error_code() == proto::ErrorCode::RateLimitExceeded
|
&& error.error_code() == proto::ErrorCode::RateLimitExceeded
|
||||||
&& !dismissed_rate_limit_notice()
|
&& !RateLimitNotice::dismissed()
|
||||||
{
|
{
|
||||||
self.show_rate_limit_notice = true;
|
self.show_rate_limit_notice = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -1063,7 +1062,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
|
|
||||||
let prompt_editor_entity = prompt_editor.downgrade();
|
let prompt_editor_entity = prompt_editor.downgrade();
|
||||||
prompt_editor.update(cx, |editor, _| {
|
prompt_editor.update(cx, |editor, _| {
|
||||||
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
@@ -1180,27 +1179,10 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DISMISSED_RATE_LIMIT_NOTICE_KEY: &str = "dismissed-rate-limit-notice";
|
struct RateLimitNotice;
|
||||||
|
|
||||||
fn dismissed_rate_limit_notice() -> bool {
|
impl Dismissable for RateLimitNotice {
|
||||||
db::kvp::KEY_VALUE_STORE
|
const KEY: &'static str = "dismissed-rate-limit-notice";
|
||||||
.read_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY)
|
|
||||||
.log_err()
|
|
||||||
.map_or(false, |s| s.is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut App) {
|
|
||||||
db::write_and_log(cx, move || async move {
|
|
||||||
if is_dismissed {
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.write_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into(), "1".into())
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
db::kvp::KEY_VALUE_STORE
|
|
||||||
.delete_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CodegenStatus {
|
pub enum CodegenStatus {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::agent_model_selector::{AgentModelSelector, ModelType};
|
use crate::agent_model_selector::{AgentModelSelector, ModelType};
|
||||||
@@ -8,7 +9,8 @@ use crate::ui::{
|
|||||||
AnimatedLabel, MaxModeTooltip,
|
AnimatedLabel, MaxModeTooltip,
|
||||||
preview::{AgentPreview, UsageCallout},
|
preview::{AgentPreview, UsageCallout},
|
||||||
};
|
};
|
||||||
use assistant_settings::{AssistantSettings, CompletionMode};
|
use agent_settings::{AgentSettings, CompletionMode};
|
||||||
|
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||||
use buffer_diff::BufferDiff;
|
use buffer_diff::BufferDiff;
|
||||||
use client::UserStore;
|
use client::UserStore;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
@@ -30,7 +32,6 @@ use language_model::{
|
|||||||
ConfiguredModel, LanguageModelRequestMessage, MessageContent, RequestUsage,
|
ConfiguredModel, LanguageModelRequestMessage, MessageContent, RequestUsage,
|
||||||
ZED_CLOUD_PROVIDER_ID,
|
ZED_CLOUD_PROVIDER_ID,
|
||||||
};
|
};
|
||||||
use language_model_selector::ToggleModelSelector;
|
|
||||||
use multi_buffer;
|
use multi_buffer;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_store::PromptStore;
|
use prompt_store::PromptStore;
|
||||||
@@ -41,6 +42,7 @@ use theme::ThemeSettings;
|
|||||||
use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use util::{ResultExt as _, maybe};
|
use util::{ResultExt as _, maybe};
|
||||||
use workspace::{CollaboratorId, Workspace};
|
use workspace::{CollaboratorId, Workspace};
|
||||||
|
use zed_llm_client::CompletionIntent;
|
||||||
|
|
||||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
@@ -49,8 +51,9 @@ use crate::profile_selector::ProfileSelector;
|
|||||||
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
||||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveThread, AgentDiffPane, Chat, ExpandMessageEditor, Follow, NewThread, OpenAgentDiff,
|
ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, NewThread,
|
||||||
RemoveAllContext, ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
OpenAgentDiff, RemoveAllContext, ToggleBurnMode, ToggleContextPicker, ToggleProfileSelector,
|
||||||
|
register_agent_preview,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(RegisterComponent)]
|
#[derive(RegisterComponent)]
|
||||||
@@ -109,6 +112,7 @@ pub(crate) fn create_editor(
|
|||||||
editor.set_placeholder_text("Message the agent – @ to include context", cx);
|
editor.set_placeholder_text("Message the agent – @ to include context", cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_soft_wrap();
|
editor.set_soft_wrap();
|
||||||
|
editor.set_use_modal_editing(true);
|
||||||
editor.set_context_menu_options(ContextMenuOptions {
|
editor.set_context_menu_options(ContextMenuOptions {
|
||||||
min_entries_visible: 12,
|
min_entries_visible: 12,
|
||||||
max_entries_visible: 12,
|
max_entries_visible: 12,
|
||||||
@@ -120,7 +124,7 @@ pub(crate) fn create_editor(
|
|||||||
|
|
||||||
let editor_entity = editor.downgrade();
|
let editor_entity = editor.downgrade();
|
||||||
editor.update(cx, |editor, _| {
|
editor.update(cx, |editor, _| {
|
||||||
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
Some(thread_store),
|
Some(thread_store),
|
||||||
@@ -278,7 +282,7 @@ impl MessageEditor {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.context_store.update(cx, |store, _cx| store.clear());
|
self.context_store.update(cx, |store, cx| store.clear(cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +306,21 @@ impl MessageEditor {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chat_with_follow(
|
||||||
|
&mut self,
|
||||||
|
_: &ChatWithFollow,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.workspace
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.follow(CollaboratorId::Agent, window, cx)
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
self.chat(&Chat, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn is_editor_empty(&self, cx: &App) -> bool {
|
fn is_editor_empty(&self, cx: &App) -> bool {
|
||||||
self.editor.read(cx).text(cx).trim().is_empty()
|
self.editor.read(cx).text(cx).trim().is_empty()
|
||||||
}
|
}
|
||||||
@@ -358,7 +377,12 @@ impl MessageEditor {
|
|||||||
thread
|
thread
|
||||||
.update(cx, |thread, cx| {
|
.update(cx, |thread, cx| {
|
||||||
thread.advance_prompt_id();
|
thread.advance_prompt_id();
|
||||||
thread.send_to_model(model, Some(window_handle), cx);
|
thread.send_to_model(
|
||||||
|
model,
|
||||||
|
CompletionIntent::UserPrompt,
|
||||||
|
Some(window_handle),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
})
|
})
|
||||||
@@ -454,6 +478,22 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_burn_mode(
|
||||||
|
&mut self,
|
||||||
|
_: &ToggleBurnMode,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.thread.update(cx, |thread, _cx| {
|
||||||
|
let active_completion_mode = thread.completion_mode();
|
||||||
|
|
||||||
|
thread.set_completion_mode(match active_completion_mode {
|
||||||
|
CompletionMode::Burn => CompletionMode::Normal,
|
||||||
|
CompletionMode::Normal => CompletionMode::Burn,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||||
let thread = self.thread.read(cx);
|
let thread = self.thread.read(cx);
|
||||||
let model = thread.configured_model();
|
let model = thread.configured_model();
|
||||||
@@ -462,27 +502,24 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let active_completion_mode = thread.completion_mode();
|
let active_completion_mode = thread.completion_mode();
|
||||||
let max_mode_enabled = active_completion_mode == CompletionMode::Max;
|
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
|
||||||
|
let icon = if burn_mode_enabled {
|
||||||
|
IconName::ZedBurnModeOn
|
||||||
|
} else {
|
||||||
|
IconName::ZedBurnMode
|
||||||
|
};
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
Button::new("max-mode", "Max Mode")
|
IconButton::new("burn-mode", icon)
|
||||||
.label_size(LabelSize::Small)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.icon(IconName::ZedMaxMode)
|
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.icon_position(IconPosition::Start)
|
.toggle_state(burn_mode_enabled)
|
||||||
.toggle_state(max_mode_enabled)
|
.selected_icon_color(Color::Error)
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.on_click(cx.listener(|this, _event, window, cx| {
|
||||||
this.thread.update(cx, |thread, _cx| {
|
this.toggle_burn_mode(&ToggleBurnMode, window, cx);
|
||||||
thread.set_completion_mode(match active_completion_mode {
|
|
||||||
CompletionMode::Max => CompletionMode::Normal,
|
|
||||||
CompletionMode::Normal => CompletionMode::Max,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}))
|
}))
|
||||||
.tooltip(move |_window, cx| {
|
.tooltip(move |_window, cx| {
|
||||||
cx.new(|_| MaxModeTooltip::new().selected(max_mode_enabled))
|
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
@@ -562,6 +599,7 @@ impl MessageEditor {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.key_context("MessageEditor")
|
.key_context("MessageEditor")
|
||||||
.on_action(cx.listener(Self::chat))
|
.on_action(cx.listener(Self::chat))
|
||||||
|
.on_action(cx.listener(Self::chat_with_follow))
|
||||||
.on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
|
.on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
|
||||||
this.profile_selector
|
this.profile_selector
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -576,6 +614,7 @@ impl MessageEditor {
|
|||||||
.on_action(cx.listener(Self::remove_all_context))
|
.on_action(cx.listener(Self::remove_all_context))
|
||||||
.on_action(cx.listener(Self::move_up))
|
.on_action(cx.listener(Self::move_up))
|
||||||
.on_action(cx.listener(Self::expand_message_editor))
|
.on_action(cx.listener(Self::expand_message_editor))
|
||||||
|
.on_action(cx.listener(Self::toggle_burn_mode))
|
||||||
.capture_action(cx.listener(Self::paste))
|
.capture_action(cx.listener(Self::paste))
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.p_2()
|
.p_2()
|
||||||
@@ -668,7 +707,6 @@ impl MessageEditor {
|
|||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
|
||||||
.child(self.render_follow_toggle(cx))
|
.child(self.render_follow_toggle(cx))
|
||||||
.children(self.render_max_mode_toggle(cx)),
|
.children(self.render_max_mode_toggle(cx)),
|
||||||
)
|
)
|
||||||
@@ -842,7 +880,7 @@ impl MessageEditor {
|
|||||||
.border_b_0()
|
.border_b_0()
|
||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.rounded_t_md()
|
.rounded_t_md()
|
||||||
.shadow(smallvec::smallvec![gpui::BoxShadow {
|
.shadow(vec![gpui::BoxShadow {
|
||||||
color: gpui::black().opacity(0.15),
|
color: gpui::black().opacity(0.15),
|
||||||
offset: point(px(1.), px(-1.)),
|
offset: point(px(1.), px(-1.)),
|
||||||
blur_radius: px(3.),
|
blur_radius: px(3.),
|
||||||
@@ -1167,9 +1205,10 @@ impl MessageEditor {
|
|||||||
fn reload_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
|
fn reload_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
|
||||||
let load_task = cx.spawn(async move |this, cx| {
|
let load_task = cx.spawn(async move |this, cx| {
|
||||||
let Ok(load_task) = this.update(cx, |this, cx| {
|
let Ok(load_task) = this.update(cx, |this, cx| {
|
||||||
let new_context = this.context_store.read_with(cx, |context_store, cx| {
|
let new_context = this
|
||||||
context_store.new_context_for_thread(this.thread.read(cx), None)
|
.context_store
|
||||||
});
|
.read(cx)
|
||||||
|
.new_context_for_thread(this.thread.read(cx), None);
|
||||||
load_context(new_context, &this.project, &this.prompt_store, cx)
|
load_context(new_context, &this.project, &this.prompt_store, cx)
|
||||||
}) else {
|
}) else {
|
||||||
return;
|
return;
|
||||||
@@ -1248,12 +1287,13 @@ impl MessageEditor {
|
|||||||
let request = language_model::LanguageModelRequest {
|
let request = language_model::LanguageModelRequest {
|
||||||
thread_id: None,
|
thread_id: None,
|
||||||
prompt_id: None,
|
prompt_id: None,
|
||||||
|
intent: None,
|
||||||
mode: None,
|
mode: None,
|
||||||
messages: vec![request_message],
|
messages: vec![request_message],
|
||||||
tools: vec![],
|
tools: vec![],
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
stop: vec![],
|
stop: vec![],
|
||||||
temperature: AssistantSettings::temperature_for_model(&model.model, cx),
|
temperature: AgentSettings::temperature_for_model(&model.model, cx),
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(model.model.count_tokens(request, cx))
|
Some(model.model.count_tokens(request, cx))
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_settings::{
|
use agent_settings::{
|
||||||
AgentProfile, AgentProfileId, AssistantDockPosition, AssistantSettings, GroupedAgentProfiles,
|
AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, GroupedAgentProfiles,
|
||||||
builtin_profiles,
|
builtin_profiles,
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Action, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
|
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
|
||||||
use language_model::LanguageModelRegistry;
|
use language_model::LanguageModelRegistry;
|
||||||
use settings::{Settings as _, SettingsStore, update_settings_file};
|
use settings::{Settings as _, SettingsStore, update_settings_file};
|
||||||
use ui::{
|
use ui::{
|
||||||
@@ -39,7 +39,7 @@ impl ProfileSelector {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
profiles: GroupedAgentProfiles::from_settings(AssistantSettings::get_global(cx)),
|
profiles: GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx)),
|
||||||
fs,
|
fs,
|
||||||
thread,
|
thread,
|
||||||
thread_store,
|
thread_store,
|
||||||
@@ -54,7 +54,7 @@ impl ProfileSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
||||||
self.profiles = GroupedAgentProfiles::from_settings(AssistantSettings::get_global(cx));
|
self.profiles = GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_context_menu(
|
fn build_context_menu(
|
||||||
@@ -63,7 +63,7 @@ impl ProfileSelector {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Entity<ContextMenu> {
|
) -> Entity<ContextMenu> {
|
||||||
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
for (profile_id, profile) in self.profiles.builtin.iter() {
|
for (profile_id, profile) in self.profiles.builtin.iter() {
|
||||||
menu = menu.item(self.menu_entry_for_profile(
|
menu = menu.item(self.menu_entry_for_profile(
|
||||||
profile_id.clone(),
|
profile_id.clone(),
|
||||||
@@ -100,7 +100,7 @@ impl ProfileSelector {
|
|||||||
&self,
|
&self,
|
||||||
profile_id: AgentProfileId,
|
profile_id: AgentProfileId,
|
||||||
profile: &AgentProfile,
|
profile: &AgentProfile,
|
||||||
settings: &AssistantSettings,
|
settings: &AgentSettings,
|
||||||
_cx: &App,
|
_cx: &App,
|
||||||
) -> ContextMenuEntry {
|
) -> ContextMenuEntry {
|
||||||
let documentation = match profile.name.to_lowercase().as_str() {
|
let documentation = match profile.name.to_lowercase().as_str() {
|
||||||
@@ -126,7 +126,7 @@ impl ProfileSelector {
|
|||||||
let thread_store = self.thread_store.clone();
|
let thread_store = self.thread_store.clone();
|
||||||
let profile_id = profile_id.clone();
|
let profile_id = profile_id.clone();
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
|
update_settings_file::<AgentSettings>(fs.clone(), cx, {
|
||||||
let profile_id = profile_id.clone();
|
let profile_id = profile_id.clone();
|
||||||
move |settings, _cx| {
|
move |settings, _cx| {
|
||||||
settings.set_profile(profile_id.clone());
|
settings.set_profile(profile_id.clone());
|
||||||
@@ -145,7 +145,7 @@ impl ProfileSelector {
|
|||||||
|
|
||||||
impl Render for ProfileSelector {
|
impl Render for ProfileSelector {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AgentSettings::get_global(cx);
|
||||||
let profile_id = &settings.default_profile;
|
let profile_id = &settings.default_profile;
|
||||||
let profile = settings.profiles.get(profile_id);
|
let profile = settings.profiles.get(profile_id);
|
||||||
|
|
||||||
@@ -153,17 +153,15 @@ impl Render for ProfileSelector {
|
|||||||
.map(|profile| profile.name.clone())
|
.map(|profile| profile.name.clone())
|
||||||
.unwrap_or_else(|| "Unknown".into());
|
.unwrap_or_else(|| "Unknown".into());
|
||||||
|
|
||||||
let configured_model = self
|
let configured_model = self.thread.read(cx).configured_model().or_else(|| {
|
||||||
.thread
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
.read_with(cx, |thread, _cx| thread.configured_model())
|
model_registry.default_model()
|
||||||
.or_else(|| {
|
});
|
||||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
let Some(configured_model) = configured_model else {
|
||||||
model_registry.default_model()
|
return Empty.into_any_element();
|
||||||
});
|
};
|
||||||
let supports_tools =
|
|
||||||
configured_model.map_or(false, |default| default.model.supports_tools());
|
|
||||||
|
|
||||||
if supports_tools {
|
if configured_model.model.supports_tools() {
|
||||||
let this = cx.entity().clone();
|
let this = cx.entity().clone();
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
let trigger_button = Button::new("profile-selector-model", selected_profile)
|
let trigger_button = Button::new("profile-selector-model", selected_profile)
|
||||||
@@ -210,10 +208,10 @@ impl Render for ProfileSelector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn documentation_side(position: AssistantDockPosition) -> DocumentationSide {
|
fn documentation_side(position: AgentDockPosition) -> DocumentationSide {
|
||||||
match position {
|
match position {
|
||||||
AssistantDockPosition::Left => DocumentationSide::Right,
|
AgentDockPosition::Left => DocumentationSide::Right,
|
||||||
AssistantDockPosition::Bottom => DocumentationSide::Left,
|
AgentDockPosition::Bottom => DocumentationSide::Left,
|
||||||
AssistantDockPosition::Right => DocumentationSide::Left,
|
AgentDockPosition::Right => DocumentationSide::Left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
crates/agent/src/prompts/stale_files_prompt_header.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
These files changed since last read:
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Generate a detailed summary of this conversation. Include:
|
||||||
|
1. A brief overview of what was discussed
|
||||||
|
2. Key facts or information discovered
|
||||||
|
3. Outcomes or conclusions reached
|
||||||
|
4. Any action items or next steps if any
|
||||||
|
Format it in Markdown with headings and bullet points.
|
||||||
4
crates/agent/src/prompts/summarize_thread_prompt.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Generate a concise 3-7 word title for this conversation, omitting punctuation.
|
||||||
|
Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`.
|
||||||
|
If the conversation is about a specific subject, include it in the title.
|
||||||
|
Be descriptive. DO NOT speak in the first person.
|
||||||
@@ -179,21 +179,21 @@ impl TerminalTransaction {
|
|||||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||||
let input = Self::sanitize_input(hunk);
|
let input = Self::sanitize_input(hunk);
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(input));
|
.update(cx, |terminal, _| terminal.input(input.into_bytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&self, cx: &mut App) {
|
pub fn undo(&self, cx: &mut App) {
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(&self, cx: &mut App) {
|
pub fn complete(&self, cx: &mut App) {
|
||||||
self.terminal.update(cx, |terminal, _| {
|
self.terminal
|
||||||
terminal.input(CARRIAGE_RETURN.to_string())
|
.update(cx, |terminal, _| terminal.input(CARRIAGE_RETURN.as_bytes()));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sanitize_input(input: String) -> String {
|
fn sanitize_input(mut input: String) -> String {
|
||||||
input.replace(['\r', '\n'], "")
|
input.retain(|c| c != '\r' && c != '\n');
|
||||||
|
input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use crate::inline_prompt_editor::{
|
|||||||
};
|
};
|
||||||
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
||||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
|
use agent_settings::AgentSettings;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use assistant_settings::AssistantSettings;
|
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
use editor::{MultiBuffer, actions::SelectAll};
|
use editor::{MultiBuffer, actions::SelectAll};
|
||||||
@@ -25,6 +25,7 @@ use terminal_view::TerminalView;
|
|||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{Toast, Workspace, notifications::NotificationId};
|
use workspace::{Toast, Workspace, notifications::NotificationId};
|
||||||
|
use zed_llm_client::CompletionIntent;
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
@@ -105,7 +106,7 @@ impl TerminalInlineAssistant {
|
|||||||
});
|
});
|
||||||
let prompt_editor_render = prompt_editor.clone();
|
let prompt_editor_render = prompt_editor.clone();
|
||||||
let block = terminal_view::BlockProperties {
|
let block = terminal_view::BlockProperties {
|
||||||
height: 2,
|
height: 4,
|
||||||
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
||||||
};
|
};
|
||||||
terminal_view.update(cx, |terminal_view, cx| {
|
terminal_view.update(cx, |terminal_view, cx| {
|
||||||
@@ -201,7 +202,7 @@ impl TerminalInlineAssistant {
|
|||||||
.update(cx, |terminal, cx| {
|
.update(cx, |terminal, cx| {
|
||||||
terminal
|
terminal
|
||||||
.terminal()
|
.terminal()
|
||||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ impl TerminalInlineAssistant {
|
|||||||
.inline_assistant_model()
|
.inline_assistant_model()
|
||||||
.context("No inline assistant model")?;
|
.context("No inline assistant model")?;
|
||||||
|
|
||||||
let temperature = AssistantSettings::temperature_for_model(&model, cx);
|
let temperature = AgentSettings::temperature_for_model(&model, cx);
|
||||||
|
|
||||||
Ok(cx.background_spawn(async move {
|
Ok(cx.background_spawn(async move {
|
||||||
let mut request_message = LanguageModelRequestMessage {
|
let mut request_message = LanguageModelRequestMessage {
|
||||||
@@ -291,6 +292,7 @@ impl TerminalInlineAssistant {
|
|||||||
thread_id: None,
|
thread_id: None,
|
||||||
prompt_id: None,
|
prompt_id: None,
|
||||||
mode: None,
|
mode: None,
|
||||||
|
intent: Some(CompletionIntent::TerminalInlineAssist),
|
||||||
messages: vec![request_message],
|
messages: vec![request_message],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::ops::Range;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use agent_settings::{AgentSettings, CompletionMode};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use assistant_settings::{AssistantSettings, CompletionMode};
|
|
||||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
|
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
@@ -38,7 +38,7 @@ use thiserror::Error;
|
|||||||
use ui::Window;
|
use ui::Window;
|
||||||
use util::{ResultExt as _, post_inc};
|
use util::{ResultExt as _, post_inc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use zed_llm_client::CompletionRequestStatus;
|
use zed_llm_client::{CompletionIntent, CompletionRequestStatus};
|
||||||
|
|
||||||
use crate::ThreadStore;
|
use crate::ThreadStore;
|
||||||
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
|
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
|
||||||
@@ -115,6 +115,7 @@ pub struct Message {
|
|||||||
pub segments: Vec<MessageSegment>,
|
pub segments: Vec<MessageSegment>,
|
||||||
pub loaded_context: LoadedContext,
|
pub loaded_context: LoadedContext,
|
||||||
pub creases: Vec<MessageCrease>,
|
pub creases: Vec<MessageCrease>,
|
||||||
|
pub is_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
@@ -214,7 +215,7 @@ pub struct GitState {
|
|||||||
pub diff: Option<String>,
|
pub diff: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ThreadCheckpoint {
|
pub struct ThreadCheckpoint {
|
||||||
message_id: MessageId,
|
message_id: MessageId,
|
||||||
git_checkpoint: GitStoreCheckpoint,
|
git_checkpoint: GitStoreCheckpoint,
|
||||||
@@ -329,7 +330,7 @@ pub struct Thread {
|
|||||||
detailed_summary_task: Task<Option<()>>,
|
detailed_summary_task: Task<Option<()>>,
|
||||||
detailed_summary_tx: postage::watch::Sender<DetailedSummaryState>,
|
detailed_summary_tx: postage::watch::Sender<DetailedSummaryState>,
|
||||||
detailed_summary_rx: postage::watch::Receiver<DetailedSummaryState>,
|
detailed_summary_rx: postage::watch::Receiver<DetailedSummaryState>,
|
||||||
completion_mode: assistant_settings::CompletionMode,
|
completion_mode: agent_settings::CompletionMode,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
next_message_id: MessageId,
|
next_message_id: MessageId,
|
||||||
last_prompt_id: PromptId,
|
last_prompt_id: PromptId,
|
||||||
@@ -415,7 +416,7 @@ impl Thread {
|
|||||||
detailed_summary_task: Task::ready(None),
|
detailed_summary_task: Task::ready(None),
|
||||||
detailed_summary_tx,
|
detailed_summary_tx,
|
||||||
detailed_summary_rx,
|
detailed_summary_rx,
|
||||||
completion_mode: AssistantSettings::get_global(cx).preferred_completion_mode,
|
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
next_message_id: MessageId(0),
|
next_message_id: MessageId(0),
|
||||||
last_prompt_id: PromptId::new(),
|
last_prompt_id: PromptId::new(),
|
||||||
@@ -493,7 +494,7 @@ impl Thread {
|
|||||||
|
|
||||||
let completion_mode = serialized
|
let completion_mode = serialized
|
||||||
.completion_mode
|
.completion_mode
|
||||||
.unwrap_or_else(|| AssistantSettings::get_global(cx).preferred_completion_mode);
|
.unwrap_or_else(|| AgentSettings::get_global(cx).preferred_completion_mode);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
@@ -540,6 +541,7 @@ impl Thread {
|
|||||||
context: None,
|
context: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
is_hidden: message.is_hidden,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
next_message_id,
|
next_message_id,
|
||||||
@@ -560,7 +562,7 @@ impl Thread {
|
|||||||
cumulative_token_usage: serialized.cumulative_token_usage,
|
cumulative_token_usage: serialized.cumulative_token_usage,
|
||||||
exceeded_window_error: None,
|
exceeded_window_error: None,
|
||||||
last_usage: None,
|
last_usage: None,
|
||||||
tool_use_limit_reached: false,
|
tool_use_limit_reached: serialized.tool_use_limit_reached,
|
||||||
feedback: None,
|
feedback: None,
|
||||||
message_feedback: HashMap::default(),
|
message_feedback: HashMap::default(),
|
||||||
last_auto_capture_at: None,
|
last_auto_capture_at: None,
|
||||||
@@ -757,6 +759,14 @@ impl Thread {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.finalize_checkpoint(pending_checkpoint, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize_checkpoint(
|
||||||
|
&mut self,
|
||||||
|
pending_checkpoint: ThreadCheckpoint,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
let git_store = self.project.read(cx).git_store().clone();
|
let git_store = self.project.read(cx).git_store().clone();
|
||||||
let final_checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
let final_checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
||||||
cx.spawn(async move |this, cx| match final_checkpoint.await {
|
cx.spawn(async move |this, cx| match final_checkpoint.await {
|
||||||
@@ -841,7 +851,7 @@ impl Thread {
|
|||||||
.get(ix + 1)
|
.get(ix + 1)
|
||||||
.and_then(|message| {
|
.and_then(|message| {
|
||||||
self.message(message.id)
|
self.message(message.id)
|
||||||
.map(|next_message| next_message.role == Role::User)
|
.map(|next_message| next_message.role == Role::User && !next_message.is_hidden)
|
||||||
})
|
})
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
@@ -881,7 +891,7 @@ impl Thread {
|
|||||||
|
|
||||||
pub fn output_for_tool(&self, id: &LanguageModelToolUseId) -> Option<&Arc<str>> {
|
pub fn output_for_tool(&self, id: &LanguageModelToolUseId) -> Option<&Arc<str>> {
|
||||||
match &self.tool_use.tool_result(id)?.content {
|
match &self.tool_use.tool_result(id)?.content {
|
||||||
LanguageModelToolResultContent::Text(str) => Some(str),
|
LanguageModelToolResultContent::Text(text) => Some(text),
|
||||||
LanguageModelToolResultContent::Image(_) => {
|
LanguageModelToolResultContent::Image(_) => {
|
||||||
// TODO: We should display image
|
// TODO: We should display image
|
||||||
None
|
None
|
||||||
@@ -940,6 +950,7 @@ impl Thread {
|
|||||||
vec![MessageSegment::Text(text.into())],
|
vec![MessageSegment::Text(text.into())],
|
||||||
loaded_context.loaded_context,
|
loaded_context.loaded_context,
|
||||||
creases,
|
creases,
|
||||||
|
false,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -955,6 +966,20 @@ impl Thread {
|
|||||||
message_id
|
message_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_invisible_continue_message(&mut self, cx: &mut Context<Self>) -> MessageId {
|
||||||
|
let id = self.insert_message(
|
||||||
|
Role::User,
|
||||||
|
vec![MessageSegment::Text("Continue where you left off".into())],
|
||||||
|
LoadedContext::default(),
|
||||||
|
vec![],
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
self.pending_checkpoint = None;
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert_assistant_message(
|
pub fn insert_assistant_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
segments: Vec<MessageSegment>,
|
segments: Vec<MessageSegment>,
|
||||||
@@ -965,6 +990,7 @@ impl Thread {
|
|||||||
segments,
|
segments,
|
||||||
LoadedContext::default(),
|
LoadedContext::default(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
|
false,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -975,6 +1001,7 @@ impl Thread {
|
|||||||
segments: Vec<MessageSegment>,
|
segments: Vec<MessageSegment>,
|
||||||
loaded_context: LoadedContext,
|
loaded_context: LoadedContext,
|
||||||
creases: Vec<MessageCrease>,
|
creases: Vec<MessageCrease>,
|
||||||
|
is_hidden: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> MessageId {
|
) -> MessageId {
|
||||||
let id = self.next_message_id.post_inc();
|
let id = self.next_message_id.post_inc();
|
||||||
@@ -984,6 +1011,7 @@ impl Thread {
|
|||||||
segments,
|
segments,
|
||||||
loaded_context,
|
loaded_context,
|
||||||
creases,
|
creases,
|
||||||
|
is_hidden,
|
||||||
});
|
});
|
||||||
self.touch_updated_at();
|
self.touch_updated_at();
|
||||||
cx.emit(ThreadEvent::MessageAdded(id));
|
cx.emit(ThreadEvent::MessageAdded(id));
|
||||||
@@ -996,6 +1024,7 @@ impl Thread {
|
|||||||
new_role: Role,
|
new_role: Role,
|
||||||
new_segments: Vec<MessageSegment>,
|
new_segments: Vec<MessageSegment>,
|
||||||
loaded_context: Option<LoadedContext>,
|
loaded_context: Option<LoadedContext>,
|
||||||
|
checkpoint: Option<GitStoreCheckpoint>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(message) = self.messages.iter_mut().find(|message| message.id == id) else {
|
let Some(message) = self.messages.iter_mut().find(|message| message.id == id) else {
|
||||||
@@ -1006,6 +1035,15 @@ impl Thread {
|
|||||||
if let Some(context) = loaded_context {
|
if let Some(context) = loaded_context {
|
||||||
message.loaded_context = context;
|
message.loaded_context = context;
|
||||||
}
|
}
|
||||||
|
if let Some(git_checkpoint) = checkpoint {
|
||||||
|
self.checkpoints_by_message.insert(
|
||||||
|
id,
|
||||||
|
ThreadCheckpoint {
|
||||||
|
message_id: id,
|
||||||
|
git_checkpoint,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
self.touch_updated_at();
|
self.touch_updated_at();
|
||||||
cx.emit(ThreadEvent::MessageEdited(id));
|
cx.emit(ThreadEvent::MessageEdited(id));
|
||||||
true
|
true
|
||||||
@@ -1114,6 +1152,7 @@ impl Thread {
|
|||||||
label: crease.metadata.label.clone(),
|
label: crease.metadata.label.clone(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
is_hidden: message.is_hidden,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
initial_project_snapshot,
|
initial_project_snapshot,
|
||||||
@@ -1129,6 +1168,7 @@ impl Thread {
|
|||||||
model: model.model.id().0.to_string(),
|
model: model.model.id().0.to_string(),
|
||||||
}),
|
}),
|
||||||
completion_mode: Some(this.completion_mode),
|
completion_mode: Some(this.completion_mode),
|
||||||
|
tool_use_limit_reached: this.tool_use_limit_reached,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1144,6 +1184,7 @@ impl Thread {
|
|||||||
pub fn send_to_model(
|
pub fn send_to_model(
|
||||||
&mut self,
|
&mut self,
|
||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
|
intent: CompletionIntent,
|
||||||
window: Option<AnyWindowHandle>,
|
window: Option<AnyWindowHandle>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
@@ -1153,7 +1194,7 @@ impl Thread {
|
|||||||
|
|
||||||
self.remaining_turns -= 1;
|
self.remaining_turns -= 1;
|
||||||
|
|
||||||
let request = self.to_completion_request(model.clone(), cx);
|
let request = self.to_completion_request(model.clone(), intent, cx);
|
||||||
|
|
||||||
self.stream_completion(request, model, window, cx);
|
self.stream_completion(request, model, window, cx);
|
||||||
}
|
}
|
||||||
@@ -1173,17 +1214,19 @@ impl Thread {
|
|||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
|
intent: CompletionIntent,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> LanguageModelRequest {
|
) -> LanguageModelRequest {
|
||||||
let mut request = LanguageModelRequest {
|
let mut request = LanguageModelRequest {
|
||||||
thread_id: Some(self.id.to_string()),
|
thread_id: Some(self.id.to_string()),
|
||||||
prompt_id: Some(self.last_prompt_id.to_string()),
|
prompt_id: Some(self.last_prompt_id.to_string()),
|
||||||
|
intent: Some(intent),
|
||||||
mode: None,
|
mode: None,
|
||||||
messages: vec![],
|
messages: vec![],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: AssistantSettings::temperature_for_model(&model, cx),
|
temperature: AgentSettings::temperature_for_model(&model, cx),
|
||||||
};
|
};
|
||||||
|
|
||||||
let available_tools = self.available_tools(cx, model.clone());
|
let available_tools = self.available_tools(cx, model.clone());
|
||||||
@@ -1331,18 +1374,20 @@ impl Thread {
|
|||||||
fn to_summarize_request(
|
fn to_summarize_request(
|
||||||
&self,
|
&self,
|
||||||
model: &Arc<dyn LanguageModel>,
|
model: &Arc<dyn LanguageModel>,
|
||||||
|
intent: CompletionIntent,
|
||||||
added_user_message: String,
|
added_user_message: String,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> LanguageModelRequest {
|
) -> LanguageModelRequest {
|
||||||
let mut request = LanguageModelRequest {
|
let mut request = LanguageModelRequest {
|
||||||
thread_id: None,
|
thread_id: None,
|
||||||
prompt_id: None,
|
prompt_id: None,
|
||||||
|
intent: Some(intent),
|
||||||
mode: None,
|
mode: None,
|
||||||
messages: vec![],
|
messages: vec![],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: AssistantSettings::temperature_for_model(model, cx),
|
temperature: AgentSettings::temperature_for_model(model, cx),
|
||||||
};
|
};
|
||||||
|
|
||||||
for message in &self.messages {
|
for message in &self.messages {
|
||||||
@@ -1383,7 +1428,7 @@ impl Thread {
|
|||||||
messages: &mut Vec<LanguageModelRequestMessage>,
|
messages: &mut Vec<LanguageModelRequestMessage>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) {
|
) {
|
||||||
const STALE_FILES_HEADER: &str = "These files changed since last read:";
|
const STALE_FILES_HEADER: &str = include_str!("./prompts/stale_files_prompt_header.txt");
|
||||||
|
|
||||||
let mut stale_message = String::new();
|
let mut stale_message = String::new();
|
||||||
|
|
||||||
@@ -1395,7 +1440,7 @@ impl Thread {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if stale_message.is_empty() {
|
if stale_message.is_empty() {
|
||||||
write!(&mut stale_message, "{}\n", STALE_FILES_HEADER).ok();
|
write!(&mut stale_message, "{}\n", STALE_FILES_HEADER.trim()).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(&mut stale_message, "- {}", file.path().display()).ok();
|
writeln!(&mut stale_message, "- {}", file.path().display()).ok();
|
||||||
@@ -1617,7 +1662,7 @@ impl Thread {
|
|||||||
CompletionRequestStatus::Failed {
|
CompletionRequestStatus::Failed {
|
||||||
code, message, request_id
|
code, message, request_id
|
||||||
} => {
|
} => {
|
||||||
return Err(anyhow!("completion request failed. request_id: {request_id}, code: {code}, message: {message}"));
|
anyhow::bail!("completion request failed. request_id: {request_id}, code: {code}, message: {message}");
|
||||||
}
|
}
|
||||||
CompletionRequestStatus::UsageUpdated {
|
CompletionRequestStatus::UsageUpdated {
|
||||||
amount, limit
|
amount, limit
|
||||||
@@ -1628,6 +1673,7 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
CompletionRequestStatus::ToolUseLimitReached => {
|
CompletionRequestStatus::ToolUseLimitReached => {
|
||||||
thread.tool_use_limit_reached = true;
|
thread.tool_use_limit_reached = true;
|
||||||
|
cx.emit(ThreadEvent::ToolUseLimitReached);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1680,6 +1726,43 @@ impl Thread {
|
|||||||
project.set_agent_location(None, cx);
|
project.set_agent_location(None, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
StopReason::Refusal => {
|
||||||
|
thread.project.update(cx, |project, cx| {
|
||||||
|
project.set_agent_location(None, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the turn that was refused.
|
||||||
|
//
|
||||||
|
// https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/handle-streaming-refusals#reset-context-after-refusal
|
||||||
|
{
|
||||||
|
let mut messages_to_remove = Vec::new();
|
||||||
|
|
||||||
|
for (ix, message) in thread.messages.iter().enumerate().rev() {
|
||||||
|
messages_to_remove.push(message.id);
|
||||||
|
|
||||||
|
if message.role == Role::User {
|
||||||
|
if ix == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(prev_message) = thread.messages.get(ix - 1) {
|
||||||
|
if prev_message.role == Role::Assistant {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for message_id in messages_to_remove {
|
||||||
|
thread.delete_message(message_id, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.emit(ThreadEvent::ShowError(ThreadError::Message {
|
||||||
|
header: "Language model refusal".into(),
|
||||||
|
message: "Model refused to generate content for safety reasons.".into(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
thread.project.update(cx, |project, cx| {
|
thread.project.update(cx, |project, cx| {
|
||||||
@@ -1723,6 +1806,7 @@ impl Thread {
|
|||||||
thread.cancel_last_completion(window, cx);
|
thread.cancel_last_completion(window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
|
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
|
||||||
|
|
||||||
if let Some((request_callback, (request, response_events))) = thread
|
if let Some((request_callback, (request, response_events))) = thread
|
||||||
@@ -1771,12 +1855,14 @@ impl Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let added_user_message = "Generate a concise 3-7 word title for this conversation, omitting punctuation. \
|
let added_user_message = include_str!("./prompts/summarize_thread_prompt.txt");
|
||||||
Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`. \
|
|
||||||
If the conversation is about a specific subject, include it in the title. \
|
|
||||||
Be descriptive. DO NOT speak in the first person.";
|
|
||||||
|
|
||||||
let request = self.to_summarize_request(&model.model, added_user_message.into(), cx);
|
let request = self.to_summarize_request(
|
||||||
|
&model.model,
|
||||||
|
CompletionIntent::ThreadSummarization,
|
||||||
|
added_user_message.into(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
self.summary = ThreadSummary::Generating;
|
self.summary = ThreadSummary::Generating;
|
||||||
|
|
||||||
@@ -1870,14 +1956,14 @@ impl Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let added_user_message = "Generate a detailed summary of this conversation. Include:\n\
|
let added_user_message = include_str!("./prompts/summarize_thread_detailed_prompt.txt");
|
||||||
1. A brief overview of what was discussed\n\
|
|
||||||
2. Key facts or information discovered\n\
|
|
||||||
3. Outcomes or conclusions reached\n\
|
|
||||||
4. Any action items or next steps if any\n\
|
|
||||||
Format it in Markdown with headings and bullet points.";
|
|
||||||
|
|
||||||
let request = self.to_summarize_request(&model, added_user_message.into(), cx);
|
let request = self.to_summarize_request(
|
||||||
|
&model,
|
||||||
|
CompletionIntent::ThreadContextSummarization,
|
||||||
|
added_user_message.into(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
*self.detailed_summary_tx.borrow_mut() = DetailedSummaryState::Generating {
|
*self.detailed_summary_tx.borrow_mut() = DetailedSummaryState::Generating {
|
||||||
message_id: last_message_id,
|
message_id: last_message_id,
|
||||||
@@ -1969,7 +2055,8 @@ impl Thread {
|
|||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
) -> Vec<PendingToolUse> {
|
) -> Vec<PendingToolUse> {
|
||||||
self.auto_capture_telemetry(cx);
|
self.auto_capture_telemetry(cx);
|
||||||
let request = Arc::new(self.to_completion_request(model.clone(), cx));
|
let request =
|
||||||
|
Arc::new(self.to_completion_request(model.clone(), CompletionIntent::ToolResults, cx));
|
||||||
let pending_tool_uses = self
|
let pending_tool_uses = self
|
||||||
.tool_use
|
.tool_use
|
||||||
.pending_tool_uses()
|
.pending_tool_uses()
|
||||||
@@ -1981,7 +2068,7 @@ impl Thread {
|
|||||||
for tool_use in pending_tool_uses.iter() {
|
for tool_use in pending_tool_uses.iter() {
|
||||||
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
|
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
|
||||||
if tool.needs_confirmation(&tool_use.input, cx)
|
if tool.needs_confirmation(&tool_use.input, cx)
|
||||||
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
|
&& !AgentSettings::get_global(cx).always_allow_tool_actions
|
||||||
{
|
{
|
||||||
self.tool_use.confirm_tool_use(
|
self.tool_use.confirm_tool_use(
|
||||||
tool_use.id.clone(),
|
tool_use.id.clone(),
|
||||||
@@ -2165,7 +2252,7 @@ impl Thread {
|
|||||||
if self.all_tools_finished() {
|
if self.all_tools_finished() {
|
||||||
if let Some(ConfiguredModel { model, .. }) = self.configured_model.as_ref() {
|
if let Some(ConfiguredModel { model, .. }) = self.configured_model.as_ref() {
|
||||||
if !canceled {
|
if !canceled {
|
||||||
self.send_to_model(model.clone(), window, cx);
|
self.send_to_model(model.clone(), CompletionIntent::ToolResults, window, cx);
|
||||||
}
|
}
|
||||||
self.auto_capture_telemetry(cx);
|
self.auto_capture_telemetry(cx);
|
||||||
}
|
}
|
||||||
@@ -2198,10 +2285,17 @@ impl Thread {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finalize_pending_checkpoint(cx);
|
|
||||||
|
|
||||||
if canceled {
|
if canceled {
|
||||||
cx.emit(ThreadEvent::CompletionCanceled);
|
cx.emit(ThreadEvent::CompletionCanceled);
|
||||||
|
|
||||||
|
// When canceled, we always want to insert the checkpoint.
|
||||||
|
// (We skip over finalize_pending_checkpoint, because it
|
||||||
|
// would conclude we didn't have anything to insert here.)
|
||||||
|
if let Some(checkpoint) = self.pending_checkpoint.take() {
|
||||||
|
self.insert_checkpoint(checkpoint, cx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.finalize_pending_checkpoint(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
canceled
|
canceled
|
||||||
@@ -2505,8 +2599,8 @@ impl Thread {
|
|||||||
|
|
||||||
writeln!(markdown, "**\n")?;
|
writeln!(markdown, "**\n")?;
|
||||||
match &tool_result.content {
|
match &tool_result.content {
|
||||||
LanguageModelToolResultContent::Text(str) => {
|
LanguageModelToolResultContent::Text(text) => {
|
||||||
writeln!(markdown, "{}", str)?;
|
writeln!(markdown, "{text}")?;
|
||||||
}
|
}
|
||||||
LanguageModelToolResultContent::Image(image) => {
|
LanguageModelToolResultContent::Image(image) => {
|
||||||
writeln!(markdown, "", image.source)?;
|
writeln!(markdown, "", image.source)?;
|
||||||
@@ -2750,6 +2844,7 @@ pub enum ThreadEvent {
|
|||||||
},
|
},
|
||||||
CheckpointChanged,
|
CheckpointChanged,
|
||||||
ToolConfirmationNeeded,
|
ToolConfirmationNeeded,
|
||||||
|
ToolUseLimitReached,
|
||||||
CancelEditing,
|
CancelEditing,
|
||||||
CompletionCanceled,
|
CompletionCanceled,
|
||||||
}
|
}
|
||||||
@@ -2766,7 +2861,7 @@ struct PendingCompletion {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
|
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
|
||||||
use assistant_settings::{AssistantSettings, LanguageModelParameters};
|
use agent_settings::{AgentSettings, LanguageModelParameters};
|
||||||
use assistant_tool::ToolRegistry;
|
use assistant_tool::ToolRegistry;
|
||||||
use editor::EditorSettings;
|
use editor::EditorSettings;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
@@ -2797,7 +2892,8 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let context = context_store.update(cx, |store, _| store.context().next().cloned().unwrap());
|
let context =
|
||||||
|
context_store.read_with(cx, |store, _| store.context().next().cloned().unwrap());
|
||||||
let loaded_context = cx
|
let loaded_context = cx
|
||||||
.update(|cx| load_context(vec![context], &project, &None, cx))
|
.update(|cx| load_context(vec![context], &project, &None, cx))
|
||||||
.await;
|
.await;
|
||||||
@@ -2848,7 +2944,7 @@ fn main() {{
|
|||||||
|
|
||||||
// Check message in request
|
// Check message in request
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(request.messages.len(), 2);
|
assert_eq!(request.messages.len(), 2);
|
||||||
@@ -2943,7 +3039,7 @@ fn main() {{
|
|||||||
|
|
||||||
// Check entire request to make sure all contexts are properly included
|
// Check entire request to make sure all contexts are properly included
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
// The request should contain all 3 messages
|
// The request should contain all 3 messages
|
||||||
@@ -3050,7 +3146,7 @@ fn main() {{
|
|||||||
|
|
||||||
// Check message in request
|
// Check message in request
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(request.messages.len(), 2);
|
assert_eq!(request.messages.len(), 2);
|
||||||
@@ -3076,7 +3172,7 @@ fn main() {{
|
|||||||
|
|
||||||
// Check that both messages appear in the request
|
// Check that both messages appear in the request
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(request.messages.len(), 3);
|
assert_eq!(request.messages.len(), 3);
|
||||||
@@ -3108,7 +3204,8 @@ fn main() {{
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let context = context_store.update(cx, |store, _| store.context().next().cloned().unwrap());
|
let context =
|
||||||
|
context_store.read_with(cx, |store, _| store.context().next().cloned().unwrap());
|
||||||
let loaded_context = cx
|
let loaded_context = cx
|
||||||
.update(|cx| load_context(vec![context], &project, &None, cx))
|
.update(|cx| load_context(vec![context], &project, &None, cx))
|
||||||
.await;
|
.await;
|
||||||
@@ -3120,7 +3217,7 @@ fn main() {{
|
|||||||
|
|
||||||
// Create a request and check that it doesn't have a stale buffer warning yet
|
// Create a request and check that it doesn't have a stale buffer warning yet
|
||||||
let initial_request = thread.update(cx, |thread, cx| {
|
let initial_request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure we don't have a stale file warning yet
|
// Make sure we don't have a stale file warning yet
|
||||||
@@ -3156,7 +3253,7 @@ fn main() {{
|
|||||||
|
|
||||||
// Create a new request and check for the stale buffer warning
|
// Create a new request and check for the stale buffer warning
|
||||||
let new_request = thread.update(cx, |thread, cx| {
|
let new_request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
// We should have a stale file warning as the last message
|
// We should have a stale file warning as the last message
|
||||||
@@ -3192,81 +3289,81 @@ fn main() {{
|
|||||||
|
|
||||||
// Both model and provider
|
// Both model and provider
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
AssistantSettings::override_global(
|
AgentSettings::override_global(
|
||||||
AssistantSettings {
|
AgentSettings {
|
||||||
model_parameters: vec![LanguageModelParameters {
|
model_parameters: vec![LanguageModelParameters {
|
||||||
provider: Some(model.provider_id().0.to_string().into()),
|
provider: Some(model.provider_id().0.to_string().into()),
|
||||||
model: Some(model.id().0.clone()),
|
model: Some(model.id().0.clone()),
|
||||||
temperature: Some(0.66),
|
temperature: Some(0.66),
|
||||||
}],
|
}],
|
||||||
..AssistantSettings::get_global(cx).clone()
|
..AgentSettings::get_global(cx).clone()
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
assert_eq!(request.temperature, Some(0.66));
|
assert_eq!(request.temperature, Some(0.66));
|
||||||
|
|
||||||
// Only model
|
// Only model
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
AssistantSettings::override_global(
|
AgentSettings::override_global(
|
||||||
AssistantSettings {
|
AgentSettings {
|
||||||
model_parameters: vec![LanguageModelParameters {
|
model_parameters: vec![LanguageModelParameters {
|
||||||
provider: None,
|
provider: None,
|
||||||
model: Some(model.id().0.clone()),
|
model: Some(model.id().0.clone()),
|
||||||
temperature: Some(0.66),
|
temperature: Some(0.66),
|
||||||
}],
|
}],
|
||||||
..AssistantSettings::get_global(cx).clone()
|
..AgentSettings::get_global(cx).clone()
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
assert_eq!(request.temperature, Some(0.66));
|
assert_eq!(request.temperature, Some(0.66));
|
||||||
|
|
||||||
// Only provider
|
// Only provider
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
AssistantSettings::override_global(
|
AgentSettings::override_global(
|
||||||
AssistantSettings {
|
AgentSettings {
|
||||||
model_parameters: vec![LanguageModelParameters {
|
model_parameters: vec![LanguageModelParameters {
|
||||||
provider: Some(model.provider_id().0.to_string().into()),
|
provider: Some(model.provider_id().0.to_string().into()),
|
||||||
model: None,
|
model: None,
|
||||||
temperature: Some(0.66),
|
temperature: Some(0.66),
|
||||||
}],
|
}],
|
||||||
..AssistantSettings::get_global(cx).clone()
|
..AgentSettings::get_global(cx).clone()
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
assert_eq!(request.temperature, Some(0.66));
|
assert_eq!(request.temperature, Some(0.66));
|
||||||
|
|
||||||
// Same model name, different provider
|
// Same model name, different provider
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
AssistantSettings::override_global(
|
AgentSettings::override_global(
|
||||||
AssistantSettings {
|
AgentSettings {
|
||||||
model_parameters: vec![LanguageModelParameters {
|
model_parameters: vec![LanguageModelParameters {
|
||||||
provider: Some("anthropic".into()),
|
provider: Some("anthropic".into()),
|
||||||
model: Some(model.id().0.clone()),
|
model: Some(model.id().0.clone()),
|
||||||
temperature: Some(0.66),
|
temperature: Some(0.66),
|
||||||
}],
|
}],
|
||||||
..AssistantSettings::get_global(cx).clone()
|
..AgentSettings::get_global(cx).clone()
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = thread.update(cx, |thread, cx| {
|
let request = thread.update(cx, |thread, cx| {
|
||||||
thread.to_completion_request(model.clone(), cx)
|
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
|
||||||
});
|
});
|
||||||
assert_eq!(request.temperature, None);
|
assert_eq!(request.temperature, None);
|
||||||
}
|
}
|
||||||
@@ -3298,7 +3395,12 @@ fn main() {{
|
|||||||
// Send a message
|
// Send a message
|
||||||
thread.update(cx, |thread, cx| {
|
thread.update(cx, |thread, cx| {
|
||||||
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
|
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
|
||||||
thread.send_to_model(model.clone(), None, cx);
|
thread.send_to_model(
|
||||||
|
model.clone(),
|
||||||
|
CompletionIntent::ThreadSummarization,
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let fake_model = model.as_fake();
|
let fake_model = model.as_fake();
|
||||||
@@ -3320,8 +3422,8 @@ fn main() {{
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
fake_model.stream_last_completion_response("Brief".into());
|
fake_model.stream_last_completion_response("Brief");
|
||||||
fake_model.stream_last_completion_response(" Introduction".into());
|
fake_model.stream_last_completion_response(" Introduction");
|
||||||
fake_model.end_last_completion_stream();
|
fake_model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
@@ -3393,7 +3495,7 @@ fn main() {{
|
|||||||
vec![],
|
vec![],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
thread.send_to_model(model.clone(), None, cx);
|
thread.send_to_model(model.clone(), CompletionIntent::UserPrompt, None, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let fake_model = model.as_fake();
|
let fake_model = model.as_fake();
|
||||||
@@ -3414,7 +3516,7 @@ fn main() {{
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
fake_model.stream_last_completion_response("A successful summary".into());
|
fake_model.stream_last_completion_response("A successful summary");
|
||||||
fake_model.end_last_completion_stream();
|
fake_model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
@@ -3431,7 +3533,12 @@ fn main() {{
|
|||||||
) {
|
) {
|
||||||
thread.update(cx, |thread, cx| {
|
thread.update(cx, |thread, cx| {
|
||||||
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
|
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
|
||||||
thread.send_to_model(model.clone(), None, cx);
|
thread.send_to_model(
|
||||||
|
model.clone(),
|
||||||
|
CompletionIntent::ThreadSummarization,
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let fake_model = model.as_fake();
|
let fake_model = model.as_fake();
|
||||||
@@ -3456,7 +3563,7 @@ fn main() {{
|
|||||||
|
|
||||||
fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
|
fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
fake_model.stream_last_completion_response("Assistant response".into());
|
fake_model.stream_last_completion_response("Assistant response");
|
||||||
fake_model.end_last_completion_stream();
|
fake_model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
}
|
}
|
||||||
@@ -3467,7 +3574,7 @@ fn main() {{
|
|||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::{Ref, RefCell};
|
use std::cell::{Ref, RefCell};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, CompletionMode};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings, CompletionMode};
|
|
||||||
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
@@ -17,8 +16,7 @@ use gpui::{
|
|||||||
App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
|
App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
|
||||||
Subscription, Task, prelude::*,
|
Subscription, Task, prelude::*,
|
||||||
};
|
};
|
||||||
use heed::Database;
|
|
||||||
use heed::types::SerdeBincode;
|
|
||||||
use language_model::{LanguageModelToolResultContent, LanguageModelToolUseId, Role, TokenUsage};
|
use language_model::{LanguageModelToolResultContent, LanguageModelToolUseId, Role, TokenUsage};
|
||||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||||
@@ -35,6 +33,42 @@ use crate::context_server_tool::ContextServerTool;
|
|||||||
use crate::thread::{
|
use crate::thread::{
|
||||||
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||||
};
|
};
|
||||||
|
use indoc::indoc;
|
||||||
|
use sqlez::{
|
||||||
|
bindable::{Bind, Column},
|
||||||
|
connection::Connection,
|
||||||
|
statement::Statement,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum DataType {
|
||||||
|
#[serde(rename = "json")]
|
||||||
|
Json,
|
||||||
|
#[serde(rename = "zstd")]
|
||||||
|
Zstd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bind for DataType {
|
||||||
|
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||||
|
let value = match self {
|
||||||
|
DataType::Json => "json",
|
||||||
|
DataType::Zstd => "zstd",
|
||||||
|
};
|
||||||
|
value.bind(statement, start_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Column for DataType {
|
||||||
|
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||||
|
let (value, next_index) = String::column(statement, start_index)?;
|
||||||
|
let data_type = match value.as_str() {
|
||||||
|
"json" => DataType::Json,
|
||||||
|
"zstd" => DataType::Zstd,
|
||||||
|
_ => anyhow::bail!("Unknown data type: {}", value),
|
||||||
|
};
|
||||||
|
Ok((data_type, next_index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const RULES_FILE_NAMES: [&'static str; 6] = [
|
const RULES_FILE_NAMES: [&'static str; 6] = [
|
||||||
".rules",
|
".rules",
|
||||||
@@ -419,7 +453,7 @@ impl ThreadStore {
|
|||||||
let thread = database
|
let thread = database
|
||||||
.try_find_thread(id.clone())
|
.try_find_thread(id.clone())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
|
.with_context(|| format!("no thread found with ID: {id:?}"))?;
|
||||||
|
|
||||||
let thread = this.update_in(cx, |this, window, cx| {
|
let thread = this.update_in(cx, |this, window, cx| {
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
@@ -485,13 +519,13 @@ impl ThreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_default_profile(&self, cx: &mut Context<Self>) {
|
fn load_default_profile(&self, cx: &mut Context<Self>) {
|
||||||
let assistant_settings = AssistantSettings::get_global(cx);
|
let assistant_settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
|
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
|
pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
|
||||||
let assistant_settings = AssistantSettings::get_global(cx);
|
let assistant_settings = AgentSettings::get_global(cx);
|
||||||
|
|
||||||
if let Some(profile) = assistant_settings.profiles.get(&profile_id) {
|
if let Some(profile) = assistant_settings.profiles.get(&profile_id) {
|
||||||
self.load_profile(profile.clone(), cx);
|
self.load_profile(profile.clone(), cx);
|
||||||
@@ -676,6 +710,8 @@ pub struct SerializedThread {
|
|||||||
pub model: Option<SerializedLanguageModel>,
|
pub model: Option<SerializedLanguageModel>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub completion_mode: Option<CompletionMode>,
|
pub completion_mode: Option<CompletionMode>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub tool_use_limit_reached: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
@@ -699,20 +735,14 @@ impl SerializedThread {
|
|||||||
SerializedThread::VERSION => Ok(serde_json::from_value::<SerializedThread>(
|
SerializedThread::VERSION => Ok(serde_json::from_value::<SerializedThread>(
|
||||||
saved_thread_json,
|
saved_thread_json,
|
||||||
)?),
|
)?),
|
||||||
_ => Err(anyhow!(
|
_ => anyhow::bail!("unrecognized serialized thread version: {version:?}"),
|
||||||
"unrecognized serialized thread version: {}",
|
|
||||||
version
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
let saved_thread =
|
let saved_thread =
|
||||||
serde_json::from_value::<LegacySerializedThread>(saved_thread_json)?;
|
serde_json::from_value::<LegacySerializedThread>(saved_thread_json)?;
|
||||||
Ok(saved_thread.upgrade())
|
Ok(saved_thread.upgrade())
|
||||||
}
|
}
|
||||||
version => Err(anyhow!(
|
version => anyhow::bail!("unrecognized serialized thread version: {version:?}"),
|
||||||
"unrecognized serialized thread version: {:?}",
|
|
||||||
version
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -763,6 +793,8 @@ pub struct SerializedMessage {
|
|||||||
pub context: String,
|
pub context: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub creases: Vec<SerializedCrease>,
|
pub creases: Vec<SerializedCrease>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub is_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -821,6 +853,7 @@ impl LegacySerializedThread {
|
|||||||
exceeded_window_error: None,
|
exceeded_window_error: None,
|
||||||
model: None,
|
model: None,
|
||||||
completion_mode: None,
|
completion_mode: None,
|
||||||
|
tool_use_limit_reached: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,6 +879,7 @@ impl LegacySerializedMessage {
|
|||||||
tool_results: self.tool_results,
|
tool_results: self.tool_results,
|
||||||
context: String::new(),
|
context: String::new(),
|
||||||
creases: Vec::new(),
|
creases: Vec::new(),
|
||||||
|
is_hidden: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -866,25 +900,27 @@ impl Global for GlobalThreadsDatabase {}
|
|||||||
|
|
||||||
pub(crate) struct ThreadsDatabase {
|
pub(crate) struct ThreadsDatabase {
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
env: heed::Env,
|
connection: Arc<Mutex<Connection>>,
|
||||||
threads: Database<SerdeBincode<ThreadId>, SerializedThread>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl heed::BytesEncode<'_> for SerializedThread {
|
impl ThreadsDatabase {
|
||||||
type EItem = SerializedThread;
|
fn connection(&self) -> Arc<Mutex<Connection>> {
|
||||||
|
self.connection.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn bytes_encode(item: &Self::EItem) -> Result<Cow<[u8]>, heed::BoxedError> {
|
const COMPRESSION_LEVEL: i32 = 3;
|
||||||
serde_json::to_vec(item).map(Cow::Owned).map_err(Into::into)
|
}
|
||||||
|
|
||||||
|
impl Bind for ThreadId {
|
||||||
|
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||||
|
self.to_string().bind(statement, start_index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> heed::BytesDecode<'a> for SerializedThread {
|
impl Column for ThreadId {
|
||||||
type DItem = SerializedThread;
|
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||||
|
let (id_str, next_index) = String::column(statement, start_index)?;
|
||||||
fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, heed::BoxedError> {
|
Ok((ThreadId::from(id_str.as_str()), next_index))
|
||||||
// We implement this type manually because we want to call `SerializedThread::from_json`,
|
|
||||||
// instead of the Deserialize trait implementation for `SerializedThread`.
|
|
||||||
SerializedThread::from_json(bytes).map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,8 +936,8 @@ impl ThreadsDatabase {
|
|||||||
let database_future = executor
|
let database_future = executor
|
||||||
.spawn({
|
.spawn({
|
||||||
let executor = executor.clone();
|
let executor = executor.clone();
|
||||||
let database_path = paths::data_dir().join("threads/threads-db.1.mdb");
|
let threads_dir = paths::data_dir().join("threads");
|
||||||
async move { ThreadsDatabase::new(database_path, executor) }
|
async move { ThreadsDatabase::new(threads_dir, executor) }
|
||||||
})
|
})
|
||||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -910,41 +946,144 @@ impl ThreadsDatabase {
|
|||||||
cx.set_global(GlobalThreadsDatabase(database_future));
|
cx.set_global(GlobalThreadsDatabase(database_future));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(path: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
|
pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
|
||||||
std::fs::create_dir_all(&path)?;
|
std::fs::create_dir_all(&threads_dir)?;
|
||||||
|
|
||||||
|
let sqlite_path = threads_dir.join("threads.db");
|
||||||
|
let mdb_path = threads_dir.join("threads-db.1.mdb");
|
||||||
|
|
||||||
|
let needs_migration_from_heed = mdb_path.exists();
|
||||||
|
|
||||||
|
let connection = Connection::open_file(&sqlite_path.to_string_lossy());
|
||||||
|
|
||||||
|
connection.exec(indoc! {"
|
||||||
|
CREATE TABLE IF NOT EXISTS threads (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
summary TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
data_type TEXT NOT NULL,
|
||||||
|
data BLOB NOT NULL
|
||||||
|
)
|
||||||
|
"})?()
|
||||||
|
.map_err(|e| anyhow!("Failed to create threads table: {}", e))?;
|
||||||
|
|
||||||
|
let db = Self {
|
||||||
|
executor: executor.clone(),
|
||||||
|
connection: Arc::new(Mutex::new(connection)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if needs_migration_from_heed {
|
||||||
|
let db_connection = db.connection();
|
||||||
|
let executor_clone = executor.clone();
|
||||||
|
executor
|
||||||
|
.spawn(async move {
|
||||||
|
log::info!("Starting threads.db migration");
|
||||||
|
Self::migrate_from_heed(&mdb_path, db_connection, executor_clone)?;
|
||||||
|
std::fs::remove_dir_all(mdb_path)?;
|
||||||
|
log::info!("threads.db migrated to sqlite");
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove this migration after 2025-09-01
|
||||||
|
fn migrate_from_heed(
|
||||||
|
mdb_path: &Path,
|
||||||
|
connection: Arc<Mutex<Connection>>,
|
||||||
|
_executor: BackgroundExecutor,
|
||||||
|
) -> Result<()> {
|
||||||
|
use heed::types::SerdeBincode;
|
||||||
|
struct SerializedThreadHeed(SerializedThread);
|
||||||
|
|
||||||
|
impl heed::BytesEncode<'_> for SerializedThreadHeed {
|
||||||
|
type EItem = SerializedThreadHeed;
|
||||||
|
|
||||||
|
fn bytes_encode(
|
||||||
|
item: &Self::EItem,
|
||||||
|
) -> Result<std::borrow::Cow<[u8]>, heed::BoxedError> {
|
||||||
|
serde_json::to_vec(&item.0)
|
||||||
|
.map(std::borrow::Cow::Owned)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> heed::BytesDecode<'a> for SerializedThreadHeed {
|
||||||
|
type DItem = SerializedThreadHeed;
|
||||||
|
|
||||||
|
fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, heed::BoxedError> {
|
||||||
|
SerializedThread::from_json(bytes)
|
||||||
|
.map(SerializedThreadHeed)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ONE_GB_IN_BYTES: usize = 1024 * 1024 * 1024;
|
const ONE_GB_IN_BYTES: usize = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
let env = unsafe {
|
let env = unsafe {
|
||||||
heed::EnvOpenOptions::new()
|
heed::EnvOpenOptions::new()
|
||||||
.map_size(ONE_GB_IN_BYTES)
|
.map_size(ONE_GB_IN_BYTES)
|
||||||
.max_dbs(1)
|
.max_dbs(1)
|
||||||
.open(path)?
|
.open(mdb_path)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut txn = env.write_txn()?;
|
let txn = env.write_txn()?;
|
||||||
let threads = env.create_database(&mut txn, Some("threads"))?;
|
let threads: heed::Database<SerdeBincode<ThreadId>, SerializedThreadHeed> = env
|
||||||
txn.commit()?;
|
.open_database(&txn, Some("threads"))?
|
||||||
|
.ok_or_else(|| anyhow!("threads database not found"))?;
|
||||||
|
|
||||||
Ok(Self {
|
for result in threads.iter(&txn)? {
|
||||||
executor,
|
let (thread_id, thread_heed) = result?;
|
||||||
env,
|
Self::save_thread_sync(&connection, thread_id, thread_heed.0)?;
|
||||||
threads,
|
}
|
||||||
})
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_thread_sync(
|
||||||
|
connection: &Arc<Mutex<Connection>>,
|
||||||
|
id: ThreadId,
|
||||||
|
thread: SerializedThread,
|
||||||
|
) -> Result<()> {
|
||||||
|
let json_data = serde_json::to_string(&thread)?;
|
||||||
|
let summary = thread.summary.to_string();
|
||||||
|
let updated_at = thread.updated_at.to_rfc3339();
|
||||||
|
|
||||||
|
let connection = connection.lock().unwrap();
|
||||||
|
|
||||||
|
let compressed = zstd::encode_all(json_data.as_bytes(), Self::COMPRESSION_LEVEL)?;
|
||||||
|
let data_type = DataType::Zstd;
|
||||||
|
let data = compressed;
|
||||||
|
|
||||||
|
let mut insert = connection.exec_bound::<(ThreadId, String, String, DataType, Vec<u8>)>(indoc! {"
|
||||||
|
INSERT OR REPLACE INTO threads (id, summary, updated_at, data_type, data) VALUES (?, ?, ?, ?, ?)
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
insert((id, summary, updated_at, data_type, data))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_threads(&self) -> Task<Result<Vec<SerializedThreadMetadata>>> {
|
pub fn list_threads(&self) -> Task<Result<Vec<SerializedThreadMetadata>>> {
|
||||||
let env = self.env.clone();
|
let connection = self.connection.clone();
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
self.executor.spawn(async move {
|
||||||
let txn = env.read_txn()?;
|
let connection = connection.lock().unwrap();
|
||||||
let mut iter = threads.iter(&txn)?;
|
let mut select =
|
||||||
|
connection.select_bound::<(), (ThreadId, String, String)>(indoc! {"
|
||||||
|
SELECT id, summary, updated_at FROM threads ORDER BY updated_at DESC
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
let rows = select(())?;
|
||||||
let mut threads = Vec::new();
|
let mut threads = Vec::new();
|
||||||
while let Some((key, value)) = iter.next().transpose()? {
|
|
||||||
|
for (id, summary, updated_at) in rows {
|
||||||
threads.push(SerializedThreadMetadata {
|
threads.push(SerializedThreadMetadata {
|
||||||
id: key,
|
id,
|
||||||
summary: value.summary,
|
summary: summary.into(),
|
||||||
updated_at: value.updated_at,
|
updated_at: DateTime::parse_from_rfc3339(&updated_at)?.with_timezone(&Utc),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -953,36 +1092,51 @@ impl ThreadsDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_find_thread(&self, id: ThreadId) -> Task<Result<Option<SerializedThread>>> {
|
pub fn try_find_thread(&self, id: ThreadId) -> Task<Result<Option<SerializedThread>>> {
|
||||||
let env = self.env.clone();
|
let connection = self.connection.clone();
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
self.executor.spawn(async move {
|
||||||
let txn = env.read_txn()?;
|
let connection = connection.lock().unwrap();
|
||||||
let thread = threads.get(&txn, &id)?;
|
let mut select = connection.select_bound::<ThreadId, (DataType, Vec<u8>)>(indoc! {"
|
||||||
Ok(thread)
|
SELECT data_type, data FROM threads WHERE id = ? LIMIT 1
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
let rows = select(id)?;
|
||||||
|
if let Some((data_type, data)) = rows.into_iter().next() {
|
||||||
|
let json_data = match data_type {
|
||||||
|
DataType::Zstd => {
|
||||||
|
let decompressed = zstd::decode_all(&data[..])?;
|
||||||
|
String::from_utf8(decompressed)?
|
||||||
|
}
|
||||||
|
DataType::Json => String::from_utf8(data)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let thread = SerializedThread::from_json(json_data.as_bytes())?;
|
||||||
|
Ok(Some(thread))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_thread(&self, id: ThreadId, thread: SerializedThread) -> Task<Result<()>> {
|
pub fn save_thread(&self, id: ThreadId, thread: SerializedThread) -> Task<Result<()>> {
|
||||||
let env = self.env.clone();
|
let connection = self.connection.clone();
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
self.executor
|
||||||
let mut txn = env.write_txn()?;
|
.spawn(async move { Self::save_thread_sync(&connection, id, thread) })
|
||||||
threads.put(&mut txn, &id, &thread)?;
|
|
||||||
txn.commit()?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_thread(&self, id: ThreadId) -> Task<Result<()>> {
|
pub fn delete_thread(&self, id: ThreadId) -> Task<Result<()>> {
|
||||||
let env = self.env.clone();
|
let connection = self.connection.clone();
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
self.executor.spawn(async move {
|
||||||
let mut txn = env.write_txn()?;
|
let connection = connection.lock().unwrap();
|
||||||
threads.delete(&mut txn, &id)?;
|
|
||||||
txn.commit()?;
|
let mut delete = connection.exec_bound::<ThreadId>(indoc! {"
|
||||||
|
DELETE FROM threads WHERE id = ?
|
||||||
|
"})?;
|
||||||
|
|
||||||
|
delete(id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# Build better with Zed Pro
|
|
||||||
|
|
||||||
Try [Zed Pro](https://zed.dev/pricing) for free for 14 days - no credit card required. Only $20/month afterward. Cancel anytime.
|
|
||||||
@@ -304,7 +304,7 @@ impl AddedContext {
|
|||||||
AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
|
AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
|
||||||
AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)),
|
AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)),
|
||||||
AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
|
AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
|
||||||
AgentContextHandle::Image(handle) => Some(Self::image(handle)),
|
AgentContextHandle::Image(handle) => Some(Self::image(handle, cx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ impl AddedContext {
|
|||||||
AgentContext::Thread(context) => Self::attached_thread(context),
|
AgentContext::Thread(context) => Self::attached_thread(context),
|
||||||
AgentContext::TextThread(context) => Self::attached_text_thread(context),
|
AgentContext::TextThread(context) => Self::attached_text_thread(context),
|
||||||
AgentContext::Rules(context) => Self::attached_rules(context),
|
AgentContext::Rules(context) => Self::attached_rules(context),
|
||||||
AgentContext::Image(context) => Self::image(context.clone()),
|
AgentContext::Image(context) => Self::image(context.clone(), cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,14 +333,8 @@ impl AddedContext {
|
|||||||
|
|
||||||
fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext {
|
fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext {
|
||||||
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
|
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
|
||||||
let name = full_path
|
let (name, parent) =
|
||||||
.file_name()
|
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
|
||||||
.map(|n| n.to_string_lossy().into_owned().into())
|
|
||||||
.unwrap_or_else(|| full_path_string.clone());
|
|
||||||
let parent = full_path
|
|
||||||
.parent()
|
|
||||||
.and_then(|p| p.file_name())
|
|
||||||
.map(|n| n.to_string_lossy().into_owned().into());
|
|
||||||
AddedContext {
|
AddedContext {
|
||||||
kind: ContextKind::File,
|
kind: ContextKind::File,
|
||||||
name,
|
name,
|
||||||
@@ -370,14 +364,8 @@ impl AddedContext {
|
|||||||
|
|
||||||
fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {
|
fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {
|
||||||
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
|
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
|
||||||
let name = full_path
|
let (name, parent) =
|
||||||
.file_name()
|
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
|
||||||
.map(|n| n.to_string_lossy().into_owned().into())
|
|
||||||
.unwrap_or_else(|| full_path_string.clone());
|
|
||||||
let parent = full_path
|
|
||||||
.parent()
|
|
||||||
.and_then(|p| p.file_name())
|
|
||||||
.map(|n| n.to_string_lossy().into_owned().into());
|
|
||||||
AddedContext {
|
AddedContext {
|
||||||
kind: ContextKind::Directory,
|
kind: ContextKind::Directory,
|
||||||
name,
|
name,
|
||||||
@@ -605,13 +593,23 @@ impl AddedContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image(context: ImageContext) -> AddedContext {
|
fn image(context: ImageContext, cx: &App) -> AddedContext {
|
||||||
|
let (name, parent, icon_path) = if let Some(full_path) = context.full_path.as_ref() {
|
||||||
|
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
|
||||||
|
let (name, parent) =
|
||||||
|
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
|
||||||
|
let icon_path = FileIcons::get_icon(&full_path, cx);
|
||||||
|
(name, parent, icon_path)
|
||||||
|
} else {
|
||||||
|
("Image".into(), None, None)
|
||||||
|
};
|
||||||
|
|
||||||
AddedContext {
|
AddedContext {
|
||||||
kind: ContextKind::Image,
|
kind: ContextKind::Image,
|
||||||
name: "Image".into(),
|
name,
|
||||||
parent: None,
|
parent,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
icon_path: None,
|
icon_path,
|
||||||
status: match context.status() {
|
status: match context.status() {
|
||||||
ImageStatus::Loading => ContextStatus::Loading {
|
ImageStatus::Loading => ContextStatus::Loading {
|
||||||
message: "Loading…".into(),
|
message: "Loading…".into(),
|
||||||
@@ -639,6 +637,22 @@ impl AddedContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_file_name_and_directory_from_full_path(
|
||||||
|
path: &Path,
|
||||||
|
name_fallback: &SharedString,
|
||||||
|
) -> (SharedString, Option<SharedString>) {
|
||||||
|
let name = path
|
||||||
|
.file_name()
|
||||||
|
.map(|n| n.to_string_lossy().into_owned().into())
|
||||||
|
.unwrap_or_else(|| name_fallback.clone());
|
||||||
|
let parent = path
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.file_name())
|
||||||
|
.map(|n| n.to_string_lossy().into_owned().into());
|
||||||
|
|
||||||
|
(name, parent)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct ContextFileExcerpt {
|
struct ContextFileExcerpt {
|
||||||
pub file_name_and_range: SharedString,
|
pub file_name_and_range: SharedString,
|
||||||
@@ -765,37 +779,49 @@ impl Component for AddedContext {
|
|||||||
let mut next_context_id = ContextId::zero();
|
let mut next_context_id = ContextId::zero();
|
||||||
let image_ready = (
|
let image_ready = (
|
||||||
"Ready",
|
"Ready",
|
||||||
AddedContext::image(ImageContext {
|
AddedContext::image(
|
||||||
context_id: next_context_id.post_inc(),
|
ImageContext {
|
||||||
project_path: None,
|
context_id: next_context_id.post_inc(),
|
||||||
original_image: Arc::new(Image::empty()),
|
project_path: None,
|
||||||
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
|
full_path: None,
|
||||||
}),
|
original_image: Arc::new(Image::empty()),
|
||||||
|
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
let image_loading = (
|
let image_loading = (
|
||||||
"Loading",
|
"Loading",
|
||||||
AddedContext::image(ImageContext {
|
AddedContext::image(
|
||||||
context_id: next_context_id.post_inc(),
|
ImageContext {
|
||||||
project_path: None,
|
context_id: next_context_id.post_inc(),
|
||||||
original_image: Arc::new(Image::empty()),
|
project_path: None,
|
||||||
image_task: cx
|
full_path: None,
|
||||||
.background_spawn(async move {
|
original_image: Arc::new(Image::empty()),
|
||||||
smol::Timer::after(Duration::from_secs(60 * 5)).await;
|
image_task: cx
|
||||||
Some(LanguageModelImage::empty())
|
.background_spawn(async move {
|
||||||
})
|
smol::Timer::after(Duration::from_secs(60 * 5)).await;
|
||||||
.shared(),
|
Some(LanguageModelImage::empty())
|
||||||
}),
|
})
|
||||||
|
.shared(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
let image_error = (
|
let image_error = (
|
||||||
"Error",
|
"Error",
|
||||||
AddedContext::image(ImageContext {
|
AddedContext::image(
|
||||||
context_id: next_context_id.post_inc(),
|
ImageContext {
|
||||||
project_path: None,
|
context_id: next_context_id.post_inc(),
|
||||||
original_image: Arc::new(Image::empty()),
|
project_path: None,
|
||||||
image_task: Task::ready(None).shared(),
|
full_path: None,
|
||||||
}),
|
original_image: Arc::new(Image::empty()),
|
||||||
|
image_task: Task::ready(None).shared(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use gpui::{Context, IntoElement, Render, Window};
|
use crate::ToggleBurnMode;
|
||||||
use ui::{prelude::*, tooltip_container};
|
use gpui::{Context, FontWeight, IntoElement, Render, Window};
|
||||||
|
use ui::{KeyBinding, prelude::*, tooltip_container};
|
||||||
|
|
||||||
pub struct MaxModeTooltip {
|
pub struct MaxModeTooltip {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
@@ -18,38 +19,48 @@ impl MaxModeTooltip {
|
|||||||
|
|
||||||
impl Render for MaxModeTooltip {
|
impl Render for MaxModeTooltip {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let (icon, color) = if self.selected {
|
||||||
|
(IconName::ZedBurnModeOn, Color::Error)
|
||||||
|
} else {
|
||||||
|
(IconName::ZedBurnMode, Color::Default)
|
||||||
|
};
|
||||||
|
|
||||||
|
let turned_on = h_flex()
|
||||||
|
.h_4()
|
||||||
|
.px_1()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.bg(cx.theme().colors().text_accent.opacity(0.1))
|
||||||
|
.rounded_sm()
|
||||||
|
.child(
|
||||||
|
Label::new("ON")
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.weight(FontWeight::SEMIBOLD)
|
||||||
|
.color(Color::Accent),
|
||||||
|
);
|
||||||
|
|
||||||
|
let title = h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(Icon::new(icon).size(IconSize::Small).color(color))
|
||||||
|
.child(Label::new("Burn Mode"))
|
||||||
|
.when(self.selected, |title| title.child(turned_on));
|
||||||
|
|
||||||
|
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.)));
|
||||||
|
|
||||||
tooltip_container(window, cx, |this, _, _| {
|
tooltip_container(window, cx, |this, _, _| {
|
||||||
this.gap_1()
|
this
|
||||||
.map(|header| if self.selected {
|
.child(
|
||||||
header.child(
|
h_flex()
|
||||||
h_flex()
|
.justify_between()
|
||||||
.justify_between()
|
.child(title)
|
||||||
.child(
|
.children(keybinding)
|
||||||
h_flex()
|
)
|
||||||
.gap_1p5()
|
|
||||||
.child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small).color(Color::Accent))
|
|
||||||
.child(Label::new("Zed's Max Mode"))
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(Icon::new(IconName::Check).size(IconSize::XSmall).color(Color::Accent))
|
|
||||||
.child(Label::new("Turned On").size(LabelSize::XSmall).color(Color::Accent))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
header.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1p5()
|
|
||||||
.child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small))
|
|
||||||
.child(Label::new("Zed's Max Mode"))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.max_w_72()
|
.max_w_64()
|
||||||
.child(
|
.child(
|
||||||
Label::new("This mode enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
|
Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning.")
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "assistant_settings"
|
name = "agent_settings"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/assistant_settings.rs"
|
path = "src/agent_settings.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anthropic = { workspace = true, features = ["schemars"] }
|
anthropic = { workspace = true, features = ["schemars"] }
|
||||||
@@ -23,6 +23,7 @@ log.workspace = true
|
|||||||
ollama = { workspace = true, features = ["schemars"] }
|
ollama = { workspace = true, features = ["schemars"] }
|
||||||
open_ai = { workspace = true, features = ["schemars"] }
|
open_ai = { workspace = true, features = ["schemars"] }
|
||||||
deepseek = { workspace = true, features = ["schemars"] }
|
deepseek = { workspace = true, features = ["schemars"] }
|
||||||
|
mistral = { workspace = true, features = ["schemars"] }
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
@@ -24,7 +24,7 @@ pub struct GroupedAgentProfiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GroupedAgentProfiles {
|
impl GroupedAgentProfiles {
|
||||||
pub fn from_settings(settings: &crate::AssistantSettings) -> Self {
|
pub fn from_settings(settings: &crate::AgentSettings) -> Self {
|
||||||
let mut builtin = IndexMap::default();
|
let mut builtin = IndexMap::default();
|
||||||
let mut custom = IndexMap::default();
|
let mut custom = IndexMap::default();
|
||||||
|
|
||||||
@@ -8,8 +8,9 @@ use anyhow::{Result, bail};
|
|||||||
use collections::IndexMap;
|
use collections::IndexMap;
|
||||||
use deepseek::Model as DeepseekModel;
|
use deepseek::Model as DeepseekModel;
|
||||||
use gpui::{App, Pixels, SharedString};
|
use gpui::{App, Pixels, SharedString};
|
||||||
use language_model::{CloudModel, LanguageModel};
|
use language_model::LanguageModel;
|
||||||
use lmstudio::Model as LmStudioModel;
|
use lmstudio::Model as LmStudioModel;
|
||||||
|
use mistral::Model as MistralModel;
|
||||||
use ollama::Model as OllamaModel;
|
use ollama::Model as OllamaModel;
|
||||||
use schemars::{JsonSchema, schema::Schema};
|
use schemars::{JsonSchema, schema::Schema};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -18,18 +19,26 @@ use settings::{Settings, SettingsSources};
|
|||||||
pub use crate::agent_profile::*;
|
pub use crate::agent_profile::*;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum AssistantDockPosition {
|
pub enum AgentDockPosition {
|
||||||
Left,
|
Left,
|
||||||
#[default]
|
#[default]
|
||||||
Right,
|
Right,
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum DefaultView {
|
||||||
|
#[default]
|
||||||
|
Thread,
|
||||||
|
TextThread,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum NotifyWhenAgentWaiting {
|
pub enum NotifyWhenAgentWaiting {
|
||||||
@@ -42,9 +51,9 @@ pub enum NotifyWhenAgentWaiting {
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
#[serde(tag = "name", rename_all = "snake_case")]
|
#[serde(tag = "name", rename_all = "snake_case")]
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub enum AssistantProviderContentV1 {
|
pub enum AgentProviderContentV1 {
|
||||||
#[serde(rename = "zed.dev")]
|
#[serde(rename = "zed.dev")]
|
||||||
ZedDotDev { default_model: Option<CloudModel> },
|
ZedDotDev { default_model: Option<String> },
|
||||||
#[serde(rename = "openai")]
|
#[serde(rename = "openai")]
|
||||||
OpenAi {
|
OpenAi {
|
||||||
default_model: Option<OpenAiModel>,
|
default_model: Option<OpenAiModel>,
|
||||||
@@ -71,13 +80,18 @@ pub enum AssistantProviderContentV1 {
|
|||||||
default_model: Option<DeepseekModel>,
|
default_model: Option<DeepseekModel>,
|
||||||
api_url: Option<String>,
|
api_url: Option<String>,
|
||||||
},
|
},
|
||||||
|
#[serde(rename = "mistral")]
|
||||||
|
Mistral {
|
||||||
|
default_model: Option<MistralModel>,
|
||||||
|
api_url: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct AssistantSettings {
|
pub struct AgentSettings {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub button: bool,
|
pub button: bool,
|
||||||
pub dock: AssistantDockPosition,
|
pub dock: AgentDockPosition,
|
||||||
pub default_width: Pixels,
|
pub default_width: Pixels,
|
||||||
pub default_height: Pixels,
|
pub default_height: Pixels,
|
||||||
pub default_model: LanguageModelSelection,
|
pub default_model: LanguageModelSelection,
|
||||||
@@ -87,9 +101,11 @@ pub struct AssistantSettings {
|
|||||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||||
pub using_outdated_settings_version: bool,
|
pub using_outdated_settings_version: bool,
|
||||||
pub default_profile: AgentProfileId,
|
pub default_profile: AgentProfileId,
|
||||||
|
pub default_view: DefaultView,
|
||||||
pub profiles: IndexMap<AgentProfileId, AgentProfile>,
|
pub profiles: IndexMap<AgentProfileId, AgentProfile>,
|
||||||
pub always_allow_tool_actions: bool,
|
pub always_allow_tool_actions: bool,
|
||||||
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
||||||
|
pub play_sound_when_agent_done: bool,
|
||||||
pub stream_edits: bool,
|
pub stream_edits: bool,
|
||||||
pub single_file_review: bool,
|
pub single_file_review: bool,
|
||||||
pub model_parameters: Vec<LanguageModelParameters>,
|
pub model_parameters: Vec<LanguageModelParameters>,
|
||||||
@@ -97,7 +113,7 @@ pub struct AssistantSettings {
|
|||||||
pub enable_feedback: bool,
|
pub enable_feedback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantSettings {
|
impl AgentSettings {
|
||||||
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
||||||
let settings = Self::get_global(cx);
|
let settings = Self::get_global(cx);
|
||||||
settings
|
settings
|
||||||
@@ -152,58 +168,56 @@ impl LanguageModelParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assistant panel settings
|
/// Agent panel settings
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct AssistantSettingsContent {
|
pub struct AgentSettingsContent {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub inner: Option<AssistantSettingsContentInner>,
|
pub inner: Option<AgentSettingsContentInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum AssistantSettingsContentInner {
|
pub enum AgentSettingsContentInner {
|
||||||
Versioned(Box<VersionedAssistantSettingsContent>),
|
Versioned(Box<VersionedAgentSettingsContent>),
|
||||||
Legacy(LegacyAssistantSettingsContent),
|
Legacy(LegacyAgentSettingsContent),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantSettingsContentInner {
|
impl AgentSettingsContentInner {
|
||||||
fn for_v2(content: AssistantSettingsContentV2) -> Self {
|
fn for_v2(content: AgentSettingsContentV2) -> Self {
|
||||||
AssistantSettingsContentInner::Versioned(Box::new(VersionedAssistantSettingsContent::V2(
|
AgentSettingsContentInner::Versioned(Box::new(VersionedAgentSettingsContent::V2(content)))
|
||||||
content,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonSchema for AssistantSettingsContent {
|
impl JsonSchema for AgentSettingsContent {
|
||||||
fn schema_name() -> String {
|
fn schema_name() -> String {
|
||||||
VersionedAssistantSettingsContent::schema_name()
|
VersionedAgentSettingsContent::schema_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||||
VersionedAssistantSettingsContent::json_schema(r#gen)
|
VersionedAgentSettingsContent::json_schema(r#gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_referenceable() -> bool {
|
fn is_referenceable() -> bool {
|
||||||
VersionedAssistantSettingsContent::is_referenceable()
|
VersionedAgentSettingsContent::is_referenceable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantSettingsContent {
|
impl AgentSettingsContent {
|
||||||
pub fn is_version_outdated(&self) -> bool {
|
pub fn is_version_outdated(&self) -> bool {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||||
VersionedAssistantSettingsContent::V1(_) => true,
|
VersionedAgentSettingsContent::V1(_) => true,
|
||||||
VersionedAssistantSettingsContent::V2(_) => false,
|
VersionedAgentSettingsContent::V2(_) => false,
|
||||||
},
|
},
|
||||||
Some(AssistantSettingsContentInner::Legacy(_)) => true,
|
Some(AgentSettingsContentInner::Legacy(_)) => true,
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade(&self) -> AssistantSettingsContentV2 {
|
fn upgrade(&self) -> AgentSettingsContentV2 {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||||
VersionedAssistantSettingsContent::V1(ref settings) => AssistantSettingsContentV2 {
|
VersionedAgentSettingsContent::V1(ref settings) => AgentSettingsContentV2 {
|
||||||
enabled: settings.enabled,
|
enabled: settings.enabled,
|
||||||
button: settings.button,
|
button: settings.button,
|
||||||
dock: settings.dock,
|
dock: settings.dock,
|
||||||
@@ -213,48 +227,49 @@ impl AssistantSettingsContent {
|
|||||||
.provider
|
.provider
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|provider| match provider {
|
.and_then(|provider| match provider {
|
||||||
AssistantProviderContentV1::ZedDotDev { default_model } => {
|
AgentProviderContentV1::ZedDotDev { default_model } => default_model
|
||||||
default_model.map(|model| LanguageModelSelection {
|
.map(|model| LanguageModelSelection {
|
||||||
provider: "zed.dev".into(),
|
provider: "zed.dev".into(),
|
||||||
model: model.id().to_string(),
|
model,
|
||||||
})
|
}),
|
||||||
}
|
AgentProviderContentV1::OpenAi { default_model, .. } => default_model
|
||||||
AssistantProviderContentV1::OpenAi { default_model, .. } => {
|
.map(|model| LanguageModelSelection {
|
||||||
default_model.map(|model| LanguageModelSelection {
|
|
||||||
provider: "openai".into(),
|
provider: "openai".into(),
|
||||||
model: model.id().to_string(),
|
model: model.id().to_string(),
|
||||||
})
|
}),
|
||||||
}
|
AgentProviderContentV1::Anthropic { default_model, .. } => {
|
||||||
AssistantProviderContentV1::Anthropic { default_model, .. } => {
|
|
||||||
default_model.map(|model| LanguageModelSelection {
|
default_model.map(|model| LanguageModelSelection {
|
||||||
provider: "anthropic".into(),
|
provider: "anthropic".into(),
|
||||||
model: model.id().to_string(),
|
model: model.id().to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AssistantProviderContentV1::Ollama { default_model, .. } => {
|
AgentProviderContentV1::Ollama { default_model, .. } => default_model
|
||||||
default_model.map(|model| LanguageModelSelection {
|
.map(|model| LanguageModelSelection {
|
||||||
provider: "ollama".into(),
|
provider: "ollama".into(),
|
||||||
model: model.id().to_string(),
|
model: model.id().to_string(),
|
||||||
})
|
}),
|
||||||
}
|
AgentProviderContentV1::LmStudio { default_model, .. } => default_model
|
||||||
AssistantProviderContentV1::LmStudio { default_model, .. } => {
|
.map(|model| LanguageModelSelection {
|
||||||
default_model.map(|model| LanguageModelSelection {
|
|
||||||
provider: "lmstudio".into(),
|
provider: "lmstudio".into(),
|
||||||
model: model.id().to_string(),
|
model: model.id().to_string(),
|
||||||
})
|
}),
|
||||||
}
|
AgentProviderContentV1::DeepSeek { default_model, .. } => default_model
|
||||||
AssistantProviderContentV1::DeepSeek { default_model, .. } => {
|
.map(|model| LanguageModelSelection {
|
||||||
default_model.map(|model| LanguageModelSelection {
|
|
||||||
provider: "deepseek".into(),
|
provider: "deepseek".into(),
|
||||||
model: model.id().to_string(),
|
model: model.id().to_string(),
|
||||||
})
|
}),
|
||||||
}
|
AgentProviderContentV1::Mistral { default_model, .. } => default_model
|
||||||
|
.map(|model| LanguageModelSelection {
|
||||||
|
provider: "mistral".into(),
|
||||||
|
model: model.id().to_string(),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
inline_assistant_model: None,
|
inline_assistant_model: None,
|
||||||
commit_message_model: None,
|
commit_message_model: None,
|
||||||
thread_summary_model: None,
|
thread_summary_model: None,
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
default_profile: None,
|
default_profile: None,
|
||||||
|
default_view: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
@@ -263,10 +278,11 @@ impl AssistantSettingsContent {
|
|||||||
model_parameters: Vec::new(),
|
model_parameters: Vec::new(),
|
||||||
preferred_completion_mode: None,
|
preferred_completion_mode: None,
|
||||||
enable_feedback: None,
|
enable_feedback: None,
|
||||||
|
play_sound_when_agent_done: None,
|
||||||
},
|
},
|
||||||
VersionedAssistantSettingsContent::V2(ref settings) => settings.clone(),
|
VersionedAgentSettingsContent::V2(ref settings) => settings.clone(),
|
||||||
},
|
},
|
||||||
Some(AssistantSettingsContentInner::Legacy(settings)) => AssistantSettingsContentV2 {
|
Some(AgentSettingsContentInner::Legacy(settings)) => AgentSettingsContentV2 {
|
||||||
enabled: None,
|
enabled: None,
|
||||||
button: settings.button,
|
button: settings.button,
|
||||||
dock: settings.dock,
|
dock: settings.dock,
|
||||||
@@ -286,6 +302,7 @@ impl AssistantSettingsContent {
|
|||||||
thread_summary_model: None,
|
thread_summary_model: None,
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
default_profile: None,
|
default_profile: None,
|
||||||
|
default_view: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
@@ -294,31 +311,30 @@ impl AssistantSettingsContent {
|
|||||||
model_parameters: Vec::new(),
|
model_parameters: Vec::new(),
|
||||||
preferred_completion_mode: None,
|
preferred_completion_mode: None,
|
||||||
enable_feedback: None,
|
enable_feedback: None,
|
||||||
|
play_sound_when_agent_done: None,
|
||||||
},
|
},
|
||||||
None => AssistantSettingsContentV2::default(),
|
None => AgentSettingsContentV2::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dock(&mut self, dock: AssistantDockPosition) {
|
pub fn set_dock(&mut self, dock: AgentDockPosition) {
|
||||||
match &mut self.inner {
|
match &mut self.inner {
|
||||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||||
VersionedAssistantSettingsContent::V1(ref mut settings) => {
|
VersionedAgentSettingsContent::V1(ref mut settings) => {
|
||||||
settings.dock = Some(dock);
|
settings.dock = Some(dock);
|
||||||
}
|
}
|
||||||
VersionedAssistantSettingsContent::V2(ref mut settings) => {
|
VersionedAgentSettingsContent::V2(ref mut settings) => {
|
||||||
settings.dock = Some(dock);
|
settings.dock = Some(dock);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
Some(AgentSettingsContentInner::Legacy(settings)) => {
|
||||||
settings.dock = Some(dock);
|
settings.dock = Some(dock);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||||
AssistantSettingsContentV2 {
|
dock: Some(dock),
|
||||||
dock: Some(dock),
|
..Default::default()
|
||||||
..Default::default()
|
}))
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,105 +344,101 @@ impl AssistantSettingsContent {
|
|||||||
let provider = language_model.provider_id().0.to_string();
|
let provider = language_model.provider_id().0.to_string();
|
||||||
|
|
||||||
match &mut self.inner {
|
match &mut self.inner {
|
||||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||||
VersionedAssistantSettingsContent::V1(ref mut settings) => {
|
VersionedAgentSettingsContent::V1(ref mut settings) => match provider.as_ref() {
|
||||||
match provider.as_ref() {
|
"zed.dev" => {
|
||||||
"zed.dev" => {
|
log::warn!("attempted to set zed.dev model on outdated settings");
|
||||||
log::warn!("attempted to set zed.dev model on outdated settings");
|
}
|
||||||
}
|
"anthropic" => {
|
||||||
"anthropic" => {
|
let api_url = match &settings.provider {
|
||||||
let api_url = match &settings.provider {
|
Some(AgentProviderContentV1::Anthropic { api_url, .. }) => {
|
||||||
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
|
api_url.clone()
|
||||||
api_url.clone()
|
}
|
||||||
}
|
_ => None,
|
||||||
_ => None,
|
};
|
||||||
};
|
settings.provider = Some(AgentProviderContentV1::Anthropic {
|
||||||
settings.provider = Some(AssistantProviderContentV1::Anthropic {
|
default_model: AnthropicModel::from_id(&model).ok(),
|
||||||
default_model: AnthropicModel::from_id(&model).ok(),
|
api_url,
|
||||||
api_url,
|
});
|
||||||
});
|
}
|
||||||
}
|
"ollama" => {
|
||||||
"ollama" => {
|
let api_url = match &settings.provider {
|
||||||
let api_url = match &settings.provider {
|
Some(AgentProviderContentV1::Ollama { api_url, .. }) => api_url.clone(),
|
||||||
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
|
_ => None,
|
||||||
api_url.clone()
|
};
|
||||||
}
|
settings.provider = Some(AgentProviderContentV1::Ollama {
|
||||||
_ => None,
|
default_model: Some(ollama::Model::new(
|
||||||
};
|
&model,
|
||||||
settings.provider = Some(AssistantProviderContentV1::Ollama {
|
None,
|
||||||
default_model: Some(ollama::Model::new(
|
None,
|
||||||
&model,
|
Some(language_model.supports_tools()),
|
||||||
None,
|
Some(language_model.supports_images()),
|
||||||
None,
|
None,
|
||||||
Some(language_model.supports_tools()),
|
)),
|
||||||
)),
|
api_url,
|
||||||
api_url,
|
});
|
||||||
});
|
}
|
||||||
}
|
"lmstudio" => {
|
||||||
"lmstudio" => {
|
let api_url = match &settings.provider {
|
||||||
let api_url = match &settings.provider {
|
Some(AgentProviderContentV1::LmStudio { api_url, .. }) => {
|
||||||
Some(AssistantProviderContentV1::LmStudio { api_url, .. }) => {
|
api_url.clone()
|
||||||
api_url.clone()
|
}
|
||||||
}
|
_ => None,
|
||||||
_ => None,
|
};
|
||||||
};
|
settings.provider = Some(AgentProviderContentV1::LmStudio {
|
||||||
settings.provider = Some(AssistantProviderContentV1::LmStudio {
|
default_model: Some(lmstudio::Model::new(&model, None, None, false)),
|
||||||
default_model: Some(lmstudio::Model::new(&model, None, None)),
|
api_url,
|
||||||
api_url,
|
});
|
||||||
});
|
}
|
||||||
}
|
"openai" => {
|
||||||
"openai" => {
|
let (api_url, available_models) = match &settings.provider {
|
||||||
let (api_url, available_models) = match &settings.provider {
|
Some(AgentProviderContentV1::OpenAi {
|
||||||
Some(AssistantProviderContentV1::OpenAi {
|
|
||||||
api_url,
|
|
||||||
available_models,
|
|
||||||
..
|
|
||||||
}) => (api_url.clone(), available_models.clone()),
|
|
||||||
_ => (None, None),
|
|
||||||
};
|
|
||||||
settings.provider = Some(AssistantProviderContentV1::OpenAi {
|
|
||||||
default_model: OpenAiModel::from_id(&model).ok(),
|
|
||||||
api_url,
|
api_url,
|
||||||
available_models,
|
available_models,
|
||||||
});
|
..
|
||||||
}
|
}) => (api_url.clone(), available_models.clone()),
|
||||||
"deepseek" => {
|
_ => (None, None),
|
||||||
let api_url = match &settings.provider {
|
};
|
||||||
Some(AssistantProviderContentV1::DeepSeek { api_url, .. }) => {
|
settings.provider = Some(AgentProviderContentV1::OpenAi {
|
||||||
api_url.clone()
|
default_model: OpenAiModel::from_id(&model).ok(),
|
||||||
}
|
api_url,
|
||||||
_ => None,
|
available_models,
|
||||||
};
|
});
|
||||||
settings.provider = Some(AssistantProviderContentV1::DeepSeek {
|
|
||||||
default_model: DeepseekModel::from_id(&model).ok(),
|
|
||||||
api_url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
"deepseek" => {
|
||||||
VersionedAssistantSettingsContent::V2(ref mut settings) => {
|
let api_url = match &settings.provider {
|
||||||
|
Some(AgentProviderContentV1::DeepSeek { api_url, .. }) => {
|
||||||
|
api_url.clone()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
settings.provider = Some(AgentProviderContentV1::DeepSeek {
|
||||||
|
default_model: DeepseekModel::from_id(&model).ok(),
|
||||||
|
api_url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
VersionedAgentSettingsContent::V2(ref mut settings) => {
|
||||||
settings.default_model = Some(LanguageModelSelection {
|
settings.default_model = Some(LanguageModelSelection {
|
||||||
provider: provider.into(),
|
provider: provider.into(),
|
||||||
model,
|
model,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
Some(AgentSettingsContentInner::Legacy(settings)) => {
|
||||||
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
|
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
|
||||||
settings.default_open_ai_model = Some(model);
|
settings.default_open_ai_model = Some(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||||
AssistantSettingsContentV2 {
|
default_model: Some(LanguageModelSelection {
|
||||||
default_model: Some(LanguageModelSelection {
|
provider: provider.into(),
|
||||||
provider: provider.into(),
|
model,
|
||||||
model,
|
}),
|
||||||
}),
|
..Default::default()
|
||||||
..Default::default()
|
}));
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,15 +467,15 @@ impl AssistantSettingsContent {
|
|||||||
|
|
||||||
pub fn v2_setting(
|
pub fn v2_setting(
|
||||||
&mut self,
|
&mut self,
|
||||||
f: impl FnOnce(&mut AssistantSettingsContentV2) -> anyhow::Result<()>,
|
f: impl FnOnce(&mut AgentSettingsContentV2) -> anyhow::Result<()>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
match self.inner.get_or_insert_with(|| {
|
match self.inner.get_or_insert_with(|| {
|
||||||
AssistantSettingsContentInner::for_v2(AssistantSettingsContentV2 {
|
AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
AssistantSettingsContentInner::Versioned(boxed) => {
|
AgentSettingsContentInner::Versioned(boxed) => {
|
||||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
if let VersionedAgentSettingsContent::V2(ref mut settings) = **boxed {
|
||||||
f(settings)
|
f(settings)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -492,6 +504,14 @@ impl AssistantSettingsContent {
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
|
||||||
|
self.v2_setting(|setting| {
|
||||||
|
setting.play_sound_when_agent_done = Some(allow);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_single_file_review(&mut self, allow: bool) {
|
pub fn set_single_file_review(&mut self, allow: bool) {
|
||||||
self.v2_setting(|setting| {
|
self.v2_setting(|setting| {
|
||||||
setting.single_file_review = Some(allow);
|
setting.single_file_review = Some(allow);
|
||||||
@@ -548,16 +568,16 @@ impl AssistantSettingsContent {
|
|||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
#[serde(tag = "version")]
|
#[serde(tag = "version")]
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub enum VersionedAssistantSettingsContent {
|
pub enum VersionedAgentSettingsContent {
|
||||||
#[serde(rename = "1")]
|
#[serde(rename = "1")]
|
||||||
V1(AssistantSettingsContentV1),
|
V1(AgentSettingsContentV1),
|
||||||
#[serde(rename = "2")]
|
#[serde(rename = "2")]
|
||||||
V2(AssistantSettingsContentV2),
|
V2(AgentSettingsContentV2),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VersionedAssistantSettingsContent {
|
impl Default for VersionedAgentSettingsContent {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::V2(AssistantSettingsContentV2 {
|
Self::V2(AgentSettingsContentV2 {
|
||||||
enabled: None,
|
enabled: None,
|
||||||
button: None,
|
button: None,
|
||||||
dock: None,
|
dock: None,
|
||||||
@@ -569,6 +589,7 @@ impl Default for VersionedAssistantSettingsContent {
|
|||||||
thread_summary_model: None,
|
thread_summary_model: None,
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
default_profile: None,
|
default_profile: None,
|
||||||
|
default_view: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
@@ -577,30 +598,31 @@ impl Default for VersionedAssistantSettingsContent {
|
|||||||
model_parameters: Vec::new(),
|
model_parameters: Vec::new(),
|
||||||
preferred_completion_mode: None,
|
preferred_completion_mode: None,
|
||||||
enable_feedback: None,
|
enable_feedback: None,
|
||||||
|
play_sound_when_agent_done: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct AssistantSettingsContentV2 {
|
pub struct AgentSettingsContentV2 {
|
||||||
/// Whether the Assistant is enabled.
|
/// Whether the Agent is enabled.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
enabled: Option<bool>,
|
enabled: Option<bool>,
|
||||||
/// Whether to show the assistant panel button in the status bar.
|
/// Whether to show the agent panel button in the status bar.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
button: Option<bool>,
|
button: Option<bool>,
|
||||||
/// Where to dock the assistant.
|
/// Where to dock the agent panel.
|
||||||
///
|
///
|
||||||
/// Default: right
|
/// Default: right
|
||||||
dock: Option<AssistantDockPosition>,
|
dock: Option<AgentDockPosition>,
|
||||||
/// Default width in pixels when the assistant is docked to the left or right.
|
/// Default width in pixels when the agent panel is docked to the left or right.
|
||||||
///
|
///
|
||||||
/// Default: 640
|
/// Default: 640
|
||||||
default_width: Option<f32>,
|
default_width: Option<f32>,
|
||||||
/// Default height in pixels when the assistant is docked to the bottom.
|
/// Default height in pixels when the agent panel is docked to the bottom.
|
||||||
///
|
///
|
||||||
/// Default: 320
|
/// Default: 320
|
||||||
default_height: Option<f32>,
|
default_height: Option<f32>,
|
||||||
@@ -618,6 +640,10 @@ pub struct AssistantSettingsContentV2 {
|
|||||||
///
|
///
|
||||||
/// Default: write
|
/// Default: write
|
||||||
default_profile: Option<AgentProfileId>,
|
default_profile: Option<AgentProfileId>,
|
||||||
|
/// Which view type to show by default in the agent panel.
|
||||||
|
///
|
||||||
|
/// Default: "thread"
|
||||||
|
default_view: Option<DefaultView>,
|
||||||
/// The available agent profiles.
|
/// The available agent profiles.
|
||||||
pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
|
pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
|
||||||
/// Whenever a tool action would normally wait for your confirmation
|
/// Whenever a tool action would normally wait for your confirmation
|
||||||
@@ -629,6 +655,10 @@ pub struct AssistantSettingsContentV2 {
|
|||||||
///
|
///
|
||||||
/// Default: "primary_screen"
|
/// Default: "primary_screen"
|
||||||
notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
|
notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
|
||||||
|
/// Whether to play a sound when the agent has either completed its response, or needs user input.
|
||||||
|
///
|
||||||
|
/// Default: false
|
||||||
|
play_sound_when_agent_done: Option<bool>,
|
||||||
/// Whether to stream edits from the agent as they are received.
|
/// Whether to stream edits from the agent as they are received.
|
||||||
///
|
///
|
||||||
/// Default: false
|
/// Default: false
|
||||||
@@ -646,7 +676,6 @@ pub struct AssistantSettingsContentV2 {
|
|||||||
/// Default: []
|
/// Default: []
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
model_parameters: Vec<LanguageModelParameters>,
|
model_parameters: Vec<LanguageModelParameters>,
|
||||||
|
|
||||||
/// What completion mode to enable for new threads
|
/// What completion mode to enable for new threads
|
||||||
///
|
///
|
||||||
/// Default: normal
|
/// Default: normal
|
||||||
@@ -662,14 +691,15 @@ pub struct AssistantSettingsContentV2 {
|
|||||||
pub enum CompletionMode {
|
pub enum CompletionMode {
|
||||||
#[default]
|
#[default]
|
||||||
Normal,
|
Normal,
|
||||||
Max,
|
#[serde(alias = "max")]
|
||||||
|
Burn,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CompletionMode> for zed_llm_client::CompletionMode {
|
impl From<CompletionMode> for zed_llm_client::CompletionMode {
|
||||||
fn from(value: CompletionMode) -> Self {
|
fn from(value: CompletionMode) -> Self {
|
||||||
match value {
|
match value {
|
||||||
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
|
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
|
||||||
CompletionMode::Max => zed_llm_client::CompletionMode::Max,
|
CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,6 +730,7 @@ impl JsonSchema for LanguageModelProviderSetting {
|
|||||||
"zed.dev".into(),
|
"zed.dev".into(),
|
||||||
"copilot_chat".into(),
|
"copilot_chat".into(),
|
||||||
"deepseek".into(),
|
"deepseek".into(),
|
||||||
|
"mistral".into(),
|
||||||
]),
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@@ -746,50 +777,50 @@ pub struct ContextServerPresetContent {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct AssistantSettingsContentV1 {
|
pub struct AgentSettingsContentV1 {
|
||||||
/// Whether the Assistant is enabled.
|
/// Whether the Agent is enabled.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
enabled: Option<bool>,
|
enabled: Option<bool>,
|
||||||
/// Whether to show the assistant panel button in the status bar.
|
/// Whether to show the Agent panel button in the status bar.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
button: Option<bool>,
|
button: Option<bool>,
|
||||||
/// Where to dock the assistant.
|
/// Where to dock the Agent.
|
||||||
///
|
///
|
||||||
/// Default: right
|
/// Default: right
|
||||||
dock: Option<AssistantDockPosition>,
|
dock: Option<AgentDockPosition>,
|
||||||
/// Default width in pixels when the assistant is docked to the left or right.
|
/// Default width in pixels when the Agent is docked to the left or right.
|
||||||
///
|
///
|
||||||
/// Default: 640
|
/// Default: 640
|
||||||
default_width: Option<f32>,
|
default_width: Option<f32>,
|
||||||
/// Default height in pixels when the assistant is docked to the bottom.
|
/// Default height in pixels when the Agent is docked to the bottom.
|
||||||
///
|
///
|
||||||
/// Default: 320
|
/// Default: 320
|
||||||
default_height: Option<f32>,
|
default_height: Option<f32>,
|
||||||
/// The provider of the assistant service.
|
/// The provider of the Agent service.
|
||||||
///
|
///
|
||||||
/// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
|
/// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
|
||||||
/// each with their respective default models and configurations.
|
/// each with their respective default models and configurations.
|
||||||
provider: Option<AssistantProviderContentV1>,
|
provider: Option<AgentProviderContentV1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct LegacyAssistantSettingsContent {
|
pub struct LegacyAgentSettingsContent {
|
||||||
/// Whether to show the assistant panel button in the status bar.
|
/// Whether to show the Agent panel button in the status bar.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub button: Option<bool>,
|
pub button: Option<bool>,
|
||||||
/// Where to dock the assistant.
|
/// Where to dock the Agent.
|
||||||
///
|
///
|
||||||
/// Default: right
|
/// Default: right
|
||||||
pub dock: Option<AssistantDockPosition>,
|
pub dock: Option<AgentDockPosition>,
|
||||||
/// Default width in pixels when the assistant is docked to the left or right.
|
/// Default width in pixels when the Agent is docked to the left or right.
|
||||||
///
|
///
|
||||||
/// Default: 640
|
/// Default: 640
|
||||||
pub default_width: Option<f32>,
|
pub default_width: Option<f32>,
|
||||||
/// Default height in pixels when the assistant is docked to the bottom.
|
/// Default height in pixels when the Agent is docked to the bottom.
|
||||||
///
|
///
|
||||||
/// Default: 320
|
/// Default: 320
|
||||||
pub default_height: Option<f32>,
|
pub default_height: Option<f32>,
|
||||||
@@ -803,20 +834,20 @@ pub struct LegacyAssistantSettingsContent {
|
|||||||
pub openai_api_url: Option<String>,
|
pub openai_api_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings for AssistantSettings {
|
impl Settings for AgentSettings {
|
||||||
const KEY: Option<&'static str> = Some("agent");
|
const KEY: Option<&'static str> = Some("agent");
|
||||||
|
|
||||||
const FALLBACK_KEY: Option<&'static str> = Some("assistant");
|
const FALLBACK_KEY: Option<&'static str> = Some("assistant");
|
||||||
|
|
||||||
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
|
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
|
||||||
|
|
||||||
type FileContent = AssistantSettingsContent;
|
type FileContent = AgentSettingsContent;
|
||||||
|
|
||||||
fn load(
|
fn load(
|
||||||
sources: SettingsSources<Self::FileContent>,
|
sources: SettingsSources<Self::FileContent>,
|
||||||
_: &mut gpui::App,
|
_: &mut gpui::App,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let mut settings = AssistantSettings::default();
|
let mut settings = AgentSettings::default();
|
||||||
|
|
||||||
for value in sources.defaults_and_customizations() {
|
for value in sources.defaults_and_customizations() {
|
||||||
if value.is_version_outdated() {
|
if value.is_version_outdated() {
|
||||||
@@ -854,9 +885,14 @@ impl Settings for AssistantSettings {
|
|||||||
&mut settings.notify_when_agent_waiting,
|
&mut settings.notify_when_agent_waiting,
|
||||||
value.notify_when_agent_waiting,
|
value.notify_when_agent_waiting,
|
||||||
);
|
);
|
||||||
|
merge(
|
||||||
|
&mut settings.play_sound_when_agent_done,
|
||||||
|
value.play_sound_when_agent_done,
|
||||||
|
);
|
||||||
merge(&mut settings.stream_edits, value.stream_edits);
|
merge(&mut settings.stream_edits, value.stream_edits);
|
||||||
merge(&mut settings.single_file_review, value.single_file_review);
|
merge(&mut settings.single_file_review, value.single_file_review);
|
||||||
merge(&mut settings.default_profile, value.default_profile);
|
merge(&mut settings.default_profile, value.default_profile);
|
||||||
|
merge(&mut settings.default_view, value.default_view);
|
||||||
merge(
|
merge(
|
||||||
&mut settings.preferred_completion_mode,
|
&mut settings.preferred_completion_mode,
|
||||||
value.preferred_completion_mode,
|
value.preferred_completion_mode,
|
||||||
@@ -906,28 +942,25 @@ impl Settings for AssistantSettings {
|
|||||||
.and_then(|b| b.as_bool())
|
.and_then(|b| b.as_bool())
|
||||||
{
|
{
|
||||||
match &mut current.inner {
|
match &mut current.inner {
|
||||||
Some(AssistantSettingsContentInner::Versioned(versioned)) => {
|
Some(AgentSettingsContentInner::Versioned(versioned)) => match versioned.as_mut() {
|
||||||
match versioned.as_mut() {
|
VersionedAgentSettingsContent::V1(setting) => {
|
||||||
VersionedAssistantSettingsContent::V1(setting) => {
|
setting.enabled = Some(b);
|
||||||
setting.enabled = Some(b);
|
setting.button = Some(b);
|
||||||
setting.button = Some(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
VersionedAssistantSettingsContent::V2(setting) => {
|
|
||||||
setting.enabled = Some(b);
|
|
||||||
setting.button = Some(b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Some(AssistantSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
|
VersionedAgentSettingsContent::V2(setting) => {
|
||||||
|
setting.enabled = Some(b);
|
||||||
|
setting.button = Some(b);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(AgentSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
|
||||||
None => {
|
None => {
|
||||||
current.inner = Some(AssistantSettingsContentInner::for_v2(
|
current.inner =
|
||||||
AssistantSettingsContentV2 {
|
Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||||
enabled: Some(b),
|
enabled: Some(b),
|
||||||
button: Some(b),
|
button: Some(b),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -949,7 +982,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_deserialize_assistant_settings_with_version(cx: &mut TestAppContext) {
|
async fn test_deserialize_agent_settings_with_version(cx: &mut TestAppContext) {
|
||||||
let fs = fs::FakeFs::new(cx.executor().clone());
|
let fs = fs::FakeFs::new(cx.executor().clone());
|
||||||
fs.create_dir(paths::settings_file().parent().unwrap())
|
fs.create_dir(paths::settings_file().parent().unwrap())
|
||||||
.await
|
.await
|
||||||
@@ -958,51 +991,51 @@ mod tests {
|
|||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let test_settings = settings::SettingsStore::test(cx);
|
let test_settings = settings::SettingsStore::test(cx);
|
||||||
cx.set_global(test_settings);
|
cx.set_global(test_settings);
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
assert!(!AssistantSettings::get_global(cx).using_outdated_settings_version);
|
assert!(!AgentSettings::get_global(cx).using_outdated_settings_version);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AssistantSettings::get_global(cx).default_model,
|
AgentSettings::get_global(cx).default_model,
|
||||||
LanguageModelSelection {
|
LanguageModelSelection {
|
||||||
provider: "zed.dev".into(),
|
provider: "zed.dev".into(),
|
||||||
model: "claude-3-7-sonnet-latest".into(),
|
model: "claude-sonnet-4".into(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
settings::SettingsStore::global(cx).update_settings_file::<AssistantSettings>(
|
settings::SettingsStore::global(cx).update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
|settings, _| {
|
|settings, _| {
|
||||||
*settings = AssistantSettingsContent {
|
*settings = AgentSettingsContent {
|
||||||
inner: Some(AssistantSettingsContentInner::for_v2(
|
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||||
AssistantSettingsContentV2 {
|
default_model: Some(LanguageModelSelection {
|
||||||
default_model: Some(LanguageModelSelection {
|
provider: "test-provider".into(),
|
||||||
provider: "test-provider".into(),
|
model: "gpt-99".into(),
|
||||||
model: "gpt-99".into(),
|
}),
|
||||||
}),
|
inline_assistant_model: None,
|
||||||
inline_assistant_model: None,
|
commit_message_model: None,
|
||||||
commit_message_model: None,
|
thread_summary_model: None,
|
||||||
thread_summary_model: None,
|
inline_alternatives: None,
|
||||||
inline_alternatives: None,
|
enabled: None,
|
||||||
enabled: None,
|
button: None,
|
||||||
button: None,
|
dock: None,
|
||||||
dock: None,
|
default_width: None,
|
||||||
default_width: None,
|
default_height: None,
|
||||||
default_height: None,
|
default_profile: None,
|
||||||
default_profile: None,
|
default_view: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
play_sound_when_agent_done: None,
|
||||||
stream_edits: None,
|
notify_when_agent_waiting: None,
|
||||||
single_file_review: None,
|
stream_edits: None,
|
||||||
enable_feedback: None,
|
single_file_review: None,
|
||||||
model_parameters: Vec::new(),
|
enable_feedback: None,
|
||||||
preferred_completion_mode: None,
|
model_parameters: Vec::new(),
|
||||||
},
|
preferred_completion_mode: None,
|
||||||
)),
|
})),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1014,14 +1047,14 @@ mod tests {
|
|||||||
assert!(raw_settings_value.contains(r#""version": "2""#));
|
assert!(raw_settings_value.contains(r#""version": "2""#));
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AssistantSettingsTest {
|
struct AgentSettingsTest {
|
||||||
agent: AssistantSettingsContent,
|
agent: AgentSettingsContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
let assistant_settings: AssistantSettingsTest =
|
let agent_settings: AgentSettingsTest =
|
||||||
serde_json_lenient::from_str(&raw_settings_value).unwrap();
|
serde_json_lenient::from_str(&raw_settings_value).unwrap();
|
||||||
|
|
||||||
assert!(!assistant_settings.agent.is_version_outdated());
|
assert!(!agent_settings.agent.is_version_outdated());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@@ -1046,29 +1079,27 @@ mod tests {
|
|||||||
.set_user_settings(user_settings_content, cx)
|
.set_user_settings(user_settings_content, cx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.set_global(test_settings);
|
cx.set_global(test_settings);
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
let assistant_settings = cx.update(|cx| AssistantSettings::get_global(cx).clone());
|
let agent_settings = cx.update(|cx| AgentSettings::get_global(cx).clone());
|
||||||
assert!(assistant_settings.enabled);
|
assert!(agent_settings.enabled);
|
||||||
assert!(!assistant_settings.using_outdated_settings_version);
|
assert!(!agent_settings.using_outdated_settings_version);
|
||||||
assert_eq!(assistant_settings.default_model.model, "gpt-99");
|
assert_eq!(agent_settings.default_model.model, "gpt-99");
|
||||||
|
|
||||||
cx.update_global::<SettingsStore, _>(|settings_store, cx| {
|
cx.update_global::<SettingsStore, _>(|settings_store, cx| {
|
||||||
settings_store.update_user_settings::<AssistantSettings>(cx, |settings| {
|
settings_store.update_user_settings::<AgentSettings>(cx, |settings| {
|
||||||
*settings = AssistantSettingsContent {
|
*settings = AgentSettingsContent {
|
||||||
inner: Some(AssistantSettingsContentInner::for_v2(
|
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||||
AssistantSettingsContentV2 {
|
enabled: Some(false),
|
||||||
enabled: Some(false),
|
default_model: Some(LanguageModelSelection {
|
||||||
default_model: Some(LanguageModelSelection {
|
provider: "xai".to_owned().into(),
|
||||||
provider: "xai".to_owned().into(),
|
model: "grok".to_owned(),
|
||||||
model: "grok".to_owned(),
|
}),
|
||||||
}),
|
..Default::default()
|
||||||
..Default::default()
|
})),
|
||||||
},
|
|
||||||
)),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1078,12 +1109,12 @@ mod tests {
|
|||||||
let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
|
let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AssistantSettingsTest {
|
struct AgentSettingsTest {
|
||||||
assistant: AssistantSettingsContent,
|
assistant: AgentSettingsContent,
|
||||||
agent: Option<serde_json_lenient::Value>,
|
agent: Option<serde_json_lenient::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let assistant_settings: AssistantSettingsTest = serde_json::from_value(settings).unwrap();
|
let agent_settings: AgentSettingsTest = serde_json::from_value(settings).unwrap();
|
||||||
assert!(assistant_settings.agent.is_none());
|
assert!(agent_settings.agent.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,6 @@ pub enum AnthropicModelMode {
|
|||||||
pub enum Model {
|
pub enum Model {
|
||||||
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
|
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
|
||||||
Claude3_5Sonnet,
|
Claude3_5Sonnet,
|
||||||
#[default]
|
|
||||||
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
|
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
|
||||||
Claude3_7Sonnet,
|
Claude3_7Sonnet,
|
||||||
#[serde(
|
#[serde(
|
||||||
@@ -42,6 +41,21 @@ pub enum Model {
|
|||||||
alias = "claude-3-7-sonnet-thinking-latest"
|
alias = "claude-3-7-sonnet-thinking-latest"
|
||||||
)]
|
)]
|
||||||
Claude3_7SonnetThinking,
|
Claude3_7SonnetThinking,
|
||||||
|
#[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
|
||||||
|
ClaudeOpus4,
|
||||||
|
#[serde(
|
||||||
|
rename = "claude-opus-4-thinking",
|
||||||
|
alias = "claude-opus-4-thinking-latest"
|
||||||
|
)]
|
||||||
|
ClaudeOpus4Thinking,
|
||||||
|
#[default]
|
||||||
|
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
|
||||||
|
ClaudeSonnet4,
|
||||||
|
#[serde(
|
||||||
|
rename = "claude-sonnet-4-thinking",
|
||||||
|
alias = "claude-sonnet-4-thinking-latest"
|
||||||
|
)]
|
||||||
|
ClaudeSonnet4Thinking,
|
||||||
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||||
Claude3_5Haiku,
|
Claude3_5Haiku,
|
||||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||||
@@ -89,13 +103,25 @@ impl Model {
|
|||||||
Ok(Self::Claude3Sonnet)
|
Ok(Self::Claude3Sonnet)
|
||||||
} else if id.starts_with("claude-3-haiku") {
|
} else if id.starts_with("claude-3-haiku") {
|
||||||
Ok(Self::Claude3Haiku)
|
Ok(Self::Claude3Haiku)
|
||||||
|
} else if id.starts_with("claude-opus-4-thinking") {
|
||||||
|
Ok(Self::ClaudeOpus4Thinking)
|
||||||
|
} else if id.starts_with("claude-opus-4") {
|
||||||
|
Ok(Self::ClaudeOpus4)
|
||||||
|
} else if id.starts_with("claude-sonnet-4-thinking") {
|
||||||
|
Ok(Self::ClaudeSonnet4Thinking)
|
||||||
|
} else if id.starts_with("claude-sonnet-4") {
|
||||||
|
Ok(Self::ClaudeSonnet4)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("invalid model id"))
|
anyhow::bail!("invalid model id {id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> &str {
|
pub fn id(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
|
Model::ClaudeOpus4 => "claude-opus-4-latest",
|
||||||
|
Model::ClaudeOpus4Thinking => "claude-opus-4-thinking-latest",
|
||||||
|
Model::ClaudeSonnet4 => "claude-sonnet-4-latest",
|
||||||
|
Model::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking-latest",
|
||||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||||
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
|
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
|
||||||
Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
|
Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
|
||||||
@@ -110,6 +136,8 @@ impl Model {
|
|||||||
/// The id of the model that should be used for making API requests
|
/// The id of the model that should be used for making API requests
|
||||||
pub fn request_id(&self) -> &str {
|
pub fn request_id(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
|
Model::ClaudeOpus4 | Model::ClaudeOpus4Thinking => "claude-opus-4-20250514",
|
||||||
|
Model::ClaudeSonnet4 | Model::ClaudeSonnet4Thinking => "claude-sonnet-4-20250514",
|
||||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||||
Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
|
Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
|
||||||
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||||
@@ -122,6 +150,10 @@ impl Model {
|
|||||||
|
|
||||||
pub fn display_name(&self) -> &str {
|
pub fn display_name(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
|
Model::ClaudeOpus4 => "Claude Opus 4",
|
||||||
|
Model::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
|
||||||
|
Model::ClaudeSonnet4 => "Claude Sonnet 4",
|
||||||
|
Model::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
|
||||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||||
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
|
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
|
||||||
@@ -137,7 +169,11 @@ impl Model {
|
|||||||
|
|
||||||
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
|
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
|
||||||
match self {
|
match self {
|
||||||
Self::Claude3_5Sonnet
|
Self::ClaudeOpus4
|
||||||
|
| Self::ClaudeOpus4Thinking
|
||||||
|
| Self::ClaudeSonnet4
|
||||||
|
| Self::ClaudeSonnet4Thinking
|
||||||
|
| Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
| Self::Claude3_7SonnetThinking
|
| Self::Claude3_7SonnetThinking
|
||||||
@@ -156,7 +192,11 @@ impl Model {
|
|||||||
|
|
||||||
pub fn max_token_count(&self) -> usize {
|
pub fn max_token_count(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Claude3_5Sonnet
|
Self::ClaudeOpus4
|
||||||
|
| Self::ClaudeOpus4Thinking
|
||||||
|
| Self::ClaudeSonnet4
|
||||||
|
| Self::ClaudeSonnet4Thinking
|
||||||
|
| Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
| Self::Claude3_7SonnetThinking
|
| Self::Claude3_7SonnetThinking
|
||||||
@@ -173,7 +213,11 @@ impl Model {
|
|||||||
Self::Claude3_5Sonnet
|
Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
| Self::Claude3_7SonnetThinking
|
| Self::Claude3_7SonnetThinking
|
||||||
| Self::Claude3_5Haiku => 8_192,
|
| Self::Claude3_5Haiku
|
||||||
|
| Self::ClaudeOpus4
|
||||||
|
| Self::ClaudeOpus4Thinking
|
||||||
|
| Self::ClaudeSonnet4
|
||||||
|
| Self::ClaudeSonnet4Thinking => 8_192,
|
||||||
Self::Custom {
|
Self::Custom {
|
||||||
max_output_tokens, ..
|
max_output_tokens, ..
|
||||||
} => max_output_tokens.unwrap_or(4_096),
|
} => max_output_tokens.unwrap_or(4_096),
|
||||||
@@ -182,7 +226,11 @@ impl Model {
|
|||||||
|
|
||||||
pub fn default_temperature(&self) -> f32 {
|
pub fn default_temperature(&self) -> f32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Claude3_5Sonnet
|
Self::ClaudeOpus4
|
||||||
|
| Self::ClaudeOpus4Thinking
|
||||||
|
| Self::ClaudeSonnet4
|
||||||
|
| Self::ClaudeSonnet4Thinking
|
||||||
|
| Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
| Self::Claude3_7SonnetThinking
|
| Self::Claude3_7SonnetThinking
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
@@ -201,10 +249,14 @@ impl Model {
|
|||||||
Self::Claude3_5Sonnet
|
Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
|
| Self::ClaudeOpus4
|
||||||
|
| Self::ClaudeSonnet4
|
||||||
| Self::Claude3Opus
|
| Self::Claude3Opus
|
||||||
| Self::Claude3Sonnet
|
| Self::Claude3Sonnet
|
||||||
| Self::Claude3Haiku => AnthropicModelMode::Default,
|
| Self::Claude3Haiku => AnthropicModelMode::Default,
|
||||||
Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
|
Self::Claude3_7SonnetThinking
|
||||||
|
| Self::ClaudeOpus4Thinking
|
||||||
|
| Self::ClaudeSonnet4Thinking => AnthropicModelMode::Thinking {
|
||||||
budget_tokens: Some(4_096),
|
budget_tokens: Some(4_096),
|
||||||
},
|
},
|
||||||
Self::Custom { mode, .. } => mode.clone(),
|
Self::Custom { mode, .. } => mode.clone(),
|
||||||
@@ -385,10 +437,10 @@ impl RateLimitInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_header<'a>(key: &str, headers: &'a HeaderMap) -> Result<&'a str, anyhow::Error> {
|
fn get_header<'a>(key: &str, headers: &'a HeaderMap) -> anyhow::Result<&'a str> {
|
||||||
Ok(headers
|
Ok(headers
|
||||||
.get(key)
|
.get(key)
|
||||||
.ok_or_else(|| anyhow!("missing header `{key}`"))?
|
.with_context(|| format!("missing header `{key}`"))?
|
||||||
.to_str()?)
|
.to_str()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
|
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
|
||||||
use anyhow::anyhow;
|
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
use gpui::{App, AssetSource, Result, SharedString};
|
use gpui::{App, AssetSource, Result, SharedString};
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ impl AssetSource for Assets {
|
|||||||
fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
|
fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
|
||||||
Self::get(path)
|
Self::get(path)
|
||||||
.map(|f| Some(f.data))
|
.map(|f| Some(f.data))
|
||||||
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
.with_context(|| format!("loading asset at path {path:?}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
|
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
|
||||||
@@ -39,7 +39,7 @@ impl AssetSource for Assets {
|
|||||||
|
|
||||||
impl Assets {
|
impl Assets {
|
||||||
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
|
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
|
||||||
pub fn load_fonts(&self, cx: &App) -> gpui::Result<()> {
|
pub fn load_fonts(&self, cx: &App) -> anyhow::Result<()> {
|
||||||
let font_paths = self.list("fonts")?;
|
let font_paths = self.list("fonts")?;
|
||||||
let mut embedded_fonts = Vec::new();
|
let mut embedded_fonts = Vec::new();
|
||||||
for font_path in font_paths {
|
for font_path in font_paths {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ workspace = true
|
|||||||
path = "src/assistant_context_editor.rs"
|
path = "src/assistant_context_editor.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
agent_settings.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
assistant_settings.workspace = true
|
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
assistant_slash_commands.workspace = true
|
assistant_slash_commands.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
@@ -22,6 +22,7 @@ clock.workspace = true
|
|||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
context_server.workspace = true
|
context_server.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
feature_flags.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
@@ -29,15 +30,16 @@ gpui.workspace = true
|
|||||||
indexed_docs.workspace = true
|
indexed_docs.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
language_model_selector.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
open_ai.workspace = true
|
open_ai.workspace = true
|
||||||
|
ordered-float.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
prompt_store.workspace = true
|
prompt_store.workspace = true
|
||||||
|
proto.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
rpc.workspace = true
|
rpc.workspace = true
|
||||||
@@ -55,8 +57,10 @@ uuid.workspace = true
|
|||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
|
zed_llm_client.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
indoc.workspace = true
|
||||||
language_model = { workspace = true, features = ["test-support"] }
|
language_model = { workspace = true, features = ["test-support"] }
|
||||||
languages = { workspace = true, features = ["test-support"] }
|
languages = { workspace = true, features = ["test-support"] }
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ mod context;
|
|||||||
mod context_editor;
|
mod context_editor;
|
||||||
mod context_history;
|
mod context_history;
|
||||||
mod context_store;
|
mod context_store;
|
||||||
|
pub mod language_model_selector;
|
||||||
|
mod max_mode_tooltip;
|
||||||
mod slash_command;
|
mod slash_command;
|
||||||
mod slash_command_picker;
|
mod slash_command_picker;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod context_tests;
|
mod context_tests;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow, bail};
|
use agent_settings::AgentSettings;
|
||||||
use assistant_settings::AssistantSettings;
|
use anyhow::{Context as _, Result, bail};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
|
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
|
||||||
SlashCommandResult, SlashCommandWorkingSet,
|
SlashCommandResult, SlashCommandWorkingSet,
|
||||||
@@ -29,6 +29,7 @@ use paths::contexts_dir;
|
|||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{Ordering, max},
|
cmp::{Ordering, max},
|
||||||
@@ -44,6 +45,7 @@ use text::{BufferSnapshot, ToPoint};
|
|||||||
use ui::IconName;
|
use ui::IconName;
|
||||||
use util::{ResultExt, TryFutureExt, post_inc};
|
use util::{ResultExt, TryFutureExt, post_inc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use zed_llm_client::CompletionIntent;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct ContextId(String);
|
pub struct ContextId(String);
|
||||||
@@ -682,6 +684,7 @@ pub struct AssistantContext {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<Entity<Project>>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
|
completion_mode: agent_settings::CompletionMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ContextAnnotation {
|
trait ContextAnnotation {
|
||||||
@@ -718,6 +721,14 @@ impl AssistantContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn completion_mode(&self) -> agent_settings::CompletionMode {
|
||||||
|
self.completion_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_completion_mode(&mut self, completion_mode: agent_settings::CompletionMode) {
|
||||||
|
self.completion_mode = completion_mode;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: ContextId,
|
id: ContextId,
|
||||||
replica_id: ReplicaId,
|
replica_id: ReplicaId,
|
||||||
@@ -764,6 +775,7 @@ impl AssistantContext {
|
|||||||
pending_cache_warming_task: Task::ready(None),
|
pending_cache_warming_task: Task::ready(None),
|
||||||
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
|
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
|
||||||
pending_save: Task::ready(Ok(())),
|
pending_save: Task::ready(Ok(())),
|
||||||
|
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
||||||
path: None,
|
path: None,
|
||||||
buffer,
|
buffer,
|
||||||
telemetry,
|
telemetry,
|
||||||
@@ -1730,9 +1742,8 @@ impl AssistantContext {
|
|||||||
merge_same_roles,
|
merge_same_roles,
|
||||||
} => {
|
} => {
|
||||||
if !merge_same_roles && Some(role) != last_role {
|
if !merge_same_roles && Some(role) != last_role {
|
||||||
let offset = this.buffer.read_with(cx, |buffer, _cx| {
|
let buffer = this.buffer.read(cx);
|
||||||
insert_position.to_offset(buffer)
|
let offset = insert_position.to_offset(buffer);
|
||||||
});
|
|
||||||
this.insert_message_at_offset(
|
this.insert_message_at_offset(
|
||||||
offset,
|
offset,
|
||||||
role,
|
role,
|
||||||
@@ -2204,6 +2215,7 @@ impl AssistantContext {
|
|||||||
StopReason::ToolUse => {}
|
StopReason::ToolUse => {}
|
||||||
StopReason::EndTurn => {}
|
StopReason::EndTurn => {}
|
||||||
StopReason::MaxTokens => {}
|
StopReason::MaxTokens => {}
|
||||||
|
StopReason::Refusal => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -2261,13 +2273,13 @@ impl AssistantContext {
|
|||||||
let mut completion_request = LanguageModelRequest {
|
let mut completion_request = LanguageModelRequest {
|
||||||
thread_id: None,
|
thread_id: None,
|
||||||
prompt_id: None,
|
prompt_id: None,
|
||||||
|
intent: Some(CompletionIntent::UserPrompt),
|
||||||
mode: None,
|
mode: None,
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: model
|
temperature: model.and_then(|model| AgentSettings::temperature_for_model(model, cx)),
|
||||||
.and_then(|model| AssistantSettings::temperature_for_model(model, cx)),
|
|
||||||
};
|
};
|
||||||
for message in self.messages(cx) {
|
for message in self.messages(cx) {
|
||||||
if message.status != MessageStatus::Done {
|
if message.status != MessageStatus::Done {
|
||||||
@@ -2322,7 +2334,15 @@ impl AssistantContext {
|
|||||||
completion_request.messages.push(request_message);
|
completion_request.messages.push(request_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let supports_max_mode = if let Some(model) = model {
|
||||||
|
model.supports_max_mode()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if supports_max_mode {
|
||||||
|
completion_request.mode = Some(self.completion_mode.into());
|
||||||
|
}
|
||||||
completion_request
|
completion_request
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3011,7 +3031,7 @@ impl SavedContext {
|
|||||||
let saved_context_json = serde_json::from_str::<serde_json::Value>(json)?;
|
let saved_context_json = serde_json::from_str::<serde_json::Value>(json)?;
|
||||||
match saved_context_json
|
match saved_context_json
|
||||||
.get("version")
|
.get("version")
|
||||||
.ok_or_else(|| anyhow!("version not found"))?
|
.context("version not found")?
|
||||||
{
|
{
|
||||||
serde_json::Value::String(version) => match version.as_str() {
|
serde_json::Value::String(version) => match version.as_str() {
|
||||||
SavedContext::VERSION => {
|
SavedContext::VERSION => {
|
||||||
@@ -3032,9 +3052,9 @@ impl SavedContext {
|
|||||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||||
Ok(saved_context.upgrade())
|
Ok(saved_context.upgrade())
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
_ => anyhow::bail!("unrecognized saved context version: {version:?}"),
|
||||||
},
|
},
|
||||||
_ => Err(anyhow!("version not found on saved context")),
|
_ => anyhow::bail!("version not found on saved context"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1210,8 +1210,8 @@ async fn test_summarization(cx: &mut TestAppContext) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
fake_model.stream_last_completion_response("Brief".into());
|
fake_model.stream_last_completion_response("Brief");
|
||||||
fake_model.stream_last_completion_response(" Introduction".into());
|
fake_model.stream_last_completion_response(" Introduction");
|
||||||
fake_model.end_last_completion_stream();
|
fake_model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
@@ -1274,7 +1274,7 @@ async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
fake_model.stream_last_completion_response("A successful summary".into());
|
fake_model.stream_last_completion_response("A successful summary");
|
||||||
fake_model.end_last_completion_stream();
|
fake_model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
@@ -1356,7 +1356,7 @@ fn setup_context_editor_with_fake_model(
|
|||||||
|
|
||||||
fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
|
fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
fake_model.stream_last_completion_response("Assistant response".into());
|
fake_model.stream_last_completion_response("Assistant response");
|
||||||
fake_model.end_last_completion_stream();
|
fake_model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
}
|
}
|
||||||
@@ -1386,7 +1386,7 @@ fn init_test(cx: &mut App) {
|
|||||||
LanguageModelRegistry::test(cx);
|
LanguageModelRegistry::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
assistant_settings::init(cx);
|
agent_settings::init(cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
|
use crate::{
|
||||||
|
language_model_selector::{
|
||||||
|
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||||
|
},
|
||||||
|
max_mode_tooltip::MaxModeTooltip,
|
||||||
|
};
|
||||||
|
use agent_settings::{AgentSettings, CompletionMode};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_settings::AssistantSettings;
|
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
|
||||||
use assistant_slash_commands::{
|
use assistant_slash_commands::{
|
||||||
DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,
|
DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,
|
||||||
@@ -36,11 +42,8 @@ use language_model::{
|
|||||||
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
|
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
|
||||||
Role,
|
Role,
|
||||||
};
|
};
|
||||||
use language_model_selector::{
|
|
||||||
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
|
|
||||||
};
|
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use picker::Picker;
|
use picker::{Picker, popover_menu::PickerPopoverMenu};
|
||||||
use project::{Project, Worktree};
|
use project::{Project, Worktree};
|
||||||
use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
|
use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
@@ -51,6 +54,7 @@ use std::{
|
|||||||
cmp,
|
cmp,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@@ -234,7 +238,7 @@ impl ContextEditor {
|
|||||||
editor.set_show_breakpoints(false, cx);
|
editor.set_show_breakpoints(false, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_completion_provider(Some(Box::new(completion_provider)));
|
editor.set_completion_provider(Some(Rc::new(completion_provider)));
|
||||||
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
|
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
|
||||||
editor.set_collaboration_hub(Box::new(project.clone()));
|
editor.set_collaboration_hub(Box::new(project.clone()));
|
||||||
|
|
||||||
@@ -279,10 +283,10 @@ impl ContextEditor {
|
|||||||
slash_menu_handle: Default::default(),
|
slash_menu_handle: Default::default(),
|
||||||
dragged_file_worktrees: Vec::new(),
|
dragged_file_worktrees: Vec::new(),
|
||||||
language_model_selector: cx.new(|cx| {
|
language_model_selector: cx.new(|cx| {
|
||||||
LanguageModelSelector::new(
|
language_model_selector(
|
||||||
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
||||||
move |model, cx| {
|
move |model, cx| {
|
||||||
update_settings_file::<AssistantSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
move |settings, _| settings.set_model(model.clone()),
|
move |settings, _| settings.set_model(model.clone()),
|
||||||
@@ -1642,34 +1646,35 @@ impl ContextEditor {
|
|||||||
let context = self.context.read(cx);
|
let context = self.context.read(cx);
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
for message in context.messages(cx) {
|
|
||||||
if message.offset_range.start >= selection.range().end {
|
// If selection is empty, we want to copy the entire line
|
||||||
break;
|
if selection.range().is_empty() {
|
||||||
} else if message.offset_range.end >= selection.range().start {
|
let snapshot = context.buffer().read(cx).snapshot();
|
||||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
let point = snapshot.offset_to_point(selection.range().start);
|
||||||
..cmp::min(message.offset_range.end, selection.range().end);
|
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
|
||||||
if range.is_empty() {
|
selection.end = snapshot
|
||||||
let snapshot = context.buffer().read(cx).snapshot();
|
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
|
||||||
let point = snapshot.offset_to_point(range.start);
|
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
|
||||||
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
|
text.push_str(chunk);
|
||||||
selection.end = snapshot.point_to_offset(cmp::min(
|
}
|
||||||
Point::new(point.row + 1, 0),
|
} else {
|
||||||
snapshot.max_point(),
|
for message in context.messages(cx) {
|
||||||
));
|
if message.offset_range.start >= selection.range().end {
|
||||||
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
|
break;
|
||||||
text.push_str(chunk);
|
} else if message.offset_range.end >= selection.range().start {
|
||||||
}
|
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||||
} else {
|
..cmp::min(message.offset_range.end, selection.range().end);
|
||||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
if !range.is_empty() {
|
||||||
text.push_str(chunk);
|
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||||
}
|
text.push_str(chunk);
|
||||||
if message.offset_range.end < selection.range().end {
|
}
|
||||||
text.push('\n');
|
if message.offset_range.end < selection.range().end {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(text, CopyMetadata { creases }, vec![selection])
|
(text, CopyMetadata { creases }, vec![selection])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1902,7 +1907,7 @@ impl ContextEditor {
|
|||||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
let client = this
|
let client = this
|
||||||
.workspace
|
.workspace
|
||||||
.update(cx, |workspace, _| workspace.client().clone())
|
.read_with(cx, |workspace, _| workspace.client().clone())
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
if let Some(client) = client {
|
if let Some(client) = client {
|
||||||
@@ -2007,17 +2012,17 @@ impl ContextEditor {
|
|||||||
None => (ButtonStyle::Filled, None),
|
None => (ButtonStyle::Filled, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
ButtonLike::new("send_button")
|
Button::new("send_button", "Send")
|
||||||
|
.label_size(LabelSize::Small)
|
||||||
.disabled(self.sending_disabled(cx))
|
.disabled(self.sending_disabled(cx))
|
||||||
.style(style)
|
.style(style)
|
||||||
.when_some(tooltip, |button, tooltip| {
|
.when_some(tooltip, |button, tooltip| {
|
||||||
button.tooltip(move |_, _| tooltip.clone())
|
button.tooltip(move |_, _| tooltip.clone())
|
||||||
})
|
})
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.layer(ElevationIndex::ModalSurface)
|
||||||
.child(Label::new("Send"))
|
.key_binding(
|
||||||
.children(
|
|
||||||
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
|
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
|
||||||
.map(|binding| binding.into_any_element()),
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
)
|
)
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click(move |_event, window, cx| {
|
||||||
focus_handle.dispatch_action(&Assist, window, cx);
|
focus_handle.dispatch_action(&Assist, window, cx);
|
||||||
@@ -2057,7 +2062,50 @@ impl ContextEditor {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||||
|
let context = self.context().read(cx);
|
||||||
|
let active_model = LanguageModelRegistry::read_global(cx)
|
||||||
|
.default_model()
|
||||||
|
.map(|default| default.model)?;
|
||||||
|
if !active_model.supports_max_mode() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let active_completion_mode = context.completion_mode();
|
||||||
|
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
|
||||||
|
let icon = if burn_mode_enabled {
|
||||||
|
IconName::ZedBurnModeOn
|
||||||
|
} else {
|
||||||
|
IconName::ZedBurnMode
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(
|
||||||
|
IconButton::new("burn-mode", icon)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.toggle_state(burn_mode_enabled)
|
||||||
|
.selected_icon_color(Color::Error)
|
||||||
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
|
this.context().update(cx, |context, _cx| {
|
||||||
|
context.set_completion_mode(match active_completion_mode {
|
||||||
|
CompletionMode::Burn => CompletionMode::Normal,
|
||||||
|
CompletionMode::Normal => CompletionMode::Burn,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
.tooltip(move |_window, cx| {
|
||||||
|
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_language_model_selector(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
let active_model = LanguageModelRegistry::read_global(cx)
|
let active_model = LanguageModelRegistry::read_global(cx)
|
||||||
.default_model()
|
.default_model()
|
||||||
.map(|default| default.model);
|
.map(|default| default.model);
|
||||||
@@ -2067,7 +2115,7 @@ impl ContextEditor {
|
|||||||
None => SharedString::from("No model selected"),
|
None => SharedString::from("No model selected"),
|
||||||
};
|
};
|
||||||
|
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
PickerPopoverMenu::new(
|
||||||
self.language_model_selector.clone(),
|
self.language_model_selector.clone(),
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
@@ -2095,8 +2143,10 @@ impl ContextEditor {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
gpui::Corner::BottomLeft,
|
gpui::Corner::BottomLeft,
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||||
|
.render(window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||||
@@ -2502,6 +2552,7 @@ impl Render for ContextEditor {
|
|||||||
let provider = LanguageModelRegistry::read_global(cx)
|
let provider = LanguageModelRegistry::read_global(cx)
|
||||||
.default_model()
|
.default_model()
|
||||||
.map(|default| default.provider);
|
.map(|default| default.provider);
|
||||||
|
|
||||||
let accept_terms = if self.show_accept_terms {
|
let accept_terms = if self.show_accept_terms {
|
||||||
provider.as_ref().and_then(|provider| {
|
provider.as_ref().and_then(|provider| {
|
||||||
provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx)
|
provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx)
|
||||||
@@ -2511,6 +2562,8 @@ impl Render for ContextEditor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let language_model_selector = self.language_model_selector_menu_handle.clone();
|
let language_model_selector = self.language_model_selector_menu_handle.clone();
|
||||||
|
let max_mode_toggle = self.render_max_mode_toggle(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("ContextEditor")
|
.key_context("ContextEditor")
|
||||||
.capture_action(cx.listener(ContextEditor::cancel))
|
.capture_action(cx.listener(ContextEditor::cancel))
|
||||||
@@ -2550,31 +2603,28 @@ impl Render for ContextEditor {
|
|||||||
})
|
})
|
||||||
.children(self.render_last_error(cx))
|
.children(self.render_last_error(cx))
|
||||||
.child(
|
.child(
|
||||||
h_flex().w_full().relative().child(
|
h_flex()
|
||||||
h_flex()
|
.relative()
|
||||||
.p_2()
|
.py_2()
|
||||||
.w_full()
|
.pl_1p5()
|
||||||
.border_t_1()
|
.pr_2()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.w_full()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.justify_between()
|
||||||
.child(
|
.border_t_1()
|
||||||
h_flex()
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.gap_1()
|
.bg(cx.theme().colors().editor_background)
|
||||||
.child(self.render_inject_context_menu(cx))
|
.child(
|
||||||
.child(ui::Divider::vertical())
|
h_flex()
|
||||||
.child(
|
.gap_0p5()
|
||||||
div()
|
.child(self.render_inject_context_menu(cx))
|
||||||
.pl_0p5()
|
.when_some(max_mode_toggle, |this, element| this.child(element)),
|
||||||
.child(self.render_language_model_selector(cx)),
|
)
|
||||||
),
|
.child(
|
||||||
)
|
h_flex()
|
||||||
.child(
|
.gap_1()
|
||||||
h_flex()
|
.child(self.render_language_model_selector(window, cx))
|
||||||
.w_full()
|
.child(self.render_send_button(window, cx)),
|
||||||
.justify_end()
|
),
|
||||||
.child(self.render_send_button(window, cx)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3215,74 +3265,92 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use gpui::{App, TestAppContext, VisualTestContext};
|
use gpui::{App, TestAppContext, VisualTestContext};
|
||||||
|
use indoc::indoc;
|
||||||
use language::{Buffer, LanguageRegistry};
|
use language::{Buffer, LanguageRegistry};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
|
use text::OffsetRangeExt;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::path;
|
use util::path;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
|
||||||
|
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
|
||||||
|
(Role::User, "What is the Zed editor?"),
|
||||||
|
(
|
||||||
|
Role::Assistant,
|
||||||
|
"Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.",
|
||||||
|
),
|
||||||
|
(Role::User, ""),
|
||||||
|
],cx).await;
|
||||||
|
|
||||||
|
// Select & Copy whole user message
|
||||||
|
assert_copy_paste_context_editor(
|
||||||
|
&context_editor,
|
||||||
|
message_range(&context, 0, &mut cx),
|
||||||
|
indoc! {"
|
||||||
|
What is the Zed editor?
|
||||||
|
Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
|
||||||
|
What is the Zed editor?
|
||||||
|
"},
|
||||||
|
&mut cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Select & Copy whole assistant message
|
||||||
|
assert_copy_paste_context_editor(
|
||||||
|
&context_editor,
|
||||||
|
message_range(&context, 1, &mut cx),
|
||||||
|
indoc! {"
|
||||||
|
What is the Zed editor?
|
||||||
|
Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
|
||||||
|
What is the Zed editor?
|
||||||
|
Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
|
||||||
|
"},
|
||||||
|
&mut cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
|
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
|
||||||
cx.update(init_test);
|
let (context, context_editor, mut cx) = setup_context_editor_text(
|
||||||
|
vec![
|
||||||
|
(Role::User, "user1"),
|
||||||
|
(Role::Assistant, "assistant1"),
|
||||||
|
(Role::Assistant, "assistant2"),
|
||||||
|
(Role::User, ""),
|
||||||
|
],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
// Copy and paste first assistant message
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
let message_2_range = message_range(&context, 1, &mut cx);
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
assert_copy_paste_context_editor(
|
||||||
let context = cx.new(|cx| {
|
&context_editor,
|
||||||
AssistantContext::local(
|
message_2_range.start..message_2_range.start,
|
||||||
registry,
|
indoc! {"
|
||||||
None,
|
user1
|
||||||
None,
|
assistant1
|
||||||
prompt_builder.clone(),
|
assistant2
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
assistant1
|
||||||
cx,
|
"},
|
||||||
)
|
&mut cx,
|
||||||
});
|
);
|
||||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
|
||||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
|
||||||
let workspace = window.root(cx).unwrap();
|
|
||||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
|
||||||
|
|
||||||
let context_editor = window
|
// Copy and cut second assistant message
|
||||||
.update(cx, |_, window, cx| {
|
let message_3_range = message_range(&context, 2, &mut cx);
|
||||||
cx.new(|cx| {
|
assert_copy_paste_context_editor(
|
||||||
ContextEditor::for_context(
|
&context_editor,
|
||||||
context,
|
message_3_range.start..message_3_range.start,
|
||||||
fs,
|
indoc! {"
|
||||||
workspace.downgrade(),
|
user1
|
||||||
project,
|
assistant1
|
||||||
None,
|
assistant2
|
||||||
window,
|
assistant1
|
||||||
cx,
|
assistant2
|
||||||
)
|
"},
|
||||||
})
|
&mut cx,
|
||||||
})
|
);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
|
||||||
context_editor.editor.update(cx, |editor, cx| {
|
|
||||||
editor.set_text("abc\ndef\nghi", window, cx);
|
|
||||||
editor.move_to_beginning(&Default::default(), window, cx);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
|
||||||
context_editor.editor.update(cx, |editor, cx| {
|
|
||||||
editor.copy(&Default::default(), window, cx);
|
|
||||||
editor.paste(&Default::default(), window, cx);
|
|
||||||
|
|
||||||
assert_eq!(editor.text(cx), "abc\nabc\ndef\nghi");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
|
||||||
context_editor.editor.update(cx, |editor, cx| {
|
|
||||||
editor.cut(&Default::default(), window, cx);
|
|
||||||
assert_eq!(editor.text(cx), "abc\ndef\nghi");
|
|
||||||
|
|
||||||
editor.paste(&Default::default(), window, cx);
|
|
||||||
assert_eq!(editor.text(cx), "abc\nabc\ndef\nghi");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@@ -3359,13 +3427,136 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn setup_context_editor_text(
|
||||||
|
messages: Vec<(Role, &str)>,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> (
|
||||||
|
Entity<AssistantContext>,
|
||||||
|
Entity<ContextEditor>,
|
||||||
|
VisualTestContext,
|
||||||
|
) {
|
||||||
|
cx.update(init_test);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
let context = create_context_with_messages(messages, cx);
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||||
|
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
let workspace = window.root(cx).unwrap();
|
||||||
|
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||||
|
|
||||||
|
let context_editor = window
|
||||||
|
.update(&mut cx, |_, window, cx| {
|
||||||
|
cx.new(|cx| {
|
||||||
|
let editor = ContextEditor::for_context(
|
||||||
|
context.clone(),
|
||||||
|
fs,
|
||||||
|
workspace.downgrade(),
|
||||||
|
project,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(context, context_editor, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_range(
|
||||||
|
context: &Entity<AssistantContext>,
|
||||||
|
message_ix: usize,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> Range<usize> {
|
||||||
|
context.update(cx, |context, cx| {
|
||||||
|
context
|
||||||
|
.messages(cx)
|
||||||
|
.nth(message_ix)
|
||||||
|
.unwrap()
|
||||||
|
.anchor_range
|
||||||
|
.to_offset(&context.buffer().read(cx).snapshot())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
|
||||||
|
context_editor: &Entity<ContextEditor>,
|
||||||
|
range: Range<T>,
|
||||||
|
expected_text: &str,
|
||||||
|
cx: &mut VisualTestContext,
|
||||||
|
) {
|
||||||
|
context_editor.update_in(cx, |context_editor, window, cx| {
|
||||||
|
context_editor.editor.update(cx, |editor, cx| {
|
||||||
|
editor.change_selections(None, window, cx, |s| s.select_ranges([range]));
|
||||||
|
});
|
||||||
|
|
||||||
|
context_editor.copy(&Default::default(), window, cx);
|
||||||
|
|
||||||
|
context_editor.editor.update(cx, |editor, cx| {
|
||||||
|
editor.move_to_end(&Default::default(), window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
context_editor.paste(&Default::default(), window, cx);
|
||||||
|
|
||||||
|
context_editor.editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(editor.text(cx), expected_text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_context_with_messages(
|
||||||
|
mut messages: Vec<(Role, &str)>,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> Entity<AssistantContext> {
|
||||||
|
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||||
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
|
cx.new(|cx| {
|
||||||
|
let mut context = AssistantContext::local(
|
||||||
|
registry,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
prompt_builder.clone(),
|
||||||
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
let mut message_1 = context.messages(cx).next().unwrap();
|
||||||
|
let (role, text) = messages.remove(0);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if role == message_1.role {
|
||||||
|
context.buffer().update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(message_1.offset_range, text)], None, cx);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let mut ids = HashSet::default();
|
||||||
|
ids.insert(message_1.id);
|
||||||
|
context.cycle_message_roles(ids, cx);
|
||||||
|
message_1 = context.messages(cx).next().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_message_id = message_1.id;
|
||||||
|
for (role, text) in messages {
|
||||||
|
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
|
||||||
|
let message = context.messages(cx).last().unwrap();
|
||||||
|
last_message_id = message.id;
|
||||||
|
context.buffer().update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(message.offset_range, text)], None, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
context
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn init_test(cx: &mut App) {
|
fn init_test(cx: &mut App) {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
LanguageModelRegistry::test(cx);
|
LanguageModelRegistry::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
assistant_settings::init(cx);
|
agent_settings::init(cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
theme::init(theme::LoadThemes::JustBase, cx);
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
||||||
SavedContextMetadata,
|
SavedContextMetadata,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result};
|
||||||
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
||||||
use client::{Client, TypedEnvelope, proto, telemetry::Telemetry};
|
use client::{Client, TypedEnvelope, proto, telemetry::Telemetry};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
@@ -164,16 +164,18 @@ impl ContextStore {
|
|||||||
) -> Result<proto::OpenContextResponse> {
|
) -> Result<proto::OpenContextResponse> {
|
||||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||||
let operations = this.update(&mut cx, |this, cx| {
|
let operations = this.update(&mut cx, |this, cx| {
|
||||||
if this.project.read(cx).is_via_collab() {
|
anyhow::ensure!(
|
||||||
return Err(anyhow!("only the host contexts can be opened"));
|
!this.project.read(cx).is_via_collab(),
|
||||||
}
|
"only the host contexts can be opened"
|
||||||
|
);
|
||||||
|
|
||||||
let context = this
|
let context = this
|
||||||
.loaded_context_for_id(&context_id, cx)
|
.loaded_context_for_id(&context_id, cx)
|
||||||
.context("context not found")?;
|
.context("context not found")?;
|
||||||
if context.read(cx).replica_id() != ReplicaId::default() {
|
anyhow::ensure!(
|
||||||
return Err(anyhow!("context must be opened via the host"));
|
context.read(cx).replica_id() == ReplicaId::default(),
|
||||||
}
|
"context must be opened via the host"
|
||||||
|
);
|
||||||
|
|
||||||
anyhow::Ok(
|
anyhow::Ok(
|
||||||
context
|
context
|
||||||
@@ -193,9 +195,10 @@ impl ContextStore {
|
|||||||
mut cx: AsyncApp,
|
mut cx: AsyncApp,
|
||||||
) -> Result<proto::CreateContextResponse> {
|
) -> Result<proto::CreateContextResponse> {
|
||||||
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
||||||
if this.project.read(cx).is_via_collab() {
|
anyhow::ensure!(
|
||||||
return Err(anyhow!("can only create contexts as the host"));
|
!this.project.read(cx).is_via_collab(),
|
||||||
}
|
"can only create contexts as the host"
|
||||||
|
);
|
||||||
|
|
||||||
let context = this.create(cx);
|
let context = this.create(cx);
|
||||||
let context_id = context.read(cx).id().clone();
|
let context_id = context.read(cx).id().clone();
|
||||||
@@ -237,9 +240,10 @@ impl ContextStore {
|
|||||||
mut cx: AsyncApp,
|
mut cx: AsyncApp,
|
||||||
) -> Result<proto::SynchronizeContextsResponse> {
|
) -> Result<proto::SynchronizeContextsResponse> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if this.project.read(cx).is_via_collab() {
|
anyhow::ensure!(
|
||||||
return Err(anyhow!("only the host can synchronize contexts"));
|
!this.project.read(cx).is_via_collab(),
|
||||||
}
|
"only the host can synchronize contexts"
|
||||||
|
);
|
||||||
|
|
||||||
let mut local_versions = Vec::new();
|
let mut local_versions = Vec::new();
|
||||||
for remote_version_proto in envelope.payload.contexts {
|
for remote_version_proto in envelope.payload.contexts {
|
||||||
@@ -370,7 +374,7 @@ impl ContextStore {
|
|||||||
) -> Task<Result<Entity<AssistantContext>>> {
|
) -> Task<Result<Entity<AssistantContext>>> {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let replica_id = project.replica_id();
|
let replica_id = project.replica_id();
|
||||||
@@ -533,7 +537,7 @@ impl ContextStore {
|
|||||||
) -> Task<Result<Entity<AssistantContext>>> {
|
) -> Task<Result<Entity<AssistantContext>>> {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
|
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use collections::{HashSet, IndexMap};
|
|||||||
use feature_flags::ZedProFeatureFlag;
|
use feature_flags::ZedProFeatureFlag;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, AnyView, App, BackgroundExecutor, Corner, DismissEvent, Entity,
|
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task,
|
||||||
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
|
||||||
action_with_deprecated_aliases,
|
action_with_deprecated_aliases,
|
||||||
};
|
};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
@@ -15,7 +14,7 @@ use language_model::{
|
|||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use proto::Plan;
|
use proto::Plan;
|
||||||
use ui::{ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger, prelude::*};
|
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||||
|
|
||||||
action_with_deprecated_aliases!(
|
action_with_deprecated_aliases!(
|
||||||
agent,
|
agent,
|
||||||
@@ -31,77 +30,128 @@ const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
|
|||||||
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
|
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
|
||||||
type GetActiveModel = Arc<dyn Fn(&App) -> Option<ConfiguredModel> + 'static>;
|
type GetActiveModel = Arc<dyn Fn(&App) -> Option<ConfiguredModel> + 'static>;
|
||||||
|
|
||||||
pub struct LanguageModelSelector {
|
pub type LanguageModelSelector = Picker<LanguageModelPickerDelegate>;
|
||||||
picker: Entity<Picker<LanguageModelPickerDelegate>>,
|
|
||||||
|
pub fn language_model_selector(
|
||||||
|
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
|
||||||
|
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<LanguageModelSelector>,
|
||||||
|
) -> LanguageModelSelector {
|
||||||
|
let delegate = LanguageModelPickerDelegate::new(get_active_model, on_model_changed, window, cx);
|
||||||
|
Picker::list(delegate, window, cx)
|
||||||
|
.show_scrollbar(true)
|
||||||
|
.width(rems(20.))
|
||||||
|
.max_height(Some(rems(20.).into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_models(cx: &App) -> GroupedModels {
|
||||||
|
let providers = LanguageModelRegistry::global(cx).read(cx).providers();
|
||||||
|
|
||||||
|
let recommended = providers
|
||||||
|
.iter()
|
||||||
|
.flat_map(|provider| {
|
||||||
|
provider
|
||||||
|
.recommended_models(cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|model| ModelInfo {
|
||||||
|
model,
|
||||||
|
icon: provider.icon(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let other = providers
|
||||||
|
.iter()
|
||||||
|
.flat_map(|provider| {
|
||||||
|
provider
|
||||||
|
.provided_models(cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|model| ModelInfo {
|
||||||
|
model,
|
||||||
|
icon: provider.icon(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
GroupedModels::new(other, recommended)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ModelInfo {
|
||||||
|
model: Arc<dyn LanguageModel>,
|
||||||
|
icon: IconName,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LanguageModelPickerDelegate {
|
||||||
|
on_model_changed: OnModelChanged,
|
||||||
|
get_active_model: GetActiveModel,
|
||||||
|
all_models: Arc<GroupedModels>,
|
||||||
|
filtered_entries: Vec<LanguageModelPickerEntry>,
|
||||||
|
selected_index: usize,
|
||||||
_authenticate_all_providers_task: Task<()>,
|
_authenticate_all_providers_task: Task<()>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageModelSelector {
|
impl LanguageModelPickerDelegate {
|
||||||
pub fn new(
|
fn new(
|
||||||
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
|
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
|
||||||
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
|
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let on_model_changed = Arc::new(on_model_changed);
|
let on_model_changed = Arc::new(on_model_changed);
|
||||||
|
let models = all_models(cx);
|
||||||
|
let entries = models.entries();
|
||||||
|
|
||||||
let all_models = Self::all_models(cx);
|
Self {
|
||||||
let entries = all_models.entries();
|
|
||||||
|
|
||||||
let delegate = LanguageModelPickerDelegate {
|
|
||||||
language_model_selector: cx.entity().downgrade(),
|
|
||||||
on_model_changed: on_model_changed.clone(),
|
on_model_changed: on_model_changed.clone(),
|
||||||
all_models: Arc::new(all_models),
|
all_models: Arc::new(models),
|
||||||
selected_index: Self::get_active_model_index(&entries, get_active_model(cx)),
|
selected_index: Self::get_active_model_index(&entries, get_active_model(cx)),
|
||||||
filtered_entries: entries,
|
filtered_entries: entries,
|
||||||
get_active_model: Arc::new(get_active_model),
|
get_active_model: Arc::new(get_active_model),
|
||||||
};
|
|
||||||
|
|
||||||
let picker = cx.new(|cx| {
|
|
||||||
Picker::list(delegate, window, cx)
|
|
||||||
.show_scrollbar(true)
|
|
||||||
.width(rems(20.))
|
|
||||||
.max_height(Some(rems(20.).into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
let subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
|
|
||||||
|
|
||||||
LanguageModelSelector {
|
|
||||||
picker,
|
|
||||||
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
|
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![cx.subscribe_in(
|
||||||
cx.subscribe_in(
|
&LanguageModelRegistry::global(cx),
|
||||||
&LanguageModelRegistry::global(cx),
|
window,
|
||||||
window,
|
|picker, _, event, window, cx| {
|
||||||
Self::handle_language_model_registry_event,
|
match event {
|
||||||
),
|
language_model::Event::ProviderStateChanged
|
||||||
subscription,
|
| language_model::Event::AddedProvider(_)
|
||||||
],
|
| language_model::Event::RemovedProvider(_) => {
|
||||||
|
let query = picker.query(cx);
|
||||||
|
picker.delegate.all_models = Arc::new(all_models(cx));
|
||||||
|
// Update matches will automatically drop the previous task
|
||||||
|
// if we get a provider event again
|
||||||
|
picker.update_matches(query, window, cx)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_language_model_registry_event(
|
fn get_active_model_index(
|
||||||
&mut self,
|
entries: &[LanguageModelPickerEntry],
|
||||||
_registry: &Entity<LanguageModelRegistry>,
|
active_model: Option<ConfiguredModel>,
|
||||||
event: &language_model::Event,
|
) -> usize {
|
||||||
window: &mut Window,
|
entries
|
||||||
cx: &mut Context<Self>,
|
.iter()
|
||||||
) {
|
.position(|entry| {
|
||||||
match event {
|
if let LanguageModelPickerEntry::Model(model) = entry {
|
||||||
language_model::Event::ProviderStateChanged
|
active_model
|
||||||
| language_model::Event::AddedProvider(_)
|
.as_ref()
|
||||||
| language_model::Event::RemovedProvider(_) => {
|
.map(|active_model| {
|
||||||
self.picker.update(cx, |this, cx| {
|
active_model.model.id() == model.model.id()
|
||||||
let query = this.query(cx);
|
&& active_model.provider.id() == model.model.provider_id()
|
||||||
this.delegate.all_models = Arc::new(Self::all_models(cx));
|
})
|
||||||
// Update matches will automatically drop the previous task
|
.unwrap_or_default()
|
||||||
// if we get a provider event again
|
} else {
|
||||||
this.update_matches(query, window, cx)
|
false
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
_ => {}
|
.unwrap_or(0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticates all providers in the [`LanguageModelRegistry`].
|
/// Authenticates all providers in the [`LanguageModelRegistry`].
|
||||||
@@ -154,169 +204,9 @@ impl LanguageModelSelector {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_models(cx: &App) -> GroupedModels {
|
|
||||||
let mut recommended = Vec::new();
|
|
||||||
let mut recommended_set = HashSet::default();
|
|
||||||
for provider in LanguageModelRegistry::global(cx)
|
|
||||||
.read(cx)
|
|
||||||
.providers()
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
let models = provider.recommended_models(cx);
|
|
||||||
recommended_set.extend(models.iter().map(|model| (model.provider_id(), model.id())));
|
|
||||||
recommended.extend(
|
|
||||||
provider
|
|
||||||
.recommended_models(cx)
|
|
||||||
.into_iter()
|
|
||||||
.map(move |model| ModelInfo {
|
|
||||||
model: model.clone(),
|
|
||||||
icon: provider.icon(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let other_models = LanguageModelRegistry::global(cx)
|
|
||||||
.read(cx)
|
|
||||||
.providers()
|
|
||||||
.iter()
|
|
||||||
.map(|provider| {
|
|
||||||
(
|
|
||||||
provider.id(),
|
|
||||||
provider
|
|
||||||
.provided_models(cx)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|model| {
|
|
||||||
let not_included =
|
|
||||||
!recommended_set.contains(&(model.provider_id(), model.id()));
|
|
||||||
not_included.then(|| ModelInfo {
|
|
||||||
model: model.clone(),
|
|
||||||
icon: provider.icon(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<IndexMap<_, _>>();
|
|
||||||
|
|
||||||
GroupedModels {
|
|
||||||
recommended,
|
|
||||||
other: other_models,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
pub fn active_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||||
(self.picker.read(cx).delegate.get_active_model)(cx)
|
(self.get_active_model)(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_active_model_index(
|
|
||||||
entries: &[LanguageModelPickerEntry],
|
|
||||||
active_model: Option<ConfiguredModel>,
|
|
||||||
) -> usize {
|
|
||||||
entries
|
|
||||||
.iter()
|
|
||||||
.position(|entry| {
|
|
||||||
if let LanguageModelPickerEntry::Model(model) = entry {
|
|
||||||
active_model
|
|
||||||
.as_ref()
|
|
||||||
.map(|active_model| {
|
|
||||||
active_model.model.id() == model.model.id()
|
|
||||||
&& active_model.provider.id() == model.model.provider_id()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for LanguageModelSelector {}
|
|
||||||
|
|
||||||
impl Focusable for LanguageModelSelector {
|
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
|
||||||
self.picker.focus_handle(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for LanguageModelSelector {
|
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
self.picker.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct LanguageModelSelectorPopoverMenu<T, TT>
|
|
||||||
where
|
|
||||||
T: PopoverTrigger + ButtonCommon,
|
|
||||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
{
|
|
||||||
language_model_selector: Entity<LanguageModelSelector>,
|
|
||||||
trigger: T,
|
|
||||||
tooltip: TT,
|
|
||||||
handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
|
|
||||||
anchor: Corner,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
|
|
||||||
where
|
|
||||||
T: PopoverTrigger + ButtonCommon,
|
|
||||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
{
|
|
||||||
pub fn new(
|
|
||||||
language_model_selector: Entity<LanguageModelSelector>,
|
|
||||||
trigger: T,
|
|
||||||
tooltip: TT,
|
|
||||||
anchor: Corner,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
language_model_selector,
|
|
||||||
trigger,
|
|
||||||
tooltip,
|
|
||||||
handle: None,
|
|
||||||
anchor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
|
|
||||||
self.handle = Some(handle);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
|
|
||||||
where
|
|
||||||
T: PopoverTrigger + ButtonCommon,
|
|
||||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
{
|
|
||||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
|
||||||
let language_model_selector = self.language_model_selector.clone();
|
|
||||||
|
|
||||||
PopoverMenu::new("model-switcher")
|
|
||||||
.menu(move |_window, _cx| Some(language_model_selector.clone()))
|
|
||||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
|
||||||
.anchor(self.anchor)
|
|
||||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
|
||||||
.offset(gpui::Point {
|
|
||||||
x: px(0.0),
|
|
||||||
y: px(-2.0),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ModelInfo {
|
|
||||||
model: Arc<dyn LanguageModel>,
|
|
||||||
icon: IconName,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LanguageModelPickerDelegate {
|
|
||||||
language_model_selector: WeakEntity<LanguageModelSelector>,
|
|
||||||
on_model_changed: OnModelChanged,
|
|
||||||
get_active_model: GetActiveModel,
|
|
||||||
all_models: Arc<GroupedModels>,
|
|
||||||
filtered_entries: Vec<LanguageModelPickerEntry>,
|
|
||||||
selected_index: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GroupedModels {
|
struct GroupedModels {
|
||||||
@@ -326,11 +216,14 @@ struct GroupedModels {
|
|||||||
|
|
||||||
impl GroupedModels {
|
impl GroupedModels {
|
||||||
pub fn new(other: Vec<ModelInfo>, recommended: Vec<ModelInfo>) -> Self {
|
pub fn new(other: Vec<ModelInfo>, recommended: Vec<ModelInfo>) -> Self {
|
||||||
let recommended_ids: HashSet<_> = recommended.iter().map(|info| info.model.id()).collect();
|
let recommended_ids = recommended
|
||||||
|
.iter()
|
||||||
|
.map(|info| (info.model.provider_id(), info.model.id()))
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
let mut other_by_provider: IndexMap<_, Vec<ModelInfo>> = IndexMap::default();
|
let mut other_by_provider: IndexMap<_, Vec<ModelInfo>> = IndexMap::default();
|
||||||
for model in other {
|
for model in other {
|
||||||
if recommended_ids.contains(&model.model.id()) {
|
if recommended_ids.contains(&(model.model.provider_id(), model.model.id())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,9 +470,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
self.language_model_selector
|
cx.emit(DismissEvent);
|
||||||
.update(cx, |_this, cx| cx.emit(DismissEvent))
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
@@ -917,4 +808,26 @@ mod tests {
|
|||||||
// Recommended models should not appear in "other"
|
// Recommended models should not appear in "other"
|
||||||
assert_models_eq(actual_other_models, vec!["zed/gemini", "copilot/o3"]);
|
assert_models_eq(actual_other_models, vec!["zed/gemini", "copilot/o3"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_dont_exclude_models_from_other_providers(_cx: &mut TestAppContext) {
|
||||||
|
let recommended_models = create_models(vec![("zed", "claude")]);
|
||||||
|
let all_models = create_models(vec![
|
||||||
|
("zed", "claude"), // Should be filtered out from "other"
|
||||||
|
("zed", "gemini"),
|
||||||
|
("copilot", "claude"), // Should not be filtered out from "other"
|
||||||
|
]);
|
||||||
|
|
||||||
|
let grouped_models = GroupedModels::new(all_models, recommended_models);
|
||||||
|
|
||||||
|
let actual_other_models = grouped_models
|
||||||
|
.other
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Recommended models should not appear in "other"
|
||||||
|
assert_models_eq(actual_other_models, vec!["zed/gemini", "copilot/claude"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
61
crates/assistant_context_editor/src/max_mode_tooltip.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use gpui::{Context, FontWeight, IntoElement, Render, Window};
|
||||||
|
use ui::{prelude::*, tooltip_container};
|
||||||
|
|
||||||
|
pub struct MaxModeTooltip {
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaxModeTooltip {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { selected: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for MaxModeTooltip {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let (icon, color) = if self.selected {
|
||||||
|
(IconName::ZedBurnModeOn, Color::Error)
|
||||||
|
} else {
|
||||||
|
(IconName::ZedBurnMode, Color::Default)
|
||||||
|
};
|
||||||
|
|
||||||
|
let turned_on = h_flex()
|
||||||
|
.h_4()
|
||||||
|
.px_1()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.bg(cx.theme().colors().text_accent.opacity(0.1))
|
||||||
|
.rounded_sm()
|
||||||
|
.child(
|
||||||
|
Label::new("ON")
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.weight(FontWeight::SEMIBOLD)
|
||||||
|
.color(Color::Accent),
|
||||||
|
);
|
||||||
|
|
||||||
|
let title = h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(Icon::new(icon).size(IconSize::Small).color(color))
|
||||||
|
.child(Label::new("Burn Mode"))
|
||||||
|
.when(self.selected, |title| title.child(turned_on));
|
||||||
|
|
||||||
|
tooltip_container(window, cx, |this, _, _| {
|
||||||
|
this
|
||||||
|
.child(title)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.max_w_64()
|
||||||
|
.child(
|
||||||
|
Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning.")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
name_range: Range<Anchor>,
|
name_range: Range<Anchor>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<Option<Vec<project::Completion>>>> {
|
) -> Task<Result<Vec<project::CompletionResponse>>> {
|
||||||
let slash_commands = self.slash_commands.clone();
|
let slash_commands = self.slash_commands.clone();
|
||||||
let candidates = slash_commands
|
let candidates = slash_commands
|
||||||
.command_names(cx)
|
.command_names(cx)
|
||||||
@@ -71,28 +71,27 @@ impl SlashCommandCompletionProvider {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx.update(|_, cx| {
|
cx.update(|_, cx| {
|
||||||
Some(
|
let completions = matches
|
||||||
matches
|
.into_iter()
|
||||||
.into_iter()
|
.filter_map(|mat| {
|
||||||
.filter_map(|mat| {
|
let command = slash_commands.command(&mat.string, cx)?;
|
||||||
let command = slash_commands.command(&mat.string, cx)?;
|
let mut new_text = mat.string.clone();
|
||||||
let mut new_text = mat.string.clone();
|
let requires_argument = command.requires_argument();
|
||||||
let requires_argument = command.requires_argument();
|
let accepts_arguments = command.accepts_arguments();
|
||||||
let accepts_arguments = command.accepts_arguments();
|
if requires_argument || accepts_arguments {
|
||||||
if requires_argument || accepts_arguments {
|
new_text.push(' ');
|
||||||
new_text.push(' ');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let confirm =
|
let confirm =
|
||||||
editor
|
editor
|
||||||
.clone()
|
.clone()
|
||||||
.zip(workspace.clone())
|
.zip(workspace.clone())
|
||||||
.map(|(editor, workspace)| {
|
.map(|(editor, workspace)| {
|
||||||
let command_name = mat.string.clone();
|
let command_name = mat.string.clone();
|
||||||
let command_range = command_range.clone();
|
let command_range = command_range.clone();
|
||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
let workspace = workspace.clone();
|
let workspace = workspace.clone();
|
||||||
Arc::new(
|
Arc::new(
|
||||||
move |intent: CompletionIntent,
|
move |intent: CompletionIntent,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App| {
|
cx: &mut App| {
|
||||||
@@ -118,22 +117,27 @@ impl SlashCommandCompletionProvider {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) as Arc<_>
|
) as Arc<_>
|
||||||
});
|
});
|
||||||
Some(project::Completion {
|
|
||||||
replace_range: name_range.clone(),
|
Some(project::Completion {
|
||||||
documentation: Some(CompletionDocumentation::SingleLine(
|
replace_range: name_range.clone(),
|
||||||
command.description().into(),
|
documentation: Some(CompletionDocumentation::SingleLine(
|
||||||
)),
|
command.description().into(),
|
||||||
new_text,
|
)),
|
||||||
label: command.label(cx),
|
new_text,
|
||||||
icon_path: None,
|
label: command.label(cx),
|
||||||
insert_text_mode: None,
|
icon_path: None,
|
||||||
confirm,
|
insert_text_mode: None,
|
||||||
source: CompletionSource::Custom,
|
confirm,
|
||||||
})
|
source: CompletionSource::Custom,
|
||||||
})
|
})
|
||||||
.collect(),
|
})
|
||||||
)
|
.collect();
|
||||||
|
|
||||||
|
vec![project::CompletionResponse {
|
||||||
|
completions,
|
||||||
|
is_incomplete: false,
|
||||||
|
}]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -147,7 +151,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
last_argument_range: Range<Anchor>,
|
last_argument_range: Range<Anchor>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<Option<Vec<project::Completion>>>> {
|
) -> Task<Result<Vec<project::CompletionResponse>>> {
|
||||||
let new_cancel_flag = Arc::new(AtomicBool::new(false));
|
let new_cancel_flag = Arc::new(AtomicBool::new(false));
|
||||||
let mut flag = self.cancel_flag.lock();
|
let mut flag = self.cancel_flag.lock();
|
||||||
flag.store(true, SeqCst);
|
flag.store(true, SeqCst);
|
||||||
@@ -165,28 +169,27 @@ impl SlashCommandCompletionProvider {
|
|||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let arguments = arguments.to_vec();
|
let arguments = arguments.to_vec();
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
Ok(Some(
|
let completions = completions
|
||||||
completions
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(|new_argument| {
|
||||||
.map(|new_argument| {
|
let confirm =
|
||||||
let confirm =
|
editor
|
||||||
editor
|
.clone()
|
||||||
.clone()
|
.zip(workspace.clone())
|
||||||
.zip(workspace.clone())
|
.map(|(editor, workspace)| {
|
||||||
.map(|(editor, workspace)| {
|
Arc::new({
|
||||||
Arc::new({
|
let mut completed_arguments = arguments.clone();
|
||||||
let mut completed_arguments = arguments.clone();
|
if new_argument.replace_previous_arguments {
|
||||||
if new_argument.replace_previous_arguments {
|
completed_arguments.clear();
|
||||||
completed_arguments.clear();
|
} else {
|
||||||
} else {
|
completed_arguments.pop();
|
||||||
completed_arguments.pop();
|
}
|
||||||
}
|
completed_arguments.push(new_argument.new_text.clone());
|
||||||
completed_arguments.push(new_argument.new_text.clone());
|
|
||||||
|
|
||||||
let command_range = command_range.clone();
|
let command_range = command_range.clone();
|
||||||
let command_name = command_name.clone();
|
let command_name = command_name.clone();
|
||||||
move |intent: CompletionIntent,
|
move |intent: CompletionIntent,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App| {
|
cx: &mut App| {
|
||||||
if new_argument.after_completion.run()
|
if new_argument.after_completion.run()
|
||||||
@@ -210,34 +213,41 @@ impl SlashCommandCompletionProvider {
|
|||||||
!new_argument.after_completion.run()
|
!new_argument.after_completion.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) as Arc<_>
|
}) as Arc<_>
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut new_text = new_argument.new_text.clone();
|
let mut new_text = new_argument.new_text.clone();
|
||||||
if new_argument.after_completion == AfterCompletion::Continue {
|
if new_argument.after_completion == AfterCompletion::Continue {
|
||||||
new_text.push(' ');
|
new_text.push(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
project::Completion {
|
project::Completion {
|
||||||
replace_range: if new_argument.replace_previous_arguments {
|
replace_range: if new_argument.replace_previous_arguments {
|
||||||
argument_range.clone()
|
argument_range.clone()
|
||||||
} else {
|
} else {
|
||||||
last_argument_range.clone()
|
last_argument_range.clone()
|
||||||
},
|
},
|
||||||
label: new_argument.label,
|
label: new_argument.label,
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
new_text,
|
new_text,
|
||||||
documentation: None,
|
documentation: None,
|
||||||
confirm,
|
confirm,
|
||||||
insert_text_mode: None,
|
insert_text_mode: None,
|
||||||
source: CompletionSource::Custom,
|
source: CompletionSource::Custom,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect();
|
||||||
))
|
|
||||||
|
Ok(vec![project::CompletionResponse {
|
||||||
|
completions,
|
||||||
|
is_incomplete: false,
|
||||||
|
}])
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(Some(Vec::new())))
|
Task::ready(Ok(vec![project::CompletionResponse {
|
||||||
|
completions: Vec::new(),
|
||||||
|
is_incomplete: false,
|
||||||
|
}]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +261,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
|||||||
_: editor::CompletionContext,
|
_: editor::CompletionContext,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Task<Result<Option<Vec<project::Completion>>>> {
|
) -> Task<Result<Vec<project::CompletionResponse>>> {
|
||||||
let Some((name, arguments, command_range, last_argument_range)) =
|
let Some((name, arguments, command_range, last_argument_range)) =
|
||||||
buffer.update(cx, |buffer, _cx| {
|
buffer.update(cx, |buffer, _cx| {
|
||||||
let position = buffer_position.to_point(buffer);
|
let position = buffer_position.to_point(buffer);
|
||||||
@@ -295,7 +305,10 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
|||||||
Some((name, arguments, command_range, last_argument_range))
|
Some((name, arguments, command_range, last_argument_range))
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
return Task::ready(Ok(Some(Vec::new())));
|
return Task::ready(Ok(vec![project::CompletionResponse {
|
||||||
|
completions: Vec::new(),
|
||||||
|
is_incomplete: false,
|
||||||
|
}]));
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((arguments, argument_range)) = arguments {
|
if let Some((arguments, argument_range)) = arguments {
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ where
|
|||||||
|
|
||||||
let handle = self
|
let handle = self
|
||||||
.active_context_editor
|
.active_context_editor
|
||||||
.update(cx, |this, _| this.slash_menu_handle.clone())
|
.read_with(cx, |this, _| this.slash_menu_handle.clone())
|
||||||
.ok();
|
.ok();
|
||||||
PopoverMenu::new("model-switcher")
|
PopoverMenu::new("model-switcher")
|
||||||
.menu(move |_window, _cx| Some(picker_view.clone()))
|
.menu(move |_window, _cx| Some(picker_view.clone()))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use anyhow::Result;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures::stream::{self, BoxStream};
|
use futures::stream::{self, BoxStream};
|
||||||
use gpui::{App, SharedString, Task, WeakEntity, Window};
|
use gpui::{App, SharedString, Task, WeakEntity, Window};
|
||||||
|
use language::HighlightId;
|
||||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||||
pub use language_model::Role;
|
pub use language_model::Role;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -16,6 +17,7 @@ use std::{
|
|||||||
ops::Range,
|
ops::Range,
|
||||||
sync::{Arc, atomic::AtomicBool},
|
sync::{Arc, atomic::AtomicBool},
|
||||||
};
|
};
|
||||||
|
use ui::ActiveTheme;
|
||||||
use workspace::{Workspace, ui::IconName};
|
use workspace::{Workspace, ui::IconName};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
@@ -325,6 +327,18 @@ impl SlashCommandLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel {
|
||||||
|
let mut label = CodeLabel::default();
|
||||||
|
label.push_str(command_name, None);
|
||||||
|
label.push_str(" ", None);
|
||||||
|
label.push_str(
|
||||||
|
&arguments.join(" "),
|
||||||
|
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
||||||
|
);
|
||||||
|
label.filter_range = 0..command_name.len();
|
||||||
|
label
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ rope.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
terminal_view.workspace = true
|
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
@@ -45,6 +44,6 @@ worktree.workspace = true
|
|||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger.workspace = true
|
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
zlog.workspace = true
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ mod selection_command;
|
|||||||
mod streaming_example_command;
|
mod streaming_example_command;
|
||||||
mod symbols_command;
|
mod symbols_command;
|
||||||
mod tab_command;
|
mod tab_command;
|
||||||
mod terminal_command;
|
|
||||||
|
|
||||||
use gpui::App;
|
|
||||||
use language::{CodeLabel, HighlightId};
|
|
||||||
use ui::ActiveTheme as _;
|
|
||||||
|
|
||||||
pub use crate::cargo_workspace_command::*;
|
pub use crate::cargo_workspace_command::*;
|
||||||
pub use crate::context_server_command::*;
|
pub use crate::context_server_command::*;
|
||||||
@@ -32,16 +27,5 @@ pub use crate::selection_command::*;
|
|||||||
pub use crate::streaming_example_command::*;
|
pub use crate::streaming_example_command::*;
|
||||||
pub use crate::symbols_command::*;
|
pub use crate::symbols_command::*;
|
||||||
pub use crate::tab_command::*;
|
pub use crate::tab_command::*;
|
||||||
pub use crate::terminal_command::*;
|
|
||||||
|
|
||||||
pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel {
|
use assistant_slash_command::create_label_for_command;
|
||||||
let mut label = CodeLabel::default();
|
|
||||||
label.push_str(command_name, None);
|
|
||||||
label.push_str(" ", None);
|
|
||||||
label.push_str(
|
|
||||||
&arguments.join(" "),
|
|
||||||
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
|
||||||
);
|
|
||||||
label.filter_range = 0..command_name.len();
|
|
||||||
label
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
|
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
|
||||||
SlashCommandOutputSection, SlashCommandResult,
|
SlashCommandOutputSection, SlashCommandResult,
|
||||||
@@ -84,9 +84,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
|
|
||||||
if let Some(server) = self.store.read(cx).get_running_server(&server_id) {
|
if let Some(server) = self.store.read(cx).get_running_server(&server_id) {
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let Some(protocol) = server.client() else {
|
let protocol = server.client().context("Context server not initialized")?;
|
||||||
return Err(anyhow!("Context server not initialized"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let completion_result = protocol
|
let completion_result = protocol
|
||||||
.completion(
|
.completion(
|
||||||
@@ -139,21 +137,16 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
let store = self.store.read(cx);
|
let store = self.store.read(cx);
|
||||||
if let Some(server) = store.get_running_server(&server_id) {
|
if let Some(server) = store.get_running_server(&server_id) {
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let Some(protocol) = server.client() else {
|
let protocol = server.client().context("Context server not initialized")?;
|
||||||
return Err(anyhow!("Context server not initialized"));
|
|
||||||
};
|
|
||||||
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
||||||
|
|
||||||
// Check that there are only user roles
|
anyhow::ensure!(
|
||||||
if result
|
result
|
||||||
.messages
|
.messages
|
||||||
.iter()
|
.iter()
|
||||||
.any(|msg| !matches!(msg.role, context_server::types::Role::User))
|
.all(|msg| matches!(msg.role, context_server::types::Role::User)),
|
||||||
{
|
"Prompt contains non-user roles, which is not supported"
|
||||||
return Err(anyhow!(
|
);
|
||||||
"Prompt contains non-user roles, which is not supported"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract text from user messages into a single prompt string
|
// Extract text from user messages into a single prompt string
|
||||||
let mut prompt = result
|
let mut prompt = result
|
||||||
@@ -192,9 +185,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn completion_argument(prompt: &Prompt, arguments: &[String]) -> Result<(String, String)> {
|
fn completion_argument(prompt: &Prompt, arguments: &[String]) -> Result<(String, String)> {
|
||||||
if arguments.is_empty() {
|
anyhow::ensure!(!arguments.is_empty(), "No arguments given");
|
||||||
return Err(anyhow!("No arguments given"));
|
|
||||||
}
|
|
||||||
|
|
||||||
match &prompt.arguments {
|
match &prompt.arguments {
|
||||||
Some(args) if args.len() == 1 => {
|
Some(args) if args.len() == 1 => {
|
||||||
@@ -202,16 +193,16 @@ fn completion_argument(prompt: &Prompt, arguments: &[String]) -> Result<(String,
|
|||||||
let arg_value = arguments.join(" ");
|
let arg_value = arguments.join(" ");
|
||||||
Ok((arg_name, arg_value))
|
Ok((arg_name, arg_value))
|
||||||
}
|
}
|
||||||
Some(_) => Err(anyhow!("Prompt must have exactly one argument")),
|
Some(_) => anyhow::bail!("Prompt must have exactly one argument"),
|
||||||
None => Err(anyhow!("Prompt has no arguments")),
|
None => anyhow::bail!("Prompt has no arguments"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt_arguments(prompt: &Prompt, arguments: &[String]) -> Result<HashMap<String, String>> {
|
fn prompt_arguments(prompt: &Prompt, arguments: &[String]) -> Result<HashMap<String, String>> {
|
||||||
match &prompt.arguments {
|
match &prompt.arguments {
|
||||||
Some(args) if args.len() > 1 => Err(anyhow!(
|
Some(args) if args.len() > 1 => {
|
||||||
"Prompt has more than one argument, which is not supported"
|
anyhow::bail!("Prompt has more than one argument, which is not supported");
|
||||||
)),
|
}
|
||||||
Some(args) if args.len() == 1 => {
|
Some(args) if args.len() == 1 => {
|
||||||
if !arguments.is_empty() {
|
if !arguments.is_empty() {
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
@@ -220,15 +211,15 @@ fn prompt_arguments(prompt: &Prompt, arguments: &[String]) -> Result<HashMap<Str
|
|||||||
} else if arguments.is_empty() && args[0].required == Some(false) {
|
} else if arguments.is_empty() && args[0].required == Some(false) {
|
||||||
Ok(HashMap::default())
|
Ok(HashMap::default())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Prompt expects argument but none given"))
|
anyhow::bail!("Prompt expects argument but none given");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(_) | None => {
|
Some(_) | None => {
|
||||||
if arguments.is_empty() {
|
anyhow::ensure!(
|
||||||
Ok(HashMap::default())
|
arguments.is_empty(),
|
||||||
} else {
|
"Prompt expects no arguments but some were given"
|
||||||
Err(anyhow!("Prompt expects no arguments but some were given"))
|
);
|
||||||
}
|
Ok(HashMap::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,10 +118,7 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changes_detected {
|
anyhow::ensure!(changes_detected, "no new changes detected");
|
||||||
return Err(anyhow!("no new changes detected"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output.to_event_stream())
|
Ok(output.to_event_stream())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
SlashCommandResult,
|
SlashCommandResult,
|
||||||
@@ -189,7 +189,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
window.spawn(cx, async move |_| {
|
window.spawn(cx, async move |_| {
|
||||||
task.await?
|
task.await?
|
||||||
.map(|output| output.to_event_stream())
|
.map(|output| output.to_event_stream())
|
||||||
.ok_or_else(|| anyhow!("No diagnostics found"))
|
.context("No diagnostics found")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Result, anyhow, bail};
|
use anyhow::{Context as _, Result, anyhow, bail};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
SlashCommandResult,
|
SlashCommandResult,
|
||||||
@@ -52,15 +52,16 @@ impl DocsSlashCommand {
|
|||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
let index_provider_deps = maybe!({
|
let index_provider_deps = maybe!({
|
||||||
let workspace = workspace.clone().ok_or_else(|| anyhow!("no workspace"))?;
|
|
||||||
let workspace = workspace
|
let workspace = workspace
|
||||||
|
.as_ref()
|
||||||
|
.context("no workspace")?
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
.context("workspace dropped")?;
|
||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
let fs = project.read(cx).fs().clone();
|
let fs = project.read(cx).fs().clone();
|
||||||
let cargo_workspace_root = Self::path_to_cargo_toml(project, cx)
|
let cargo_workspace_root = Self::path_to_cargo_toml(project, cx)
|
||||||
.and_then(|path| path.parent().map(|path| path.to_path_buf()))
|
.and_then(|path| path.parent().map(|path| path.to_path_buf()))
|
||||||
.ok_or_else(|| anyhow!("no Cargo workspace root found"))?;
|
.context("no Cargo workspace root found")?;
|
||||||
|
|
||||||
anyhow::Ok((fs, cargo_workspace_root))
|
anyhow::Ok((fs, cargo_workspace_root))
|
||||||
});
|
});
|
||||||
@@ -78,10 +79,11 @@ impl DocsSlashCommand {
|
|||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
let http_client = maybe!({
|
let http_client = maybe!({
|
||||||
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
|
|
||||||
let workspace = workspace
|
let workspace = workspace
|
||||||
|
.as_ref()
|
||||||
|
.context("no workspace")?
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
.context("workspace was dropped")?;
|
||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
anyhow::Ok(project.read(cx).client().http_client())
|
anyhow::Ok(project.read(cx).client().http_client())
|
||||||
});
|
});
|
||||||
@@ -174,7 +176,7 @@ impl SlashCommand for DocsSlashCommand {
|
|||||||
let args = DocsSlashCommandArgs::parse(arguments);
|
let args = DocsSlashCommandArgs::parse(arguments);
|
||||||
let store = args
|
let store = args
|
||||||
.provider()
|
.provider()
|
||||||
.ok_or_else(|| anyhow!("no docs provider specified"))
|
.context("no docs provider specified")
|
||||||
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
|
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
fn build_completions(items: Vec<String>) -> Vec<ArgumentCompletion> {
|
fn build_completions(items: Vec<String>) -> Vec<ArgumentCompletion> {
|
||||||
@@ -287,7 +289,7 @@ impl SlashCommand for DocsSlashCommand {
|
|||||||
let task = cx.background_spawn({
|
let task = cx.background_spawn({
|
||||||
let store = args
|
let store = args
|
||||||
.provider()
|
.provider()
|
||||||
.ok_or_else(|| anyhow!("no docs provider specified"))
|
.context("no docs provider specified")
|
||||||
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
|
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
|
||||||
async move {
|
async move {
|
||||||
let (provider, key) = match args.clone() {
|
let (provider, key) = match args.clone() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::rc::Rc;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow, bail};
|
use anyhow::{Context as _, Result, anyhow, bail};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
SlashCommandResult,
|
SlashCommandResult,
|
||||||
|
|||||||
@@ -230,7 +230,10 @@ fn collect_files(
|
|||||||
})
|
})
|
||||||
.collect::<anyhow::Result<Vec<custom_path_matcher::PathMatcher>>>()
|
.collect::<anyhow::Result<Vec<custom_path_matcher::PathMatcher>>>()
|
||||||
else {
|
else {
|
||||||
return futures::stream::once(async { Err(anyhow!("invalid path")) }).boxed();
|
return futures::stream::once(async {
|
||||||
|
anyhow::bail!("invalid path");
|
||||||
|
})
|
||||||
|
.boxed();
|
||||||
};
|
};
|
||||||
|
|
||||||
let project_handle = project.downgrade();
|
let project_handle = project.downgrade();
|
||||||
@@ -584,9 +587,7 @@ mod test {
|
|||||||
use super::collect_files;
|
use super::collect_files;
|
||||||
|
|
||||||
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
||||||
if std::env::var("RUST_LOG").is_ok() {
|
zlog::init_test();
|
||||||
env_logger::try_init().ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ buffer_diff = { workspace = true, features = ["test-support"] }
|
|||||||
collections = { workspace = true, features = ["test-support"] }
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
clock = { workspace = true, features = ["test-support"] }
|
clock = { workspace = true, features = ["test-support"] }
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
env_logger.workspace = true
|
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
language_model = { workspace = true, features = ["test-support"] }
|
language_model = { workspace = true, features = ["test-support"] }
|
||||||
@@ -48,3 +47,4 @@ rand.workspace = true
|
|||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
text = { workspace = true, features = ["test-support"] }
|
text = { workspace = true, features = ["test-support"] }
|
||||||
util = { workspace = true, features = ["test-support"] }
|
util = { workspace = true, features = ["test-support"] }
|
||||||
|
zlog.workspace = true
|
||||||
|
|||||||
@@ -415,14 +415,38 @@ impl ActionLog {
|
|||||||
self.project
|
self.project
|
||||||
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
|
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
|
||||||
} else {
|
} else {
|
||||||
buffer
|
// For a file created by AI with no pre-existing content,
|
||||||
.read(cx)
|
// only delete the file if we're certain it contains only AI content
|
||||||
.entry_id(cx)
|
// with no edits from the user.
|
||||||
.and_then(|entry_id| {
|
|
||||||
self.project
|
let initial_version = tracked_buffer.version.clone();
|
||||||
.update(cx, |project, cx| project.delete_entry(entry_id, false, cx))
|
let current_version = buffer.read(cx).version();
|
||||||
})
|
|
||||||
.unwrap_or(Task::ready(Ok(())))
|
let current_content = buffer.read(cx).text();
|
||||||
|
let tracked_content = tracked_buffer.snapshot.text();
|
||||||
|
|
||||||
|
let is_ai_only_content =
|
||||||
|
initial_version == current_version && current_content == tracked_content;
|
||||||
|
|
||||||
|
if is_ai_only_content {
|
||||||
|
buffer
|
||||||
|
.read(cx)
|
||||||
|
.entry_id(cx)
|
||||||
|
.and_then(|entry_id| {
|
||||||
|
self.project.update(cx, |project, cx| {
|
||||||
|
project.delete_entry(entry_id, false, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(Task::ready(Ok(())))
|
||||||
|
} else {
|
||||||
|
// Not sure how to disentangle edits made by the user
|
||||||
|
// from edits made by the AI at this point.
|
||||||
|
// For now, preserve both to avoid data loss.
|
||||||
|
//
|
||||||
|
// TODO: Better solution (disable "Reject" after user makes some
|
||||||
|
// edit or find a way to differentiate between AI and user edits)
|
||||||
|
Task::ready(Ok(()))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.tracked_buffers.remove(&buffer);
|
self.tracked_buffers.remove(&buffer);
|
||||||
@@ -717,9 +741,7 @@ mod tests {
|
|||||||
|
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
if std::env::var("RUST_LOG").is_ok() {
|
zlog::init_test();
|
||||||
env_logger::init();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) {
|
fn init_test(cx: &mut TestAppContext) {
|
||||||
@@ -1578,7 +1600,6 @@ mod tests {
|
|||||||
project.find_project_path("dir/new_file", cx)
|
project.find_project_path("dir/new_file", cx)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_buffer(file_path, cx))
|
.update(cx, |project, cx| project.open_buffer(file_path, cx))
|
||||||
.await
|
.await
|
||||||
@@ -1621,6 +1642,72 @@ mod tests {
|
|||||||
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
|
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_reject_created_file_with_user_edits(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||||
|
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
||||||
|
|
||||||
|
let file_path = project
|
||||||
|
.read_with(cx, |project, cx| {
|
||||||
|
project.find_project_path("dir/new_file", cx)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| project.open_buffer(file_path, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// AI creates file with initial content
|
||||||
|
cx.update(|cx| {
|
||||||
|
action_log.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx));
|
||||||
|
buffer.update(cx, |buffer, cx| buffer.set_text("ai content", cx));
|
||||||
|
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
|
||||||
|
});
|
||||||
|
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
// User makes additional edits
|
||||||
|
cx.update(|cx| {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(10..10, "\nuser added this line")], None, cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
|
||||||
|
|
||||||
|
// Reject all
|
||||||
|
action_log
|
||||||
|
.update(cx, |log, cx| {
|
||||||
|
log.reject_edits_in_ranges(
|
||||||
|
buffer.clone(),
|
||||||
|
vec![Point::new(0, 0)..Point::new(100, 0)],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
// File should still contain all the content
|
||||||
|
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
|
||||||
|
|
||||||
|
let content = buffer.read_with(cx, |buffer, _| buffer.text());
|
||||||
|
assert_eq!(content, "ai content\nuser added this line");
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 100)]
|
#[gpui::test(iterations = 100)]
|
||||||
async fn test_random_diffs(mut rng: StdRng, cx: &mut TestAppContext) {
|
async fn test_random_diffs(mut rng: StdRng, cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::ActionLog;
|
use crate::ActionLog;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Context as _, Result};
|
||||||
use gpui::{AsyncApp, Entity};
|
use gpui::{AsyncApp, Entity};
|
||||||
use language::{OutlineItem, ParseStatus};
|
use language::{OutlineItem, ParseStatus};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
@@ -22,7 +22,7 @@ pub async fn file_outline(
|
|||||||
let project_path = project.read_with(cx, |project, cx| {
|
let project_path = project.read_with(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.find_project_path(&path, cx)
|
.find_project_path(&path, cx)
|
||||||
.ok_or_else(|| anyhow!("Path {path} not found in project"))
|
.with_context(|| format!("Path {path} not found in project"))
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
project
|
project
|
||||||
@@ -41,9 +41,9 @@ pub async fn file_outline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||||
let Some(outline) = snapshot.outline(None) else {
|
let outline = snapshot
|
||||||
return Err(anyhow!("No outline information available for this file."));
|
.outline(None)
|
||||||
};
|
.context("No outline information available for this file at path {path}")?;
|
||||||
|
|
||||||
render_outline(
|
render_outline(
|
||||||
outline
|
outline
|
||||||
|
|||||||
@@ -27,12 +27,10 @@ fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> {
|
|||||||
const UNSUPPORTED_KEYS: [&str; 4] = ["if", "then", "else", "$ref"];
|
const UNSUPPORTED_KEYS: [&str; 4] = ["if", "then", "else", "$ref"];
|
||||||
|
|
||||||
for key in UNSUPPORTED_KEYS {
|
for key in UNSUPPORTED_KEYS {
|
||||||
if obj.contains_key(key) {
|
anyhow::ensure!(
|
||||||
return Err(anyhow::anyhow!(
|
!obj.contains_key(key),
|
||||||
"Schema cannot be made compatible because it contains \"{}\" ",
|
"Schema cannot be made compatible because it contains \"{key}\""
|
||||||
key
|
);
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const KEYS_TO_REMOVE: [&str; 5] = [
|
const KEYS_TO_REMOVE: [&str; 5] = [
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ path = "src/assistant_tools.rs"
|
|||||||
eval = []
|
eval = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aho-corasick.workspace = true
|
agent_settings.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
assistant_settings.workspace = true
|
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
|
async-watch.workspace = true
|
||||||
buffer_diff.workspace = true
|
buffer_diff.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
@@ -36,11 +36,13 @@ itertools.workspace = true
|
|||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
lsp.workspace = true
|
||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
open.workspace = true
|
open.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
portable-pty.workspace = true
|
portable-pty.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
prompt_store.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
rust-embed.workspace = true
|
rust-embed.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
@@ -63,6 +65,7 @@ workspace.workspace = true
|
|||||||
zed_llm_client.workspace = true
|
zed_llm_client.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
lsp = { workspace = true, features = ["test-support"] }
|
||||||
client = { workspace = true, features = ["test-support"] }
|
client = { workspace = true, features = ["test-support"] }
|
||||||
clock = { workspace = true, features = ["test-support"] }
|
clock = { workspace = true, features = ["test-support"] }
|
||||||
collections = { workspace = true, features = ["test-support"] }
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ fn register_web_search_tool(registry: &Entity<LanguageModelRegistry>, cx: &mut A
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use assistant_settings::AssistantSettings;
|
use agent_settings::AgentSettings;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use clock::FakeSystemClock;
|
use clock::FakeSystemClock;
|
||||||
use http_client::FakeHttpClient;
|
use http_client::FakeHttpClient;
|
||||||
@@ -133,7 +133,7 @@ mod tests {
|
|||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_builtin_tool_schema_compatibility(cx: &mut App) {
|
fn test_builtin_tool_schema_compatibility(cx: &mut App) {
|
||||||
settings::init(cx);
|
settings::init(cx);
|
||||||
AssistantSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
Arc::new(FakeSystemClock::new()),
|
Arc::new(FakeSystemClock::new()),
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
Invoke multiple other tool calls either sequentially or concurrently.
|
|
||||||
|
|
||||||
This tool is useful when you need to perform several operations at once, improving efficiency by reducing the number of back-and-forth interactions needed to complete complex tasks.
|
|
||||||
|
|
||||||
If the tool calls are set to be run sequentially, then each tool call within the batch is executed in the order provided. If it's set to run concurrently, then they may run in a different order. Regardless, all tool calls will have the same permissions and context as if they were called individually.
|
|
||||||
|
|
||||||
This tool should never be used to run a total of one tool. Instead, just run that one tool directly. You can run batches within batches if desired, which is a way you can mix concurrent and sequential tool call execution.
|
|
||||||
|
|
||||||
When it's possible to run tools in a batch, you should run as many as possible in the batch, up to a maximum of 32. For example, don't run multiple consecutive batches of 10 when you could instead run one batch of 30.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
A tool for applying code actions to specific sections of your code. It uses language servers to provide refactoring capabilities similar to what you'd find in an IDE.
|
|
||||||
|
|
||||||
This tool can:
|
|
||||||
- List all available code actions for a selected text range
|
|
||||||
- Execute a specific code action on that range
|
|
||||||
- Rename symbols across your codebase. This tool is the preferred way to rename things, and you should always prefer to rename code symbols using this tool rather than using textual find/replace when both are available.
|
|
||||||
|
|
||||||
Use this tool when you want to:
|
|
||||||
- Discover what code actions are available for a piece of code
|
|
||||||
- Apply automatic fixes and code transformations
|
|
||||||
- Rename variables, functions, or other symbols consistently throughout your project
|
|
||||||
- Clean up imports, implement interfaces, or perform other language-specific operations
|
|
||||||
|
|
||||||
- If unsure what actions are available, call the tool without specifying an action to get a list
|
|
||||||
- For common operations, you can directly specify actions like "quickfix.all" or "source.organizeImports"
|
|
||||||
- For renaming, use the special "textDocument/rename" action and provide the new name in the arguments field
|
|
||||||
- Be specific with your text range and context to ensure the tool identifies the correct code location
|
|
||||||
|
|
||||||
The tool will automatically save any changes it makes to your files.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
Returns either an outline of the public code symbols in the entire project (grouped by file) or else an outline of both the public and private code symbols within a particular file.
|
|
||||||
|
|
||||||
When a path is provided, this tool returns a hierarchical outline of code symbols for that specific file.
|
|
||||||
When no path is provided, it returns a list of all public code symbols in the project, organized by file.
|
|
||||||
|
|
||||||
You can also provide an optional regular expression which filters the output by only showing code symbols which match that regex.
|
|
||||||
|
|
||||||
Results are paginated with 2000 entries per page. Use the optional 'offset' parameter to request subsequent pages.
|
|
||||||
|
|
||||||
Markdown headings indicate the structure of the output; just like
|
|
||||||
with markdown headings, the more # symbols there are at the beginning of a line,
|
|
||||||
the deeper it is in the hierarchy.
|
|
||||||
|
|
||||||
Each code symbol entry ends with a line number or range, which tells you what portion of the
|
|
||||||
underlying source code file corresponds to that part of the outline. You can use
|
|
||||||
that line information with other tools, to strategically read portions of the source code.
|
|
||||||
|
|
||||||
For example, you can use this tool to find a relevant symbol in the project, then get the outline of the file which contains that symbol, then use the line number information from that file's outline to read different sections of that file, without having to read the entire file all at once (which can be slow, or use a lot of tokens).
|
|
||||||
|
|
||||||
<example>
|
|
||||||
# class Foo [L123-136]
|
|
||||||
## method do_something(arg1, arg2) [L124-126]
|
|
||||||
## method process_data(data) [L128-135]
|
|
||||||
# class Bar [L145-161]
|
|
||||||
## method initialize() [L146-149]
|
|
||||||
## method update_state(new_state) [L160]
|
|
||||||
## private method _validate_state(state) [L161-162]
|
|
||||||
</example>
|
|
||||||
|
|
||||||
This example shows how tree-sitter outlines the structure of source code:
|
|
||||||
|
|
||||||
1. `class Foo` is defined on lines 123-136
|
|
||||||
- It contains a method `do_something` spanning lines 124-126
|
|
||||||
- It also has a method `process_data` spanning lines 128-135
|
|
||||||
|
|
||||||
2. `class Bar` is defined on lines 145-161
|
|
||||||
- It has an `initialize` method spanning lines 146-149
|
|
||||||
- It has an `update_state` method on line 160
|
|
||||||
- It has a private method `_validate_state` spanning lines 161-162
|
|
||||||