Compare commits
646 Commits
project-en
...
example2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c4519c870 | ||
|
|
09db31288a | ||
|
|
a320d324f1 | ||
|
|
266c41ed9a | ||
|
|
4f4bbf264f | ||
|
|
990ca48744 | ||
|
|
f69aeb6311 | ||
|
|
d5f3fbdc88 | ||
|
|
76a78b550b | ||
|
|
e515b2c714 | ||
|
|
55ea481707 | ||
|
|
5e31d86f1f | ||
|
|
4a8f114528 | ||
|
|
ce1a674eba | ||
|
|
0d3fe474db | ||
|
|
6a009b447a | ||
|
|
75ab8ff9a1 | ||
|
|
3705986fac | ||
|
|
aefb3aa2fa | ||
|
|
8e7c145f20 | ||
|
|
a2a502f026 | ||
|
|
c231c95521 | ||
|
|
fcc6a86c90 | ||
|
|
338a6a3b7e | ||
|
|
a0eaede13d | ||
|
|
abf2b9d7d3 | ||
|
|
a50fbc9b5c | ||
|
|
9bbc2e0fb2 | ||
|
|
6caf34ab7e | ||
|
|
8607c7d3ee | ||
|
|
e26bb05567 | ||
|
|
b3b89c8443 | ||
|
|
962b024248 | ||
|
|
833653a3ea | ||
|
|
886f0b7214 | ||
|
|
207fb04969 | ||
|
|
36d02de784 | ||
|
|
36da97935a | ||
|
|
19b547565d | ||
|
|
109f1d43fc | ||
|
|
a5852d4537 | ||
|
|
10ded0ab75 | ||
|
|
b0b620af56 | ||
|
|
eca6d5a04e | ||
|
|
3357736aea | ||
|
|
458ffaa134 | ||
|
|
b14356d1d3 | ||
|
|
19ef56ba7c | ||
|
|
dfbd132d9f | ||
|
|
2e8ee9b64f | ||
|
|
c15382c4d8 | ||
|
|
70c51b513b | ||
|
|
38afae86a9 | ||
|
|
9249919b7a | ||
|
|
9fe4a14f73 | ||
|
|
7cc3c03b08 | ||
|
|
4f2f9ff762 | ||
|
|
7aa0fa1543 | ||
|
|
3b31860d52 | ||
|
|
733cd6b68c | ||
|
|
e8fe0eb2e6 | ||
|
|
0f3ac38332 | ||
|
|
32e9757a85 | ||
|
|
be76942a69 | ||
|
|
942d4eb126 | ||
|
|
9d35f0389d | ||
|
|
d13cd007a2 | ||
|
|
f8ac6eef75 | ||
|
|
6d2bdc3bac | ||
|
|
9a3434efb4 | ||
|
|
333de5d673 | ||
|
|
97ab0980d1 | ||
|
|
3a27e8c311 | ||
|
|
bfb2ed3824 | ||
|
|
9db0c4f19a | ||
|
|
a4f5c4fef2 | ||
|
|
4dcfe0cff9 | ||
|
|
4473b45c3d | ||
|
|
ceeae790b7 | ||
|
|
107d8ca483 | ||
|
|
4278d894d2 | ||
|
|
a91948aeb4 | ||
|
|
2178b36cbc | ||
|
|
0fb0059b5f | ||
|
|
fbf7caf93e | ||
|
|
d48152d958 | ||
|
|
bafc086d27 | ||
|
|
f737c4d01e | ||
|
|
8f308d835a | ||
|
|
703a68eedf | ||
|
|
cc2fcb2f42 | ||
|
|
f0ef3110d3 | ||
|
|
0454e7a22e | ||
|
|
d88b06a5dc | ||
|
|
98ceffe026 | ||
|
|
bab28560ef | ||
|
|
8102a16747 | ||
|
|
9875521d4e | ||
|
|
8c55063417 | ||
|
|
7abe2c9c31 | ||
|
|
73a767fc45 | ||
|
|
327fee4d22 | ||
|
|
b1d5918fdc | ||
|
|
6f685b9f8e | ||
|
|
65a7076ba8 | ||
|
|
3fe15ee915 | ||
|
|
bae3ef01c6 | ||
|
|
810b39ce34 | ||
|
|
a93aa598d6 | ||
|
|
28aba94369 | ||
|
|
e147ef006c | ||
|
|
e18d787dbc | ||
|
|
d02c8bcc02 | ||
|
|
e27f6a984f | ||
|
|
cce661b64b | ||
|
|
3932a6c51e | ||
|
|
1e0ae35f69 | ||
|
|
4405ed04d0 | ||
|
|
c585dbd8ff | ||
|
|
c7fc95e732 | ||
|
|
f97546b6ef | ||
|
|
7badd6053d | ||
|
|
502a0f6535 | ||
|
|
eea6cfb383 | ||
|
|
0dc0701967 | ||
|
|
62b8ef980b | ||
|
|
269f6403dd | ||
|
|
3538acec7c | ||
|
|
87512d0814 | ||
|
|
6254efe39d | ||
|
|
72218f4a61 | ||
|
|
5f7189e5af | ||
|
|
f6d13645ce | ||
|
|
6ffd3f034f | ||
|
|
6e0732a9d7 | ||
|
|
f8d097acd6 | ||
|
|
7cf4926130 | ||
|
|
676cc109a3 | ||
|
|
ba7f886c62 | ||
|
|
c2cd4fd7a1 | ||
|
|
4095011af5 | ||
|
|
80a2f71d8e | ||
|
|
d93141bded | ||
|
|
b402007de6 | ||
|
|
be63d51eb7 | ||
|
|
8660101b83 | ||
|
|
1aa1b2bede | ||
|
|
58d8b91131 | ||
|
|
ba588161d9 | ||
|
|
7e928dd615 | ||
|
|
fade49a11a | ||
|
|
e4f692ac75 | ||
|
|
c21bca07e2 | ||
|
|
acc4a5ccb3 | ||
|
|
7a95c14625 | ||
|
|
6dd622d6c3 | ||
|
|
e7afbbd725 | ||
|
|
133932ed74 | ||
|
|
3ca63584b9 | ||
|
|
2a878ee6d0 | ||
|
|
8117940aca | ||
|
|
002235d0da | ||
|
|
f07695c4cd | ||
|
|
bdd0cbb717 | ||
|
|
022a110f8e | ||
|
|
b0200c4368 | ||
|
|
ae47829fa8 | ||
|
|
5ebb18c47e | ||
|
|
ded1c7012c | ||
|
|
ad25cd09b6 | ||
|
|
a7a7335da4 | ||
|
|
cbb6c221b3 | ||
|
|
63b4b60b79 | ||
|
|
9ea8a9a1d3 | ||
|
|
19f542b8d6 | ||
|
|
70b3cb04bb | ||
|
|
0170f522bf | ||
|
|
602ae84542 | ||
|
|
3fef3cc392 | ||
|
|
78c856cb75 | ||
|
|
21946691a4 | ||
|
|
fcb1efdf21 | ||
|
|
54b46fdfaa | ||
|
|
94cf1b0353 | ||
|
|
56856fb992 | ||
|
|
64a67a1071 | ||
|
|
040046ed2a | ||
|
|
0286b8ab3e | ||
|
|
8de53bd89f | ||
|
|
10507f9a4c | ||
|
|
7bdde8f14f | ||
|
|
7c7f69f4c5 | ||
|
|
12c9526f6a | ||
|
|
97b044acf5 | ||
|
|
1e25e6b3cc | ||
|
|
f565994da9 | ||
|
|
db94d6d767 | ||
|
|
456e54b87c | ||
|
|
2b277123be | ||
|
|
bb0b2a5b7b | ||
|
|
5c2c6d7e5e | ||
|
|
4f58bdee28 | ||
|
|
486a9e4d61 | ||
|
|
0d8f77b5de | ||
|
|
cb79420773 | ||
|
|
c641209341 | ||
|
|
48a716fcb5 | ||
|
|
25956c49c1 | ||
|
|
4efabe17dd | ||
|
|
320abe9b22 | ||
|
|
9a9f2e71ca | ||
|
|
609895d95f | ||
|
|
da2d8bd845 | ||
|
|
6267a147ba | ||
|
|
aceecec6bf | ||
|
|
f3f2c6d811 | ||
|
|
41cffa64b0 | ||
|
|
b486e32f05 | ||
|
|
222d4a2546 | ||
|
|
1eb948654a | ||
|
|
35da1502e1 | ||
|
|
1d98b33ae0 | ||
|
|
4e8ecfc0c4 | ||
|
|
134a0563c2 | ||
|
|
3f4d4af080 | ||
|
|
68ec1d724c | ||
|
|
102ea6ac79 | ||
|
|
5d3718df2d | ||
|
|
f1f5d602fc | ||
|
|
60624d81ba | ||
|
|
91755b2db1 | ||
|
|
e34fee55a0 | ||
|
|
dad6067e18 | ||
|
|
5619a3e618 | ||
|
|
06ad45ce08 | ||
|
|
7e6387052f | ||
|
|
0182e09e33 | ||
|
|
6f6e207eb5 | ||
|
|
149cdeca29 | ||
|
|
92dc812aea | ||
|
|
c7e80c80c6 | ||
|
|
c381a500f8 | ||
|
|
ff4334efc7 | ||
|
|
b1e4e6048a | ||
|
|
d0f806456c | ||
|
|
b6cce1ed91 | ||
|
|
05fc9ee396 | ||
|
|
8f52bb92b6 | ||
|
|
144fd0b00d | ||
|
|
cd4a3fd679 | ||
|
|
42c3f4e7cf | ||
|
|
90dec1d451 | ||
|
|
afabcd1547 | ||
|
|
ccf9aef767 | ||
|
|
aef78dcffd | ||
|
|
6f0951ff77 | ||
|
|
5e094553fa | ||
|
|
32829d9f12 | ||
|
|
e4cf7fe8f5 | ||
|
|
2b89b97cd1 | ||
|
|
e26f0a331f | ||
|
|
7e1b419243 | ||
|
|
98d001bad5 | ||
|
|
d4a985a6e3 | ||
|
|
616d17f517 | ||
|
|
e1c42315dc | ||
|
|
cfc848d24b | ||
|
|
d4761cea47 | ||
|
|
b794919842 | ||
|
|
fc1252b0cd | ||
|
|
12b012eab3 | ||
|
|
77f32582e2 | ||
|
|
0d6e455bf6 | ||
|
|
5f897b0e00 | ||
|
|
d74f0735c2 | ||
|
|
a8b1ef3531 | ||
|
|
c8ccc472b5 | ||
|
|
26b9c32e96 | ||
|
|
db56254517 | ||
|
|
9d91908256 | ||
|
|
6b80eb556c | ||
|
|
2603f36737 | ||
|
|
6c93d107c2 | ||
|
|
5b6efa4c02 | ||
|
|
84aa480344 | ||
|
|
6db29eb90a | ||
|
|
ff41be30dc | ||
|
|
47b663a8df | ||
|
|
1d9915f88a | ||
|
|
584fa3db53 | ||
|
|
a051194195 | ||
|
|
78ecc3cef0 | ||
|
|
ac8a4ba5d4 | ||
|
|
9863b48dd7 | ||
|
|
fddaa31655 | ||
|
|
b45230784d | ||
|
|
4a57664c7f | ||
|
|
0eb0a3c7dc | ||
|
|
6278761460 | ||
|
|
f2ce183286 | ||
|
|
98891e4c70 | ||
|
|
5e57f148ac | ||
|
|
128779f615 | ||
|
|
b25c3334cc | ||
|
|
77544f42b1 | ||
|
|
b864a9b0ae | ||
|
|
e4844b281d | ||
|
|
d1ffda9bfe | ||
|
|
8ffa58414d | ||
|
|
fb78cbbd45 | ||
|
|
17719f9f87 | ||
|
|
055df30757 | ||
|
|
429d4580cf | ||
|
|
62ebae96e3 | ||
|
|
0036a33263 | ||
|
|
b22faf96e0 | ||
|
|
dafe994eef | ||
|
|
5994ac5cec | ||
|
|
97a9a5de10 | ||
|
|
730f2e7083 | ||
|
|
141ad72d97 | ||
|
|
a5fe6d1e61 | ||
|
|
5734ffbb18 | ||
|
|
932a7c6440 | ||
|
|
6a60bb189b | ||
|
|
5909d1258b | ||
|
|
78662f8fea | ||
|
|
c2e3134963 | ||
|
|
66b3e03baa | ||
|
|
7caa2c2ea0 | ||
|
|
08ce230bae | ||
|
|
1df01eabfe | ||
|
|
2f5c662c42 | ||
|
|
a03fb3791e | ||
|
|
dd7bc5f199 | ||
|
|
c7d3fbcac1 | ||
|
|
1164829cad | ||
|
|
e09eeb7446 | ||
|
|
cdcad708f6 | ||
|
|
bd4c9b45b6 | ||
|
|
d4736a5427 | ||
|
|
353ae2335b | ||
|
|
ad39d3226f | ||
|
|
c35238bd72 | ||
|
|
5757e352b0 | ||
|
|
c124838a73 | ||
|
|
5ebac7e30c | ||
|
|
c143846e42 | ||
|
|
71c2a11bd9 | ||
|
|
2440faf4b2 | ||
|
|
c0262cf62f | ||
|
|
fd256d159d | ||
|
|
a2a3d1a4bd | ||
|
|
294a1b63c0 | ||
|
|
ffdf725f32 | ||
|
|
8ee6a2b454 | ||
|
|
cf65d9437a | ||
|
|
66dd6726df | ||
|
|
44cb8e582b | ||
|
|
73305ce45e | ||
|
|
94b75f3ad9 | ||
|
|
384868e597 | ||
|
|
d88694f8da | ||
|
|
90f30b5c20 | ||
|
|
24d4f8ca18 | ||
|
|
804066a047 | ||
|
|
4a356466b1 | ||
|
|
0921762b59 | ||
|
|
46b1df2e2d | ||
|
|
986da332db | ||
|
|
dad33f7cc2 | ||
|
|
64241f7d2f | ||
|
|
fbbc23bec3 | ||
|
|
26f4705198 | ||
|
|
3abf95216c | ||
|
|
b0b52f299c | ||
|
|
53cde329da | ||
|
|
b55b310ad0 | ||
|
|
8ab25e2bac | ||
|
|
c10b1f7c61 | ||
|
|
cb1ee01a66 | ||
|
|
90bcde116f | ||
|
|
8ac378b86e | ||
|
|
55760295d9 | ||
|
|
9dfb907f97 | ||
|
|
e20daa7639 | ||
|
|
b46ab367ef | ||
|
|
12212dc329 | ||
|
|
324e4658ba | ||
|
|
ed500dacb6 | ||
|
|
2f4b48129b | ||
|
|
ed7c55a04e | ||
|
|
6db4ab381c | ||
|
|
0e72a7e6ce | ||
|
|
3dc3ab062d | ||
|
|
ed63f216e3 | ||
|
|
ba767a1998 | ||
|
|
23c3f5f410 | ||
|
|
b3be294c90 | ||
|
|
af5318df98 | ||
|
|
60c420a2da | ||
|
|
ee6c33ffb3 | ||
|
|
9ae4f4b158 | ||
|
|
915a1cb116 | ||
|
|
aead0e11ff | ||
|
|
2752c08810 | ||
|
|
780143298a | ||
|
|
088d7c1342 | ||
|
|
64de6bd2a8 | ||
|
|
6aa0248ab3 | ||
|
|
342134fbab | ||
|
|
b47aa33459 | ||
|
|
9f6c5e2877 | ||
|
|
7bf6cd4ccf | ||
|
|
c7963c8a93 | ||
|
|
dd4629433b | ||
|
|
2e56935997 | ||
|
|
e43a397f1d | ||
|
|
9d0fe164a7 | ||
|
|
6d7fef6fd3 | ||
|
|
b67d3fd21b | ||
|
|
1cb4f8288d | ||
|
|
3a8fe4d973 | ||
|
|
9d6d152918 | ||
|
|
31034f8296 | ||
|
|
c441b651fa | ||
|
|
61ddcd516f | ||
|
|
f12a554f86 | ||
|
|
9dae4d8c59 | ||
|
|
f0b7f355a2 | ||
|
|
b687a5e56d | ||
|
|
e66a24edcf | ||
|
|
301fc7cd7b | ||
|
|
020a1071d5 | ||
|
|
38d2487630 | ||
|
|
79c9f2bbd9 | ||
|
|
c8caae03df | ||
|
|
dabc4d8ff5 | ||
|
|
c0ad3e8183 | ||
|
|
afde25a5cb | ||
|
|
9f708ee789 | ||
|
|
58731e2fd1 | ||
|
|
d0632a5332 | ||
|
|
64cea2f1f1 | ||
|
|
ac958d4a2d | ||
|
|
2df06cd2e4 | ||
|
|
0d4ca71e68 | ||
|
|
e2d6505d12 | ||
|
|
f7c3c533a3 | ||
|
|
c05bf096f8 | ||
|
|
b15ee1b1cc | ||
|
|
0459b1d303 | ||
|
|
246013cfc2 | ||
|
|
47eaf274d6 | ||
|
|
ef4b5b0698 | ||
|
|
39c98ce882 | ||
|
|
763cc6dba3 | ||
|
|
0b75c13034 | ||
|
|
38ec45008c | ||
|
|
97641c3298 | ||
|
|
ca8f6e8a3f | ||
|
|
db53da49e1 | ||
|
|
df94dcdea6 | ||
|
|
1c85901440 | ||
|
|
9fb77ad176 | ||
|
|
feafad2f9d | ||
|
|
86ef00054b | ||
|
|
9e8afa8daa | ||
|
|
698cdc4d1a | ||
|
|
85c5d8af3a | ||
|
|
1774cad933 | ||
|
|
ee7b1ec7f2 | ||
|
|
14b43d573c | ||
|
|
d39e1e03b8 | ||
|
|
9e504a1ed9 | ||
|
|
ca4cc4764b | ||
|
|
ea33d78ae4 | ||
|
|
e36a2f2739 | ||
|
|
cbd9b4cc39 | ||
|
|
64f8b1e739 | ||
|
|
4f936d8100 | ||
|
|
97abf21a28 | ||
|
|
5fb1411e4d | ||
|
|
b04f7a4c7c | ||
|
|
61b7a05792 | ||
|
|
cc15598e09 | ||
|
|
7ee9109ade | ||
|
|
c21fdd212b | ||
|
|
a28929592e | ||
|
|
3b787e85a4 | ||
|
|
1264e7a200 | ||
|
|
bfe08e449f | ||
|
|
df3c7a73b5 | ||
|
|
0dc3dffe38 | ||
|
|
b306a0221b | ||
|
|
d385a60ed1 | ||
|
|
b27922129c | ||
|
|
6220b86f94 | ||
|
|
448db20eaa | ||
|
|
e4a6943c76 | ||
|
|
f03efeda73 | ||
|
|
862d0c07ca | ||
|
|
56ed5dcc89 | ||
|
|
b3f47dc5e0 | ||
|
|
fe1ae1860e | ||
|
|
c165729b3f | ||
|
|
22b937f27f | ||
|
|
fdaf2a27bf | ||
|
|
0414908c4a | ||
|
|
d3abc61728 | ||
|
|
e7a0f0e876 | ||
|
|
5996c58452 | ||
|
|
b6ee367ee0 | ||
|
|
aa026156f2 | ||
|
|
d5cc576b0c | ||
|
|
f3274851d9 | ||
|
|
500d8f2943 | ||
|
|
e3830d2ef5 | ||
|
|
4f9f443452 | ||
|
|
1556b446e7 | ||
|
|
5ca8a3e342 | ||
|
|
aeea3645ff | ||
|
|
a577a72f69 | ||
|
|
5a7222edc5 | ||
|
|
097aefeac4 | ||
|
|
99a9647b78 | ||
|
|
fa90b3a986 | ||
|
|
8049fc1038 | ||
|
|
85b811a783 | ||
|
|
d60dbbc791 | ||
|
|
656302ee4c | ||
|
|
956f359045 | ||
|
|
3b46fca64c | ||
|
|
d6d9c383cb | ||
|
|
8cfb9beb17 | ||
|
|
0708d476ca | ||
|
|
57669b4908 | ||
|
|
b1f7133a7b | ||
|
|
ac9e2f30bb | ||
|
|
a2fbe82c42 | ||
|
|
57d8c99473 | ||
|
|
41827372fe | ||
|
|
9949512b64 | ||
|
|
b9051e65d4 | ||
|
|
adbebb28dc | ||
|
|
caf0d6c5fa | ||
|
|
525755c28e | ||
|
|
ea0f5144c9 | ||
|
|
b78ac5410f | ||
|
|
2462b949bc | ||
|
|
ec7d28648a | ||
|
|
c1259c136e | ||
|
|
6ddad64af1 | ||
|
|
69d7ea7b60 | ||
|
|
d0e82b0538 | ||
|
|
10821aae2c | ||
|
|
03aadb4e5b | ||
|
|
8ab252c42d | ||
|
|
e74af03065 | ||
|
|
4bcd37a537 | ||
|
|
e3d212ac60 | ||
|
|
8b077f0c41 | ||
|
|
288da0f072 | ||
|
|
b8d05bb641 | ||
|
|
02e4267bc6 | ||
|
|
c2afc2271b | ||
|
|
7bc62de267 | ||
|
|
f3adf41c25 | ||
|
|
6162d9942d | ||
|
|
156dd32a35 | ||
|
|
2747915569 | ||
|
|
75b9a3b6a8 | ||
|
|
9bd3dbcf28 | ||
|
|
435fff94bd | ||
|
|
558d61b907 | ||
|
|
02a8ece074 | ||
|
|
1a899fda60 | ||
|
|
183f57f318 | ||
|
|
cc9cc12f7b | ||
|
|
1bc5618f61 | ||
|
|
ef8fe52877 | ||
|
|
982196343f | ||
|
|
cfe5620a2a | ||
|
|
5fe86f7e70 | ||
|
|
c94b587e1a | ||
|
|
43cb925a59 | ||
|
|
cf0d1e4229 | ||
|
|
2f5a4f7e80 | ||
|
|
80441f675b | ||
|
|
393d6560a3 | ||
|
|
3d48efad67 | ||
|
|
277a3f8d6f | ||
|
|
5e286897d3 | ||
|
|
9e38c45a9b | ||
|
|
1db3d92066 | ||
|
|
a7674d3edc | ||
|
|
ee4b6a8db4 | ||
|
|
c04c5812b6 | ||
|
|
cba96b5a38 | ||
|
|
8b5ea05163 | ||
|
|
ec40e2d85c | ||
|
|
819bb8fffb | ||
|
|
c6e2d20a02 | ||
|
|
7492ec3f67 | ||
|
|
4d8df0a00b | ||
|
|
3f71ae9897 | ||
|
|
2086f7d85b | ||
|
|
315f1bf168 | ||
|
|
0c82541f0a | ||
|
|
b9724d9cbe | ||
|
|
e5b347b03a | ||
|
|
e123c4bced | ||
|
|
ed3722023e | ||
|
|
ece4a1cd7c | ||
|
|
9986a21970 | ||
|
|
c674e8d62d | ||
|
|
e5e3e9ac8c | ||
|
|
399d19231b | ||
|
|
c98bcc72b8 | ||
|
|
fe27d11f08 | ||
|
|
9693eab098 | ||
|
|
e2aaf9b704 | ||
|
|
9abfbdff43 | ||
|
|
cd85b430e4 | ||
|
|
d3e4de7c72 | ||
|
|
ee950f5bc4 | ||
|
|
501b539286 | ||
|
|
444b7b8acb | ||
|
|
8a6ed4a2ca | ||
|
|
b4af5b2ce0 | ||
|
|
ee33d313e2 | ||
|
|
0a132779a1 | ||
|
|
d23c2d4b02 | ||
|
|
b9f10c0adb | ||
|
|
9f9746872e | ||
|
|
01ec6e0f77 | ||
|
|
07a77792c5 | ||
|
|
108ae0b5b0 | ||
|
|
500964a6fa | ||
|
|
0a58e54477 | ||
|
|
8539e23018 | ||
|
|
c7d27753ee | ||
|
|
b7b7f1ccdd | ||
|
|
142f9917d0 | ||
|
|
f8092bf0d2 | ||
|
|
0ba8432b0b |
1
.clinerules
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
.rules
|
||||||
43
.config/hakari.toml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# This file contains settings for `cargo hakari`.
|
||||||
|
# See https://docs.rs/cargo-hakari/latest/cargo_hakari/config for a full list of options.
|
||||||
|
|
||||||
|
hakari-package = "workspace-hack"
|
||||||
|
|
||||||
|
resolver = "2"
|
||||||
|
dep-format-version = "4"
|
||||||
|
workspace-hack-line-style = "workspace-dotted"
|
||||||
|
|
||||||
|
# this should be the same list as "targets" in ../rust-toolchain.toml
|
||||||
|
platforms = [
|
||||||
|
"x86_64-apple-darwin",
|
||||||
|
"aarch64-apple-darwin",
|
||||||
|
"x86_64-unknown-linux-gnu",
|
||||||
|
"aarch64-unknown-linux-gnu",
|
||||||
|
"x86_64-pc-windows-msvc",
|
||||||
|
"x86_64-unknown-linux-musl", # remote server
|
||||||
|
]
|
||||||
|
|
||||||
|
[traversal-excludes]
|
||||||
|
workspace-members = [
|
||||||
|
"remote_server",
|
||||||
|
]
|
||||||
|
third-party = [
|
||||||
|
{ name = "reqwest", version = "0.11.27" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[final-excludes]
|
||||||
|
workspace-members = [
|
||||||
|
"zed_extension_api",
|
||||||
|
|
||||||
|
# exclude all extensions
|
||||||
|
"zed_emmet",
|
||||||
|
"zed_glsl",
|
||||||
|
"zed_html",
|
||||||
|
"perplexity",
|
||||||
|
"zed_proto",
|
||||||
|
"zed_ruff",
|
||||||
|
"slash_commands_example",
|
||||||
|
"zed_snippets",
|
||||||
|
"zed_test_extension",
|
||||||
|
"zed_toml",
|
||||||
|
]
|
||||||
1
.cursorrules
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
.rules
|
||||||
36
.github/ISSUE_TEMPLATE/01_bug_agent.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Bug Report (Agent Panel)
|
||||||
|
description: Zed Agent Panel Bugs
|
||||||
|
type: "Bug"
|
||||||
|
labels: ["agent", "ai"]
|
||||||
|
title: "Agent Panel: <a short description of the Agent Panel bug>"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||||
|
value: |
|
||||||
|
<!-- Please insert a one line summary of the issue below -->
|
||||||
|
SUMMARY_SENTENCE_HERE
|
||||||
|
|
||||||
|
### Description
|
||||||
|
<!-- 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:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
Actual Behavior:
|
||||||
|
Expected Behavior:
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Zed Version and System Specs
|
||||||
|
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||||
|
placeholder: |
|
||||||
|
Output of "zed: Copy System Specs Into Clipboard"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
36
.github/ISSUE_TEMPLATE/02_bug_edit_predictions.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Bug Report (Edit Predictions)
|
||||||
|
description: Zed Edit Predictions bugs
|
||||||
|
type: "Bug"
|
||||||
|
labels: ["ai", "inline completion", "zeta"]
|
||||||
|
title: "Edit Predictions: <a short description of the Edit Prediction bug>"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||||
|
value: |
|
||||||
|
<!-- Please insert a one line summary of the issue below -->
|
||||||
|
SUMMARY_SENTENCE_HERE
|
||||||
|
|
||||||
|
### Description
|
||||||
|
<!-- 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:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
Actual Behavior:
|
||||||
|
Expected Behavior:
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Zed Version and System Specs
|
||||||
|
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||||
|
placeholder: |
|
||||||
|
Output of "zed: Copy System Specs Into Clipboard"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Bug Report (Git)
|
||||||
|
description: Zed Git-Related Bugs
|
||||||
|
type: "Bug"
|
||||||
|
labels: ["git"]
|
||||||
|
title: "Git: <a short description of the Git bug>"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||||
|
value: |
|
||||||
|
<!-- Please insert a one line summary of the issue below -->
|
||||||
|
SUMMARY_SENTENCE_HERE
|
||||||
|
|
||||||
|
### Description
|
||||||
|
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||||
|
Steps to trigger the problem:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
Actual Behavior:
|
||||||
|
Expected Behavior:
|
||||||
|
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Zed Version and System Specs
|
||||||
|
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||||
|
placeholder: |
|
||||||
|
Output of "zed: Copy System Specs Into Clipboard"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
56
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: Bug Report (Other)
|
||||||
|
description: |
|
||||||
|
Something else is broken in Zed (exclude crashing).
|
||||||
|
type: "Bug"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: Provide a one sentence summary and detailed reproduction steps
|
||||||
|
value: |
|
||||||
|
<!-- Begin your issue with a one sentence summary -->
|
||||||
|
SUMMARY_SENTENCE_HERE
|
||||||
|
|
||||||
|
### Description
|
||||||
|
<!-- Describe with sufficient detail to reproduce from a clean Zed install.
|
||||||
|
- Any code must be sufficient to reproduce (include context!)
|
||||||
|
- Code must as text, not just as a screenshot.
|
||||||
|
- Issues with insufficient detail may be summarily closed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
Steps to reproduce:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
Expected Behavior:
|
||||||
|
Actual Behavior:
|
||||||
|
|
||||||
|
<!-- Before Submitting, did you:
|
||||||
|
1. Include settings.json, keymap.json, .editorconfig if relevant?
|
||||||
|
2. Check your Zed.log for relevant errors? (please include!)
|
||||||
|
3. Click Preview to ensure everything looks right?
|
||||||
|
4. Hide videos, large images and logs in ``` inside collapsible blocks:
|
||||||
|
|
||||||
|
<details><summary>click to expand</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
-->
|
||||||
|
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Zed Version and System Specs
|
||||||
|
description: |
|
||||||
|
Open Zed, from the command palette select "zed: Copy System Specs Into Clipboard"
|
||||||
|
placeholder: |
|
||||||
|
Output of "zed: Copy System Specs Into Clipboard"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -5,10 +5,12 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Summary
|
label: Summary
|
||||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
description: Summarize the issue with detailed reproduction steps
|
||||||
value: |
|
value: |
|
||||||
<!-- Please insert a one line summary of the issue below -->
|
<!-- Begin your issue with a one sentence summary -->
|
||||||
|
SUMMARY_SENTENCE_HERE
|
||||||
|
|
||||||
|
### Description
|
||||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||||
Steps to trigger the problem:
|
Steps to trigger the problem:
|
||||||
1.
|
1.
|
||||||
@@ -16,7 +18,6 @@ body:
|
|||||||
3.
|
3.
|
||||||
|
|
||||||
Actual Behavior:
|
Actual Behavior:
|
||||||
|
|
||||||
Expected Behavior:
|
Expected Behavior:
|
||||||
|
|
||||||
validations:
|
validations:
|
||||||
@@ -40,10 +41,11 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
<details><summary>Zed.log</summary>
|
<details><summary>Zed.log</summary>
|
||||||
|
|
||||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
<!-- Paste your log inside the code block. -->
|
||||||
```
|
```log
|
||||||
|
|
||||||
```
|
```
|
||||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
|
||||||
|
</details>
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
57
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -1,57 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: |
|
|
||||||
Something is broken in Zed (exclude crashing).
|
|
||||||
type: "Bug"
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Summary
|
|
||||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
|
||||||
value: |
|
|
||||||
<!-- Please insert a one line summary of the issue below -->
|
|
||||||
|
|
||||||
SUMMARY_SENTENCE_HERE
|
|
||||||
|
|
||||||
<!-- Be verbose: Include all steps necessary to reproduce from a clean Zed installation. -->
|
|
||||||
<!-- Code snippets are better than images, a repository link that reproduces the issue is ideal. -->
|
|
||||||
|
|
||||||
Steps to trigger the problem:
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
4.
|
|
||||||
|
|
||||||
Actual Behavior:
|
|
||||||
|
|
||||||
Expected Behavior:
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Is there anything additional necessary to reproduce this issue?
|
|
||||||
- settings.json, keymap.json, .editorconfig etc?
|
|
||||||
- Does it happen intermittently or only with specific projects / file types?
|
|
||||||
- Have you found a workaround?
|
|
||||||
|
|
||||||
Did you check your Zed.log to see if there is any relevant details there?
|
|
||||||
- When including large items (videos, screenshots, logs, configs) please wrap with:
|
|
||||||
|
|
||||||
<details><summary>See inside for XXXXYYY</summary>
|
|
||||||
|
|
||||||
```shell
|
|
||||||
code
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
-->
|
|
||||||
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: environment
|
|
||||||
attributes:
|
|
||||||
label: Zed Version and System Specs
|
|
||||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
|
||||||
placeholder: |
|
|
||||||
Output of "zed: Copy System Specs Into Clipboard"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
19
.github/ISSUE_TEMPLATE/99_other.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Other [Staff Only]
|
||||||
|
description: Zed Staff Only
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
value: |
|
||||||
|
<!-- Please insert a one line summary of the issue below -->
|
||||||
|
SUMMARY_SENTENCE_HERE
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
IF YOU DO NOT WORK FOR ZED INDUSTRIES DO NOT CREATE ISSUES WITH THIS TEMPLATE.
|
||||||
|
THEY WILL BE AUTO-CLOSED AND MAY RESULT IN YOU BEING BANNED FROM THE ZED ISSUE TRACKER.
|
||||||
|
|
||||||
|
FEATURE REQUESTS / SUPPORT REQUESTS SHOULD BE OPENED AS DISCUSSIONS:
|
||||||
|
https://github.com/zed-industries/zed/discussions/new/choose
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -4,9 +4,6 @@ contact_links:
|
|||||||
- name: Feature Request
|
- name: Feature Request
|
||||||
url: https://github.com/zed-industries/zed/discussions/new/choose
|
url: https://github.com/zed-industries/zed/discussions/new/choose
|
||||||
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
|
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
|
||||||
- name: Zed Discussion Forum
|
- name: "Zed Discord"
|
||||||
url: https://github.com/zed-industries/zed/discussions
|
|
||||||
about: A community discussion forum
|
|
||||||
- name: "Zed Discord: #Support Channel"
|
|
||||||
url: https://zed.dev/community-links
|
url: https://zed.dev/community-links
|
||||||
about: Real-time discussion and user support
|
about: Real-time discussion and user support
|
||||||
|
|||||||
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
|||||||
cargo install cargo-nextest --locked
|
cargo install cargo-nextest --locked
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
4
.github/actions/run_tests_windows/action.yml
vendored
@@ -16,11 +16,11 @@ runs:
|
|||||||
run: cargo install cargo-nextest --locked
|
run: cargo install cargo-nextest --locked
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
working-directory: ${{ inputs.working-directory }}
|
working-directory: ${{ inputs.working-directory }}
|
||||||
run: cargo nextest run --workspace --no-fail-fast
|
run: cargo nextest run --workspace --no-fail-fast --config='profile.dev.debug="limited"'
|
||||||
|
|||||||
121
.github/workflows/ci.yml
vendored
@@ -110,6 +110,39 @@ jobs:
|
|||||||
input: "crates/proto/proto/"
|
input: "crates/proto/proto/"
|
||||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
|
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
|
||||||
|
|
||||||
|
workspace_hack:
|
||||||
|
timeout-minutes: 60
|
||||||
|
name: Check workspace-hack crate
|
||||||
|
needs: [job_spec]
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
|
runs-on:
|
||||||
|
- buildjet-8vcpu-ubuntu-2204
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
- name: Add Rust to the PATH
|
||||||
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
- name: Install cargo-hakari
|
||||||
|
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cargo-hakari@0.9.35
|
||||||
|
|
||||||
|
- name: Check workspace-hack Cargo.toml is up-to-date
|
||||||
|
run: |
|
||||||
|
cargo hakari generate --diff || {
|
||||||
|
echo "To fix, run script/update-workspace-hack or script/update-workspace-hack.ps1";
|
||||||
|
false
|
||||||
|
}
|
||||||
|
- name: Check all crates depend on workspace-hack
|
||||||
|
run: |
|
||||||
|
cargo hakari manage-deps --dry-run || {
|
||||||
|
echo "To fix, run script/update-workspace-hack or script/update-workspace-hack.ps1"
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
style:
|
style:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Check formatting and spelling
|
name: Check formatting and spelling
|
||||||
@@ -192,7 +225,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for new vulnerable dependencies
|
- name: Check for new vulnerable dependencies
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
|
uses: actions/dependency-review-action@67d4f4bd7a9b17a0db54d2a7519187c65e339de8 # v4
|
||||||
with:
|
with:
|
||||||
license-check: false
|
license-check: false
|
||||||
|
|
||||||
@@ -432,6 +465,8 @@ jobs:
|
|||||||
- job_spec
|
- job_spec
|
||||||
- style
|
- style
|
||||||
- migration_checks
|
- migration_checks
|
||||||
|
# run_tests: If adding required tests, add them here and to script below.
|
||||||
|
- workspace_hack
|
||||||
- linux_tests
|
- linux_tests
|
||||||
- build_remote_server
|
- build_remote_server
|
||||||
- macos_tests
|
- macos_tests
|
||||||
@@ -448,11 +483,14 @@ jobs:
|
|||||||
|
|
||||||
# Only check test jobs if they were supposed to run
|
# Only check test jobs if they were supposed to run
|
||||||
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
||||||
|
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
|
||||||
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
|
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
|
||||||
[[ "${{ needs.linux_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
|
[[ "${{ needs.linux_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
|
||||||
[[ "${{ needs.windows_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
|
[[ "${{ needs.windows_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
|
||||||
[[ "${{ needs.windows_clippy.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows clippy failed"; }
|
[[ "${{ needs.windows_clippy.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows clippy failed"; }
|
||||||
[[ "${{ needs.build_remote_server.result }}" != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
|
[[ "${{ needs.build_remote_server.result }}" != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
|
||||||
|
# This check is intentionally disabled. See: https://github.com/zed-industries/zed/pull/28431
|
||||||
|
# [[ "${{ needs.migration_checks.result }}" != 'success' ]] && { RET_CODE=1; echo "Migration Checks failed"; }
|
||||||
fi
|
fi
|
||||||
if [[ "$RET_CODE" -eq 0 ]]; then
|
if [[ "$RET_CODE" -eq 0 ]]; then
|
||||||
echo "All tests passed successfully!"
|
echo "All tests passed successfully!"
|
||||||
@@ -481,7 +519,7 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
@@ -556,7 +594,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Linux x86_x64 release bundle
|
name: Linux x86_x64 release bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2004
|
- buildjet-16vcpu-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||||
if: |
|
if: |
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
@@ -584,26 +622,23 @@ jobs:
|
|||||||
- name: Create Linux .tar.gz bundle
|
- name: Create Linux .tar.gz bundle
|
||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
- name: Upload Artifact to Workflow - zed (run-bundling)
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: |
|
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
github.ref == 'refs/heads/main'
|
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|
||||||
with:
|
with:
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||||
path: target/release/zed-*.tar.gz
|
path: target/release/zed-*.tar.gz
|
||||||
|
|
||||||
- name: Upload Linux remote server to workflow run if main branch or specific label
|
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: |
|
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
github.ref == 'refs/heads/main'
|
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|
||||||
with:
|
with:
|
||||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
|
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
|
||||||
path: target/zed-remote-server-linux-x86_64.gz
|
path: target/zed-remote-server-linux-x86_64.gz
|
||||||
|
|
||||||
- name: Upload app bundle to release
|
- name: Upload Artifacts to release
|
||||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||||
|
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||||
@@ -642,29 +677,26 @@ jobs:
|
|||||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||||
script/determine-release-channel
|
script/determine-release-channel
|
||||||
|
|
||||||
- name: Create and upload Linux .tar.gz bundle
|
- name: Create and upload Linux .tar.gz bundles
|
||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
- name: Upload Artifact to Workflow - zed (run-bundling)
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: |
|
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
github.ref == 'refs/heads/main'
|
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|
||||||
with:
|
with:
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||||
path: target/release/zed-*.tar.gz
|
path: target/release/zed-*.tar.gz
|
||||||
|
|
||||||
- name: Upload Linux remote server to workflow run if main branch or specific label
|
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: |
|
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
github.ref == 'refs/heads/main'
|
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|
||||||
with:
|
with:
|
||||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
|
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
|
||||||
path: target/zed-remote-server-linux-aarch64.gz
|
path: target/zed-remote-server-linux-aarch64.gz
|
||||||
|
|
||||||
- name: Upload app bundle to release
|
- name: Upload Artifacts to release
|
||||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||||
|
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||||
@@ -674,6 +706,51 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
nix-build:
|
||||||
|
timeout-minutes: 60
|
||||||
|
name: Nix Build
|
||||||
|
continue-on-error: true
|
||||||
|
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||||
|
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
|
||||||
|
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
|
||||||
if: |
|
if: |
|
||||||
|
|||||||
2
.github/workflows/danger.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
8
.github/workflows/deploy_collab.yml
vendored
@@ -117,12 +117,10 @@ jobs:
|
|||||||
export ZED_KUBE_NAMESPACE=production
|
export ZED_KUBE_NAMESPACE=production
|
||||||
export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
|
export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
|
||||||
export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
|
export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
|
||||||
export ZED_LLM_LOAD_BALANCER_SIZE_UNIT=2
|
|
||||||
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
|
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
|
||||||
export ZED_KUBE_NAMESPACE=staging
|
export ZED_KUBE_NAMESPACE=staging
|
||||||
export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
|
export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
|
||||||
export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
|
export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
|
||||||
export ZED_LLM_LOAD_BALANCER_SIZE_UNIT=1
|
|
||||||
else
|
else
|
||||||
echo "cowardly refusing to deploy from an unknown branch"
|
echo "cowardly refusing to deploy from an unknown branch"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -147,9 +145,3 @@ jobs:
|
|||||||
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
|
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
|
||||||
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
|
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
|
||||||
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
|
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
|
||||||
|
|
||||||
export ZED_SERVICE_NAME=llm
|
|
||||||
export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_LLM_LOAD_BALANCER_SIZE_UNIT
|
|
||||||
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
|
|
||||||
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
|
|
||||||
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
|
|
||||||
|
|||||||
71
.github/workflows/eval.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Run Agent Eval
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
types: [opened, synchronize, reopened, labeled]
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
# Allow only one workflow per any non-`main` branch.
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
|
ZED_EVAL_TELEMETRY: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run_eval:
|
||||||
|
timeout-minutes: 60
|
||||||
|
name: Run Agent Eval
|
||||||
|
if: >
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
|
||||||
|
runs-on:
|
||||||
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
|
steps:
|
||||||
|
- name: Add Rust to the PATH
|
||||||
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
with:
|
||||||
|
clean: false
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
|
with:
|
||||||
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-provider: "buildjet"
|
||||||
|
|
||||||
|
- name: Install Linux dependencies
|
||||||
|
run: ./script/linux
|
||||||
|
|
||||||
|
- name: Configure CI
|
||||||
|
run: |
|
||||||
|
mkdir -p ./../.cargo
|
||||||
|
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||||
|
|
||||||
|
- name: Compile eval
|
||||||
|
run: cargo build --package=eval
|
||||||
|
|
||||||
|
- name: Run eval
|
||||||
|
run: cargo run --package=eval -- --repetitions=3 --concurrency=1
|
||||||
|
|
||||||
|
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
|
||||||
|
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
|
||||||
|
# to clean up the config file, I’ve included the cleanup code here as a precaution.
|
||||||
|
# While it’s not strictly necessary at this moment, I believe it’s better to err on the side of caution.
|
||||||
|
- name: Clean CI config file
|
||||||
|
if: always()
|
||||||
|
run: rm -rf ./../.cargo
|
||||||
2
.github/workflows/issue_response.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
2
.github/workflows/randomized_tests.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
7
.github/workflows/release_nightly.yml
vendored
@@ -71,7 +71,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ jobs:
|
|||||||
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
||||||
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
|
||||||
if: ${{ matrix.system.install_nix }}
|
if: ${{ matrix.system.install_nix }}
|
||||||
with:
|
with:
|
||||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -216,7 +216,8 @@ jobs:
|
|||||||
name: zed-industries
|
name: zed-industries
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
- run: nix build
|
- run: nix build
|
||||||
- run: nix-collect-garbage -d
|
- name: Limit /nix/store to 50GB
|
||||||
|
run: '[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d'
|
||||||
|
|
||||||
update-nightly-tag:
|
update-nightly-tag:
|
||||||
name: Update nightly tag
|
name: Update nightly tag
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -18,7 +18,6 @@
|
|||||||
.venv
|
.venv
|
||||||
.vscode
|
.vscode
|
||||||
.wrangler
|
.wrangler
|
||||||
/.direnv
|
|
||||||
/assets/*licenses.*
|
/assets/*licenses.*
|
||||||
/crates/collab/seed.json
|
/crates/collab/seed.json
|
||||||
/crates/theme/schemas/theme.json
|
/crates/theme/schemas/theme.json
|
||||||
|
|||||||
121
.rules
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# Rust coding guidelines
|
||||||
|
|
||||||
|
* Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
|
||||||
|
* Do not write organizational or comments that summarize the code. Comments should only be written in order to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
|
||||||
|
* Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
|
||||||
|
* Avoid using functions that panic like `unwrap()`, instead use mechanisms like `?` to propagate errors.
|
||||||
|
* Be careful with operations like indexing which may panic if the indexes are out of bounds.
|
||||||
|
* Never create files with `mod.rs` paths - prefer `src/some_module.rs` instead of `src/some_module/mod.rs`.
|
||||||
|
|
||||||
|
# GPUI
|
||||||
|
|
||||||
|
GPUI is a UI framework which also provides primitives for state and concurrency management.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Context types allow interaction with global state, windows, entities, and system services. They are typically passed to functions as the argument named `cx`. When a function takes callbacks they come after the `cx` parameter.
|
||||||
|
|
||||||
|
* `App` is the root context type, providing access to global state and read and update of entities.
|
||||||
|
* `Context<T>` is provided when updating an `Entity<T>`. This context dereferences into `App`, so functions which take `&App` can also take `&Context<T>`.
|
||||||
|
* `AsyncApp` and `AsyncWindowContext` are provided by `cx.spawn` and `cx.spawn_in`. These can be held across await points.
|
||||||
|
|
||||||
|
## `Window`
|
||||||
|
|
||||||
|
`Window` provides access to the state of an application window. It is passed to functions as an argument named `window` and comes before `cx` when present. It is used for managing focus, dispatching actions, directly drawing, getting user input state, etc.
|
||||||
|
|
||||||
|
## Entities
|
||||||
|
|
||||||
|
An `Entity<T>` is a handle to state of type `T`. With `thing: Entity<T>`:
|
||||||
|
|
||||||
|
* `thing.entity_id()` returns `EntityId`
|
||||||
|
* `thing.downgrade()` returns `WeakEntity<T>`
|
||||||
|
* `thing.read(cx: &App)` returns `&T`.
|
||||||
|
* `thing.read_with(cx, |thing: &T, cx: &App| ...)` returns the closure's return value.
|
||||||
|
* `thing.update(cx, |thing: &mut T, cx: &mut Context<T>| ...)` allows the closure to mutate the state, and provides a `Context<T>` for interacting with the entity. It returns the closure's return value.
|
||||||
|
* `thing.update_in(cx, |thing: &mut T, window: &mut Window, cx: &mut Context<T>| ...)` takes a `AsyncWindowContext` or `VisualTestContext`. It's the same as `update` while also providing the `Window`.
|
||||||
|
|
||||||
|
Within the closures, the inner `cx` provided to the closure must be used instead of the outer `cx` to avoid issues with multiple borrows.
|
||||||
|
|
||||||
|
Trying to update an entity while it's already being updated must be avoided as this will cause a panic.
|
||||||
|
|
||||||
|
When `read_with`, `update`, or `update_in` are used with an async context, the closure's return value is wrapped in an `anyhow::Result`.
|
||||||
|
|
||||||
|
`WeakEntity<T>` is a weak handle. It has `read_with`, `update`, and `update_in` methods that work the same, but always return an `anyhow::Result` so that they can fail if the entity no longer exists. This can be useful to avoid memory leaks - if entities have mutually recursive handles to eachother they will never be dropped.
|
||||||
|
|
||||||
|
## Concurrency
|
||||||
|
|
||||||
|
All use of entities and UI rendering occurs on a single foreground thread.
|
||||||
|
|
||||||
|
`cx.spawn(async move |cx| ...)` runs an async closure on the foreground thread. Within the closure, `cx` is an async context like `AsyncApp` or `AsyncWindowContext`.
|
||||||
|
|
||||||
|
When the outer cx is a `Context<T>`, the use of `spawn` instead looks like `cx.spawn(async move |handle, cx| ...)`, where `handle: WeakEntity<T>`.
|
||||||
|
|
||||||
|
To do work on other threads, `cx.background_spawn(async move { ... })` is used. Often this background task is awaited on by a foreground task which uses the results to update state.
|
||||||
|
|
||||||
|
Both `cx.spawn` and `cx.background_spawn` return a `Task<R>`, which is a future that can be awaited upon. If this task is dropped, then its work is cancelled. To prevent this one of the following must be done:
|
||||||
|
|
||||||
|
* Awaiting the task in some other async context.
|
||||||
|
* Detaching the task via `task.detach()` or `task.detach_and_log_err(cx)`, allowing it to run indefinitely.
|
||||||
|
* Storing the task in a field, if the work should be halted when the struct is dropped.
|
||||||
|
|
||||||
|
A task which doesn't do anything but provide a value can be created with `Task::ready(value)`.
|
||||||
|
|
||||||
|
## Elements
|
||||||
|
|
||||||
|
The `Render` trait is used to render some state into an element tree that is laid out using flexbox layout. An `Entity<T>` where `T` implements `Render` is sometimes called a "view".
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
struct TextWithBorder(SharedString);
|
||||||
|
|
||||||
|
impl Render for TextWithBorder {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div().border_1().child(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since `impl IntoElement for SharedString` exists, it can be used as an argument to `child`. `SharedString` is used to avoid copying strings, and is either an `&'static str` or `Arc<str>`.
|
||||||
|
|
||||||
|
UI components that are constructed just to be turned into elements can instead implement the `RenderOnce` trait, which is similar to `Render`, but its `render` method takes ownership of `self`. Types that implement this trait can use `#[derive(IntoElement)]` to use them directly as children.
|
||||||
|
|
||||||
|
The style methods on elements are similar to those used by Tailwind CSS.
|
||||||
|
|
||||||
|
If some attributes or children of an element tree are conditional, `.when(condition, |this| ...)` can be used to run the closure only when `condition` is true. Similarly, `.when_some(option, |this, value| ...)` runs the closure when the `Option` has a value.
|
||||||
|
|
||||||
|
## Input events
|
||||||
|
|
||||||
|
Input event handlers can be registered on an element via methods like `.on_click(|event, window, cx: &mut App| ...)`.
|
||||||
|
|
||||||
|
Often event handlers will want to update the entity that's in the current `Context<T>`. The `cx.listener` method provides this - its use looks like `.on_click(cx.listener(|this: &mut T, event, window, cx: &mut Context<T>| ...)`.
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
Actions are dispatched via user keyboard interaction or in code via `window.dispatch_action(SomeAction.boxed_clone(), cx)` or `focus_handle.dispatch_action(&SomeAction, window, cx)`.
|
||||||
|
|
||||||
|
Actions which have no data inside are created and registered with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call.
|
||||||
|
|
||||||
|
Actions that do have data must implement `Clone, Default, PartialEq, Deserialize, JsonSchema` and can be registered with an `impl_actions!(some_namespace, [SomeActionWithData])` macro call.
|
||||||
|
|
||||||
|
Action handlers can be registered on an element via the event handler `.on_action(|action, window, cx| ...)`. Like other event handlers, this is often used with `cx.listener`.
|
||||||
|
|
||||||
|
## Notify
|
||||||
|
|
||||||
|
When a view's state has changed in a way that may affect its rendering, it should call `cx.notify()`. This will cause the view to be rerendered. It will also cause any observe callbacks registered for the entity with `cx.observe` to be called.
|
||||||
|
|
||||||
|
## Entity events
|
||||||
|
|
||||||
|
While updating an entity (`cx: Context<T>`), it can emit an event using `cx.emit(event)`. Entities register which events they can emit by declaring `impl EventEmittor<EventType> for EntityType {}`.
|
||||||
|
|
||||||
|
Other entities can then register a callback to handle these events by doing `cx.subscribe(other_entity, |this, other_entity, event, cx| ...)`. This will return a `Subscription` which deregisters the callback when dropped. Typically `cx.subscribe` happens when creating a new entity and the subscriptions are stored in a `_subscriptions: Vec<Subscription>` field.
|
||||||
|
|
||||||
|
## Recent API changes
|
||||||
|
|
||||||
|
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| ...)`.
|
||||||
|
* Use `Entity<T>`. This replaces `Model<T>` and `View<T>` which 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.
|
||||||
|
* `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.
|
||||||
1
.windsurfrules
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
.rules
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": "Debug Zed with LLDB",
|
"label": "Debug Zed (CodeLLDB)",
|
||||||
"adapter": "lldb",
|
"adapter": "CodeLLDB",
|
||||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Debug Zed with GDB",
|
"label": "Debug Zed (GDB)",
|
||||||
"adapter": "gdb",
|
"adapter": "GDB",
|
||||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
|
|||||||
@@ -45,5 +45,6 @@
|
|||||||
"hard_tabs": false,
|
"hard_tabs": false,
|
||||||
"formatter": "auto",
|
"formatter": "auto",
|
||||||
"remove_trailing_whitespace_on_save": true,
|
"remove_trailing_whitespace_on_save": true,
|
||||||
"ensure_final_newline_on_save": true
|
"ensure_final_newline_on_save": true,
|
||||||
|
"file_scan_exclusions": ["crates/eval/worktrees/", "crates/eval/repos/"]
|
||||||
}
|
}
|
||||||
|
|||||||
2625
Cargo.lock
generated
85
Cargo.toml
@@ -8,7 +8,6 @@ members = [
|
|||||||
"crates/assets",
|
"crates/assets",
|
||||||
"crates/assistant",
|
"crates/assistant",
|
||||||
"crates/assistant_context_editor",
|
"crates/assistant_context_editor",
|
||||||
"crates/assistant_eval",
|
|
||||||
"crates/assistant_settings",
|
"crates/assistant_settings",
|
||||||
"crates/assistant_slash_command",
|
"crates/assistant_slash_command",
|
||||||
"crates/assistant_slash_commands",
|
"crates/assistant_slash_commands",
|
||||||
@@ -16,6 +15,7 @@ members = [
|
|||||||
"crates/assistant_tools",
|
"crates/assistant_tools",
|
||||||
"crates/audio",
|
"crates/audio",
|
||||||
"crates/auto_update",
|
"crates/auto_update",
|
||||||
|
"crates/auto_update_helper",
|
||||||
"crates/auto_update_ui",
|
"crates/auto_update_ui",
|
||||||
"crates/aws_http_client",
|
"crates/aws_http_client",
|
||||||
"crates/bedrock",
|
"crates/bedrock",
|
||||||
@@ -46,7 +46,7 @@ members = [
|
|||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
"crates/docs_preprocessor",
|
"crates/docs_preprocessor",
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
"crates/evals",
|
"crates/eval",
|
||||||
"crates/extension",
|
"crates/extension",
|
||||||
"crates/extension_api",
|
"crates/extension_api",
|
||||||
"crates/extension_cli",
|
"crates/extension_cli",
|
||||||
@@ -164,6 +164,8 @@ members = [
|
|||||||
"crates/util_macros",
|
"crates/util_macros",
|
||||||
"crates/vim",
|
"crates/vim",
|
||||||
"crates/vim_mode_setting",
|
"crates/vim_mode_setting",
|
||||||
|
"crates/web_search",
|
||||||
|
"crates/web_search_providers",
|
||||||
"crates/welcome",
|
"crates/welcome",
|
||||||
"crates/workspace",
|
"crates/workspace",
|
||||||
"crates/worktree",
|
"crates/worktree",
|
||||||
@@ -192,6 +194,7 @@ members = [
|
|||||||
# Tooling
|
# Tooling
|
||||||
#
|
#
|
||||||
|
|
||||||
|
"tooling/workspace-hack",
|
||||||
"tooling/xtask",
|
"tooling/xtask",
|
||||||
]
|
]
|
||||||
default-members = ["crates/zed"]
|
default-members = ["crates/zed"]
|
||||||
@@ -214,7 +217,6 @@ askpass = { path = "crates/askpass" }
|
|||||||
assets = { path = "crates/assets" }
|
assets = { path = "crates/assets" }
|
||||||
assistant = { path = "crates/assistant" }
|
assistant = { path = "crates/assistant" }
|
||||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||||
assistant_eval = { path = "crates/assistant_eval" }
|
|
||||||
assistant_settings = { path = "crates/assistant_settings" }
|
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" }
|
||||||
@@ -222,6 +224,7 @@ assistant_tool = { path = "crates/assistant_tool" }
|
|||||||
assistant_tools = { path = "crates/assistant_tools" }
|
assistant_tools = { path = "crates/assistant_tools" }
|
||||||
audio = { path = "crates/audio" }
|
audio = { path = "crates/audio" }
|
||||||
auto_update = { path = "crates/auto_update" }
|
auto_update = { path = "crates/auto_update" }
|
||||||
|
auto_update_helper = { path = "crates/auto_update_helper" }
|
||||||
auto_update_ui = { path = "crates/auto_update_ui" }
|
auto_update_ui = { path = "crates/auto_update_ui" }
|
||||||
aws_http_client = { path = "crates/aws_http_client" }
|
aws_http_client = { path = "crates/aws_http_client" }
|
||||||
bedrock = { path = "crates/bedrock" }
|
bedrock = { path = "crates/bedrock" }
|
||||||
@@ -368,6 +371,8 @@ util = { path = "crates/util" }
|
|||||||
util_macros = { path = "crates/util_macros" }
|
util_macros = { path = "crates/util_macros" }
|
||||||
vim = { path = "crates/vim" }
|
vim = { path = "crates/vim" }
|
||||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||||
|
web_search = { path = "crates/web_search" }
|
||||||
|
web_search_providers = { path = "crates/web_search_providers" }
|
||||||
welcome = { path = "crates/welcome" }
|
welcome = { path = "crates/welcome" }
|
||||||
workspace = { path = "crates/workspace" }
|
workspace = { path = "crates/workspace" }
|
||||||
worktree = { path = "crates/worktree" }
|
worktree = { path = "crates/worktree" }
|
||||||
@@ -395,14 +400,18 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
|
|||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
async-tar = "0.5.0"
|
async-tar = "0.5.0"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
async-tungstenite = "0.28"
|
async-tungstenite = "0.29.1"
|
||||||
async-watch = "0.3.1"
|
async-watch = "0.3.1"
|
||||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||||
aws-config = { version = "1.5.16", features = ["behavior-version-latest"] }
|
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
|
||||||
aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] }
|
aws-credential-types = { version = "1.2.2", features = [
|
||||||
aws-sdk-bedrockruntime = { version = "1.73.0", features = ["behavior-version-latest"] }
|
"hardcoded-credentials",
|
||||||
aws-smithy-runtime-api = { version = "1.7.3", features = ["http-1x", "client"] }
|
] }
|
||||||
aws-smithy-types = { version = "1.2.13", features = ["http-body-1-x"] }
|
aws-sdk-bedrockruntime = { version = "1.80.0", features = [
|
||||||
|
"behavior-version-latest",
|
||||||
|
] }
|
||||||
|
aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
|
||||||
|
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
||||||
@@ -424,7 +433,7 @@ core-foundation = "0.10.0"
|
|||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
ctor = "0.4.0"
|
ctor = "0.4.0"
|
||||||
dashmap = "6.0"
|
dashmap = "6.0"
|
||||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "bfd4af0" }
|
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
dirs = "4.0"
|
dirs = "4.0"
|
||||||
ec4rs = "1.1"
|
ec4rs = "1.1"
|
||||||
@@ -439,6 +448,7 @@ 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"
|
||||||
handlebars = "4.3"
|
handlebars = "4.3"
|
||||||
|
heck = "0.5"
|
||||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
html5ever = "0.27.0"
|
html5ever = "0.27.0"
|
||||||
@@ -452,8 +462,8 @@ indoc = "2"
|
|||||||
inventory = "0.3.19"
|
inventory = "0.3.19"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
jupyter-protocol = { version = "0.6.0" }
|
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
jupyter-websocket-client = { version = "0.9.0" }
|
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
@@ -462,21 +472,23 @@ log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
|||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
nbformat = { version = "0.10.0" }
|
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
|
objc = "0.2"
|
||||||
open = "5.0.0"
|
open = "5.0.0"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
|
partial-json-fixer = "0.5.3"
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||||
postage = { version = "0.5", features = ["futures-traits"] }
|
postage = { version = "0.5", features = ["futures-traits"] }
|
||||||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||||
proc-macro2 = "1.0.93"
|
proc-macro2 = "1.0.93"
|
||||||
@@ -499,14 +511,15 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
|||||||
"stream",
|
"stream",
|
||||||
] }
|
] }
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.6"
|
||||||
runtimelib = { version = "0.25.0", default-features = false, features = [
|
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
||||||
"async-dispatcher-runtime",
|
"async-dispatcher-runtime",
|
||||||
] }
|
] }
|
||||||
rustc-demangle = "0.1.23"
|
rustc-demangle = "0.1.23"
|
||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||||
rustc-hash = "2.1.0"
|
rustc-hash = "2.1.0"
|
||||||
rustls = { version = "0.23.22" }
|
rustls = { version = "0.23.26" }
|
||||||
rustls-platform-verifier = "0.5.0"
|
rustls-platform-verifier = "0.5.0"
|
||||||
|
scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false }
|
||||||
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
@@ -527,7 +540,7 @@ smol = "2.0"
|
|||||||
sqlformat = "0.2"
|
sqlformat = "0.2"
|
||||||
streaming-iterator = "0.1"
|
streaming-iterator = "0.1"
|
||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum = { version = "0.26.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 = "1.0.72", features = ["full", "extra-traits"] }
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
@@ -546,7 +559,7 @@ time = { version = "0.3", features = [
|
|||||||
tiny_http = "0.8"
|
tiny_http = "0.8"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"]}
|
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
||||||
tower-http = "0.4.4"
|
tower-http = "0.4.4"
|
||||||
tree-sitter = { version = "0.25.3", features = ["wasm"] }
|
tree-sitter = { version = "0.25.3", features = ["wasm"] }
|
||||||
tree-sitter-bash = "0.23"
|
tree-sitter-bash = "0.23"
|
||||||
@@ -578,6 +591,7 @@ unicode-script = "0.5.7"
|
|||||||
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"
|
||||||
wasmparser = "0.221"
|
wasmparser = "0.221"
|
||||||
wasm-encoder = "0.221"
|
wasm-encoder = "0.221"
|
||||||
wasmtime = { version = "29", default-features = false, features = [
|
wasmtime = { version = "29", default-features = false, features = [
|
||||||
@@ -590,7 +604,8 @@ wasmtime = { version = "29", default-features = false, features = [
|
|||||||
wasmtime-wasi = "29"
|
wasmtime-wasi = "29"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
wit-component = "0.221"
|
wit-component = "0.221"
|
||||||
zed_llm_client = "0.4"
|
workspace-hack = "0.1.0"
|
||||||
|
zed_llm_client = "0.6.1"
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
metal = "0.29"
|
metal = "0.29"
|
||||||
|
|
||||||
@@ -611,12 +626,10 @@ features = [
|
|||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
version = "0.61"
|
version = "0.61"
|
||||||
features = [
|
features = [
|
||||||
"Foundation_Collections",
|
|
||||||
"Foundation_Numerics",
|
"Foundation_Numerics",
|
||||||
"Storage_Search",
|
"Storage_Search",
|
||||||
"Storage_Streams",
|
"Storage_Streams",
|
||||||
"System_Threading",
|
"System_Threading",
|
||||||
"UI_StartScreen",
|
|
||||||
"UI_ViewManagement",
|
"UI_ViewManagement",
|
||||||
"Wdk_System_SystemServices",
|
"Wdk_System_SystemServices",
|
||||||
"Win32_Globalization",
|
"Win32_Globalization",
|
||||||
@@ -643,6 +656,7 @@ features = [
|
|||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
|
"Win32_System_Variant",
|
||||||
"Win32_System_WinRT",
|
"Win32_System_WinRT",
|
||||||
"Win32_UI_Controls",
|
"Win32_UI_Controls",
|
||||||
"Win32_UI_HiDpi",
|
"Win32_UI_HiDpi",
|
||||||
@@ -650,19 +664,21 @@ features = [
|
|||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_UI_Shell_Common",
|
"Win32_UI_Shell_Common",
|
||||||
|
"Win32_UI_Shell_PropertiesSystem",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
||||||
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" }
|
|
||||||
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
|
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
|
||||||
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
|
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
|
||||||
|
|
||||||
|
# Makes the workspace hack crate refer to the local one, but only when you're building locally
|
||||||
|
workspace-hack = { path = "tooling/workspace-hack" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
debug = "limited"
|
|
||||||
codegen-units = 16
|
codegen-units = 16
|
||||||
|
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
@@ -680,7 +696,6 @@ breadcrumbs = { codegen-units = 1 }
|
|||||||
collections = { codegen-units = 1 }
|
collections = { codegen-units = 1 }
|
||||||
command_palette = { codegen-units = 1 }
|
command_palette = { codegen-units = 1 }
|
||||||
command_palette_hooks = { codegen-units = 1 }
|
command_palette_hooks = { codegen-units = 1 }
|
||||||
evals = { codegen-units = 1 }
|
|
||||||
extension_cli = { codegen-units = 1 }
|
extension_cli = { codegen-units = 1 }
|
||||||
feature_flags = { codegen-units = 1 }
|
feature_flags = { codegen-units = 1 }
|
||||||
file_icons = { codegen-units = 1 }
|
file_icons = { codegen-units = 1 }
|
||||||
@@ -772,4 +787,12 @@ let_underscore_future = "allow"
|
|||||||
too_many_arguments = "allow"
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
[workspace.metadata.cargo-machete]
|
[workspace.metadata.cargo-machete]
|
||||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"]
|
ignored = [
|
||||||
|
"bindgen",
|
||||||
|
"cbindgen",
|
||||||
|
"prost_build",
|
||||||
|
"serde",
|
||||||
|
"component",
|
||||||
|
"linkme",
|
||||||
|
"workspace-hack",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax = docker/dockerfile:1.2
|
# syntax = docker/dockerfile:1.2
|
||||||
|
|
||||||
FROM rust:1.81-bookworm as builder
|
FROM rust:1.86-bookworm as builder
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="16" height="16" rx="2" fill="black" fill-opacity="0.2"/>
|
|
||||||
<g clip-path="url(#clip0_1916_18)">
|
|
||||||
<path d="M10.652 3.79999H8.816L12.164 12.2H14L10.652 3.79999Z" fill="#1F1F1E"/>
|
|
||||||
<path d="M5.348 3.79999L2 12.2H3.872L4.55672 10.436H8.05927L8.744 12.2H10.616L7.268 3.79999H5.348ZM5.16224 8.87599L6.308 5.92399L7.45374 8.87599H5.16224Z" fill="#1F1F1E"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_1916_18">
|
|
||||||
<rect width="12" height="8.4" fill="white" transform="translate(2 3.79999)"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 601 B |
1
assets/icons/arrow_down_right.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down-right-icon lucide-arrow-down-right"><path d="m7 7 10 10"/><path d="M17 7v10H7"/></svg>
|
||||||
|
After Width: | Height: | Size: 300 B |
@@ -1,3 +1 @@
|
|||||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-right-icon lucide-arrow-up-right"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
|
||||||
<path d="M6 2H6.5C6.5 1.86739 6.44732 1.74021 6.35355 1.64645C6.25979 1.55268 6.13261 1.5 6 1.5V2ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2C1.5 2.27614 1.72386 2.5 2 2.5L2 1.5ZM5.5 6C5.5 6.27614 5.72386 6.5 6 6.5C6.27614 6.5 6.5 6.27614 6.5 6H5.5ZM1.64645 5.64645C1.45118 5.84171 1.45118 6.15829 1.64645 6.35355C1.84171 6.54882 2.15829 6.54882 2.35355 6.35355L1.64645 5.64645ZM6 1.5H2L2 2.5H6V1.5ZM5.5 2V6H6.5V2H5.5ZM5.64645 1.64645L1.64645 5.64645L2.35355 6.35355L6.35355 2.35355L5.64645 1.64645Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 296 B |
1
assets/icons/binary.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-binary-icon lucide-binary"><rect x="14" y="14" width="4" height="6" rx="2"/><rect x="6" y="4" width="4" height="6" rx="2"/><path d="M6 20h4"/><path d="M14 10h4"/><path d="M6 14h2v6"/><path d="M14 4h2v6"/></svg>
|
||||||
|
After Width: | Height: | Size: 413 B |
1
assets/icons/bug_off.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug-off-icon lucide-bug-off"><path d="M15 7.13V6a3 3 0 0 0-5.14-2.1L8 2"/><path d="M14.12 3.88 16 2"/><path d="M22 13h-4v-2a4 4 0 0 0-4-4h-1.3"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="m2 2 20 20"/><path d="M7.7 7.7A4 4 0 0 0 6 11v3a6 6 0 0 0 11.13 3.13"/><path d="M12 20v-8"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/></svg>
|
||||||
|
After Width: | Height: | Size: 551 B |
1
assets/icons/circle_off.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-off-icon lucide-circle-off"><path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/></svg>
|
||||||
|
After Width: | Height: | Size: 357 B |
1
assets/icons/file_icons/vyper.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="16" height="16" fill="none" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g style="fill:#000;fill-opacity:1" fill="#180c25"><path d="m-116.1-101.4-28.9-28.9a6.7 6.7 0 0 1-1.8-4.7v-41.2c0-2.4-2.4-4.8-4.8-4.8h-9.6a5.2 5.2 0 0 0-4.8 4.8v48c0 2.5 1 5 2.7 6.8l33.6 33.6a9.6 9.6 0 0 0 6.8 2.8h4.8c2.7 0 4.8-2.2 4.8-4.8v-4.8c0-2.5-1-5-2.8-6.8zM-79.6-176.2c0-2.4-2.4-4.8-4.8-4.8h-9.7a5.2 5.2 0 0 0-4.7 4.8v41.2c0 1.8-.8 3.5-2 4.7l-9.6 9.7a9.5 9.5 0 0 0-2.8 6.8v4.8c0 2.6 2.1 4.7 4.8 4.7h4.8c2.4 0 4.9-.9 6.7-2.8l14.4-14.3a9.6 9.6 0 0 0 2.8-6.8v-48z" style="fill:#000;fill-opacity:1;stroke-width:.255894" transform="translate(21.6 22.7) scale(.11067)"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 677 B |
1
assets/icons/flame.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame-icon lucide-flame"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 415 B |
1
assets/icons/forward_arrow.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-forward-icon lucide-forward"><polyline points="15 17 20 12 15 7"/><path d="M4 18v-2a4 4 0 0 1 4-4h12"/></svg>
|
||||||
|
After Width: | Height: | Size: 312 B |
1
assets/icons/function.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg>
|
||||||
|
After Width: | Height: | Size: 387 B |
1
assets/icons/image.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-icon lucide-image"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>
|
||||||
|
After Width: | Height: | Size: 372 B |
5
assets/icons/layout.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 14H4C3.44772 14 3 14.4477 3 15V20C3 20.5523 3.44772 21 4 21H20C20.5523 21 21 20.5523 21 20V15C21 14.4477 20.5523 14 20 14Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11 3H4C3.44772 3 3 3.44772 3 4V9C3 9.55228 3.44772 10 4 10H11C11.5523 10 12 9.55228 12 9V4C12 3.44772 11.5523 3 11 3Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20 3H17C16.4477 3 16 3.44772 16 4V9C16 9.55228 16.4477 10 17 10H20C20.5523 10 21 9.55228 21 9V4C21 3.44772 20.5523 3 20 3Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 746 B |
3
assets/icons/light_bulb.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="M10.1331 11.3776C10.2754 10.6665 10.1331 9.78593 11.1998 8.53327C11.82 7.80489 12.2664 6.96894 12.2664 6.04456C12.2664 4.91305 11.8169 3.82788 11.0168 3.02778C10.2167 2.22769 9.13152 1.7782 8.00001 1.7782C6.8685 1.7782 5.78334 2.22769 4.98324 3.02778C4.18314 3.82788 3.73364 4.91305 3.73364 6.04456C3.73364 6.75562 3.87586 7.6089 4.80024 8.53327C5.86683 9.80679 5.72462 10.6665 5.86683 11.3776M10.1331 11.3776V12.8821C10.1331 13.622 9.53341 14.2218 8.79353 14.2218H7.2065C6.46662 14.2218 5.86683 13.622 5.86683 12.8821V11.3776M10.1331 11.3776H5.86683" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 751 B |
1
assets/icons/power.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-power-icon lucide-power"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>
|
||||||
|
After Width: | Height: | Size: 294 B |
4
assets/icons/send.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.09666 3.02263C3.0567 3.00312 3.01178 2.9961 2.96778 3.0025C2.92377 3.00889 2.88271 3.02839 2.84995 3.05847C2.8172 3.08854 2.79426 3.12778 2.78413 3.17108C2.77401 3.21439 2.77716 3.25973 2.79319 3.30121L4.05638 6.69C4.13088 6.89005 4.13088 7.11022 4.05638 7.31027L2.79363 10.6991C2.77769 10.7405 2.77457 10.7858 2.78469 10.829C2.79481 10.8722 2.8177 10.9114 2.85038 10.9414C2.88306 10.9715 2.92402 10.991 2.96794 10.9975C3.01186 11.0039 3.05671 10.997 3.09666 10.9776L11.0943 7.20097C11.1324 7.18297 11.1645 7.15455 11.187 7.11899C11.2096 7.08344 11.2215 7.04222 11.2215 7.00014C11.2215 6.95805 11.2096 6.91683 11.187 6.88128C11.1645 6.84573 11.1324 6.8173 11.0943 6.79931L3.09666 3.02263Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4.11255 7.00014H11.2216" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1014 B |
3
assets/icons/stop_filled.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 9.8V4.2C4 4.08954 4.08954 4 4.2 4H9.8C9.91046 4 10 4.08954 10 4.2V9.8C10 9.91046 9.91046 10 9.8 10H4.2C4.08954 10 4 9.91046 4 9.8Z" fill="#C56757" stroke="#C56757" stroke-width="1.25" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
@@ -49,15 +49,6 @@
|
|||||||
"down": "menu::SelectNext"
|
"down": "menu::SelectNext"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "Prompt",
|
|
||||||
"bindings": {
|
|
||||||
"left": "menu::SelectPrevious",
|
|
||||||
"right": "menu::SelectNext",
|
|
||||||
"h": "menu::SelectPrevious",
|
|
||||||
"l": "menu::SelectNext"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -134,23 +125,9 @@
|
|||||||
"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"
|
||||||
{
|
|
||||||
"context": "Editor && !agent_diff",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-k ctrl-r": "git::Restore",
|
|
||||||
"ctrl-alt-y": "git::ToggleStaged",
|
|
||||||
"alt-y": "git::StageAndNext",
|
|
||||||
"alt-shift-y": "git::UnstageAndNext"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "AgentDiff",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-y": "agent::Keep",
|
|
||||||
"ctrl-k ctrl-r": "agent::Reject"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,6 +178,31 @@
|
|||||||
"ctrl-c": "markdown::Copy"
|
"ctrl-c": "markdown::Copy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && jupyter && !ContextEditor",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-shift-enter": "repl::Run",
|
||||||
|
"ctrl-alt-enter": "repl::RunInPlace"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && !agent_diff",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-k ctrl-r": "git::Restore",
|
||||||
|
"ctrl-alt-y": "git::ToggleStaged",
|
||||||
|
"alt-y": "git::StageAndNext",
|
||||||
|
"alt-shift-y": "git::UnstageAndNext"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentDiff",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-y": "agent::Keep",
|
||||||
|
"ctrl-n": "agent::Reject",
|
||||||
|
"ctrl-shift-y": "agent::KeepAll",
|
||||||
|
"ctrl-shift-n": "agent::RejectAll"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "AssistantPanel",
|
"context": "AssistantPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -216,6 +218,93 @@
|
|||||||
"ctrl-n": "assistant::NewChat"
|
"ctrl-n": "assistant::NewChat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "ContextEditor > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-enter": "assistant::Assist",
|
||||||
|
"ctrl-shift-enter": "assistant::Edit",
|
||||||
|
"ctrl-s": "workspace::Save",
|
||||||
|
"save": "workspace::Save",
|
||||||
|
"ctrl->": "assistant::QuoteSelection",
|
||||||
|
"ctrl-<": "assistant::InsertIntoEditor",
|
||||||
|
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||||
|
"shift-enter": "assistant::Split",
|
||||||
|
"ctrl-r": "assistant::CycleMessageRole",
|
||||||
|
"enter": "assistant::ConfirmCommand",
|
||||||
|
"alt-enter": "editor::Newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentPanel",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-n": "agent::NewThread",
|
||||||
|
"ctrl-alt-n": "agent::NewTextThread",
|
||||||
|
"ctrl-shift-h": "agent::OpenHistory",
|
||||||
|
"ctrl-alt-c": "agent::OpenConfiguration",
|
||||||
|
"ctrl-alt-p": "assistant::OpenPromptLibrary",
|
||||||
|
"ctrl-i": "agent::ToggleProfileSelector",
|
||||||
|
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||||
|
"ctrl-shift-a": "agent::ToggleContextPicker",
|
||||||
|
"shift-escape": "agent::ExpandMessageEditor",
|
||||||
|
"ctrl-e": "agent::ChatMode",
|
||||||
|
"ctrl-alt-e": "agent::RemoveAllContext"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentPanel > Markdown",
|
||||||
|
"bindings": {
|
||||||
|
"copy": "markdown::CopyAsMarkdown",
|
||||||
|
"ctrl-c": "markdown::CopyAsMarkdown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentPanel && prompt_editor",
|
||||||
|
"bindings": {
|
||||||
|
"cmd-n": "agent::NewTextThread",
|
||||||
|
"cmd-alt-t": "agent::NewThread"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "MessageEditor > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"enter": "agent::Chat",
|
||||||
|
"ctrl-i": "agent::ToggleProfileSelector",
|
||||||
|
"shift-ctrl-r": "agent::OpenAgentDiff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "EditMessageEditor > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"escape": "menu::Cancel",
|
||||||
|
"enter": "menu::Confirm",
|
||||||
|
"alt-enter": "editor::Newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentFeedbackMessageEditor > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"escape": "menu::Cancel",
|
||||||
|
"enter": "menu::Confirm",
|
||||||
|
"alt-enter": "editor::Newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "ContextStrip",
|
||||||
|
"bindings": {
|
||||||
|
"up": "agent::FocusUp",
|
||||||
|
"right": "agent::FocusRight",
|
||||||
|
"left": "agent::FocusLeft",
|
||||||
|
"down": "agent::FocusDown",
|
||||||
|
"backspace": "agent::RemoveFocusedContext",
|
||||||
|
"enter": "agent::AcceptSuggestedContext"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "ThreadHistory",
|
||||||
|
"bindings": {
|
||||||
|
"backspace": "agent::RemoveSelectedThread"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "PromptLibrary",
|
"context": "PromptLibrary",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -311,6 +400,7 @@
|
|||||||
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||||
|
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
|
||||||
"back": "pane::GoBack",
|
"back": "pane::GoBack",
|
||||||
"ctrl-alt--": "pane::GoBack",
|
"ctrl-alt--": "pane::GoBack",
|
||||||
"ctrl-alt-_": "pane::GoForward",
|
"ctrl-alt-_": "pane::GoForward",
|
||||||
@@ -351,11 +441,11 @@
|
|||||||
"alt-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection
|
"alt-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection
|
||||||
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
||||||
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
|
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
|
||||||
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }],
|
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||||
"ctrl-shift-down": ["editor::SelectNext", { "replace_newest": false }], // Add selection to Next Find Match
|
"ctrl-shift-down": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch
|
||||||
"ctrl-shift-up": ["editor::SelectPrevious", { "replace_newest": false }],
|
"ctrl-shift-up": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch
|
||||||
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }],
|
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip
|
||||||
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }],
|
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
|
||||||
"ctrl-k ctrl-i": "editor::Hover",
|
"ctrl-k ctrl-i": "editor::Hover",
|
||||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||||
"ctrl-u": "editor::UndoSelection",
|
"ctrl-u": "editor::UndoSelection",
|
||||||
@@ -481,6 +571,8 @@
|
|||||||
"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:
|
||||||
|
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -529,6 +621,7 @@
|
|||||||
"context": "Editor && showing_completions",
|
"context": "Editor && showing_completions",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::ConfirmCompletion",
|
"enter": "editor::ConfirmCompletion",
|
||||||
|
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||||
"tab": "editor::ComposeCompletion"
|
"tab": "editor::ComposeCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -594,86 +687,6 @@
|
|||||||
"ctrl-:": "editor::ToggleInlayHints"
|
"ctrl-:": "editor::ToggleInlayHints"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "Editor && jupyter && !ContextEditor",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-shift-enter": "repl::Run",
|
|
||||||
"ctrl-alt-enter": "repl::RunInPlace"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "ContextEditor > Editor",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-enter": "assistant::Assist",
|
|
||||||
"ctrl-shift-enter": "assistant::Edit",
|
|
||||||
"ctrl-s": "workspace::Save",
|
|
||||||
"save": "workspace::Save",
|
|
||||||
"ctrl->": "assistant::QuoteSelection",
|
|
||||||
"ctrl-<": "assistant::InsertIntoEditor",
|
|
||||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
|
||||||
"shift-enter": "assistant::Split",
|
|
||||||
"ctrl-r": "assistant::CycleMessageRole",
|
|
||||||
"enter": "assistant::ConfirmCommand",
|
|
||||||
"alt-enter": "editor::Newline"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "AgentPanel",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-n": "agent::NewThread",
|
|
||||||
"new": "agent::NewThread",
|
|
||||||
"ctrl-alt-n": "agent::NewPromptEditor",
|
|
||||||
"ctrl-shift-h": "agent::OpenHistory",
|
|
||||||
"ctrl-alt-c": "agent::OpenConfiguration",
|
|
||||||
"ctrl-i": "agent::ToggleProfileSelector",
|
|
||||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
|
||||||
"ctrl-shift-a": "agent::ToggleContextPicker",
|
|
||||||
"ctrl-e": "agent::ChatMode",
|
|
||||||
"ctrl-alt-e": "agent::RemoveAllContext"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "AgentPanel && prompt_editor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"cmd-n": "agent::NewPromptEditor",
|
|
||||||
"cmd-alt-t": "agent::NewThread"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "MessageEditor > Editor",
|
|
||||||
"bindings": {
|
|
||||||
"enter": "agent::Chat",
|
|
||||||
"ctrl-i": "agent::ToggleProfileSelector",
|
|
||||||
"shift-ctrl-r": "agent::OpenAgentDiff"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "EditMessageEditor > Editor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"escape": "menu::Cancel",
|
|
||||||
"enter": "menu::Confirm",
|
|
||||||
"alt-enter": "editor::Newline"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "ContextStrip",
|
|
||||||
"bindings": {
|
|
||||||
"up": "agent::FocusUp",
|
|
||||||
"right": "agent::FocusRight",
|
|
||||||
"left": "agent::FocusLeft",
|
|
||||||
"down": "agent::FocusDown",
|
|
||||||
"backspace": "agent::RemoveFocusedContext",
|
|
||||||
"enter": "agent::AcceptSuggestedContext"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "ThreadHistory",
|
|
||||||
"bindings": {
|
|
||||||
"backspace": "agent::RemoveSelectedThread"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "PromptEditor",
|
"context": "PromptEditor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -682,6 +695,15 @@
|
|||||||
"ctrl-alt-e": "agent::RemoveAllContext"
|
"ctrl-alt-e": "agent::RemoveAllContext"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Prompt",
|
||||||
|
"bindings": {
|
||||||
|
"left": "menu::SelectPrevious",
|
||||||
|
"right": "menu::SelectNext",
|
||||||
|
"h": "menu::SelectPrevious",
|
||||||
|
"l": "menu::SelectNext"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar && !in_replace",
|
"context": "ProjectSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -699,7 +721,7 @@
|
|||||||
"alt-shift-copy": "workspace::CopyRelativePath",
|
"alt-shift-copy": "workspace::CopyRelativePath",
|
||||||
"alt-ctrl-shift-c": "workspace::CopyRelativePath",
|
"alt-ctrl-shift-c": "workspace::CopyRelativePath",
|
||||||
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
||||||
"space": "outline_panel::Open",
|
"space": "outline_panel::OpenSelectedEntry",
|
||||||
"shift-down": "menu::SelectNext",
|
"shift-down": "menu::SelectNext",
|
||||||
"shift-up": "menu::SelectPrevious",
|
"shift-up": "menu::SelectPrevious",
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
@@ -763,6 +785,7 @@
|
|||||||
"shift-tab": "git_panel::FocusEditor",
|
"shift-tab": "git_panel::FocusEditor",
|
||||||
"escape": "git_panel::ToggleFocus",
|
"escape": "git_panel::ToggleFocus",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"alt-enter": "menu::SecondaryConfirm",
|
"alt-enter": "menu::SecondaryConfirm",
|
||||||
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
@@ -771,18 +794,25 @@
|
|||||||
"ctrl-delete": ["git::RestoreFile", { "skip_prompt": false }]
|
"ctrl-delete": ["git::RestoreFile", { "skip_prompt": false }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitPanel && CommitEditor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"escape": "git::Cancel"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitCommit > Editor",
|
"context": "GitCommit > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitPanel",
|
"context": "GitPanel",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-g ctrl-g": "git::Fetch",
|
"ctrl-g ctrl-g": "git::Fetch",
|
||||||
"ctrl-g up": "git::Push",
|
"ctrl-g up": "git::Push",
|
||||||
@@ -799,6 +829,7 @@
|
|||||||
"context": "GitDiff > Editor",
|
"context": "GitDiff > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"ctrl-space": "git::StageAll",
|
"ctrl-space": "git::StageAll",
|
||||||
"ctrl-shift-space": "git::UnstageAll"
|
"ctrl-shift-space": "git::UnstageAll"
|
||||||
}
|
}
|
||||||
@@ -817,6 +848,7 @@
|
|||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-shift-enter": "git::Amend",
|
||||||
"alt-up": "git_panel::FocusChanges",
|
"alt-up": "git_panel::FocusChanges",
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
@@ -888,6 +920,7 @@
|
|||||||
"ctrl-enter": "assistant::InlineAssist",
|
"ctrl-enter": "assistant::InlineAssist",
|
||||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||||
|
"alt-.": ["terminal::SendText", "\u001b."],
|
||||||
// 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"],
|
||||||
|
|||||||
@@ -242,7 +242,9 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-y": "agent::Keep",
|
"cmd-y": "agent::Keep",
|
||||||
"cmd-alt-z": "agent::Reject"
|
"cmd-n": "agent::Reject",
|
||||||
|
"cmd-shift-y": "agent::KeepAll",
|
||||||
|
"cmd-shift-n": "agent::RejectAll"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -281,21 +283,30 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-n": "agent::NewThread",
|
"cmd-n": "agent::NewThread",
|
||||||
"cmd-alt-n": "agent::NewPromptEditor",
|
"cmd-alt-n": "agent::NewTextThread",
|
||||||
"cmd-shift-h": "agent::OpenHistory",
|
"cmd-shift-h": "agent::OpenHistory",
|
||||||
"cmd-alt-c": "agent::OpenConfiguration",
|
"cmd-alt-c": "agent::OpenConfiguration",
|
||||||
|
"cmd-alt-p": "assistant::OpenPromptLibrary",
|
||||||
"cmd-i": "agent::ToggleProfileSelector",
|
"cmd-i": "agent::ToggleProfileSelector",
|
||||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||||
"cmd-shift-a": "agent::ToggleContextPicker",
|
"cmd-shift-a": "agent::ToggleContextPicker",
|
||||||
|
"shift-escape": "agent::ExpandMessageEditor",
|
||||||
"cmd-e": "agent::ChatMode",
|
"cmd-e": "agent::ChatMode",
|
||||||
"cmd-alt-e": "agent::RemoveAllContext"
|
"cmd-alt-e": "agent::RemoveAllContext"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentPanel > Markdown",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-c": "markdown::CopyAsMarkdown"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "AgentPanel && prompt_editor",
|
"context": "AgentPanel && prompt_editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-n": "agent::NewPromptEditor",
|
"cmd-n": "agent::NewTextThread",
|
||||||
"cmd-alt-t": "agent::NewThread"
|
"cmd-alt-t": "agent::NewThread"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -317,6 +328,15 @@
|
|||||||
"alt-enter": "editor::Newline"
|
"alt-enter": "editor::Newline"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentFeedbackMessageEditor > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"escape": "menu::Cancel",
|
||||||
|
"enter": "menu::Confirm",
|
||||||
|
"alt-enter": "editor::Newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ContextStrip",
|
"context": "ContextStrip",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -329,10 +349,28 @@
|
|||||||
"enter": "agent::AcceptSuggestedContext"
|
"enter": "agent::AcceptSuggestedContext"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "AgentConfiguration",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl--": "pane::GoBack"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ThreadHistory",
|
"context": "ThreadHistory",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"backspace": "agent::RemoveSelectedThread"
|
"ctrl--": "pane::GoBack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "ThreadHistory",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl--": "pane::GoBack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "ThreadHistory > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"shift-backspace": "agent::RemoveSelectedThread"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -421,7 +459,8 @@
|
|||||||
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||||
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||||
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||||
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
|
"cmd-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||||
|
"cmd-k cmd-w": "workspace::CloseAllItemsAndPanes",
|
||||||
"cmd-f": "project_search::ToggleFocus",
|
"cmd-f": "project_search::ToggleFocus",
|
||||||
"cmd-g": "search::SelectNextMatch",
|
"cmd-g": "search::SelectNextMatch",
|
||||||
"cmd-shift-g": "search::SelectPreviousMatch",
|
"cmd-shift-g": "search::SelectPreviousMatch",
|
||||||
@@ -453,12 +492,15 @@
|
|||||||
"alt-shift-down": "editor::DuplicateLineDown",
|
"alt-shift-down": "editor::DuplicateLineDown",
|
||||||
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection
|
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection
|
||||||
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection
|
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection
|
||||||
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // Add selection to Next Find Match
|
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||||
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
||||||
"cmd-f2": "editor::SelectAllMatches", // Select all occurrences of current word
|
"cmd-f2": "editor::SelectAllMatches", // Select all occurrences of current word
|
||||||
"ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }],
|
"cmd-k cmd-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip
|
||||||
"cmd-k cmd-d": ["editor::SelectNext", { "replace_newest": true }],
|
// macOS binds `ctrl-cmd-d` to Show Dictionary which breaks these two binds
|
||||||
"cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }],
|
// To use `ctrl-cmd-d` or `ctrl-k ctrl-cmd-d` in Zed you must execute this command and then restart:
|
||||||
|
// defaults write com.apple.symbolichotkeys AppleSymbolicHotKeys -dict-add 70 '<dict><key>enabled</key><false/></dict>'
|
||||||
|
"ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch
|
||||||
|
"cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
|
||||||
"cmd-k cmd-i": "editor::Hover",
|
"cmd-k cmd-i": "editor::Hover",
|
||||||
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||||
"cmd-u": "editor::UndoSelection",
|
"cmd-u": "editor::UndoSelection",
|
||||||
@@ -490,7 +532,7 @@
|
|||||||
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
|
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
|
||||||
"cmd-k cmd-0": "editor::FoldAll",
|
"cmd-k cmd-0": "editor::FoldAll",
|
||||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||||
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
|
// Using `ctrl-space` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
|
||||||
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||||
@@ -500,7 +542,9 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -598,6 +642,8 @@
|
|||||||
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
"ctrl-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:
|
||||||
|
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Bindings from Sublime Text
|
// Bindings from Sublime Text
|
||||||
@@ -644,6 +690,7 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::ConfirmCompletion",
|
"enter": "editor::ConfirmCompletion",
|
||||||
|
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||||
"tab": "editor::ComposeCompletion"
|
"tab": "editor::ComposeCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -742,7 +789,7 @@
|
|||||||
"cmd-alt-c": "workspace::CopyPath",
|
"cmd-alt-c": "workspace::CopyPath",
|
||||||
"alt-cmd-shift-c": "workspace::CopyRelativePath",
|
"alt-cmd-shift-c": "workspace::CopyRelativePath",
|
||||||
"alt-cmd-r": "outline_panel::RevealInFileManager",
|
"alt-cmd-r": "outline_panel::RevealInFileManager",
|
||||||
"space": "outline_panel::Open",
|
"space": "outline_panel::OpenSelectedEntry",
|
||||||
"shift-down": "menu::SelectNext",
|
"shift-down": "menu::SelectNext",
|
||||||
"shift-up": "menu::SelectPrevious",
|
"shift-up": "menu::SelectPrevious",
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
@@ -811,17 +858,26 @@
|
|||||||
"shift-tab": "git_panel::FocusEditor",
|
"shift-tab": "git_panel::FocusEditor",
|
||||||
"escape": "git_panel::ToggleFocus",
|
"escape": "git_panel::ToggleFocus",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
|
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
|
||||||
"cmd-delete": ["git::RestoreFile", { "skip_prompt": true }]
|
"cmd-delete": ["git::RestoreFile", { "skip_prompt": true }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitPanel && CommitEditor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"escape": "git::Cancel"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitDiff > Editor",
|
"context": "GitDiff > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"cmd-ctrl-y": "git::StageAll",
|
"cmd-ctrl-y": "git::StageAll",
|
||||||
"cmd-ctrl-shift-y": "git::UnstageAll"
|
"cmd-ctrl-shift-y": "git::UnstageAll"
|
||||||
}
|
}
|
||||||
@@ -832,6 +888,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"tab": "git_panel::FocusChanges",
|
"tab": "git_panel::FocusChanges",
|
||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
"alt-up": "git_panel::FocusChanges",
|
"alt-up": "git_panel::FocusChanges",
|
||||||
@@ -861,6 +918,7 @@
|
|||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
|
"cmd-shift-enter": "git::Amend",
|
||||||
"alt-tab": "git::GenerateCommitMessage"
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -947,6 +1005,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."],
|
||||||
// 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"],
|
||||||
@@ -966,11 +1025,13 @@
|
|||||||
"cmd-home": "terminal::ScrollToTop",
|
"cmd-home": "terminal::ScrollToTop",
|
||||||
"shift-end": "terminal::ScrollToBottom",
|
"shift-end": "terminal::ScrollToBottom",
|
||||||
"cmd-end": "terminal::ScrollToBottom",
|
"cmd-end": "terminal::ScrollToBottom",
|
||||||
|
// Using `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
|
||||||
|
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
||||||
"ctrl-shift-space": "terminal::ToggleViMode",
|
"ctrl-shift-space": "terminal::ToggleViMode",
|
||||||
"ctrl-k up": "pane::SplitUp",
|
"ctrl-alt-up": "pane::SplitUp",
|
||||||
"ctrl-k down": "pane::SplitDown",
|
"ctrl-alt-down": "pane::SplitDown",
|
||||||
"ctrl-k left": "pane::SplitLeft",
|
"ctrl-alt-left": "pane::SplitLeft",
|
||||||
"ctrl-k right": "pane::SplitRight"
|
"ctrl-alt-right": "pane::SplitRight"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,7 +58,8 @@
|
|||||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||||
"ctrl-shift-end": "editor::SelectToEnd",
|
"ctrl-shift-end": "editor::SelectToEnd",
|
||||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||||
"ctrl-shift-f8": "editor::EditLogBreakpoint"
|
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||||
|
"ctrl-shift-u": "editor::ToggleCase"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
||||||
"ctrl-shift-d": "editor::DuplicateSelection",
|
"ctrl-shift-d": "editor::DuplicateSelection",
|
||||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||||
|
// "ctrl-f3": "", // find_under (cancels any selections)
|
||||||
|
// "cmd-alt-shift-g": "" // find_under_prev (cancels any selections)
|
||||||
"f9": "editor::SortLinesCaseSensitive",
|
"f9": "editor::SortLinesCaseSensitive",
|
||||||
"ctrl-f9": "editor::SortLinesCaseInsensitive",
|
"ctrl-f9": "editor::SortLinesCaseInsensitive",
|
||||||
"f12": "editor::GoToDefinition",
|
"f12": "editor::GoToDefinition",
|
||||||
@@ -49,7 +51,9 @@
|
|||||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
|
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
|
||||||
"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",
|
||||||
|
"shift-f3": "editor::FindPreviousMatch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,6 +62,12 @@
|
|||||||
"ctrl-r": "outline::Toggle"
|
"ctrl-r": "outline::Toggle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && !agent_diff",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-k ctrl-z": "git::Restore"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -55,7 +55,8 @@
|
|||||||
"cmd-shift-home": "editor::SelectToBeginning",
|
"cmd-shift-home": "editor::SelectToBeginning",
|
||||||
"cmd-shift-end": "editor::SelectToEnd",
|
"cmd-shift-end": "editor::SelectToEnd",
|
||||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||||
"ctrl-shift-f8": "editor::EditLogBreakpoint"
|
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||||
|
"cmd-shift-u": "editor::ToggleCase"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||||
"cmd-shift-d": "editor::DuplicateSelection",
|
"cmd-shift-d": "editor::DuplicateSelection",
|
||||||
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
||||||
|
// "cmd-alt-g": "", // find_under (cancels any selections)
|
||||||
|
// "cmd-alt-shift-g": "" // find_under_prev (cancels any selections)
|
||||||
"f5": "editor::SortLinesCaseSensitive",
|
"f5": "editor::SortLinesCaseSensitive",
|
||||||
"ctrl-f5": "editor::SortLinesCaseInsensitive",
|
"ctrl-f5": "editor::SortLinesCaseInsensitive",
|
||||||
"shift-f12": "editor::FindAllReferences",
|
"shift-f12": "editor::FindAllReferences",
|
||||||
@@ -51,7 +53,9 @@
|
|||||||
"cmd-shift-j": "editor::JoinLines",
|
"cmd-shift-j": "editor::JoinLines",
|
||||||
"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",
|
||||||
|
"cmd-shift-g": "editor::FindPreviousMatch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,6 +64,12 @@
|
|||||||
"cmd-r": "outline::Toggle"
|
"cmd-r": "outline::Toggle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && !agent_diff",
|
||||||
|
"bindings": {
|
||||||
|
"cmd-k cmd-z": "git::Restore"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -44,6 +44,12 @@
|
|||||||
"[ /": "vim::PreviousComment",
|
"[ /": "vim::PreviousComment",
|
||||||
"] *": "vim::NextComment",
|
"] *": "vim::NextComment",
|
||||||
"] /": "vim::NextComment",
|
"] /": "vim::NextComment",
|
||||||
|
"[ -": "vim::PreviousLesserIndent",
|
||||||
|
"[ +": "vim::PreviousGreaterIndent",
|
||||||
|
"[ =": "vim::PreviousSameIndent",
|
||||||
|
"] -": "vim::NextLesserIndent",
|
||||||
|
"] +": "vim::NextGreaterIndent",
|
||||||
|
"] =": "vim::NextSameIndent",
|
||||||
// Word motions
|
// Word motions
|
||||||
"w": "vim::NextWordStart",
|
"w": "vim::NextWordStart",
|
||||||
"e": "vim::NextWordEnd",
|
"e": "vim::NextWordEnd",
|
||||||
@@ -184,7 +190,8 @@
|
|||||||
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
||||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-6": "pane::AlternateFile"
|
"ctrl-6": "pane::AlternateFile",
|
||||||
|
"ctrl-^": "pane::AlternateFile"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -197,6 +204,7 @@
|
|||||||
"c": "vim::PushChange",
|
"c": "vim::PushChange",
|
||||||
"shift-c": "vim::ChangeToEndOfLine",
|
"shift-c": "vim::ChangeToEndOfLine",
|
||||||
"d": "vim::PushDelete",
|
"d": "vim::PushDelete",
|
||||||
|
"delete": "vim::DeleteRight",
|
||||||
"shift-d": "vim::DeleteToEndOfLine",
|
"shift-d": "vim::DeleteToEndOfLine",
|
||||||
"shift-j": "vim::JoinLines",
|
"shift-j": "vim::JoinLines",
|
||||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||||
@@ -335,27 +343,106 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == helix_normal",
|
"context": "vim_mode == helix_normal && !menu",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"escape": "editor::Cancel",
|
||||||
|
"ctrl-[": "editor::Cancel",
|
||||||
|
":": "command_palette::Toggle",
|
||||||
|
"shift-d": "vim::DeleteToEndOfLine",
|
||||||
|
"shift-j": "vim::JoinLines",
|
||||||
|
"y": "editor::Copy",
|
||||||
|
"shift-y": "vim::YankLine",
|
||||||
"i": "vim::InsertBefore",
|
"i": "vim::InsertBefore",
|
||||||
|
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||||
"a": "vim::InsertAfter",
|
"a": "vim::InsertAfter",
|
||||||
"d": "vim::HelixDelete",
|
"shift-a": "vim::InsertEndOfLine",
|
||||||
"w": "vim::NextWordStart",
|
"o": "vim::InsertLineBelow",
|
||||||
"e": "vim::NextWordEnd",
|
"shift-o": "vim::InsertLineAbove",
|
||||||
"b": "vim::PreviousWordStart",
|
"~": "vim::ChangeCase",
|
||||||
|
"ctrl-a": "vim::Increment",
|
||||||
|
"ctrl-x": "vim::Decrement",
|
||||||
|
"p": "vim::Paste",
|
||||||
|
"shift-p": ["vim::Paste", { "before": true }],
|
||||||
|
"u": "vim::Undo",
|
||||||
|
"ctrl-r": "vim::Redo",
|
||||||
|
"r": "vim::PushReplace",
|
||||||
|
"s": "vim::Substitute",
|
||||||
|
"shift-s": "vim::SubstituteLine",
|
||||||
|
">": "vim::Indent",
|
||||||
|
"<": "vim::Outdent",
|
||||||
|
"=": "vim::AutoIndent",
|
||||||
|
"g u": "vim::PushLowercase",
|
||||||
|
"g shift-u": "vim::PushUppercase",
|
||||||
|
"g ~": "vim::PushOppositeCase",
|
||||||
|
"\"": "vim::PushRegister",
|
||||||
|
"g q": "vim::PushRewrap",
|
||||||
|
"g w": "vim::PushRewrap",
|
||||||
|
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||||
|
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||||
|
"insert": "vim::InsertBefore",
|
||||||
|
// tree-sitter related commands
|
||||||
|
"[ x": "editor::SelectLargerSyntaxNode",
|
||||||
|
"] x": "editor::SelectSmallerSyntaxNode",
|
||||||
|
"] d": "editor::GoToDiagnostic",
|
||||||
|
"[ d": "editor::GoToPreviousDiagnostic",
|
||||||
|
"] c": "editor::GoToHunk",
|
||||||
|
"[ c": "editor::GoToPreviousHunk",
|
||||||
|
// Goto mode
|
||||||
|
"g n": "pane::ActivateNextItem",
|
||||||
|
"g p": "pane::ActivatePreviousItem",
|
||||||
|
// "tab": "pane::ActivateNextItem",
|
||||||
|
// "shift-tab": "pane::ActivatePrevItem",
|
||||||
|
"shift-h": "pane::ActivatePreviousItem",
|
||||||
|
"shift-l": "pane::ActivateNextItem",
|
||||||
|
"g l": "vim::EndOfLine",
|
||||||
|
"g h": "vim::StartOfLine",
|
||||||
|
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||||
|
"g e": "vim::EndOfDocument",
|
||||||
|
"g y": "editor::GoToTypeDefinition",
|
||||||
|
"g r": "editor::FindAllReferences", // zed specific
|
||||||
|
"g t": "vim::WindowTop",
|
||||||
|
"g c": "vim::WindowMiddle",
|
||||||
|
"g b": "vim::WindowBottom",
|
||||||
|
|
||||||
"h": "vim::Left",
|
"x": "editor::SelectLine",
|
||||||
"j": "vim::Down",
|
"shift-x": "editor::SelectLine",
|
||||||
"k": "vim::Up",
|
// Window mode
|
||||||
"l": "vim::Right"
|
"space w h": "workspace::ActivatePaneLeft",
|
||||||
|
"space w l": "workspace::ActivatePaneRight",
|
||||||
|
"space w k": "workspace::ActivatePaneUp",
|
||||||
|
"space w j": "workspace::ActivatePaneDown",
|
||||||
|
"space w q": "pane::CloseActiveItem",
|
||||||
|
"space w s": "pane::SplitRight",
|
||||||
|
"space w r": "pane::SplitRight",
|
||||||
|
"space w v": "pane::SplitDown",
|
||||||
|
"space w d": "pane::SplitDown",
|
||||||
|
// Space mode
|
||||||
|
"space f": "file_finder::Toggle",
|
||||||
|
"space k": "editor::Hover",
|
||||||
|
"space s": "outline::Toggle",
|
||||||
|
"space shift-s": "project_symbols::Toggle",
|
||||||
|
"space d": "editor::GoToDiagnostic",
|
||||||
|
"space r": "editor::Rename",
|
||||||
|
"space a": "editor::ToggleCodeActions",
|
||||||
|
"space h": "editor::SelectAllMatches",
|
||||||
|
"space c": "editor::ToggleComments",
|
||||||
|
"space y": "editor::Copy",
|
||||||
|
"space p": "editor::Paste",
|
||||||
|
// Match mode
|
||||||
|
"m m": "vim::Matching",
|
||||||
|
"m i w": ["workspace::SendKeystrokes", "v i w"],
|
||||||
|
"shift-u": "editor::Redo",
|
||||||
|
"ctrl-c": "editor::ToggleComments",
|
||||||
|
"d": "vim::HelixDelete",
|
||||||
|
"c": "vim::Substitute",
|
||||||
|
"shift-c": "editor::AddSelectionBelow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::ShowCompletions",
|
"ctrl-p": "editor::ShowWordCompletions",
|
||||||
"ctrl-n": "editor::ShowCompletions"
|
"ctrl-n": "editor::ShowWordCompletions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -453,6 +540,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"d": "vim::CurrentLine",
|
"d": "vim::CurrentLine",
|
||||||
"s": "vim::PushDeleteSurrounds",
|
"s": "vim::PushDeleteSurrounds",
|
||||||
|
"v": "vim::PushForcedMotion", // "d v"
|
||||||
"o": "editor::ToggleSelectedDiffHunks", // "d o"
|
"o": "editor::ToggleSelectedDiffHunks", // "d o"
|
||||||
"shift-o": "git::ToggleStaged",
|
"shift-o": "git::ToggleStaged",
|
||||||
"p": "git::Restore", // "d p"
|
"p": "git::Restore", // "d p"
|
||||||
@@ -501,6 +589,7 @@
|
|||||||
"context": "vim_operator == y",
|
"context": "vim_operator == y",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"y": "vim::CurrentLine",
|
"y": "vim::CurrentLine",
|
||||||
|
"v": "vim::PushForcedMotion",
|
||||||
"s": ["vim::PushAddSurrounds", {}]
|
"s": ["vim::PushAddSurrounds", {}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -698,8 +787,7 @@
|
|||||||
"{": "project_panel::SelectPrevDirectory",
|
"{": "project_panel::SelectPrevDirectory",
|
||||||
"shift-g": "menu::SelectLast",
|
"shift-g": "menu::SelectLast",
|
||||||
"g g": "menu::SelectFirst",
|
"g g": "menu::SelectFirst",
|
||||||
"-": "project_panel::SelectParent",
|
"-": "project_panel::SelectParent"
|
||||||
"ctrl-6": "pane::AlternateFile"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -742,5 +830,13 @@
|
|||||||
// 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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,54 +1,88 @@
|
|||||||
You are an AI assistant integrated into a code editor. You have the programming ability of an expert programmer who takes pride in writing high-quality code and is driven to the point of obsession about solving problems effectively. Your goal is to do one of the following two things:
|
You are a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||||
|
|
||||||
1. Help users answer questions and perform tasks related to their codebase.
|
## Communication
|
||||||
2. Answer general-purpose questions unrelated to their particular codebase.
|
|
||||||
|
|
||||||
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
|
1. Be conversational but professional.
|
||||||
|
2. Refer to the USER in the second person and yourself in the first person.
|
||||||
|
3. Format your responses in markdown. Use backticks to format file, directory, function, and class names.
|
||||||
|
4. NEVER lie or make things up.
|
||||||
|
5. Refrain from apologizing all the time when results are unexpected. Instead, just try your best to proceed or explain the circumstances to the user without apologizing.
|
||||||
|
|
||||||
You should only perform actions that modify the user's system if explicitly requested by the user:
|
## Tool Use
|
||||||
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user’s system without explicit instruction.
|
|
||||||
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
|
|
||||||
|
|
||||||
Editing code:
|
1. Make sure to adhere to the tools schema.
|
||||||
- Make sure to take previous edits into account.
|
2. Provide every required argument.
|
||||||
- The edits you perform might lead to errors or warnings. At the end of your changes, check whether you introduced any problems, and fix them before providing a summary of the changes you made.
|
3. DO NOT use tools to access items that are already available in the context section.
|
||||||
- You may only attempt to fix these up to 3 times. If you have tried 3 times to fix them, and there are still problems remaining, you must not continue trying to fix them, and must instead tell the user that there are problems remaining - and ask if the user would like you to attempt to solve them further.
|
4. Use only the tools that are currently available.
|
||||||
- Do not fix errors unrelated to your changes unless the user explicitly asks you to do so.
|
5. DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
|
||||||
- Prefer to move files over recreating them. The move can be followed by minor edits if required.
|
|
||||||
|
|
||||||
Tool use:
|
## Searching and Reading
|
||||||
- Make sure to adhere to the tools schema.
|
|
||||||
- Provide every required argument.
|
|
||||||
- DO NOT use tools to access items that are already available in the context section.
|
|
||||||
- Use only the tools that are currently available.
|
|
||||||
- DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
|
|
||||||
|
|
||||||
Responding:
|
If you are unsure how to fulfill the user's request, gather more information with tool calls and/or clarifying questions.
|
||||||
- Be concise and direct in your responses.
|
|
||||||
- Never apologize or thank the user.
|
|
||||||
- Don't comment that you have just realized or understood something.
|
|
||||||
- When you are going to make a tool call, tersely explain your reasoning for choosing to use that tool, with no flourishes or commentary beyond that information.
|
|
||||||
For example, rather than saying "You're absolutely right! Thank you for providing that context. Now I understand that we're missing a dependency, and I need to add it:" say "I'll add that missing dependency:" instead.
|
|
||||||
- Also, don't restate what a tool call is about to do (or just did).
|
|
||||||
For example, don't say "Now I'm going to check diagnostics to see if there are any warnings or errors," followed by running a tool which checks diagnostics and reports warnings or errors; instead, just request the tool call without saying anything.
|
|
||||||
- All tool results are provided to you automatically, so DO NOT thank the user when this happens.
|
|
||||||
|
|
||||||
The user has opened a project that contains the following root directories/files. Whenever you specify a path in the project, it must be a relative path which begins with one of these root directories/files:
|
{{! TODO: If there are files, we should mention it but otherwise omit that fact }}
|
||||||
|
If appropriate, use tool calls to explore the current project, which contains the following root directories:
|
||||||
|
|
||||||
{{#each worktrees}}
|
{{#each worktrees}}
|
||||||
- `{{root_name}}` (absolute path: `{{abs_path}}`)
|
- `{{root_name}}`
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if has_rules}}
|
|
||||||
|
|
||||||
There are rules that apply to these root directories:
|
- When providing paths to tools, the path should always begin with a path that starts with a project root directory listed above.
|
||||||
|
- When looking for symbols in the project, prefer the `grep` tool.
|
||||||
|
- As you learn about the structure of the project, use that information to scope `grep` searches to targeted subtrees of the project.
|
||||||
|
- Bias towards not asking the user for help if you can find the answer yourself.
|
||||||
|
|
||||||
|
## Fixing Diagnostics
|
||||||
|
|
||||||
|
1. Make 1-2 attempts at fixing diagnostics, then defer to the user.
|
||||||
|
2. Never simplify code you've written just to solve diagnostics. Complete, mostly correct code is more valuable than perfect code that doesn't solve the problem.
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
When debugging, only make code changes if you are certain that you can solve the problem.
|
||||||
|
Otherwise, follow debugging best practices:
|
||||||
|
1. Address the root cause instead of the symptoms.
|
||||||
|
2. Add descriptive logging statements and error messages to track variable and code state.
|
||||||
|
3. Add test functions and statements to isolate the problem.
|
||||||
|
|
||||||
|
## Calling External APIs
|
||||||
|
|
||||||
|
1. Unless explicitly requested by the user, use the best suited external APIs and packages to solve the task. There is no need to ask the user for permission.
|
||||||
|
2. When selecting which version of an API or package to use, choose one that is compatible with the user's dependency management file. If no such file exists or if the package is not present, use the latest version that is in your training data.
|
||||||
|
3. If an external API requires an API Key, be sure to point this out to the user. Adhere to best security practices (e.g. DO NOT hardcode an API key in a place where it can be exposed)
|
||||||
|
|
||||||
|
## System Information
|
||||||
|
|
||||||
|
Operating System: {{os}}
|
||||||
|
Default Shell: {{shell}}
|
||||||
|
|
||||||
|
{{#if (or has_rules has_default_user_rules)}}
|
||||||
|
## User's Custom Instructions
|
||||||
|
|
||||||
|
The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the tool use guidelines.
|
||||||
|
|
||||||
|
{{#if has_rules}}
|
||||||
|
There are project rules that apply to these root directories:
|
||||||
{{#each worktrees}}
|
{{#each worktrees}}
|
||||||
{{#if rules_file}}
|
{{#if rules_file}}
|
||||||
|
`{{root_name}}/{{rules_file.path_in_worktree}}`:
|
||||||
`{{root_name}}/{{rules_file.rel_path}}`:
|
|
||||||
|
|
||||||
``````
|
``````
|
||||||
{{{rules_file.text}}}
|
{{{rules_file.text}}}
|
||||||
``````
|
``````
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if has_user_rules}}
|
||||||
|
The user has specified the following rules that should be applied:
|
||||||
|
{{#each user_rules}}
|
||||||
|
|
||||||
|
{{#if title}}
|
||||||
|
Rules title: {{title}}
|
||||||
|
{{/if}}
|
||||||
|
``````
|
||||||
|
{{contents}}}
|
||||||
|
``````
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
A software developer is asking a question about their project. The source files in their project have been indexed into a database of semantic text embeddings.
|
|
||||||
Your task is to generate a list of 4 diverse search queries that can be run on this embedding database, in order to retrieve a list of code snippets
|
|
||||||
that are relevant to the developer's question. Redundant search queries will be heavily penalized, so only include another query if it's sufficiently
|
|
||||||
distinct from previous ones.
|
|
||||||
|
|
||||||
Here is the question that's been asked, together with context that the developer has added manually:
|
|
||||||
|
|
||||||
{{{context_buffer}}}
|
|
||||||
@@ -80,6 +80,8 @@
|
|||||||
// Values are clamped to the [0.0, 1.0] range.
|
// Values are clamped to the [0.0, 1.0] range.
|
||||||
"inactive_opacity": 1.0
|
"inactive_opacity": 1.0
|
||||||
},
|
},
|
||||||
|
// Layout mode of the bottom dock. Defaults to "contained"
|
||||||
|
"bottom_dock_layout": "contained",
|
||||||
// The direction that you want to split panes horizontally. Defaults to "up"
|
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||||
"pane_split_direction_horizontal": "up",
|
"pane_split_direction_horizontal": "up",
|
||||||
// The direction that you want to split panes horizontally. Defaults to "left"
|
// The direction that you want to split panes horizontally. Defaults to "left"
|
||||||
@@ -179,8 +181,6 @@
|
|||||||
"current_line_highlight": "all",
|
"current_line_highlight": "all",
|
||||||
// Whether to highlight all occurrences of the selected text in an editor.
|
// Whether to highlight all occurrences of the selected text in an editor.
|
||||||
"selection_highlight": true,
|
"selection_highlight": true,
|
||||||
// The debounce delay before querying highlights based on the selected text.
|
|
||||||
"selection_highlight_debounce": 50,
|
|
||||||
// The debounce delay before querying highlights from the language
|
// The debounce delay before querying highlights from the language
|
||||||
// server based on the current cursor location.
|
// server based on the current cursor location.
|
||||||
"lsp_highlight_debounce": 75,
|
"lsp_highlight_debounce": 75,
|
||||||
@@ -585,7 +585,6 @@
|
|||||||
//
|
//
|
||||||
// Default: main
|
// Default: main
|
||||||
"fallback_branch_name": "main",
|
"fallback_branch_name": "main",
|
||||||
|
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the git panel.
|
// When to show the scrollbar in the git panel.
|
||||||
//
|
//
|
||||||
@@ -624,14 +623,14 @@
|
|||||||
// 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-5-sonnet-latest"
|
"model": "claude-3-7-sonnet-latest"
|
||||||
},
|
},
|
||||||
// The model to use when applying edits from the assistant.
|
// The model to use when applying edits from the assistant.
|
||||||
"editor_model": {
|
"editor_model": {
|
||||||
// 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-5-sonnet-latest"
|
"model": "claude-3-7-sonnet-latest"
|
||||||
},
|
},
|
||||||
// When enabled, the agent can run potentially destructive actions without asking for your confirmation.
|
// When enabled, the agent can run potentially destructive actions without asking for your confirmation.
|
||||||
"always_allow_tool_actions": false,
|
"always_allow_tool_actions": false,
|
||||||
@@ -642,38 +641,43 @@
|
|||||||
// We don't know which of the context server tools are safe for the "Ask" profile, so we don't enable them by default.
|
// We don't know which of the context server tools are safe for the "Ask" profile, so we don't enable them by default.
|
||||||
// "enable_all_context_servers": true,
|
// "enable_all_context_servers": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
|
"contents": true,
|
||||||
"diagnostics": true,
|
"diagnostics": true,
|
||||||
"fetch": true,
|
"fetch": true,
|
||||||
"list_directory": true,
|
"list_directory": false,
|
||||||
"now": true,
|
"now": true,
|
||||||
"path_search": true,
|
"path_search": true,
|
||||||
"read_file": true,
|
"read_file": true,
|
||||||
"regex_search": true,
|
"grep": true,
|
||||||
"thinking": true
|
"thinking": true,
|
||||||
|
"web_search": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
"name": "Write",
|
"name": "Write",
|
||||||
"enable_all_context_servers": true,
|
"enable_all_context_servers": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"bash": true,
|
"batch_tool": false,
|
||||||
"batch_tool": true,
|
"code_actions": false,
|
||||||
"code_symbols": true,
|
"code_symbols": false,
|
||||||
"copy_path": true,
|
"contents": false,
|
||||||
|
"copy_path": false,
|
||||||
"create_file": true,
|
"create_file": true,
|
||||||
"delete_path": true,
|
"delete_path": false,
|
||||||
"diagnostics": true,
|
"diagnostics": true,
|
||||||
"find_replace_file": true,
|
"edit_file": true,
|
||||||
"edit_files": false,
|
|
||||||
"fetch": true,
|
"fetch": true,
|
||||||
"list_directory": true,
|
"list_directory": true,
|
||||||
"move_path": true,
|
"move_path": false,
|
||||||
"now": true,
|
"now": false,
|
||||||
"path_search": true,
|
"path_search": true,
|
||||||
"read_file": true,
|
"read_file": true,
|
||||||
"regex_search": true,
|
"grep": true,
|
||||||
"symbol_info": true,
|
"rename": false,
|
||||||
"thinking": true
|
"symbol_info": false,
|
||||||
|
"terminal": true,
|
||||||
|
"thinking": true,
|
||||||
|
"web_search": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1137,7 +1141,8 @@
|
|||||||
"code_actions_on_format": {},
|
"code_actions_on_format": {},
|
||||||
// Settings related to running tasks.
|
// Settings related to running tasks.
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"variables": {}
|
"variables": {},
|
||||||
|
"enabled": 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
|
||||||
@@ -1201,7 +1206,27 @@
|
|||||||
// When set to 0, waits indefinitely.
|
// When set to 0, waits indefinitely.
|
||||||
//
|
//
|
||||||
// Default: 0
|
// Default: 0
|
||||||
"lsp_fetch_timeout_ms": 0
|
"lsp_fetch_timeout_ms": 0,
|
||||||
|
// Controls what range to replace when accepting LSP completions.
|
||||||
|
//
|
||||||
|
// When LSP servers give an `InsertReplaceEdit` completion, they provides two ranges: `insert` and `replace`. Usually, `insert`
|
||||||
|
// contains the word prefix before your cursor and `replace` contains the whole word.
|
||||||
|
//
|
||||||
|
// Effectively, this setting just changes whether Zed will use the received range for `insert` or `replace`, so the results may
|
||||||
|
// differ depending on the underlying LSP server.
|
||||||
|
//
|
||||||
|
// Possible values:
|
||||||
|
// 1. "insert"
|
||||||
|
// Replaces text before the cursor, using the `insert` range described in the LSP specification.
|
||||||
|
// 2. "replace"
|
||||||
|
// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
|
||||||
|
// 3. "replace_subsequence"
|
||||||
|
// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
|
||||||
|
// and like `"insert"` otherwise.
|
||||||
|
// 4. "replace_suffix"
|
||||||
|
// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
|
||||||
|
// `"insert"` otherwise.
|
||||||
|
"lsp_insert_mode": "replace_suffix"
|
||||||
},
|
},
|
||||||
// Different settings for specific languages.
|
// Different settings for specific languages.
|
||||||
"languages": {
|
"languages": {
|
||||||
@@ -1437,6 +1462,8 @@
|
|||||||
"lsp": {
|
"lsp": {
|
||||||
// Specify the LSP name as a key here.
|
// Specify the LSP name as a key here.
|
||||||
// "rust-analyzer": {
|
// "rust-analyzer": {
|
||||||
|
// // A special flag for rust-analyzer integration, to use server-provided tasks
|
||||||
|
// enable_lsp_tasks": true,
|
||||||
// // These initialization options are merged into Zed's defaults
|
// // These initialization options are merged into Zed's defaults
|
||||||
// "initialization_options": {
|
// "initialization_options": {
|
||||||
// "check": {
|
// "check": {
|
||||||
@@ -1462,7 +1489,12 @@
|
|||||||
"use_multiline_find": false,
|
"use_multiline_find": false,
|
||||||
"use_smartcase_find": false,
|
"use_smartcase_find": false,
|
||||||
"highlight_on_yank_duration": 200,
|
"highlight_on_yank_duration": 200,
|
||||||
"custom_digraphs": {}
|
"custom_digraphs": {},
|
||||||
|
// Cursor shape for the each mode.
|
||||||
|
// Specify the mode as the key and the shape as the value.
|
||||||
|
// The mode can be one of the following: "normal", "replace", "insert", "visual".
|
||||||
|
// The shape can be one of the following: "block", "bar", "underline", "hollow".
|
||||||
|
"cursor_shape": {}
|
||||||
},
|
},
|
||||||
// The server to connect to. If the environment variable
|
// The server to connect to. If the environment variable
|
||||||
// ZED_SERVER_URL is set, it will override this setting.
|
// ZED_SERVER_URL is set, it will override this setting.
|
||||||
@@ -1532,7 +1564,6 @@
|
|||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
"ssh_connections": [],
|
"ssh_connections": [],
|
||||||
|
|
||||||
// Configures context servers for use in the Assistant.
|
// Configures context servers for use in the Assistant.
|
||||||
"context_servers": {},
|
"context_servers": {},
|
||||||
"debugger": {
|
"debugger": {
|
||||||
|
|||||||
@@ -43,6 +43,8 @@
|
|||||||
// "args": ["--login"]
|
// "args": ["--login"]
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
"shell": "system"
|
"shell": "system",
|
||||||
|
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||||
|
"tags": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -87,9 +87,9 @@
|
|||||||
"terminal.ansi.blue": "#83a598ff",
|
"terminal.ansi.blue": "#83a598ff",
|
||||||
"terminal.ansi.bright_blue": "#414f4aff",
|
"terminal.ansi.bright_blue": "#414f4aff",
|
||||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||||
"terminal.ansi.magenta": "#a89984ff",
|
"terminal.ansi.magenta": "#d3869bff",
|
||||||
"terminal.ansi.bright_magenta": "#514a41ff",
|
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||||
"terminal.ansi.dim_magenta": "#d2cabfff",
|
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||||
"terminal.ansi.cyan": "#8ec07cff",
|
"terminal.ansi.cyan": "#8ec07cff",
|
||||||
"terminal.ansi.bright_cyan": "#45603eff",
|
"terminal.ansi.bright_cyan": "#45603eff",
|
||||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||||
@@ -472,9 +472,9 @@
|
|||||||
"terminal.ansi.blue": "#83a598ff",
|
"terminal.ansi.blue": "#83a598ff",
|
||||||
"terminal.ansi.bright_blue": "#414f4aff",
|
"terminal.ansi.bright_blue": "#414f4aff",
|
||||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||||
"terminal.ansi.magenta": "#a89984ff",
|
"terminal.ansi.magenta": "#d3869bff",
|
||||||
"terminal.ansi.bright_magenta": "#514a41ff",
|
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||||
"terminal.ansi.dim_magenta": "#d2cabfff",
|
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||||
"terminal.ansi.cyan": "#8ec07cff",
|
"terminal.ansi.cyan": "#8ec07cff",
|
||||||
"terminal.ansi.bright_cyan": "#45603eff",
|
"terminal.ansi.bright_cyan": "#45603eff",
|
||||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||||
@@ -857,9 +857,9 @@
|
|||||||
"terminal.ansi.blue": "#83a598ff",
|
"terminal.ansi.blue": "#83a598ff",
|
||||||
"terminal.ansi.bright_blue": "#414f4aff",
|
"terminal.ansi.bright_blue": "#414f4aff",
|
||||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||||
"terminal.ansi.magenta": "#a89984ff",
|
"terminal.ansi.magenta": "#d3869bff",
|
||||||
"terminal.ansi.bright_magenta": "#514a41ff",
|
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||||
"terminal.ansi.dim_magenta": "#d2cabfff",
|
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||||
"terminal.ansi.cyan": "#8ec07cff",
|
"terminal.ansi.cyan": "#8ec07cff",
|
||||||
"terminal.ansi.bright_cyan": "#45603eff",
|
"terminal.ansi.bright_cyan": "#45603eff",
|
||||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||||
@@ -1242,9 +1242,9 @@
|
|||||||
"terminal.ansi.blue": "#0b6678ff",
|
"terminal.ansi.blue": "#0b6678ff",
|
||||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||||
"terminal.ansi.dim_blue": "#14333bff",
|
"terminal.ansi.dim_blue": "#14333bff",
|
||||||
"terminal.ansi.magenta": "#7c6f64ff",
|
"terminal.ansi.magenta": "#8f3e71ff",
|
||||||
"terminal.ansi.bright_magenta": "#bcb5afff",
|
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||||
"terminal.ansi.dim_magenta": "#3e3833ff",
|
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||||
"terminal.ansi.cyan": "#437b59ff",
|
"terminal.ansi.cyan": "#437b59ff",
|
||||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||||
@@ -1627,9 +1627,9 @@
|
|||||||
"terminal.ansi.blue": "#0b6678ff",
|
"terminal.ansi.blue": "#0b6678ff",
|
||||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||||
"terminal.ansi.dim_blue": "#14333bff",
|
"terminal.ansi.dim_blue": "#14333bff",
|
||||||
"terminal.ansi.magenta": "#7c6f64ff",
|
"terminal.ansi.magenta": "#8f3e71ff",
|
||||||
"terminal.ansi.bright_magenta": "#bcb5afff",
|
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||||
"terminal.ansi.dim_magenta": "#3e3833ff",
|
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||||
"terminal.ansi.cyan": "#437b59ff",
|
"terminal.ansi.cyan": "#437b59ff",
|
||||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||||
@@ -2012,9 +2012,9 @@
|
|||||||
"terminal.ansi.blue": "#0b6678ff",
|
"terminal.ansi.blue": "#0b6678ff",
|
||||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||||
"terminal.ansi.dim_blue": "#14333bff",
|
"terminal.ansi.dim_blue": "#14333bff",
|
||||||
"terminal.ansi.magenta": "#7c6f64ff",
|
"terminal.ansi.magenta": "#8f3e71ff",
|
||||||
"terminal.ansi.bright_magenta": "#bcb5afff",
|
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||||
"terminal.ansi.dim_magenta": "#3e3833ff",
|
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||||
"terminal.ansi.cyan": "#437b59ff",
|
"terminal.ansi.cyan": "#437b59ff",
|
||||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ smallvec.workspace = true
|
|||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -11,13 +11,22 @@ use language::{BinaryStatus, LanguageRegistry, LanguageServerId};
|
|||||||
use project::{
|
use project::{
|
||||||
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
||||||
ProjectEnvironmentEvent,
|
ProjectEnvironmentEvent,
|
||||||
|
git_store::{GitStoreEvent, Repository},
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp::Reverse, fmt::Write, path::Path, sync::Arc, time::Duration};
|
use std::{
|
||||||
|
cmp::Reverse,
|
||||||
|
fmt::Write,
|
||||||
|
path::Path,
|
||||||
|
sync::Arc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
use ui::{ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use util::truncate_and_trailoff;
|
use util::truncate_and_trailoff;
|
||||||
use workspace::{StatusItemView, Workspace, item::ItemHandle};
|
use workspace::{StatusItemView, Workspace, item::ItemHandle};
|
||||||
|
|
||||||
|
const GIT_OPERATION_DELAY: Duration = Duration::from_millis(0);
|
||||||
|
|
||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
@@ -105,6 +114,15 @@ impl ActivityIndicator {
|
|||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
cx.subscribe(
|
||||||
|
&project.read(cx).git_store().clone(),
|
||||||
|
|_, _, event: &GitStoreEvent, cx| match event {
|
||||||
|
project::git_store::GitStoreEvent::JobsUpdated => cx.notify(),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
if let Some(auto_updater) = auto_updater.as_ref() {
|
||||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
||||||
}
|
}
|
||||||
@@ -285,6 +303,34 @@ impl ActivityIndicator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let current_job = self
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.active_repository(cx)
|
||||||
|
.map(|r| r.read(cx))
|
||||||
|
.and_then(Repository::current_job);
|
||||||
|
// Show any long-running git command
|
||||||
|
if let Some(job_info) = current_job {
|
||||||
|
if Instant::now() - job_info.start >= GIT_OPERATION_DELAY {
|
||||||
|
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: job_info.message.into(),
|
||||||
|
on_click: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show any language server installation info.
|
// Show any language server installation info.
|
||||||
let mut downloading = SmallVec::<[_; 3]>::new();
|
let mut downloading = SmallVec::<[_; 3]>::new();
|
||||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ client.workspace = true
|
|||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
|
component.workspace = true
|
||||||
context_server.workspace = true
|
context_server.workspace = true
|
||||||
convert_case.workspace = true
|
convert_case.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
@@ -50,6 +51,7 @@ itertools.workspace = true
|
|||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
language_model_selector.workspace = true
|
language_model_selector.workspace = true
|
||||||
|
linkme.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp.workspace = true
|
lsp.workspace = true
|
||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
@@ -78,14 +80,17 @@ terminal.workspace = true
|
|||||||
terminal_view.workspace = true
|
terminal_view.workspace = true
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
time_format.workspace = true
|
time_format.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
ui_input.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
vim_mode_setting.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]
|
||||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
use crate::{Thread, ThreadEvent};
|
use crate::{Keep, KeepAll, Reject, RejectAll, Thread, ThreadEvent, ui::AnimatedLabel};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use buffer_diff::DiffHunkStatus;
|
use buffer_diff::DiffHunkStatus;
|
||||||
use collections::HashSet;
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
AnchorRangeExt, Direction, Editor, EditorEvent, MultiBuffer, ToPoint,
|
Direction, Editor, EditorEvent, MultiBuffer, ToPoint,
|
||||||
actions::{GoToHunk, GoToPreviousHunk},
|
actions::{GoToHunk, GoToPreviousHunk},
|
||||||
scroll::Autoscroll,
|
scroll::Autoscroll,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, SharedString,
|
Action, AnyElement, AnyView, App, Empty, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
Subscription, Task, WeakEntity, Window, prelude::*,
|
SharedString, Subscription, Task, WeakEntity, Window, prelude::*,
|
||||||
};
|
};
|
||||||
use language::{Capability, DiskState, OffsetRangeExt, Point};
|
use language::{Capability, DiskState, OffsetRangeExt, Point};
|
||||||
use multi_buffer::PathKey;
|
use multi_buffer::PathKey;
|
||||||
@@ -26,6 +26,7 @@ use workspace::{
|
|||||||
item::{BreadcrumbText, ItemEvent, TabContentParams},
|
item::{BreadcrumbText, ItemEvent, TabContentParams},
|
||||||
searchable::SearchableItemHandle,
|
searchable::SearchableItemHandle,
|
||||||
};
|
};
|
||||||
|
use zed_actions::assistant::ToggleFocus;
|
||||||
|
|
||||||
pub struct AgentDiff {
|
pub struct AgentDiff {
|
||||||
multibuffer: Entity<MultiBuffer>,
|
multibuffer: Entity<MultiBuffer>,
|
||||||
@@ -43,22 +44,29 @@ impl AgentDiff {
|
|||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<()> {
|
) -> Result<Entity<Self>> {
|
||||||
let existing_diff = workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace
|
Self::deploy_in_workspace(thread, workspace, window, cx)
|
||||||
.items_of_type::<AgentDiff>(cx)
|
})
|
||||||
.find(|diff| diff.read(cx).thread == thread)
|
}
|
||||||
})?;
|
|
||||||
|
pub fn deploy_in_workspace(
|
||||||
|
thread: Entity<Thread>,
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Workspace>,
|
||||||
|
) -> Entity<Self> {
|
||||||
|
let existing_diff = workspace
|
||||||
|
.items_of_type::<AgentDiff>(cx)
|
||||||
|
.find(|diff| diff.read(cx).thread == thread);
|
||||||
if let Some(existing_diff) = existing_diff {
|
if let Some(existing_diff) = existing_diff {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.activate_item(&existing_diff, true, true, window, cx);
|
||||||
workspace.activate_item(&existing_diff, true, true, window, cx);
|
existing_diff
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
let agent_diff =
|
let agent_diff =
|
||||||
cx.new(|cx| AgentDiff::new(thread.clone(), workspace.clone(), window, cx));
|
cx.new(|cx| AgentDiff::new(thread.clone(), workspace.weak_handle(), window, cx));
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.add_item_to_center(Box::new(agent_diff.clone()), window, cx);
|
||||||
workspace.add_item_to_center(Box::new(agent_diff), window, cx);
|
agent_diff
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +141,11 @@ impl AgentDiff {
|
|||||||
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
|
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
|
||||||
|
|
||||||
for (buffer, diff_handle) in changed_buffers {
|
for (buffer, diff_handle) in changed_buffers {
|
||||||
let Some(file) = buffer.read(cx).file().cloned() else {
|
if buffer.read(cx).file().is_none() {
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
|
|
||||||
let path_key = PathKey::namespaced(0, file.full_path(cx).into());
|
let path_key = PathKey::for_buffer(&buffer, cx);
|
||||||
paths_to_delete.remove(&path_key);
|
paths_to_delete.remove(&path_key);
|
||||||
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
@@ -235,11 +243,31 @@ impl AgentDiff {
|
|||||||
|
|
||||||
fn handle_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) {
|
fn handle_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) {
|
||||||
match event {
|
match event {
|
||||||
ThreadEvent::SummaryChanged => self.update_title(cx),
|
ThreadEvent::SummaryGenerated => self.update_title(cx),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
let first_hunk = editor
|
||||||
|
.diff_hunks_in_ranges(
|
||||||
|
&[position..editor::Anchor::max()],
|
||||||
|
&self.multibuffer.read(cx).read(cx),
|
||||||
|
)
|
||||||
|
.next();
|
||||||
|
|
||||||
|
if let Some(first_hunk) = first_hunk {
|
||||||
|
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||||
|
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn keep(&mut self, _: &crate::Keep, window: &mut Window, cx: &mut Context<Self>) {
|
fn keep(&mut self, _: &crate::Keep, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let ranges = self
|
let ranges = self
|
||||||
.editor
|
.editor
|
||||||
@@ -279,6 +307,10 @@ impl AgentDiff {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
if self.thread.read(cx).is_generating() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let snapshot = self.multibuffer.read(cx).snapshot(cx);
|
let snapshot = self.multibuffer.read(cx).snapshot(cx);
|
||||||
let diff_hunks_in_ranges = self
|
let diff_hunks_in_ranges = self
|
||||||
.editor
|
.editor
|
||||||
@@ -311,6 +343,10 @@ impl AgentDiff {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
if self.thread.read(cx).is_generating() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let snapshot = self.multibuffer.read(cx).snapshot(cx);
|
let snapshot = self.multibuffer.read(cx).snapshot(cx);
|
||||||
let diff_hunks_in_ranges = self
|
let diff_hunks_in_ranges = self
|
||||||
.editor
|
.editor
|
||||||
@@ -327,13 +363,24 @@ impl AgentDiff {
|
|||||||
self.update_selection(&diff_hunks_in_ranges, window, cx);
|
self.update_selection(&diff_hunks_in_ranges, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let point_ranges = ranges
|
let mut ranges_by_buffer = HashMap::default();
|
||||||
.into_iter()
|
for hunk in &diff_hunks_in_ranges {
|
||||||
.map(|range| range.to_point(&snapshot))
|
let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id);
|
||||||
.collect();
|
if let Some(buffer) = buffer {
|
||||||
self.editor.update(cx, |editor, cx| {
|
ranges_by_buffer
|
||||||
editor.restore_hunks_in_ranges(point_ranges, window, cx)
|
.entry(buffer.clone())
|
||||||
});
|
.or_insert_with(Vec::new)
|
||||||
|
.push(hunk.buffer_range.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (buffer, ranges) in ranges_by_buffer {
|
||||||
|
self.thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.reject_edits_in_ranges(buffer, ranges, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_selection(
|
fn update_selection(
|
||||||
@@ -553,11 +600,12 @@ impl Item for AgentDiff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentDiff {
|
impl Render for AgentDiff {
|
||||||
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 is_empty = self.multibuffer.read(cx).is_empty();
|
let is_empty = self.multibuffer.read(cx).is_empty();
|
||||||
|
let focus_handle = &self.focus_handle;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(focus_handle)
|
||||||
.key_context(if is_empty { "EmptyPane" } else { "AgentDiff" })
|
.key_context(if is_empty { "EmptyPane" } else { "AgentDiff" })
|
||||||
.on_action(cx.listener(Self::keep))
|
.on_action(cx.listener(Self::keep))
|
||||||
.on_action(cx.listener(Self::reject))
|
.on_action(cx.listener(Self::reject))
|
||||||
@@ -568,7 +616,32 @@ impl Render for AgentDiff {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.size_full()
|
.size_full()
|
||||||
.when(is_empty, |el| el.child("No changes to review"))
|
.when(is_empty, |el| {
|
||||||
|
el.child(
|
||||||
|
v_flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child("No changes to review")
|
||||||
|
.child(
|
||||||
|
Button::new("continue-iterating", "Continue Iterating")
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.icon(IconName::ForwardArrow)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.full_width()
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&ToggleFocus,
|
||||||
|
&focus_handle.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(ToggleFocus.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.when(!is_empty, |el| el.child(self.editor.clone()))
|
.when(!is_empty, |el| el.child(self.editor.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,6 +658,11 @@ fn render_diff_hunk_controls(
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
|
|
||||||
|
if agent_diff.read(cx).thread.read(cx).is_generating() {
|
||||||
|
return Empty.into_any();
|
||||||
|
}
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.h(line_height)
|
.h(line_height)
|
||||||
.mr_0p5()
|
.mr_0p5()
|
||||||
@@ -604,7 +682,7 @@ fn render_diff_hunk_controls(
|
|||||||
.disabled(is_created_file)
|
.disabled(is_created_file)
|
||||||
.key_binding(
|
.key_binding(
|
||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(
|
||||||
&crate::Reject,
|
&Reject,
|
||||||
&editor.read(cx).focus_handle(cx),
|
&editor.read(cx).focus_handle(cx),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -625,13 +703,8 @@ fn render_diff_hunk_controls(
|
|||||||
}),
|
}),
|
||||||
Button::new(("keep", row as u64), "Keep")
|
Button::new(("keep", row as u64), "Keep")
|
||||||
.key_binding(
|
.key_binding(
|
||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
|
||||||
&crate::Keep,
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
&editor.read(cx).focus_handle(cx),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.map(|kb| kb.size(rems_from_px(12.))),
|
|
||||||
)
|
)
|
||||||
.on_click({
|
.on_click({
|
||||||
let agent_diff = agent_diff.clone();
|
let agent_diff = agent_diff.clone();
|
||||||
@@ -740,15 +813,11 @@ impl editor::Addon for AgentDiffAddon {
|
|||||||
|
|
||||||
pub struct AgentDiffToolbar {
|
pub struct AgentDiffToolbar {
|
||||||
agent_diff: Option<WeakEntity<AgentDiff>>,
|
agent_diff: Option<WeakEntity<AgentDiff>>,
|
||||||
_workspace: WeakEntity<Workspace>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentDiffToolbar {
|
impl AgentDiffToolbar {
|
||||||
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self { agent_diff: None }
|
||||||
agent_diff: None,
|
|
||||||
_workspace: workspace.weak_handle(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn agent_diff(&self, _: &App) -> Option<Entity<AgentDiff>> {
|
fn agent_diff(&self, _: &App) -> Option<Entity<AgentDiff>> {
|
||||||
@@ -795,18 +864,26 @@ impl ToolbarItemView for AgentDiffToolbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentDiffToolbar {
|
impl Render for AgentDiffToolbar {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let agent_diff = match self.agent_diff(cx) {
|
let agent_diff = match self.agent_diff(cx) {
|
||||||
Some(ad) => ad,
|
Some(ad) => ad,
|
||||||
None => return div(),
|
None => return div(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_empty = agent_diff.read(cx).multibuffer.read(cx).is_empty();
|
let is_generating = agent_diff.read(cx).thread.read(cx).is_generating();
|
||||||
|
if is_generating {
|
||||||
|
return div()
|
||||||
|
.w(rems(6.5625)) // Arbitrary 105px size—so the label doesn't dance around
|
||||||
|
.child(AnimatedLabel::new("Generating"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_empty = agent_diff.read(cx).multibuffer.read(cx).is_empty();
|
||||||
if is_empty {
|
if is_empty {
|
||||||
return div();
|
return div();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let focus_handle = agent_diff.focus_handle(cx);
|
||||||
|
|
||||||
h_group_xl()
|
h_group_xl()
|
||||||
.my_neg_1()
|
.my_neg_1()
|
||||||
.items_center()
|
.items_center()
|
||||||
@@ -816,15 +893,25 @@ impl Render for AgentDiffToolbar {
|
|||||||
.child(
|
.child(
|
||||||
h_group_sm()
|
h_group_sm()
|
||||||
.child(
|
.child(
|
||||||
Button::new("reject-all", "Reject All").on_click(cx.listener(
|
Button::new("reject-all", "Reject All")
|
||||||
|this, _, window, cx| {
|
.key_binding({
|
||||||
this.dispatch_action(&crate::RejectAll, window, cx)
|
KeyBinding::for_action_in(&RejectAll, &focus_handle, window, cx)
|
||||||
},
|
.map(|kb| kb.size(rems_from_px(12.)))
|
||||||
)),
|
})
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.dispatch_action(&RejectAll, window, cx)
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
.child(Button::new("keep-all", "Keep All").on_click(cx.listener(
|
.child(
|
||||||
|this, _, window, cx| this.dispatch_action(&crate::KeepAll, window, cx),
|
Button::new("keep-all", "Keep All")
|
||||||
))),
|
.key_binding({
|
||||||
|
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.)))
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.dispatch_action(&KeepAll, window, cx)
|
||||||
|
})),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -834,6 +921,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ThreadStore, thread_store};
|
use crate::{ThreadStore, thread_store};
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
|
use assistant_tool::ToolWorkingSet;
|
||||||
use context_server::ContextServerSettings;
|
use context_server::ContextServerSettings;
|
||||||
use editor::EditorSettings;
|
use editor::EditorSettings;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
@@ -853,6 +941,7 @@ mod tests {
|
|||||||
language::init(cx);
|
language::init(cx);
|
||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
AssistantSettings::register(cx);
|
AssistantSettings::register(cx);
|
||||||
|
prompt_store::init(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
ThemeSettings::register(cx);
|
ThemeSettings::register(cx);
|
||||||
@@ -873,15 +962,17 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let thread_store = cx.update(|cx| {
|
let thread_store = cx
|
||||||
ThreadStore::new(
|
.update(|cx| {
|
||||||
project.clone(),
|
ThreadStore::load(
|
||||||
Arc::default(),
|
project.clone(),
|
||||||
Arc::new(PromptBuilder::new(None).unwrap()),
|
cx.new(|_| ToolWorkingSet::default()),
|
||||||
cx,
|
Arc::new(PromptBuilder::new(None).unwrap()),
|
||||||
)
|
cx,
|
||||||
.unwrap()
|
)
|
||||||
});
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||||
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
|
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
|
||||||
|
|
||||||
@@ -897,7 +988,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.update(|_, cx| {
|
cx.update(|_, cx| {
|
||||||
action_log.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
|
action_log.update(cx, |log, cx| log.track_buffer(buffer.clone(), cx));
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer
|
buffer
|
||||||
.edit(
|
.edit(
|
||||||
@@ -942,7 +1033,7 @@ mod tests {
|
|||||||
Point::new(3, 0)..Point::new(3, 0)
|
Point::new(3, 0)..Point::new(3, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restoring a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||||
editor.update_in(cx, |editor, window, cx| {
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
editor.change_selections(None, window, cx, |selections| {
|
editor.change_selections(None, window, cx, |selections| {
|
||||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ mod terminal_inline_assistant;
|
|||||||
mod thread;
|
mod thread;
|
||||||
mod thread_history;
|
mod thread_history;
|
||||||
mod thread_store;
|
mod thread_store;
|
||||||
|
mod tool_compatibility;
|
||||||
mod tool_use;
|
mod tool_use;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
@@ -39,19 +40,19 @@ pub use crate::active_thread::ActiveThread;
|
|||||||
use crate::assistant_configuration::{AddContextServerModal, ManageProfilesModal};
|
use crate::assistant_configuration::{AddContextServerModal, ManageProfilesModal};
|
||||||
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
|
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
|
||||||
pub use crate::inline_assistant::InlineAssistant;
|
pub use crate::inline_assistant::InlineAssistant;
|
||||||
pub use crate::thread::{Message, RequestKind, Thread, ThreadEvent};
|
pub use crate::thread::{Message, Thread, ThreadEvent};
|
||||||
pub use crate::thread_store::ThreadStore;
|
pub use crate::thread_store::ThreadStore;
|
||||||
pub use agent_diff::{AgentDiff, AgentDiffToolbar};
|
pub use agent_diff::{AgentDiff, AgentDiffToolbar};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
agent,
|
agent,
|
||||||
[
|
[
|
||||||
NewPromptEditor,
|
NewTextThread,
|
||||||
ToggleContextPicker,
|
ToggleContextPicker,
|
||||||
ToggleProfileSelector,
|
ToggleProfileSelector,
|
||||||
RemoveAllContext,
|
RemoveAllContext,
|
||||||
|
ExpandMessageEditor,
|
||||||
OpenHistory,
|
OpenHistory,
|
||||||
OpenConfiguration,
|
|
||||||
AddContextServer,
|
AddContextServer,
|
||||||
RemoveSelectedThread,
|
RemoveSelectedThread,
|
||||||
Chat,
|
Chat,
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ use assistant_tool::{ToolSource, ToolWorkingSet};
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::ContextServerManager;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription};
|
use gpui::{
|
||||||
|
Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription,
|
||||||
|
};
|
||||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||||
use settings::{Settings, update_settings_file};
|
use settings::{Settings, update_settings_file};
|
||||||
use ui::{Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Switch, prelude::*};
|
use ui::{
|
||||||
|
Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState,
|
||||||
|
Switch, SwitchColor, Tooltip, prelude::*,
|
||||||
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use zed_actions::ExtensionCategoryFilter;
|
use zed_actions::ExtensionCategoryFilter;
|
||||||
|
|
||||||
@@ -27,15 +32,17 @@ pub struct AssistantConfiguration {
|
|||||||
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
expanded_context_server_tools: HashMap<Arc<str>, bool>,
|
expanded_context_server_tools: HashMap<Arc<str>, bool>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
_registry_subscription: Subscription,
|
_registry_subscription: Subscription,
|
||||||
|
scroll_handle: ScrollHandle,
|
||||||
|
scrollbar_state: ScrollbarState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantConfiguration {
|
impl AssistantConfiguration {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -58,6 +65,9 @@ impl AssistantConfiguration {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let scroll_handle = ScrollHandle::new();
|
||||||
|
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
fs,
|
fs,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
@@ -66,6 +76,8 @@ impl AssistantConfiguration {
|
|||||||
expanded_context_server_tools: HashMap::default(),
|
expanded_context_server_tools: HashMap::default(),
|
||||||
tools,
|
tools,
|
||||||
_registry_subscription: registry_subscription,
|
_registry_subscription: registry_subscription,
|
||||||
|
scroll_handle,
|
||||||
|
scrollbar_state,
|
||||||
};
|
};
|
||||||
this.build_provider_configuration_views(window, cx);
|
this.build_provider_configuration_views(window, cx);
|
||||||
this
|
this
|
||||||
@@ -107,7 +119,7 @@ pub enum AssistantConfigurationEvent {
|
|||||||
impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
|
impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
|
||||||
|
|
||||||
impl AssistantConfiguration {
|
impl AssistantConfiguration {
|
||||||
fn render_provider_configuration(
|
fn render_provider_configuration_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: &Arc<dyn LanguageModelProvider>,
|
provider: &Arc<dyn LanguageModelProvider>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
@@ -120,7 +132,11 @@ impl AssistantConfiguration {
|
|||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.pt_3()
|
||||||
|
.pb_1()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(cx.theme().colors().border.opacity(0.6))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
@@ -132,7 +148,7 @@ impl AssistantConfiguration {
|
|||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(Label::new(provider_name.clone())),
|
.child(Label::new(provider_name.clone()).size(LabelSize::Large)),
|
||||||
)
|
)
|
||||||
.when(provider.is_authenticated(cx), |parent| {
|
.when(provider.is_authenticated(cx), |parent| {
|
||||||
parent.child(
|
parent.child(
|
||||||
@@ -157,39 +173,54 @@ impl AssistantConfiguration {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.map(|parent| match configuration_view {
|
||||||
|
Some(configuration_view) => parent.child(configuration_view),
|
||||||
|
None => parent.child(div().child(Label::new(format!(
|
||||||
|
"No configuration view for {provider_name}",
|
||||||
|
)))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_provider_configuration_section(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.p(DynamicSpacing::Base16.rems(cx))
|
||||||
|
.pr(DynamicSpacing::Base20.rems(cx))
|
||||||
|
.gap_4()
|
||||||
|
.flex_1()
|
||||||
.child(
|
.child(
|
||||||
div()
|
v_flex()
|
||||||
.p(DynamicSpacing::Base08.rems(cx))
|
.gap_0p5()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.child(Headline::new("LLM Providers"))
|
||||||
.border_1()
|
.child(
|
||||||
.border_color(cx.theme().colors().border_variant)
|
Label::new("Add at least one provider to use AI-powered features.")
|
||||||
.rounded_sm()
|
.color(Color::Muted),
|
||||||
.map(|parent| match configuration_view {
|
),
|
||||||
Some(configuration_view) => parent.child(configuration_view),
|
)
|
||||||
None => parent.child(div().child(Label::new(format!(
|
.children(
|
||||||
"No configuration view for {provider_name}",
|
providers
|
||||||
)))),
|
.into_iter()
|
||||||
}),
|
.map(|provider| self.render_provider_configuration_block(&provider, cx)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = AssistantSettings::get_global(cx).always_allow_tool_actions;
|
||||||
|
|
||||||
const HEADING: &str = "Allow running tools without asking for confirmation";
|
const HEADING: &str = "Allow running editing tools without asking for confirmation";
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
.p(DynamicSpacing::Base16.rems(cx))
|
||||||
|
.pr(DynamicSpacing::Base20.rems(cx))
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child(Headline::new("General Settings").size(HeadlineSize::Small))
|
.child(Headline::new("General Settings"))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.p_2p5()
|
|
||||||
.rounded_sm()
|
|
||||||
.bg(cx.theme().colors().editor_background)
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.flex_wrap()
|
.flex_wrap()
|
||||||
@@ -205,6 +236,7 @@ impl AssistantConfiguration {
|
|||||||
"always-allow-tool-actions-switch",
|
"always-allow-tool-actions-switch",
|
||||||
always_allow_tool_actions.into(),
|
always_allow_tool_actions.into(),
|
||||||
)
|
)
|
||||||
|
.color(SwitchColor::Accent)
|
||||||
.on_click({
|
.on_click({
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
move |state, _window, cx| {
|
move |state, _window, cx| {
|
||||||
@@ -224,19 +256,20 @@ impl AssistantConfiguration {
|
|||||||
|
|
||||||
fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let context_servers = self.context_server_manager.read(cx).all_servers().clone();
|
let context_servers = self.context_server_manager.read(cx).all_servers().clone();
|
||||||
let tools_by_source = self.tools.tools_by_source(cx);
|
let tools_by_source = self.tools.read(cx).tools_by_source(cx);
|
||||||
let empty = Vec::new();
|
let empty = Vec::new();
|
||||||
|
|
||||||
const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly.";
|
const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly.";
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
.p(DynamicSpacing::Base16.rems(cx))
|
||||||
|
.pr(DynamicSpacing::Base20.rems(cx))
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.child(Headline::new("Context Servers (MCP)").size(HeadlineSize::Small))
|
.child(Headline::new("Model Context Protocol (MCP) Servers"))
|
||||||
.child(Label::new(SUBHEADING).color(Color::Muted)),
|
.child(Label::new(SUBHEADING).color(Color::Muted)),
|
||||||
)
|
)
|
||||||
.children(context_servers.into_iter().map(|context_server| {
|
.children(context_servers.into_iter().map(|context_server| {
|
||||||
@@ -257,15 +290,14 @@ impl AssistantConfiguration {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.id(SharedString::from(context_server.id()))
|
.id(SharedString::from(context_server.id()))
|
||||||
.border_1()
|
.border_1()
|
||||||
.rounded_sm()
|
.rounded_md()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().background.opacity(0.25))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.p_1()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.px_2()
|
.when(are_tools_expanded && tool_count > 1, |element| {
|
||||||
.py_1()
|
|
||||||
.when(are_tools_expanded, |element| {
|
|
||||||
element
|
element
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
@@ -275,6 +307,7 @@ impl AssistantConfiguration {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
Disclosure::new("tool-list-disclosure", are_tools_expanded)
|
Disclosure::new("tool-list-disclosure", are_tools_expanded)
|
||||||
|
.disabled(tool_count == 0)
|
||||||
.on_click(cx.listener({
|
.on_click(cx.listener({
|
||||||
let context_server_id = context_server.id();
|
let context_server_id = context_server.id();
|
||||||
move |this, _event, _window, _cx| {
|
move |this, _event, _window, _cx| {
|
||||||
@@ -295,65 +328,78 @@ impl AssistantConfiguration {
|
|||||||
.child(Label::new(context_server.id()))
|
.child(Label::new(context_server.id()))
|
||||||
.child(
|
.child(
|
||||||
Label::new(format!("{tool_count} tools"))
|
Label::new(format!("{tool_count} tools"))
|
||||||
.color(Color::Muted),
|
.color(Color::Muted)
|
||||||
|
.size(LabelSize::Small),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(h_flex().child(
|
.child(
|
||||||
Switch::new("context-server-switch", is_running.into()).on_click({
|
Switch::new("context-server-switch", is_running.into())
|
||||||
let context_server_manager =
|
.color(SwitchColor::Accent)
|
||||||
self.context_server_manager.clone();
|
.on_click({
|
||||||
let context_server = context_server.clone();
|
let context_server_manager =
|
||||||
move |state, _window, cx| match state {
|
self.context_server_manager.clone();
|
||||||
ToggleState::Unselected | ToggleState::Indeterminate => {
|
let context_server = context_server.clone();
|
||||||
context_server_manager.update(cx, |this, cx| {
|
move |state, _window, cx| match state {
|
||||||
this.stop_server(context_server.clone(), cx)
|
ToggleState::Unselected
|
||||||
.log_err();
|
| ToggleState::Indeterminate => {
|
||||||
});
|
context_server_manager.update(cx, |this, cx| {
|
||||||
}
|
this.stop_server(context_server.clone(), cx)
|
||||||
ToggleState::Selected => {
|
.log_err();
|
||||||
cx.spawn({
|
});
|
||||||
let context_server_manager =
|
}
|
||||||
context_server_manager.clone();
|
ToggleState::Selected => {
|
||||||
let context_server = context_server.clone();
|
cx.spawn({
|
||||||
async move |cx| {
|
let context_server_manager =
|
||||||
if let Some(start_server_task) =
|
context_server_manager.clone();
|
||||||
context_server_manager
|
let context_server = context_server.clone();
|
||||||
.update(cx, |this, cx| {
|
async move |cx| {
|
||||||
this.start_server(
|
if let Some(start_server_task) =
|
||||||
context_server,
|
context_server_manager
|
||||||
cx,
|
.update(cx, |this, cx| {
|
||||||
)
|
this.start_server(
|
||||||
})
|
context_server,
|
||||||
.log_err()
|
cx,
|
||||||
{
|
)
|
||||||
start_server_task.await.log_err();
|
})
|
||||||
|
.log_err()
|
||||||
|
{
|
||||||
|
start_server_task.await.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.detach();
|
||||||
.detach();
|
}
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
}),
|
),
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
.map(|parent| {
|
.map(|parent| {
|
||||||
if !are_tools_expanded {
|
if !are_tools_expanded {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.child(v_flex().children(tools.into_iter().enumerate().map(
|
parent.child(v_flex().py_1p5().px_1().gap_1().children(
|
||||||
|(ix, tool)| {
|
tools.into_iter().enumerate().map(|(ix, tool)| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.px_2()
|
.id(("tool-item", ix))
|
||||||
.py_1()
|
.px_1()
|
||||||
.when(ix < tool_count - 1, |element| {
|
.gap_2()
|
||||||
element
|
.justify_between()
|
||||||
.border_b_1()
|
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||||
.border_color(cx.theme().colors().border)
|
.rounded_sm()
|
||||||
})
|
.child(
|
||||||
.child(Label::new(tool.name()))
|
Label::new(tool.name())
|
||||||
},
|
.buffer_font(cx)
|
||||||
)))
|
.size(LabelSize::Small),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Info)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Ignored),
|
||||||
|
)
|
||||||
|
.tooltip(Tooltip::text(tool.description()))
|
||||||
|
}),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
@@ -362,7 +408,7 @@ impl AssistantConfiguration {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
h_flex().w_full().child(
|
h_flex().w_full().child(
|
||||||
Button::new("add-context-server", "Add Context Server")
|
Button::new("add-context-server", "Add Custom Server")
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.layer(ElevationIndex::ModalSurface)
|
||||||
.full_width()
|
.full_width()
|
||||||
@@ -378,7 +424,7 @@ impl AssistantConfiguration {
|
|||||||
h_flex().w_full().child(
|
h_flex().w_full().child(
|
||||||
Button::new(
|
Button::new(
|
||||||
"install-context-server-extensions",
|
"install-context-server-extensions",
|
||||||
"Install Context Server Extensions",
|
"Install MCP Extensions",
|
||||||
)
|
)
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.layer(ElevationIndex::ModalSurface)
|
||||||
@@ -405,38 +451,51 @@ impl AssistantConfiguration {
|
|||||||
|
|
||||||
impl Render for AssistantConfiguration {
|
impl Render for AssistantConfiguration {
|
||||||
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 providers = LanguageModelRegistry::read_global(cx).providers();
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("assistant-configuration")
|
.id("assistant-configuration")
|
||||||
|
.key_context("AgentConfiguration")
|
||||||
.track_focus(&self.focus_handle(cx))
|
.track_focus(&self.focus_handle(cx))
|
||||||
.bg(cx.theme().colors().panel_background)
|
.relative()
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_y_scroll()
|
.pb_8()
|
||||||
.child(self.render_command_permission(cx))
|
.bg(cx.theme().colors().panel_background)
|
||||||
.child(Divider::horizontal().color(DividerColor::Border))
|
|
||||||
.child(self.render_context_servers_section(cx))
|
|
||||||
.child(Divider::horizontal().color(DividerColor::Border))
|
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
.id("assistant-configuration-content")
|
||||||
.mt_1()
|
.track_scroll(&self.scroll_handle)
|
||||||
.gap_6()
|
.size_full()
|
||||||
.flex_1()
|
.overflow_y_scroll()
|
||||||
.child(
|
.child(self.render_command_permission(cx))
|
||||||
v_flex()
|
.child(Divider::horizontal().color(DividerColor::Border))
|
||||||
.gap_0p5()
|
.child(self.render_context_servers_section(cx))
|
||||||
.child(Headline::new("LLM Providers").size(HeadlineSize::Small))
|
.child(Divider::horizontal().color(DividerColor::Border))
|
||||||
.child(
|
.child(self.render_provider_configuration_section(cx)),
|
||||||
Label::new("Add at least one provider to use AI-powered features.")
|
)
|
||||||
.color(Color::Muted),
|
.child(
|
||||||
),
|
div()
|
||||||
)
|
.id("assistant-configuration-scrollbar")
|
||||||
.children(
|
.occlude()
|
||||||
providers
|
.absolute()
|
||||||
.into_iter()
|
.right(px(3.))
|
||||||
.map(|provider| self.render_provider_configuration(&provider, cx)),
|
.top_0()
|
||||||
),
|
.bottom_0()
|
||||||
|
.pb_6()
|
||||||
|
.w(px(12.))
|
||||||
|
.cursor_default()
|
||||||
|
.on_mouse_move(cx.listener(|_, _, _window, cx| {
|
||||||
|
cx.notify();
|
||||||
|
cx.stop_propagation()
|
||||||
|
}))
|
||||||
|
.on_hover(|_, _window, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_any_mouse_down(|_, _window, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use context_server::{ContextServerSettings, ServerCommand, ServerConfig};
|
use context_server::{ContextServerSettings, ServerCommand, ServerConfig};
|
||||||
use editor::Editor;
|
|
||||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use ui::{Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||||
|
use ui_input::SingleLineInput;
|
||||||
use workspace::{ModalView, Workspace};
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
use crate::AddContextServer;
|
use crate::AddContextServer;
|
||||||
|
|
||||||
pub struct AddContextServerModal {
|
pub struct AddContextServerModal {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
name_editor: Entity<Editor>,
|
name_editor: Entity<SingleLineInput>,
|
||||||
command_editor: Entity<Editor>,
|
command_editor: Entity<SingleLineInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddContextServerModal {
|
impl AddContextServerModal {
|
||||||
@@ -33,15 +33,10 @@ impl AddContextServerModal {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name_editor = cx.new(|cx| Editor::single_line(window, cx));
|
let name_editor =
|
||||||
let command_editor = cx.new(|cx| Editor::single_line(window, cx));
|
cx.new(|cx| SingleLineInput::new(window, cx, "my-custom-server").label("Name"));
|
||||||
|
let command_editor = cx.new(|cx| {
|
||||||
name_editor.update(cx, |editor, cx| {
|
SingleLineInput::new(window, cx, "Command").label("Command to run the MCP server")
|
||||||
editor.set_placeholder_text("Context server name", cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
command_editor.update(cx, |editor, cx| {
|
|
||||||
editor.set_placeholder_text("Command to run the context server", cx);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -51,9 +46,23 @@ impl AddContextServerModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, cx: &mut Context<Self>) {
|
fn confirm(&mut self, _: &menu::Confirm, cx: &mut Context<Self>) {
|
||||||
let name = self.name_editor.read(cx).text(cx).trim().to_string();
|
let name = self
|
||||||
let command = self.command_editor.read(cx).text(cx).trim().to_string();
|
.name_editor
|
||||||
|
.read(cx)
|
||||||
|
.editor()
|
||||||
|
.read(cx)
|
||||||
|
.text(cx)
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
let command = self
|
||||||
|
.command_editor
|
||||||
|
.read(cx)
|
||||||
|
.editor()
|
||||||
|
.read(cx)
|
||||||
|
.text(cx)
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
if name.is_empty() || command.is_empty() {
|
if name.is_empty() || command.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -87,7 +96,7 @@ impl AddContextServerModal {
|
|||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel(&mut self, cx: &mut Context<Self>) {
|
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,48 +112,68 @@ impl Focusable for AddContextServerModal {
|
|||||||
impl EventEmitter<DismissEvent> for AddContextServerModal {}
|
impl EventEmitter<DismissEvent> for AddContextServerModal {}
|
||||||
|
|
||||||
impl Render for AddContextServerModal {
|
impl Render for AddContextServerModal {
|
||||||
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 is_name_empty = self.name_editor.read(cx).text(cx).trim().is_empty();
|
let is_name_empty = self.name_editor.read(cx).is_empty(cx);
|
||||||
let is_command_empty = self.command_editor.read(cx).text(cx).trim().is_empty();
|
let is_command_empty = self.command_editor.read(cx).is_empty(cx);
|
||||||
|
|
||||||
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
.w(rems(34.))
|
.w(rems(34.))
|
||||||
.key_context("AddContextServerModal")
|
.key_context("AddContextServerModal")
|
||||||
.on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(cx)))
|
.on_action(
|
||||||
.on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
|
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||||
|
)
|
||||||
|
.on_action(
|
||||||
|
cx.listener(|this, _: &menu::Confirm, _window, cx| {
|
||||||
|
this.confirm(&menu::Confirm, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
this.focus_handle(cx).focus(window);
|
this.focus_handle(cx).focus(window);
|
||||||
}))
|
}))
|
||||||
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
||||||
.child(
|
.child(
|
||||||
Modal::new("add-context-server", None)
|
Modal::new("add-context-server", None)
|
||||||
.header(ModalHeader::new().headline("Add Context Server"))
|
.header(ModalHeader::new().headline("Add MCP Server"))
|
||||||
.section(
|
.section(
|
||||||
Section::new()
|
Section::new().child(
|
||||||
.child(
|
v_flex()
|
||||||
v_flex()
|
.gap_2()
|
||||||
.gap_1()
|
.child(self.name_editor.clone())
|
||||||
.child(Label::new("Name"))
|
.child(self.command_editor.clone()),
|
||||||
.child(self.name_editor.clone()),
|
),
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(Label::new("Command"))
|
|
||||||
.child(self.command_editor.clone()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.footer(
|
.footer(
|
||||||
ModalFooter::new()
|
ModalFooter::new()
|
||||||
.start_slot(
|
.start_slot(
|
||||||
Button::new("cancel", "Cancel").on_click(
|
Button::new("cancel", "Cancel")
|
||||||
cx.listener(|this, _event, _window, cx| this.cancel(cx)),
|
.key_binding(
|
||||||
),
|
KeyBinding::for_action_in(
|
||||||
|
&menu::Cancel,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
|
this.cancel(&menu::Cancel, cx)
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
.end_slot(
|
.end_slot(
|
||||||
Button::new("add-server", "Add Server")
|
Button::new("add-server", "Add Server")
|
||||||
.disabled(is_name_empty || is_command_empty)
|
.disabled(is_name_empty || is_command_empty)
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&menu::Confirm,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
.map(|button| {
|
.map(|button| {
|
||||||
if is_name_empty {
|
if is_name_empty {
|
||||||
button.tooltip(Tooltip::text("Name is required"))
|
button.tooltip(Tooltip::text("Name is required"))
|
||||||
@@ -154,9 +183,9 @@ impl Render for AddContextServerModal {
|
|||||||
button
|
button
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
cx.listener(|this, _event, _window, cx| this.confirm(cx)),
|
this.confirm(&menu::Confirm, cx)
|
||||||
),
|
})),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ pub struct NewProfileMode {
|
|||||||
|
|
||||||
pub struct ManageProfilesModal {
|
pub struct ManageProfilesModal {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
@@ -117,7 +117,7 @@ impl ManageProfilesModal {
|
|||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ pub struct ToolPickerDelegate {
|
|||||||
impl ToolPickerDelegate {
|
impl ToolPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
tool_set: Arc<ToolWorkingSet>,
|
tool_set: Entity<ToolWorkingSet>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
profile_id: AgentProfileId,
|
profile_id: AgentProfileId,
|
||||||
profile: AgentProfile,
|
profile: AgentProfile,
|
||||||
@@ -68,7 +68,7 @@ impl ToolPickerDelegate {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let mut tool_entries = Vec::new();
|
let mut tool_entries = Vec::new();
|
||||||
|
|
||||||
for (source, tools) in tool_set.tools_by_source(cx) {
|
for (source, tools) in tool_set.read(cx).tools_by_source(cx) {
|
||||||
tool_entries.extend(tools.into_iter().map(|tool| ToolEntry {
|
tool_entries.extend(tools.into_iter().map(|tool| ToolEntry {
|
||||||
name: tool.name().into(),
|
name: tool.name().into(),
|
||||||
source: source.clone(),
|
source: source.clone(),
|
||||||
@@ -192,7 +192,7 @@ impl PickerDelegate for ToolPickerDelegate {
|
|||||||
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| {
|
||||||
this.load_profile(&self.profile, cx);
|
this.load_profile(self.profile.clone(), cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
@@ -202,43 +202,43 @@ impl PickerDelegate for ToolPickerDelegate {
|
|||||||
let default_profile = self.profile.clone();
|
let default_profile = self.profile.clone();
|
||||||
let tool = tool.clone();
|
let tool = tool.clone();
|
||||||
move |settings, _cx| match settings {
|
move |settings, _cx| match settings {
|
||||||
AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(
|
AssistantSettingsContent::Versioned(boxed) => {
|
||||||
settings,
|
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||||
)) => {
|
let profiles = settings.profiles.get_or_insert_default();
|
||||||
let profiles = settings.profiles.get_or_insert_default();
|
let profile =
|
||||||
let profile =
|
profiles
|
||||||
profiles
|
.entry(profile_id)
|
||||||
.entry(profile_id)
|
.or_insert_with(|| AgentProfileContent {
|
||||||
.or_insert_with(|| AgentProfileContent {
|
name: default_profile.name.into(),
|
||||||
name: default_profile.name.into(),
|
tools: default_profile.tools,
|
||||||
tools: default_profile.tools,
|
enable_all_context_servers: Some(
|
||||||
enable_all_context_servers: Some(
|
default_profile.enable_all_context_servers,
|
||||||
default_profile.enable_all_context_servers,
|
),
|
||||||
),
|
context_servers: default_profile
|
||||||
context_servers: default_profile
|
.context_servers
|
||||||
.context_servers
|
.into_iter()
|
||||||
.into_iter()
|
.map(|(server_id, preset)| {
|
||||||
.map(|(server_id, preset)| {
|
(
|
||||||
(
|
server_id,
|
||||||
server_id,
|
ContextServerPresetContent {
|
||||||
ContextServerPresetContent {
|
tools: preset.tools,
|
||||||
tools: preset.tools,
|
},
|
||||||
},
|
)
|
||||||
)
|
})
|
||||||
})
|
.collect(),
|
||||||
.collect(),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
match tool.source {
|
match tool.source {
|
||||||
ToolSource::Native => {
|
ToolSource::Native => {
|
||||||
*profile.tools.entry(tool.name).or_default() = is_enabled;
|
*profile.tools.entry(tool.name).or_default() = is_enabled;
|
||||||
}
|
}
|
||||||
ToolSource::ContextServer { id } => {
|
ToolSource::ContextServer { id } => {
|
||||||
let preset = profile
|
let preset = profile
|
||||||
.context_servers
|
.context_servers
|
||||||
.entry(id.clone().into())
|
.entry(id.clone().into())
|
||||||
.or_default();
|
.or_default();
|
||||||
*preset.tools.entry(tool.name.clone()).or_default() = is_enabled;
|
*preset.tools.entry(tool.name.clone()).or_default() = is_enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Entity, FocusHandle, SharedString};
|
use gpui::{Entity, FocusHandle, SharedString};
|
||||||
use language_model::LanguageModelRegistry;
|
|
||||||
use language_model_selector::{
|
use language_model_selector::{
|
||||||
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
|
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
|
||||||
};
|
};
|
||||||
@@ -9,6 +9,8 @@ use settings::update_settings_file;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
|
|
||||||
|
pub use language_model_selector::ModelType;
|
||||||
|
|
||||||
pub struct AssistantModelSelector {
|
pub struct AssistantModelSelector {
|
||||||
selector: Entity<LanguageModelSelector>,
|
selector: Entity<LanguageModelSelector>,
|
||||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||||
@@ -20,6 +22,7 @@ impl AssistantModelSelector {
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
model_type: ModelType,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -28,12 +31,34 @@ impl AssistantModelSelector {
|
|||||||
let fs = fs.clone();
|
let fs = fs.clone();
|
||||||
LanguageModelSelector::new(
|
LanguageModelSelector::new(
|
||||||
move |model, cx| {
|
move |model, cx| {
|
||||||
update_settings_file::<AssistantSettings>(
|
let provider = model.provider_id().0.to_string();
|
||||||
fs.clone(),
|
let model_id = model.id().0.to_string();
|
||||||
cx,
|
|
||||||
move |settings, _cx| settings.set_model(model.clone()),
|
match model_type {
|
||||||
);
|
ModelType::Default => {
|
||||||
|
update_settings_file::<AssistantSettings>(
|
||||||
|
fs.clone(),
|
||||||
|
cx,
|
||||||
|
move |settings, _cx| {
|
||||||
|
settings.set_model(model.clone());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ModelType::InlineAssistant => {
|
||||||
|
update_settings_file::<AssistantSettings>(
|
||||||
|
fs.clone(),
|
||||||
|
cx,
|
||||||
|
move |settings, _cx| {
|
||||||
|
settings.set_inline_assistant_model(
|
||||||
|
provider.clone(),
|
||||||
|
model_id.clone(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
model_type,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -50,11 +75,12 @@ impl AssistantModelSelector {
|
|||||||
|
|
||||||
impl Render for AssistantModelSelector {
|
impl Render for AssistantModelSelector {
|
||||||
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 active_model = LanguageModelRegistry::read_global(cx).active_model();
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
let model_name = match active_model {
|
|
||||||
Some(model) => model.name().0,
|
let model = self.selector.read(cx).active_model(cx);
|
||||||
_ => SharedString::from("No model selected"),
|
let (model_name, model_icon) = match model {
|
||||||
|
Some(model) => (model.model.name().0, Some(model.provider.icon())),
|
||||||
|
_ => (SharedString::from("No model selected"), None),
|
||||||
};
|
};
|
||||||
|
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
LanguageModelSelectorPopoverMenu::new(
|
||||||
@@ -64,10 +90,16 @@ impl Render for AssistantModelSelector {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
|
.children(
|
||||||
|
model_icon.map(|icon| {
|
||||||
|
Icon::new(icon).color(Color::Muted).size(IconSize::Small)
|
||||||
|
}),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
Label::new(model_name)
|
Label::new(model_name)
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.color(Color::Muted),
|
.color(Color::Muted)
|
||||||
|
.ml_1(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::ChevronDown)
|
Icon::new(IconName::ChevronDown)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::context::attach_context_to_message;
|
use crate::context::attach_context_to_message;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::inline_prompt_editor::CodegenStatus;
|
use crate::inline_prompt_editor::CodegenStatus;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::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};
|
||||||
@@ -28,7 +28,7 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
|
|
||||||
pub struct BufferCodegen {
|
pub struct BufferCodegen {
|
||||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
alternatives: Vec<Entity<CodegenAlternative>>,
|
||||||
@@ -131,7 +131,12 @@ impl BufferCodegen {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, user_prompt: String, cx: &mut Context<Self>) -> Result<()> {
|
pub fn start(
|
||||||
|
&mut self,
|
||||||
|
primary_model: Arc<dyn LanguageModel>,
|
||||||
|
user_prompt: String,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
let alternative_models = LanguageModelRegistry::read_global(cx)
|
let alternative_models = LanguageModelRegistry::read_global(cx)
|
||||||
.inline_alternative_models()
|
.inline_alternative_models()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
@@ -155,10 +160,6 @@ impl BufferCodegen {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let primary_model = LanguageModelRegistry::read_global(cx)
|
|
||||||
.active_model()
|
|
||||||
.context("no active model")?;
|
|
||||||
|
|
||||||
for (model, alternative) in iter::once(primary_model)
|
for (model, alternative) in iter::once(primary_model)
|
||||||
.chain(alternative_models)
|
.chain(alternative_models)
|
||||||
.zip(&self.alternatives)
|
.zip(&self.alternatives)
|
||||||
@@ -424,6 +425,8 @@ impl CodegenAlternative {
|
|||||||
request_message.content.push(prompt.into());
|
request_message.content.push(prompt.into());
|
||||||
|
|
||||||
Ok(LanguageModelRequest {
|
Ok(LanguageModelRequest {
|
||||||
|
thread_id: None,
|
||||||
|
prompt_id: None,
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: None,
|
temperature: None,
|
||||||
@@ -600,7 +603,7 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||||
report_assistant_event(
|
report_assistant_event(
|
||||||
AssistantEvent {
|
AssistantEventData {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
message_id,
|
message_id,
|
||||||
kind: AssistantKind::Inline,
|
kind: AssistantKind::Inline,
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
use std::{ops::Range, sync::Arc};
|
use std::{
|
||||||
|
ops::Range,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use gpui::{App, Entity, SharedString};
|
use futures::{FutureExt, future::Shared};
|
||||||
use language::{Buffer, File};
|
use gpui::{App, Entity, SharedString, Task};
|
||||||
use language_model::LanguageModelRequestMessage;
|
use language::Buffer;
|
||||||
use project::ProjectPath;
|
use language_model::{LanguageModelImage, LanguageModelRequestMessage};
|
||||||
|
use project::{ProjectEntryId, ProjectPath, Worktree};
|
||||||
|
use prompt_store::UserPromptId;
|
||||||
|
use rope::Point;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use text::{Anchor, BufferId};
|
use text::{Anchor, BufferId};
|
||||||
use ui::IconName;
|
use ui::IconName;
|
||||||
@@ -11,6 +18,8 @@ use util::post_inc;
|
|||||||
|
|
||||||
use crate::thread::Thread;
|
use crate::thread::Thread;
|
||||||
|
|
||||||
|
pub const RULES_ICON: IconName = IconName::Context;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct ContextId(pub(crate) usize);
|
pub struct ContextId(pub(crate) usize);
|
||||||
|
|
||||||
@@ -19,12 +28,16 @@ impl ContextId {
|
|||||||
Self(post_inc(&mut self.0))
|
Self(post_inc(&mut self.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ContextKind {
|
pub enum ContextKind {
|
||||||
File,
|
File,
|
||||||
Directory,
|
Directory,
|
||||||
Symbol,
|
Symbol,
|
||||||
|
Selection,
|
||||||
FetchedUrl,
|
FetchedUrl,
|
||||||
Thread,
|
Thread,
|
||||||
|
Rules,
|
||||||
|
Image,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextKind {
|
impl ContextKind {
|
||||||
@@ -33,8 +46,11 @@ impl ContextKind {
|
|||||||
ContextKind::File => IconName::File,
|
ContextKind::File => IconName::File,
|
||||||
ContextKind::Directory => IconName::Folder,
|
ContextKind::Directory => IconName::Folder,
|
||||||
ContextKind::Symbol => IconName::Code,
|
ContextKind::Symbol => IconName::Code,
|
||||||
|
ContextKind::Selection => IconName::Context,
|
||||||
ContextKind::FetchedUrl => IconName::Globe,
|
ContextKind::FetchedUrl => IconName::Globe,
|
||||||
ContextKind::Thread => IconName::MessageBubbles,
|
ContextKind::Thread => IconName::MessageBubbles,
|
||||||
|
ContextKind::Rules => RULES_ICON,
|
||||||
|
ContextKind::Image => IconName::Image,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +62,9 @@ pub enum AssistantContext {
|
|||||||
Symbol(SymbolContext),
|
Symbol(SymbolContext),
|
||||||
FetchedUrl(FetchedUrlContext),
|
FetchedUrl(FetchedUrlContext),
|
||||||
Thread(ThreadContext),
|
Thread(ThreadContext),
|
||||||
|
Selection(SelectionContext),
|
||||||
|
Rules(RulesContext),
|
||||||
|
Image(ImageContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantContext {
|
impl AssistantContext {
|
||||||
@@ -56,6 +75,9 @@ impl AssistantContext {
|
|||||||
Self::Symbol(symbol) => symbol.id,
|
Self::Symbol(symbol) => symbol.id,
|
||||||
Self::FetchedUrl(url) => url.id,
|
Self::FetchedUrl(url) => url.id,
|
||||||
Self::Thread(thread) => thread.id,
|
Self::Thread(thread) => thread.id,
|
||||||
|
Self::Selection(selection) => selection.id,
|
||||||
|
Self::Rules(rules) => rules.id,
|
||||||
|
Self::Image(image) => image.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,10 +91,29 @@ pub struct FileContext {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DirectoryContext {
|
pub struct DirectoryContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
pub project_path: ProjectPath,
|
pub worktree: Entity<Worktree>,
|
||||||
|
pub entry_id: ProjectEntryId,
|
||||||
|
pub last_path: Arc<Path>,
|
||||||
|
/// Buffers of the files within the directory.
|
||||||
pub context_buffers: Vec<ContextBuffer>,
|
pub context_buffers: Vec<ContextBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DirectoryContext {
|
||||||
|
pub fn entry<'a>(&self, cx: &'a App) -> Option<&'a project::Entry> {
|
||||||
|
self.worktree.read(cx).entry_for_id(self.entry_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||||
|
let worktree = self.worktree.read(cx);
|
||||||
|
worktree
|
||||||
|
.entry_for_id(self.entry_id)
|
||||||
|
.map(|entry| ProjectPath {
|
||||||
|
worktree_id: worktree.id(),
|
||||||
|
path: entry.path.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SymbolContext {
|
pub struct SymbolContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
@@ -86,12 +127,11 @@ pub struct FetchedUrlContext {
|
|||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Model<Thread> holds onto the thread even if the thread is deleted. Can either handle this
|
|
||||||
// explicitly or have a WeakModel<Thread> and remove during snapshot.
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThreadContext {
|
pub struct ThreadContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
|
// TODO: Entity<Thread> holds onto the thread even if the thread is deleted. Should probably be
|
||||||
|
// a WeakEntity and handle removal from the UI when it has dropped.
|
||||||
pub thread: Entity<Thread>,
|
pub thread: Entity<Thread>,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
@@ -105,18 +145,51 @@ impl ThreadContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Model<Buffer> holds onto the buffer even if the file is deleted and closed. Should remove
|
#[derive(Debug, Clone)]
|
||||||
// the context from the message editor in this case.
|
pub struct ImageContext {
|
||||||
|
pub id: ContextId,
|
||||||
|
pub original_image: Arc<gpui::Image>,
|
||||||
|
pub image_task: Shared<Task<Option<LanguageModelImage>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageContext {
|
||||||
|
pub fn image(&self) -> Option<LanguageModelImage> {
|
||||||
|
self.image_task.clone().now_or_never().flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_loading(&self) -> bool {
|
||||||
|
self.image_task.clone().now_or_never().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_error(&self) -> bool {
|
||||||
|
self.image_task
|
||||||
|
.clone()
|
||||||
|
.now_or_never()
|
||||||
|
.map(|result| result.is_none())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ContextBuffer {
|
pub struct ContextBuffer {
|
||||||
pub id: BufferId,
|
pub id: BufferId,
|
||||||
|
// TODO: Entity<Buffer> holds onto the buffer even if the buffer is deleted. Should probably be
|
||||||
|
// a WeakEntity and handle removal from the UI when it has dropped.
|
||||||
pub buffer: Entity<Buffer>,
|
pub buffer: Entity<Buffer>,
|
||||||
pub file: Arc<dyn File>,
|
pub last_full_path: Arc<Path>,
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContextBuffer {
|
||||||
|
pub fn full_path(&self, cx: &App) -> PathBuf {
|
||||||
|
let file = self.buffer.read(cx).file();
|
||||||
|
// Note that in practice file can't be `None` because it is present when this is created and
|
||||||
|
// there's no way for buffers to go from having a file to not.
|
||||||
|
file.map_or(self.last_full_path.to_path_buf(), |file| file.full_path(cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for ContextBuffer {
|
impl std::fmt::Debug for ContextBuffer {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("ContextBuffer")
|
f.debug_struct("ContextBuffer")
|
||||||
@@ -146,85 +219,143 @@ pub struct ContextSymbolId {
|
|||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_context_to_message<'a>(
|
#[derive(Debug, Clone)]
|
||||||
message: &mut LanguageModelRequestMessage,
|
pub struct SelectionContext {
|
||||||
|
pub id: ContextId,
|
||||||
|
pub range: Range<Anchor>,
|
||||||
|
pub line_range: Range<Point>,
|
||||||
|
pub context_buffer: ContextBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RulesContext {
|
||||||
|
pub id: ContextId,
|
||||||
|
pub prompt_id: UserPromptId,
|
||||||
|
pub title: SharedString,
|
||||||
|
pub text: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a collection of contexts into a string representation
|
||||||
|
pub fn format_context_as_string<'a>(
|
||||||
contexts: impl Iterator<Item = &'a AssistantContext>,
|
contexts: impl Iterator<Item = &'a AssistantContext>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) {
|
) -> Option<String> {
|
||||||
let mut file_context = Vec::new();
|
let mut file_context = Vec::new();
|
||||||
let mut directory_context = Vec::new();
|
let mut directory_context = Vec::new();
|
||||||
let mut symbol_context = Vec::new();
|
let mut symbol_context = Vec::new();
|
||||||
|
let mut selection_context = Vec::new();
|
||||||
let mut fetch_context = Vec::new();
|
let mut fetch_context = Vec::new();
|
||||||
let mut thread_context = Vec::new();
|
let mut thread_context = Vec::new();
|
||||||
|
let mut rules_context = Vec::new();
|
||||||
|
|
||||||
for context in contexts {
|
for context in contexts {
|
||||||
match context {
|
match context {
|
||||||
AssistantContext::File(context) => file_context.push(context),
|
AssistantContext::File(context) => file_context.push(context),
|
||||||
AssistantContext::Directory(context) => directory_context.push(context),
|
AssistantContext::Directory(context) => directory_context.push(context),
|
||||||
AssistantContext::Symbol(context) => symbol_context.push(context),
|
AssistantContext::Symbol(context) => symbol_context.push(context),
|
||||||
|
AssistantContext::Selection(context) => selection_context.push(context),
|
||||||
AssistantContext::FetchedUrl(context) => fetch_context.push(context),
|
AssistantContext::FetchedUrl(context) => fetch_context.push(context),
|
||||||
AssistantContext::Thread(context) => thread_context.push(context),
|
AssistantContext::Thread(context) => thread_context.push(context),
|
||||||
|
AssistantContext::Rules(context) => rules_context.push(context),
|
||||||
|
AssistantContext::Image(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut context_chunks = Vec::new();
|
if file_context.is_empty()
|
||||||
|
&& directory_context.is_empty()
|
||||||
|
&& symbol_context.is_empty()
|
||||||
|
&& selection_context.is_empty()
|
||||||
|
&& fetch_context.is_empty()
|
||||||
|
&& thread_context.is_empty()
|
||||||
|
&& rules_context.is_empty()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = String::new();
|
||||||
|
result.push_str("\n<context>\n\
|
||||||
|
The following items were attached by the user. You don't need to use other tools to read them.\n\n");
|
||||||
|
|
||||||
if !file_context.is_empty() {
|
if !file_context.is_empty() {
|
||||||
context_chunks.push("<files>\n");
|
result.push_str("<files>\n");
|
||||||
for context in file_context {
|
for context in file_context {
|
||||||
context_chunks.push(&context.context_buffer.text);
|
result.push_str(&context.context_buffer.text);
|
||||||
}
|
}
|
||||||
context_chunks.push("\n</files>\n");
|
result.push_str("</files>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !directory_context.is_empty() {
|
if !directory_context.is_empty() {
|
||||||
context_chunks.push("<directories>\n");
|
result.push_str("<directories>\n");
|
||||||
for context in directory_context {
|
for context in directory_context {
|
||||||
for context_buffer in &context.context_buffers {
|
for context_buffer in &context.context_buffers {
|
||||||
context_chunks.push(&context_buffer.text);
|
result.push_str(&context_buffer.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context_chunks.push("\n</directories>\n");
|
result.push_str("</directories>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !symbol_context.is_empty() {
|
if !symbol_context.is_empty() {
|
||||||
context_chunks.push("<symbols>\n");
|
result.push_str("<symbols>\n");
|
||||||
for context in symbol_context {
|
for context in symbol_context {
|
||||||
context_chunks.push(&context.context_symbol.text);
|
result.push_str(&context.context_symbol.text);
|
||||||
|
result.push('\n');
|
||||||
}
|
}
|
||||||
context_chunks.push("\n</symbols>\n");
|
result.push_str("</symbols>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !selection_context.is_empty() {
|
||||||
|
result.push_str("<selections>\n");
|
||||||
|
for context in selection_context {
|
||||||
|
result.push_str(&context.context_buffer.text);
|
||||||
|
result.push('\n');
|
||||||
|
}
|
||||||
|
result.push_str("</selections>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fetch_context.is_empty() {
|
if !fetch_context.is_empty() {
|
||||||
context_chunks.push("<fetched_urls>\n");
|
result.push_str("<fetched_urls>\n");
|
||||||
for context in &fetch_context {
|
for context in &fetch_context {
|
||||||
context_chunks.push(&context.url);
|
result.push_str(&context.url);
|
||||||
context_chunks.push(&context.text);
|
result.push('\n');
|
||||||
|
result.push_str(&context.text);
|
||||||
|
result.push('\n');
|
||||||
}
|
}
|
||||||
context_chunks.push("\n</fetched_urls>\n");
|
result.push_str("</fetched_urls>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to own the SharedString for summary so that it can be referenced.
|
|
||||||
let mut thread_context_chunks = Vec::new();
|
|
||||||
if !thread_context.is_empty() {
|
if !thread_context.is_empty() {
|
||||||
context_chunks.push("<conversation_threads>\n");
|
result.push_str("<conversation_threads>\n");
|
||||||
for context in &thread_context {
|
for context in &thread_context {
|
||||||
thread_context_chunks.push(context.summary(cx));
|
result.push_str(&context.summary(cx));
|
||||||
thread_context_chunks.push(context.text.clone());
|
result.push('\n');
|
||||||
|
result.push_str(&context.text);
|
||||||
|
result.push('\n');
|
||||||
}
|
}
|
||||||
context_chunks.push("\n</conversation_threads>\n");
|
result.push_str("</conversation_threads>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
for chunk in &thread_context_chunks {
|
if !rules_context.is_empty() {
|
||||||
context_chunks.push(chunk);
|
result.push_str(
|
||||||
}
|
"<user_rules>\n\
|
||||||
|
The user has specified the following rules that should be applied:\n\n",
|
||||||
if !context_chunks.is_empty() {
|
|
||||||
message.content.push(
|
|
||||||
"\n<context>\n\
|
|
||||||
The following items were attached by the user. You don't need to use other tools to read them.\n\n".into(),
|
|
||||||
);
|
);
|
||||||
message.content.push(context_chunks.join("\n").into());
|
for context in &rules_context {
|
||||||
message.content.push("\n</context>\n".into());
|
result.push_str(&context.text);
|
||||||
|
result.push('\n');
|
||||||
|
}
|
||||||
|
result.push_str("</user_rules>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_str("</context>\n");
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attach_context_to_message<'a>(
|
||||||
|
message: &mut LanguageModelRequestMessage,
|
||||||
|
contexts: impl Iterator<Item = &'a AssistantContext>,
|
||||||
|
cx: &App,
|
||||||
|
) {
|
||||||
|
if let Some(context_string) = format_context_as_string(contexts, cx) {
|
||||||
|
message.content.push(context_string.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod completion_provider;
|
mod completion_provider;
|
||||||
mod fetch_context_picker;
|
mod fetch_context_picker;
|
||||||
mod file_context_picker;
|
mod file_context_picker;
|
||||||
|
mod rules_context_picker;
|
||||||
mod symbol_context_picker;
|
mod symbol_context_picker;
|
||||||
mod thread_context_picker;
|
mod thread_context_picker;
|
||||||
|
|
||||||
@@ -13,30 +14,60 @@ use editor::display_map::{Crease, FoldId};
|
|||||||
use editor::{Anchor, AnchorRangeExt as _, Editor, ExcerptId, FoldPlaceholder, ToOffset};
|
use editor::{Anchor, AnchorRangeExt as _, Editor, ExcerptId, FoldPlaceholder, ToOffset};
|
||||||
use file_context_picker::render_file_context_entry;
|
use file_context_picker::render_file_context_entry;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity,
|
App, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task,
|
||||||
|
WeakEntity,
|
||||||
};
|
};
|
||||||
|
use language::Buffer;
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use project::{Entry, ProjectPath};
|
use project::{Entry, ProjectPath};
|
||||||
|
use prompt_store::UserPromptId;
|
||||||
|
use rules_context_picker::RulesContextEntry;
|
||||||
use symbol_context_picker::SymbolContextPicker;
|
use symbol_context_picker::SymbolContextPicker;
|
||||||
use thread_context_picker::{ThreadContextEntry, render_thread_context_entry};
|
use thread_context_picker::{ThreadContextEntry, render_thread_context_entry};
|
||||||
use ui::{
|
use ui::{
|
||||||
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
||||||
};
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||||
|
|
||||||
use crate::AssistantPanel;
|
use crate::AssistantPanel;
|
||||||
|
use crate::context::RULES_ICON;
|
||||||
pub use crate::context_picker::completion_provider::ContextPickerCompletionProvider;
|
pub use crate::context_picker::completion_provider::ContextPickerCompletionProvider;
|
||||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||||
|
use crate::context_picker::rules_context_picker::RulesContextPicker;
|
||||||
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread::ThreadId;
|
use crate::thread::ThreadId;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ConfirmBehavior {
|
enum ContextPickerEntry {
|
||||||
KeepOpen,
|
Mode(ContextPickerMode),
|
||||||
Close,
|
Action(ContextPickerAction),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextPickerEntry {
|
||||||
|
pub fn keyword(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Mode(mode) => mode.keyword(),
|
||||||
|
Self::Action(action) => action.keyword(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Mode(mode) => mode.label(),
|
||||||
|
Self::Action(action) => action.label(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> IconName {
|
||||||
|
match self {
|
||||||
|
Self::Mode(mode) => mode.icon(),
|
||||||
|
Self::Action(action) => action.icon(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -45,6 +76,32 @@ enum ContextPickerMode {
|
|||||||
Symbol,
|
Symbol,
|
||||||
Fetch,
|
Fetch,
|
||||||
Thread,
|
Thread,
|
||||||
|
Rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum ContextPickerAction {
|
||||||
|
AddSelections,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextPickerAction {
|
||||||
|
pub fn keyword(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::AddSelections => "selection",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::AddSelections => "Selection",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> IconName {
|
||||||
|
match self {
|
||||||
|
Self::AddSelections => IconName::Context,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for ContextPickerMode {
|
impl TryFrom<&str> for ContextPickerMode {
|
||||||
@@ -56,18 +113,20 @@ impl TryFrom<&str> for ContextPickerMode {
|
|||||||
"symbol" => Ok(Self::Symbol),
|
"symbol" => Ok(Self::Symbol),
|
||||||
"fetch" => Ok(Self::Fetch),
|
"fetch" => Ok(Self::Fetch),
|
||||||
"thread" => Ok(Self::Thread),
|
"thread" => Ok(Self::Thread),
|
||||||
|
"rules" => Ok(Self::Rules),
|
||||||
_ => Err(format!("Invalid context picker mode: {}", value)),
|
_ => Err(format!("Invalid context picker mode: {}", value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextPickerMode {
|
impl ContextPickerMode {
|
||||||
pub fn mention_prefix(&self) -> &'static str {
|
pub fn keyword(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::File => "file",
|
Self::File => "file",
|
||||||
Self::Symbol => "symbol",
|
Self::Symbol => "symbol",
|
||||||
Self::Fetch => "fetch",
|
Self::Fetch => "fetch",
|
||||||
Self::Thread => "thread",
|
Self::Thread => "thread",
|
||||||
|
Self::Rules => "rules",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +135,8 @@ impl ContextPickerMode {
|
|||||||
Self::File => "Files & Directories",
|
Self::File => "Files & Directories",
|
||||||
Self::Symbol => "Symbols",
|
Self::Symbol => "Symbols",
|
||||||
Self::Fetch => "Fetch",
|
Self::Fetch => "Fetch",
|
||||||
Self::Thread => "Thread",
|
Self::Thread => "Threads",
|
||||||
|
Self::Rules => "Rules",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +146,7 @@ impl ContextPickerMode {
|
|||||||
Self::Symbol => IconName::Code,
|
Self::Symbol => IconName::Code,
|
||||||
Self::Fetch => IconName::Globe,
|
Self::Fetch => IconName::Globe,
|
||||||
Self::Thread => IconName::MessageBubbles,
|
Self::Thread => IconName::MessageBubbles,
|
||||||
|
Self::Rules => RULES_ICON,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +158,7 @@ enum ContextPickerState {
|
|||||||
Symbol(Entity<SymbolContextPicker>),
|
Symbol(Entity<SymbolContextPicker>),
|
||||||
Fetch(Entity<FetchContextPicker>),
|
Fetch(Entity<FetchContextPicker>),
|
||||||
Thread(Entity<ThreadContextPicker>),
|
Thread(Entity<ThreadContextPicker>),
|
||||||
|
Rules(Entity<RulesContextPicker>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct ContextPicker {
|
pub(super) struct ContextPicker {
|
||||||
@@ -104,7 +166,7 @@ pub(super) struct ContextPicker {
|
|||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextPicker {
|
impl ContextPicker {
|
||||||
@@ -112,10 +174,25 @@ impl ContextPicker {
|
|||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let subscriptions = context_store
|
||||||
|
.upgrade()
|
||||||
|
.map(|context_store| {
|
||||||
|
cx.observe(&context_store, |this, _, cx| this.notify_current_picker(cx))
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
thread_store
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|thread_store| thread_store.upgrade())
|
||||||
|
.map(|thread_store| {
|
||||||
|
cx.observe(&thread_store, |this, _, cx| this.notify_current_picker(cx))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.collect::<Vec<Subscription>>();
|
||||||
|
|
||||||
ContextPicker {
|
ContextPicker {
|
||||||
mode: ContextPickerState::Default(ContextMenu::build(
|
mode: ContextPickerState::Default(ContextMenu::build(
|
||||||
window,
|
window,
|
||||||
@@ -125,7 +202,7 @@ impl ContextPicker {
|
|||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
confirm_behavior,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,39 +222,40 @@ impl ContextPicker {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
||||||
|
|
||||||
let modes = supported_context_picker_modes(&self.thread_store);
|
let entries = self
|
||||||
|
.workspace
|
||||||
let menu = menu
|
.upgrade()
|
||||||
.when(has_recent, |menu| {
|
.map(|workspace| {
|
||||||
menu.custom_row(|_, _| {
|
available_context_picker_entries(&self.thread_store, &workspace, cx)
|
||||||
div()
|
|
||||||
.mb_1()
|
|
||||||
.child(
|
|
||||||
Label::new("Recent")
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(LabelSize::Small),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.extend(recent_entries)
|
.unwrap_or_default();
|
||||||
.when(has_recent, |menu| menu.separator())
|
|
||||||
.extend(modes.into_iter().map(|mode| {
|
|
||||||
let context_picker = context_picker.clone();
|
|
||||||
|
|
||||||
ContextMenuEntry::new(mode.label())
|
menu.when(has_recent, |menu| {
|
||||||
.icon(mode.icon())
|
menu.custom_row(|_, _| {
|
||||||
.icon_size(IconSize::XSmall)
|
div()
|
||||||
.icon_color(Color::Muted)
|
.mb_1()
|
||||||
.handler(move |window, cx| {
|
.child(
|
||||||
context_picker.update(cx, |this, cx| this.select_mode(mode, window, cx))
|
Label::new("Recent")
|
||||||
})
|
.color(Color::Muted)
|
||||||
}));
|
.size(LabelSize::Small),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.extend(recent_entries)
|
||||||
|
.when(has_recent, |menu| menu.separator())
|
||||||
|
.extend(entries.into_iter().map(|entry| {
|
||||||
|
let context_picker = context_picker.clone();
|
||||||
|
|
||||||
match self.confirm_behavior {
|
ContextMenuEntry::new(entry.label())
|
||||||
ConfirmBehavior::KeepOpen => menu.keep_open_on_confirm(),
|
.icon(entry.icon())
|
||||||
ConfirmBehavior::Close => menu,
|
.icon_size(IconSize::XSmall)
|
||||||
}
|
.icon_color(Color::Muted)
|
||||||
|
.handler(move |window, cx| {
|
||||||
|
context_picker.update(cx, |this, cx| this.select_entry(entry, window, cx))
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.keep_open_on_confirm()
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&menu, move |_, _, _: &DismissEvent, cx| {
|
cx.subscribe(&menu, move |_, _, _: &DismissEvent, cx| {
|
||||||
@@ -193,65 +271,87 @@ impl ContextPicker {
|
|||||||
self.thread_store.is_some()
|
self.thread_store.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_mode(
|
fn select_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
mode: ContextPickerMode,
|
entry: ContextPickerEntry,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let context_picker = cx.entity().downgrade();
|
let context_picker = cx.entity().downgrade();
|
||||||
|
|
||||||
match mode {
|
match entry {
|
||||||
ContextPickerMode::File => {
|
ContextPickerEntry::Mode(mode) => match mode {
|
||||||
self.mode = ContextPickerState::File(cx.new(|cx| {
|
ContextPickerMode::File => {
|
||||||
FileContextPicker::new(
|
self.mode = ContextPickerState::File(cx.new(|cx| {
|
||||||
context_picker.clone(),
|
FileContextPicker::new(
|
||||||
self.workspace.clone(),
|
|
||||||
self.context_store.clone(),
|
|
||||||
self.confirm_behavior,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ContextPickerMode::Symbol => {
|
|
||||||
self.mode = ContextPickerState::Symbol(cx.new(|cx| {
|
|
||||||
SymbolContextPicker::new(
|
|
||||||
context_picker.clone(),
|
|
||||||
self.workspace.clone(),
|
|
||||||
self.context_store.clone(),
|
|
||||||
self.confirm_behavior,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ContextPickerMode::Fetch => {
|
|
||||||
self.mode = ContextPickerState::Fetch(cx.new(|cx| {
|
|
||||||
FetchContextPicker::new(
|
|
||||||
context_picker.clone(),
|
|
||||||
self.workspace.clone(),
|
|
||||||
self.context_store.clone(),
|
|
||||||
self.confirm_behavior,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ContextPickerMode::Thread => {
|
|
||||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
|
||||||
self.mode = ContextPickerState::Thread(cx.new(|cx| {
|
|
||||||
ThreadContextPicker::new(
|
|
||||||
thread_store.clone(),
|
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
|
self.workspace.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
ContextPickerMode::Symbol => {
|
||||||
|
self.mode = ContextPickerState::Symbol(cx.new(|cx| {
|
||||||
|
SymbolContextPicker::new(
|
||||||
|
context_picker.clone(),
|
||||||
|
self.workspace.clone(),
|
||||||
|
self.context_store.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
ContextPickerMode::Rules => {
|
||||||
|
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||||
|
self.mode = ContextPickerState::Rules(cx.new(|cx| {
|
||||||
|
RulesContextPicker::new(
|
||||||
|
thread_store.clone(),
|
||||||
|
context_picker.clone(),
|
||||||
|
self.context_store.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextPickerMode::Fetch => {
|
||||||
|
self.mode = ContextPickerState::Fetch(cx.new(|cx| {
|
||||||
|
FetchContextPicker::new(
|
||||||
|
context_picker.clone(),
|
||||||
|
self.workspace.clone(),
|
||||||
|
self.context_store.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
ContextPickerMode::Thread => {
|
||||||
|
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||||
|
self.mode = ContextPickerState::Thread(cx.new(|cx| {
|
||||||
|
ThreadContextPicker::new(
|
||||||
|
thread_store.clone(),
|
||||||
|
context_picker.clone(),
|
||||||
|
self.context_store.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ContextPickerEntry::Action(action) => match action {
|
||||||
|
ContextPickerAction::AddSelections => {
|
||||||
|
if let Some((context_store, workspace)) =
|
||||||
|
self.context_store.upgrade().zip(self.workspace.upgrade())
|
||||||
|
{
|
||||||
|
add_selections_as_context(&context_store, &workspace, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -270,12 +370,14 @@ impl ContextPicker {
|
|||||||
path_prefix,
|
path_prefix,
|
||||||
} => {
|
} => {
|
||||||
let context_store = self.context_store.clone();
|
let context_store = self.context_store.clone();
|
||||||
|
let worktree_id = project_path.worktree_id;
|
||||||
let path = project_path.path.clone();
|
let path = project_path.path.clone();
|
||||||
|
|
||||||
ContextMenuItem::custom_entry(
|
ContextMenuItem::custom_entry(
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
render_file_context_entry(
|
render_file_context_entry(
|
||||||
ElementId::NamedInteger("ctx-recent".into(), ix),
|
ElementId::NamedInteger("ctx-recent".into(), ix),
|
||||||
|
worktree_id,
|
||||||
&path,
|
&path,
|
||||||
&path_prefix,
|
&path_prefix,
|
||||||
false,
|
false,
|
||||||
@@ -360,73 +462,26 @@ impl ContextPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
|
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
|
||||||
let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(context_store) = self.context_store.upgrade().map(|cs| cs.read(cx)) else {
|
let Some(context_store) = self.context_store.upgrade() else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut recent = Vec::with_capacity(6);
|
recent_context_picker_entries(context_store, self.thread_store.clone(), workspace, cx)
|
||||||
|
}
|
||||||
|
|
||||||
let mut current_files = context_store.file_paths(cx);
|
fn notify_current_picker(&mut self, cx: &mut Context<Self>) {
|
||||||
|
match &self.mode {
|
||||||
if let Some(active_path) = active_singleton_buffer_path(&workspace, cx) {
|
ContextPickerState::Default(entity) => entity.update(cx, |_, cx| cx.notify()),
|
||||||
current_files.insert(active_path);
|
ContextPickerState::File(entity) => entity.update(cx, |_, cx| cx.notify()),
|
||||||
|
ContextPickerState::Symbol(entity) => entity.update(cx, |_, cx| cx.notify()),
|
||||||
|
ContextPickerState::Fetch(entity) => entity.update(cx, |_, cx| cx.notify()),
|
||||||
|
ContextPickerState::Thread(entity) => entity.update(cx, |_, cx| cx.notify()),
|
||||||
|
ContextPickerState::Rules(entity) => entity.update(cx, |_, cx| cx.notify()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let project = workspace.project().read(cx);
|
|
||||||
|
|
||||||
recent.extend(
|
|
||||||
workspace
|
|
||||||
.recent_navigation_history_iter(cx)
|
|
||||||
.filter(|(path, _)| !current_files.contains(&path.path.to_path_buf()))
|
|
||||||
.take(4)
|
|
||||||
.filter_map(|(project_path, _)| {
|
|
||||||
project
|
|
||||||
.worktree_for_id(project_path.worktree_id, cx)
|
|
||||||
.map(|worktree| RecentEntry::File {
|
|
||||||
project_path,
|
|
||||||
path_prefix: worktree.read(cx).root_name().into(),
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut current_threads = context_store.thread_ids();
|
|
||||||
|
|
||||||
if let Some(active_thread) = workspace
|
|
||||||
.panel::<AssistantPanel>(cx)
|
|
||||||
.map(|panel| panel.read(cx).active_thread(cx))
|
|
||||||
{
|
|
||||||
current_threads.insert(active_thread.read(cx).id().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(thread_store) = self
|
|
||||||
.thread_store
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|thread_store| thread_store.upgrade())
|
|
||||||
else {
|
|
||||||
return recent;
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_store.update(cx, |thread_store, _cx| {
|
|
||||||
recent.extend(
|
|
||||||
thread_store
|
|
||||||
.threads()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|thread| !current_threads.contains(&thread.id))
|
|
||||||
.take(2)
|
|
||||||
.map(|thread| {
|
|
||||||
RecentEntry::Thread(ThreadContextEntry {
|
|
||||||
id: thread.id,
|
|
||||||
summary: thread.summary,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
recent
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,6 +495,7 @@ impl Focusable for ContextPicker {
|
|||||||
ContextPickerState::Symbol(symbol_picker) => symbol_picker.focus_handle(cx),
|
ContextPickerState::Symbol(symbol_picker) => symbol_picker.focus_handle(cx),
|
||||||
ContextPickerState::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
ContextPickerState::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
||||||
ContextPickerState::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
ContextPickerState::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
||||||
|
ContextPickerState::Rules(user_rules_picker) => user_rules_picker.focus_handle(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,6 +511,9 @@ impl Render for ContextPicker {
|
|||||||
ContextPickerState::Symbol(symbol_picker) => parent.child(symbol_picker.clone()),
|
ContextPickerState::Symbol(symbol_picker) => parent.child(symbol_picker.clone()),
|
||||||
ContextPickerState::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
ContextPickerState::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
||||||
ContextPickerState::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
ContextPickerState::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
||||||
|
ContextPickerState::Rules(user_rules_picker) => {
|
||||||
|
parent.child(user_rules_picker.clone())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,28 +525,37 @@ enum RecentEntry {
|
|||||||
Thread(ThreadContextEntry),
|
Thread(ThreadContextEntry),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_context_picker_modes(
|
fn available_context_picker_entries(
|
||||||
thread_store: &Option<WeakEntity<ThreadStore>>,
|
thread_store: &Option<WeakEntity<ThreadStore>>,
|
||||||
) -> Vec<ContextPickerMode> {
|
workspace: &Entity<Workspace>,
|
||||||
let mut modes = vec![
|
cx: &mut App,
|
||||||
ContextPickerMode::File,
|
) -> Vec<ContextPickerEntry> {
|
||||||
ContextPickerMode::Symbol,
|
let mut entries = vec![
|
||||||
ContextPickerMode::Fetch,
|
ContextPickerEntry::Mode(ContextPickerMode::File),
|
||||||
|
ContextPickerEntry::Mode(ContextPickerMode::Symbol),
|
||||||
];
|
];
|
||||||
if thread_store.is_some() {
|
|
||||||
modes.push(ContextPickerMode::Thread);
|
let has_selection = workspace
|
||||||
|
.read(cx)
|
||||||
|
.active_item(cx)
|
||||||
|
.and_then(|item| item.downcast::<Editor>())
|
||||||
|
.map_or(false, |editor| {
|
||||||
|
editor.update(cx, |editor, cx| editor.has_non_empty_selection(cx))
|
||||||
|
});
|
||||||
|
if has_selection {
|
||||||
|
entries.push(ContextPickerEntry::Action(
|
||||||
|
ContextPickerAction::AddSelections,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
modes
|
|
||||||
}
|
|
||||||
|
|
||||||
fn active_singleton_buffer_path(workspace: &Workspace, cx: &App) -> Option<PathBuf> {
|
if thread_store.is_some() {
|
||||||
let active_item = workspace.active_item(cx)?;
|
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Thread));
|
||||||
|
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Rules));
|
||||||
|
}
|
||||||
|
|
||||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Fetch));
|
||||||
let buffer = editor.buffer().read(cx).as_singleton()?;
|
|
||||||
|
|
||||||
let path = buffer.read(cx).file()?.path().to_path_buf();
|
entries
|
||||||
Some(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recent_context_picker_entries(
|
fn recent_context_picker_entries(
|
||||||
@@ -498,20 +566,14 @@ fn recent_context_picker_entries(
|
|||||||
) -> Vec<RecentEntry> {
|
) -> Vec<RecentEntry> {
|
||||||
let mut recent = Vec::with_capacity(6);
|
let mut recent = Vec::with_capacity(6);
|
||||||
|
|
||||||
let mut current_files = context_store.read(cx).file_paths(cx);
|
let current_files = context_store.read(cx).file_paths(cx);
|
||||||
|
|
||||||
let workspace = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
|
|
||||||
if let Some(active_path) = active_singleton_buffer_path(workspace, cx) {
|
|
||||||
current_files.insert(active_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let project = workspace.project().read(cx);
|
let project = workspace.project().read(cx);
|
||||||
|
|
||||||
recent.extend(
|
recent.extend(
|
||||||
workspace
|
workspace
|
||||||
.recent_navigation_history_iter(cx)
|
.recent_navigation_history_iter(cx)
|
||||||
.filter(|(path, _)| !current_files.contains(&path.path.to_path_buf()))
|
.filter(|(path, _)| !current_files.contains(path))
|
||||||
.take(4)
|
.take(4)
|
||||||
.filter_map(|(project_path, _)| {
|
.filter_map(|(project_path, _)| {
|
||||||
project
|
project
|
||||||
@@ -552,14 +614,61 @@ fn recent_context_picker_entries(
|
|||||||
recent
|
recent
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_crease_for_mention(
|
fn add_selections_as_context(
|
||||||
|
context_store: &Entity<ContextStore>,
|
||||||
|
workspace: &Entity<Workspace>,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
|
let selection_ranges = selection_ranges(workspace, cx);
|
||||||
|
context_store.update(cx, |context_store, cx| {
|
||||||
|
for (buffer, range) in selection_ranges {
|
||||||
|
context_store
|
||||||
|
.add_selection(buffer, range, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_ranges(
|
||||||
|
workspace: &Entity<Workspace>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Vec<(Entity<Buffer>, Range<text::Anchor>)> {
|
||||||
|
let Some(editor) = workspace
|
||||||
|
.read(cx)
|
||||||
|
.active_item(cx)
|
||||||
|
.and_then(|item| item.act_as::<Editor>(cx))
|
||||||
|
else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let selections = editor.selections.all_adjusted(cx);
|
||||||
|
|
||||||
|
let buffer = editor.buffer().clone().read(cx);
|
||||||
|
let snapshot = buffer.snapshot(cx);
|
||||||
|
|
||||||
|
selections
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| snapshot.anchor_after(s.start)..snapshot.anchor_before(s.end))
|
||||||
|
.flat_map(|range| {
|
||||||
|
let (start_buffer, start) = buffer.text_anchor_for_position(range.start, cx)?;
|
||||||
|
let (end_buffer, end) = buffer.text_anchor_for_position(range.end, cx)?;
|
||||||
|
if start_buffer != end_buffer {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((start_buffer, start..end))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert_fold_for_mention(
|
||||||
excerpt_id: ExcerptId,
|
excerpt_id: ExcerptId,
|
||||||
crease_start: text::Anchor,
|
crease_start: text::Anchor,
|
||||||
content_len: usize,
|
content_len: usize,
|
||||||
crease_label: SharedString,
|
crease_label: SharedString,
|
||||||
crease_icon_path: SharedString,
|
crease_icon_path: SharedString,
|
||||||
editor_entity: Entity<Editor>,
|
editor_entity: Entity<Editor>,
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
editor_entity.update(cx, |editor, cx| {
|
editor_entity.update(cx, |editor, cx| {
|
||||||
@@ -572,30 +681,42 @@ pub(crate) fn insert_crease_for_mention(
|
|||||||
let start = start.bias_right(&snapshot);
|
let start = start.bias_right(&snapshot);
|
||||||
let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
|
let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
|
||||||
|
|
||||||
let placeholder = FoldPlaceholder {
|
let crease = crease_for_mention(
|
||||||
render: render_fold_icon_button(
|
crease_label,
|
||||||
crease_icon_path,
|
crease_icon_path,
|
||||||
crease_label,
|
|
||||||
editor_entity.downgrade(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let render_trailer =
|
|
||||||
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
|
||||||
|
|
||||||
let crease = Crease::inline(
|
|
||||||
start..end,
|
start..end,
|
||||||
placeholder.clone(),
|
editor_entity.downgrade(),
|
||||||
fold_toggle("mention"),
|
|
||||||
render_trailer,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.insert_creases(vec![crease.clone()], cx);
|
editor.display_map.update(cx, |display_map, cx| {
|
||||||
editor.fold_creases(vec![crease], false, window, cx);
|
display_map.fold(vec![crease], cx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn crease_for_mention(
|
||||||
|
label: SharedString,
|
||||||
|
icon_path: SharedString,
|
||||||
|
range: Range<Anchor>,
|
||||||
|
editor_entity: WeakEntity<Editor>,
|
||||||
|
) -> Crease<Anchor> {
|
||||||
|
let placeholder = FoldPlaceholder {
|
||||||
|
render: render_fold_icon_button(icon_path, label, editor_entity),
|
||||||
|
merge_adjacent: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_trailer = move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
||||||
|
|
||||||
|
let crease = Crease::inline(
|
||||||
|
range,
|
||||||
|
placeholder.clone(),
|
||||||
|
fold_toggle("mention"),
|
||||||
|
render_trailer,
|
||||||
|
);
|
||||||
|
crease
|
||||||
|
}
|
||||||
|
|
||||||
fn render_fold_icon_button(
|
fn render_fold_icon_button(
|
||||||
icon_path: SharedString,
|
icon_path: SharedString,
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
@@ -649,12 +770,13 @@ fn render_fold_icon_button(
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
Icon::from_path(icon_path.clone())
|
Icon::from_path(icon_path.clone())
|
||||||
.size(IconSize::Small)
|
.size(IconSize::XSmall)
|
||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Label::new(label.clone())
|
Label::new(label.clone())
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
|
.buffer_font(cx)
|
||||||
.single_line(),
|
.single_line(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -683,24 +805,68 @@ fn fold_toggle(
|
|||||||
pub enum MentionLink {
|
pub enum MentionLink {
|
||||||
File(ProjectPath, Entry),
|
File(ProjectPath, Entry),
|
||||||
Symbol(ProjectPath, String),
|
Symbol(ProjectPath, String),
|
||||||
|
Selection(ProjectPath, Range<usize>),
|
||||||
|
Fetch(String),
|
||||||
Thread(ThreadId),
|
Thread(ThreadId),
|
||||||
|
Rules(UserPromptId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MentionLink {
|
impl MentionLink {
|
||||||
|
const FILE: &str = "@file";
|
||||||
|
const SYMBOL: &str = "@symbol";
|
||||||
|
const SELECTION: &str = "@selection";
|
||||||
|
const THREAD: &str = "@thread";
|
||||||
|
const FETCH: &str = "@fetch";
|
||||||
|
const RULES: &str = "@rules";
|
||||||
|
|
||||||
|
const SEPARATOR: &str = ":";
|
||||||
|
|
||||||
|
pub fn is_valid(url: &str) -> bool {
|
||||||
|
url.starts_with(Self::FILE)
|
||||||
|
|| url.starts_with(Self::SYMBOL)
|
||||||
|
|| url.starts_with(Self::FETCH)
|
||||||
|
|| url.starts_with(Self::SELECTION)
|
||||||
|
|| url.starts_with(Self::THREAD)
|
||||||
|
|| url.starts_with(Self::RULES)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn for_file(file_name: &str, full_path: &str) -> String {
|
pub fn for_file(file_name: &str, full_path: &str) -> String {
|
||||||
format!("[@{}](file:{})", file_name, full_path)
|
format!("[@{}]({}:{})", file_name, Self::FILE, full_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_symbol(symbol_name: &str, full_path: &str) -> String {
|
pub fn for_symbol(symbol_name: &str, full_path: &str) -> String {
|
||||||
format!("[@{}](symbol:{}:{})", symbol_name, full_path, symbol_name)
|
format!(
|
||||||
|
"[@{}]({}:{}:{})",
|
||||||
|
symbol_name,
|
||||||
|
Self::SYMBOL,
|
||||||
|
full_path,
|
||||||
|
symbol_name
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_fetch(url: &str) -> String {
|
pub fn for_selection(file_name: &str, full_path: &str, line_range: Range<usize>) -> String {
|
||||||
format!("[@{}]({})", url, url)
|
format!(
|
||||||
|
"[@{} ({}-{})]({}:{}:{}-{})",
|
||||||
|
file_name,
|
||||||
|
line_range.start,
|
||||||
|
line_range.end,
|
||||||
|
Self::SELECTION,
|
||||||
|
full_path,
|
||||||
|
line_range.start,
|
||||||
|
line_range.end
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_thread(thread: &ThreadContextEntry) -> String {
|
pub fn for_thread(thread: &ThreadContextEntry) -> String {
|
||||||
format!("[@{}](thread:{})", thread.summary, thread.id)
|
format!("[@{}]({}:{})", thread.summary, Self::THREAD, thread.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_fetch(url: &str) -> String {
|
||||||
|
format!("[@{}]({}:{})", url, Self::FETCH, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_rules(rules: &RulesContextEntry) -> String {
|
||||||
|
format!("[@{}]({}:{})", rules.title, Self::RULES, rules.prompt_id.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_parse(link: &str, workspace: &Entity<Workspace>, cx: &App) -> Option<Self> {
|
pub fn try_parse(link: &str, workspace: &Entity<Workspace>, cx: &App) -> Option<Self> {
|
||||||
@@ -723,17 +889,10 @@ impl MentionLink {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let (prefix, link, target) = {
|
let (prefix, argument) = link.split_once(Self::SEPARATOR)?;
|
||||||
let mut parts = link.splitn(3, ':');
|
match prefix {
|
||||||
let prefix = parts.next();
|
Self::FILE => {
|
||||||
let link = parts.next();
|
let project_path = extract_project_path_from_link(argument, workspace, cx)?;
|
||||||
let target = parts.next();
|
|
||||||
(prefix, link, target)
|
|
||||||
};
|
|
||||||
|
|
||||||
match (prefix, link, target) {
|
|
||||||
(Some("file"), Some(path), _) => {
|
|
||||||
let project_path = extract_project_path_from_link(path, workspace, cx)?;
|
|
||||||
let entry = workspace
|
let entry = workspace
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.project()
|
.project()
|
||||||
@@ -741,14 +900,34 @@ impl MentionLink {
|
|||||||
.entry_for_path(&project_path, cx)?;
|
.entry_for_path(&project_path, cx)?;
|
||||||
Some(MentionLink::File(project_path, entry))
|
Some(MentionLink::File(project_path, entry))
|
||||||
}
|
}
|
||||||
(Some("symbol"), Some(path), Some(symbol_name)) => {
|
Self::SYMBOL => {
|
||||||
|
let (path, symbol) = argument.split_once(Self::SEPARATOR)?;
|
||||||
let project_path = extract_project_path_from_link(path, workspace, cx)?;
|
let project_path = extract_project_path_from_link(path, workspace, cx)?;
|
||||||
Some(MentionLink::Symbol(project_path, symbol_name.to_string()))
|
Some(MentionLink::Symbol(project_path, symbol.to_string()))
|
||||||
}
|
}
|
||||||
(Some("thread"), Some(thread_id), _) => {
|
Self::SELECTION => {
|
||||||
let thread_id = ThreadId::from(thread_id);
|
let (path, line_args) = argument.split_once(Self::SEPARATOR)?;
|
||||||
|
let project_path = extract_project_path_from_link(path, workspace, cx)?;
|
||||||
|
|
||||||
|
let line_range = {
|
||||||
|
let (start, end) = line_args
|
||||||
|
.trim_start_matches('(')
|
||||||
|
.trim_end_matches(')')
|
||||||
|
.split_once('-')?;
|
||||||
|
start.parse::<usize>().ok()?..end.parse::<usize>().ok()?
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(MentionLink::Selection(project_path, line_range))
|
||||||
|
}
|
||||||
|
Self::THREAD => {
|
||||||
|
let thread_id = ThreadId::from(argument);
|
||||||
Some(MentionLink::Thread(thread_id))
|
Some(MentionLink::Thread(thread_id))
|
||||||
}
|
}
|
||||||
|
Self::FETCH => Some(MentionLink::Fetch(argument.to_string())),
|
||||||
|
Self::RULES => {
|
||||||
|
let prompt_id = UserPromptId(Uuid::try_parse(argument).ok()?);
|
||||||
|
Some(MentionLink::Rules(prompt_id))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use picker::{Picker, PickerDelegate};
|
|||||||
use ui::{Context, ListItem, Window, prelude::*};
|
use ui::{Context, ListItem, Window, prelude::*};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
|
|
||||||
pub struct FetchContextPicker {
|
pub struct FetchContextPicker {
|
||||||
@@ -23,16 +23,10 @@ impl FetchContextPicker {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = FetchContextPickerDelegate::new(
|
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
);
|
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
Self { picker }
|
Self { picker }
|
||||||
@@ -62,7 +56,6 @@ pub struct FetchContextPickerDelegate {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,13 +64,11 @@ impl FetchContextPickerDelegate {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
FetchContextPickerDelegate {
|
FetchContextPickerDelegate {
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
|
||||||
url: String::new(),
|
url: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,25 +195,15 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
|
|
||||||
let http_client = workspace.read(cx).client().http_client().clone();
|
let http_client = workspace.read(cx).client().http_client().clone();
|
||||||
let url = self.url.clone();
|
let url = self.url.clone();
|
||||||
let confirm_behavior = self.confirm_behavior;
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let text = cx
|
let text = cx
|
||||||
.background_spawn(fetch_url_content(http_client, url.clone()))
|
.background_spawn(fetch_url_content(http_client, url.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.delegate
|
this.delegate.context_store.update(cx, |context_store, cx| {
|
||||||
.context_store
|
context_store.add_fetched_url(url, text, cx)
|
||||||
.update(cx, |context_store, _cx| {
|
})
|
||||||
context_store.add_fetched_url(url, text);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match confirm_behavior {
|
|
||||||
ConfirmBehavior::KeepOpen => {}
|
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ use picker::{Picker, PickerDelegate};
|
|||||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||||
use ui::{ListItem, Tooltip, prelude::*};
|
use ui::{ListItem, Tooltip, prelude::*};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::{ContextStore, FileInclusion};
|
use crate::context_store::{ContextStore, FileInclusion};
|
||||||
|
|
||||||
pub struct FileContextPicker {
|
pub struct FileContextPicker {
|
||||||
@@ -25,16 +25,10 @@ impl FileContextPicker {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = FileContextPickerDelegate::new(
|
let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
);
|
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
Self { picker }
|
Self { picker }
|
||||||
@@ -57,8 +51,7 @@ pub struct FileContextPickerDelegate {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
matches: Vec<FileMatch>,
|
||||||
matches: Vec<PathMatch>,
|
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +60,11 @@ impl FileContextPickerDelegate {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
}
|
}
|
||||||
@@ -114,7 +105,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_task = search_paths(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
let search_task = search_files(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
// TODO: This should be probably be run in the background.
|
// TODO: This should be probably be run in the background.
|
||||||
@@ -127,8 +118,8 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let Some(mat) = self.matches.get(self.selected_index) else {
|
let Some(FileMatch { mat, .. }) = self.matches.get(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,17 +144,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let confirm_behavior = self.confirm_behavior;
|
task.detach_and_log_err(cx);
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
|
||||||
match task.await.notify_async_err(cx) {
|
|
||||||
None => anyhow::Ok(()),
|
|
||||||
Some(()) => this.update_in(cx, |this, window, cx| match confirm_behavior {
|
|
||||||
ConfirmBehavior::KeepOpen => {}
|
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
@@ -181,7 +162,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let path_match = &self.matches[ix];
|
let FileMatch { mat, .. } = &self.matches[ix];
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
@@ -189,9 +170,10 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.child(render_file_context_entry(
|
.child(render_file_context_entry(
|
||||||
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
||||||
&path_match.path,
|
WorktreeId::from_usize(mat.worktree_id),
|
||||||
&path_match.path_prefix,
|
&mat.path,
|
||||||
path_match.is_dir,
|
&mat.path_prefix,
|
||||||
|
mat.is_dir,
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
@@ -199,12 +181,17 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn search_paths(
|
pub struct FileMatch {
|
||||||
|
pub mat: PathMatch,
|
||||||
|
pub is_recent: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn search_files(
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: &Entity<Workspace>,
|
workspace: &Entity<Workspace>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Task<Vec<PathMatch>> {
|
) -> Task<Vec<FileMatch>> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
let workspace = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
let project = workspace.project().read(cx);
|
let project = workspace.project().read(cx);
|
||||||
@@ -213,28 +200,34 @@ pub(crate) fn search_paths(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(project_path, _)| {
|
.filter_map(|(project_path, _)| {
|
||||||
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
||||||
Some(PathMatch {
|
Some(FileMatch {
|
||||||
score: 0.,
|
mat: PathMatch {
|
||||||
positions: Vec::new(),
|
score: 0.,
|
||||||
worktree_id: project_path.worktree_id.to_usize(),
|
positions: Vec::new(),
|
||||||
path: project_path.path,
|
worktree_id: project_path.worktree_id.to_usize(),
|
||||||
path_prefix: worktree.read(cx).root_name().into(),
|
path: project_path.path,
|
||||||
distance_to_relative_ancestor: 0,
|
path_prefix: worktree.read(cx).root_name().into(),
|
||||||
is_dir: false,
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
},
|
||||||
|
is_recent: true,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
let path_prefix: Arc<str> = worktree.root_name().into();
|
||||||
worktree.entries(false, 0).map(move |entry| PathMatch {
|
worktree.entries(false, 0).map(move |entry| FileMatch {
|
||||||
score: 0.,
|
mat: PathMatch {
|
||||||
positions: Vec::new(),
|
score: 0.,
|
||||||
worktree_id: worktree.id().to_usize(),
|
positions: Vec::new(),
|
||||||
path: entry.path.clone(),
|
worktree_id: worktree.id().to_usize(),
|
||||||
path_prefix: path_prefix.clone(),
|
path: entry.path.clone(),
|
||||||
distance_to_relative_ancestor: 0,
|
path_prefix: path_prefix.clone(),
|
||||||
is_dir: entry.is_dir(),
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: entry.is_dir(),
|
||||||
|
},
|
||||||
|
is_recent: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -269,6 +262,12 @@ pub(crate) fn search_paths(
|
|||||||
executor,
|
executor,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(|mat| FileMatch {
|
||||||
|
mat,
|
||||||
|
is_recent: false,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,19 +310,26 @@ pub fn extract_file_name_and_directory(
|
|||||||
|
|
||||||
pub fn render_file_context_entry(
|
pub fn render_file_context_entry(
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
path: &Path,
|
worktree_id: WorktreeId,
|
||||||
|
path: &Arc<Path>,
|
||||||
path_prefix: &Arc<str>,
|
path_prefix: &Arc<str>,
|
||||||
is_directory: bool,
|
is_directory: bool,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Stateful<Div> {
|
) -> Stateful<Div> {
|
||||||
let (file_name, directory) = extract_file_name_and_directory(path, path_prefix);
|
let (file_name, directory) = extract_file_name_and_directory(&path, path_prefix);
|
||||||
|
|
||||||
let added = context_store.upgrade().and_then(|context_store| {
|
let added = context_store.upgrade().and_then(|context_store| {
|
||||||
|
let project_path = ProjectPath {
|
||||||
|
worktree_id,
|
||||||
|
path: path.clone(),
|
||||||
|
};
|
||||||
if is_directory {
|
if is_directory {
|
||||||
context_store.read(cx).includes_directory(path)
|
context_store.read(cx).includes_directory(&project_path)
|
||||||
} else {
|
} else {
|
||||||
context_store.read(cx).will_include_file_path(path, cx)
|
context_store
|
||||||
|
.read(cx)
|
||||||
|
.will_include_file_path(&project_path, cx)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -363,8 +369,9 @@ pub fn render_file_context_entry(
|
|||||||
)
|
)
|
||||||
.child(Label::new("Added").size(LabelSize::Small)),
|
.child(Label::new("Added").size(LabelSize::Small)),
|
||||||
),
|
),
|
||||||
FileInclusion::InDirectory(dir_name) => {
|
FileInclusion::InDirectory(directory_project_path) => {
|
||||||
let dir_name = dir_name.to_string_lossy().into_owned();
|
// TODO: Consider using worktree full_path to include worktree name.
|
||||||
|
let directory_path = directory_project_path.path.to_string_lossy().into_owned();
|
||||||
|
|
||||||
el.child(
|
el.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -378,7 +385,7 @@ pub fn render_file_context_entry(
|
|||||||
)
|
)
|
||||||
.child(Label::new("Included").size(LabelSize::Small)),
|
.child(Label::new("Included").size(LabelSize::Small)),
|
||||||
)
|
)
|
||||||
.tooltip(Tooltip::text(format!("in {dir_name}")))
|
.tooltip(Tooltip::text(format!("in {directory_path}")))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
248
crates/agent/src/context_picker/rules_context_picker.rs
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||||
|
use picker::{Picker, PickerDelegate};
|
||||||
|
use prompt_store::{PromptId, UserPromptId};
|
||||||
|
use ui::{ListItem, prelude::*};
|
||||||
|
|
||||||
|
use crate::context::RULES_ICON;
|
||||||
|
use crate::context_picker::ContextPicker;
|
||||||
|
use crate::context_store::{self, ContextStore};
|
||||||
|
use crate::thread_store::ThreadStore;
|
||||||
|
|
||||||
|
pub struct RulesContextPicker {
|
||||||
|
picker: Entity<Picker<RulesContextPickerDelegate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RulesContextPicker {
|
||||||
|
pub fn new(
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let delegate = RulesContextPickerDelegate::new(thread_store, context_picker, context_store);
|
||||||
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
|
RulesContextPicker { picker }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Focusable for RulesContextPicker {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
self.picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for RulesContextPicker {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
self.picker.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RulesContextEntry {
|
||||||
|
pub prompt_id: UserPromptId,
|
||||||
|
pub title: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RulesContextPickerDelegate {
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
|
matches: Vec<RulesContextEntry>,
|
||||||
|
selected_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RulesContextPickerDelegate {
|
||||||
|
pub fn new(
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
|
) -> Self {
|
||||||
|
RulesContextPickerDelegate {
|
||||||
|
thread_store,
|
||||||
|
context_picker,
|
||||||
|
context_store,
|
||||||
|
matches: Vec::new(),
|
||||||
|
selected_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickerDelegate for RulesContextPickerDelegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
|
fn match_count(&self) -> usize {
|
||||||
|
self.matches.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_index(&self) -> usize {
|
||||||
|
self.selected_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Picker<Self>>,
|
||||||
|
) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||||
|
"Search available rules…".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(
|
||||||
|
&mut self,
|
||||||
|
query: String,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Task<()> {
|
||||||
|
let Some(thread_store) = self.thread_store.upgrade() else {
|
||||||
|
return Task::ready(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_task = search_rules(query, Arc::new(AtomicBool::default()), thread_store, cx);
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let matches = search_task.await;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.delegate.matches = matches;
|
||||||
|
this.delegate.selected_index = 0;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
let Some(entry) = self.matches.get(self.selected_index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(thread_store) = self.thread_store.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let prompt_id = entry.prompt_id;
|
||||||
|
|
||||||
|
let load_rules_task = thread_store.update(cx, |thread_store, cx| {
|
||||||
|
thread_store.load_rules(prompt_id, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let (metadata, text) = load_rules_task.await?;
|
||||||
|
let Some(title) = metadata.title else {
|
||||||
|
return Err(anyhow!("Encountered user rule with no title when attempting to add it to agent context."));
|
||||||
|
};
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.delegate
|
||||||
|
.context_store
|
||||||
|
.update(cx, |context_store, cx| {
|
||||||
|
context_store.add_rules(prompt_id, title, text, true, cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
self.context_picker
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
selected: bool,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Option<Self::ListItem> {
|
||||||
|
let thread = &self.matches[ix];
|
||||||
|
|
||||||
|
Some(ListItem::new(ix).inset(true).toggle_state(selected).child(
|
||||||
|
render_thread_context_entry(thread, self.context_store.clone(), cx),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_thread_context_entry(
|
||||||
|
user_rules: &RulesContextEntry,
|
||||||
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Div {
|
||||||
|
let added = context_store.upgrade().map_or(false, |ctx_store| {
|
||||||
|
ctx_store
|
||||||
|
.read(cx)
|
||||||
|
.includes_user_rules(&user_rules.prompt_id)
|
||||||
|
.is_some()
|
||||||
|
});
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.max_w_72()
|
||||||
|
.child(
|
||||||
|
Icon::new(RULES_ICON)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(Label::new(user_rules.title.clone()).truncate()),
|
||||||
|
)
|
||||||
|
.when(added, |el| {
|
||||||
|
el.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Check)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Success),
|
||||||
|
)
|
||||||
|
.child(Label::new("Added").size(LabelSize::Small)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn search_rules(
|
||||||
|
query: String,
|
||||||
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
thread_store: Entity<ThreadStore>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Vec<RulesContextEntry>> {
|
||||||
|
let Some(prompt_store) = thread_store.read(cx).prompt_store() else {
|
||||||
|
return Task::ready(vec![]);
|
||||||
|
};
|
||||||
|
let search_task = prompt_store.read(cx).search(query, cancellation_flag, cx);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
search_task
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|metadata| {
|
||||||
|
// Default prompts are filtered out as they are automatically included.
|
||||||
|
if metadata.default {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match metadata.id {
|
||||||
|
PromptId::EditWorkflow => None,
|
||||||
|
PromptId::User { uuid } => Some(RulesContextEntry {
|
||||||
|
prompt_id: uuid,
|
||||||
|
title: metadata.title?,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use std::cmp::Reverse;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::Result;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity,
|
App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity,
|
||||||
@@ -15,7 +15,7 @@ use ui::{ListItem, prelude::*};
|
|||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
|
|
||||||
pub struct SymbolContextPicker {
|
pub struct SymbolContextPicker {
|
||||||
@@ -27,16 +27,10 @@ impl SymbolContextPicker {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = SymbolContextPickerDelegate::new(
|
let delegate = SymbolContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
);
|
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
Self { picker }
|
Self { picker }
|
||||||
@@ -59,7 +53,6 @@ pub struct SymbolContextPickerDelegate {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
matches: Vec<SymbolEntry>,
|
matches: Vec<SymbolEntry>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
@@ -69,13 +62,11 @@ impl SymbolContextPickerDelegate {
|
|||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
}
|
}
|
||||||
@@ -119,11 +110,7 @@ impl PickerDelegate for SymbolContextPickerDelegate {
|
|||||||
let search_task = search_symbols(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
let search_task = search_symbols(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
||||||
let context_store = self.context_store.clone();
|
let context_store = self.context_store.clone();
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let symbols = search_task
|
let symbols = search_task.await;
|
||||||
.await
|
|
||||||
.context("Failed to load symbols")
|
|
||||||
.log_err()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let symbol_entries = context_store
|
let symbol_entries = context_store
|
||||||
.read_with(cx, |context_store, cx| {
|
.read_with(cx, |context_store, cx| {
|
||||||
@@ -139,7 +126,7 @@ impl PickerDelegate for SymbolContextPickerDelegate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let Some(mat) = self.matches.get(self.selected_index) else {
|
let Some(mat) = self.matches.get(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -147,7 +134,6 @@ impl PickerDelegate for SymbolContextPickerDelegate {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let confirm_behavior = self.confirm_behavior;
|
|
||||||
let add_symbol_task = add_symbol(
|
let add_symbol_task = add_symbol(
|
||||||
mat.symbol.clone(),
|
mat.symbol.clone(),
|
||||||
true,
|
true,
|
||||||
@@ -157,16 +143,12 @@ impl PickerDelegate for SymbolContextPickerDelegate {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let selected_index = self.selected_index;
|
let selected_index = self.selected_index;
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let included = add_symbol_task.await?;
|
let included = add_symbol_task.await?;
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update(cx, |this, _| {
|
||||||
if let Some(mat) = this.delegate.matches.get_mut(selected_index) {
|
if let Some(mat) = this.delegate.matches.get_mut(selected_index) {
|
||||||
mat.is_included = included;
|
mat.is_included = included;
|
||||||
}
|
}
|
||||||
match confirm_behavior {
|
|
||||||
ConfirmBehavior::KeepOpen => {}
|
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
@@ -285,12 +267,16 @@ fn find_matching_symbol(symbol: &Symbol, candidates: &[DocumentSymbol]) -> Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SymbolMatch {
|
||||||
|
pub symbol: Symbol,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn search_symbols(
|
pub(crate) fn search_symbols(
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: &Entity<Workspace>,
|
workspace: &Entity<Workspace>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<Vec<(StringMatch, Symbol)>>> {
|
) -> Task<Vec<SymbolMatch>> {
|
||||||
let symbols_task = workspace.update(cx, |workspace, cx| {
|
let symbols_task = workspace.update(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.project()
|
.project()
|
||||||
@@ -298,19 +284,28 @@ pub(crate) fn search_symbols(
|
|||||||
});
|
});
|
||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let symbols = symbols_task.await?;
|
let Some(symbols) = symbols_task.await.log_err() else {
|
||||||
let (visible_match_candidates, external_match_candidates): (Vec<_>, Vec<_>) = project
|
return Vec::new();
|
||||||
.update(cx, |project, cx| {
|
};
|
||||||
symbols
|
let Some((visible_match_candidates, external_match_candidates)): Option<(Vec<_>, Vec<_>)> =
|
||||||
.iter()
|
project
|
||||||
.enumerate()
|
.update(cx, |project, cx| {
|
||||||
.map(|(id, symbol)| StringMatchCandidate::new(id, &symbol.label.filter_text()))
|
symbols
|
||||||
.partition(|candidate| {
|
.iter()
|
||||||
project
|
.enumerate()
|
||||||
.entry_for_path(&symbols[candidate.id].path, cx)
|
.map(|(id, symbol)| {
|
||||||
.map_or(false, |e| !e.is_ignored)
|
StringMatchCandidate::new(id, &symbol.label.filter_text())
|
||||||
})
|
})
|
||||||
})?;
|
.partition(|candidate| {
|
||||||
|
project
|
||||||
|
.entry_for_path(&symbols[candidate.id].path, cx)
|
||||||
|
.map_or(false, |e| !e.is_ignored)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.log_err()
|
||||||
|
else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
const MAX_MATCHES: usize = 100;
|
const MAX_MATCHES: usize = 100;
|
||||||
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
|
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
|
||||||
@@ -339,7 +334,7 @@ pub(crate) fn search_symbols(
|
|||||||
let mut matches = visible_matches;
|
let mut matches = visible_matches;
|
||||||
matches.append(&mut external_matches);
|
matches.append(&mut external_matches);
|
||||||
|
|
||||||
Ok(matches
|
matches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut mat| {
|
.map(|mut mat| {
|
||||||
let symbol = symbols[mat.candidate_id].clone();
|
let symbol = symbols[mat.candidate_id].clone();
|
||||||
@@ -347,19 +342,19 @@ pub(crate) fn search_symbols(
|
|||||||
for position in &mut mat.positions {
|
for position in &mut mat.positions {
|
||||||
*position += filter_start;
|
*position += filter_start;
|
||||||
}
|
}
|
||||||
(mat, symbol)
|
SymbolMatch { symbol }
|
||||||
})
|
})
|
||||||
.collect())
|
.collect()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_symbol_entries(
|
fn compute_symbol_entries(
|
||||||
symbols: Vec<(StringMatch, Symbol)>,
|
symbols: Vec<SymbolMatch>,
|
||||||
context_store: &ContextStore,
|
context_store: &ContextStore,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Vec<SymbolEntry> {
|
) -> Vec<SymbolEntry> {
|
||||||
let mut symbol_entries = Vec::with_capacity(symbols.len());
|
let mut symbol_entries = Vec::with_capacity(symbols.len());
|
||||||
for (_, symbol) in symbols {
|
for SymbolMatch { symbol, .. } in symbols {
|
||||||
let symbols_for_path = context_store.included_symbols_by_path().get(&symbol.path);
|
let symbols_for_path = context_store.included_symbols_by_path().get(&symbol.path);
|
||||||
let is_included = if let Some(symbols_for_path) = symbols_for_path {
|
let is_included = if let Some(symbols_for_path) = symbols_for_path {
|
||||||
let mut is_included = false;
|
let mut is_included = false;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use ui::{ListItem, prelude::*};
|
use ui::{ListItem, prelude::*};
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::{self, ContextStore};
|
use crate::context_store::{self, ContextStore};
|
||||||
use crate::thread::ThreadId;
|
use crate::thread::ThreadId;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
@@ -19,16 +20,11 @@ impl ThreadContextPicker {
|
|||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = ThreadContextPickerDelegate::new(
|
let delegate =
|
||||||
thread_store,
|
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
|
||||||
context_picker,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
);
|
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
ThreadContextPicker { picker }
|
ThreadContextPicker { picker }
|
||||||
@@ -57,7 +53,6 @@ pub struct ThreadContextPickerDelegate {
|
|||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
matches: Vec<ThreadContextEntry>,
|
matches: Vec<ThreadContextEntry>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
@@ -67,13 +62,11 @@ impl ThreadContextPickerDelegate {
|
|||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ThreadContextPickerDelegate {
|
ThreadContextPickerDelegate {
|
||||||
thread_store,
|
thread_store,
|
||||||
context_picker,
|
context_picker,
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
}
|
}
|
||||||
@@ -110,15 +103,15 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let Some(threads) = self.thread_store.upgrade() else {
|
let Some(thread_store) = self.thread_store.upgrade() else {
|
||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_task = search_threads(query, threads, cx);
|
let search_task = search_threads(query, Arc::new(AtomicBool::default()), thread_store, cx);
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let matches = search_task.await;
|
let matches = search_task.await;
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.delegate.matches = matches;
|
this.delegate.matches = matches.into_iter().map(|mat| mat.thread).collect();
|
||||||
this.delegate.selected_index = 0;
|
this.delegate.selected_index = 0;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -126,7 +119,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let Some(entry) = self.matches.get(self.selected_index) else {
|
let Some(entry) = self.matches.get(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -137,20 +130,15 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let thread = open_thread_task.await?;
|
let thread = open_thread_task.await?;
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.delegate
|
this.delegate
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, cx| {
|
.update(cx, |context_store, cx| {
|
||||||
context_store.add_thread(thread, true, cx)
|
context_store.add_thread(thread, true, cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
match this.delegate.confirm_behavior {
|
|
||||||
ConfirmBehavior::KeepOpen => {}
|
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
@@ -217,25 +205,38 @@ pub fn render_thread_context_entry(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ThreadMatch {
|
||||||
|
pub thread: ThreadContextEntry,
|
||||||
|
pub is_recent: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn search_threads(
|
pub(crate) fn search_threads(
|
||||||
query: String,
|
query: String,
|
||||||
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Vec<ThreadContextEntry>> {
|
) -> Task<Vec<ThreadMatch>> {
|
||||||
let threads = thread_store.update(cx, |this, _cx| {
|
let threads = thread_store
|
||||||
this.threads()
|
.read(cx)
|
||||||
.into_iter()
|
.threads()
|
||||||
.map(|thread| ThreadContextEntry {
|
.into_iter()
|
||||||
id: thread.id,
|
.map(|thread| ThreadContextEntry {
|
||||||
summary: thread.summary,
|
id: thread.id,
|
||||||
})
|
summary: thread.summary,
|
||||||
.collect::<Vec<_>>()
|
})
|
||||||
});
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
let executor = cx.background_executor().clone();
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
threads
|
threads
|
||||||
|
.into_iter()
|
||||||
|
.map(|thread| ThreadMatch {
|
||||||
|
thread,
|
||||||
|
is_recent: false,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
let candidates = threads
|
let candidates = threads
|
||||||
.iter()
|
.iter()
|
||||||
@@ -247,14 +248,17 @@ pub(crate) fn search_threads(
|
|||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&cancellation_flag,
|
||||||
executor,
|
executor,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
matches
|
matches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mat| threads[mat.candidate_id].clone())
|
.map(|mat| ThreadMatch {
|
||||||
|
thread: threads[mat.candidate_id].clone(),
|
||||||
|
is_recent: false,
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
@@ -9,11 +10,12 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
|
use project::ProjectItem;
|
||||||
use ui::{KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||||
|
|
||||||
use crate::context::{ContextId, ContextKind};
|
use crate::context::{ContextId, ContextKind};
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread::Thread;
|
use crate::thread::Thread;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
@@ -50,7 +52,6 @@ impl ContextStrip {
|
|||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
ConfirmBehavior::KeepOpen,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -59,6 +60,7 @@ impl ContextStrip {
|
|||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
|
cx.observe(&context_store, |_, _, cx| cx.notify()),
|
||||||
cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
|
cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
|
||||||
cx.on_focus(&focus_handle, window, Self::handle_focus),
|
cx.on_focus(&focus_handle, window, Self::handle_focus),
|
||||||
cx.on_blur(&focus_handle, window, Self::handle_blur),
|
cx.on_blur(&focus_handle, window, Self::handle_blur),
|
||||||
@@ -92,26 +94,23 @@ impl ContextStrip {
|
|||||||
let active_buffer_entity = editor.buffer().read(cx).as_singleton()?;
|
let active_buffer_entity = editor.buffer().read(cx).as_singleton()?;
|
||||||
let active_buffer = active_buffer_entity.read(cx);
|
let active_buffer = active_buffer_entity.read(cx);
|
||||||
|
|
||||||
let path = active_buffer.file()?.full_path(cx);
|
let project_path = active_buffer.project_path(cx)?;
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.context_store
|
.context_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.will_include_buffer(active_buffer.remote_id(), &path)
|
.will_include_buffer(active_buffer.remote_id(), &project_path)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = match path.file_name() {
|
let file_name = active_buffer.file()?.file_name(cx);
|
||||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
|
||||||
None => path.to_string_lossy().into_owned().into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let icon_path = FileIcons::get_icon(&path, cx);
|
let icon_path = FileIcons::get_icon(&Path::new(&file_name), cx);
|
||||||
|
|
||||||
Some(SuggestedContext::File {
|
Some(SuggestedContext::File {
|
||||||
name,
|
name: file_name.to_string_lossy().into_owned().into(),
|
||||||
buffer: active_buffer_entity.downgrade(),
|
buffer: active_buffer_entity.downgrade(),
|
||||||
icon_path,
|
icon_path,
|
||||||
})
|
})
|
||||||
@@ -290,9 +289,9 @@ impl ContextStrip {
|
|||||||
if let Some(index) = self.focused_index {
|
if let Some(index) = self.focused_index {
|
||||||
let mut is_empty = false;
|
let mut is_empty = false;
|
||||||
|
|
||||||
self.context_store.update(cx, |this, _cx| {
|
self.context_store.update(cx, |this, cx| {
|
||||||
if let Some(item) = this.context().get(index) {
|
if let Some(item) = this.context().get(index) {
|
||||||
this.remove_context(item.id());
|
this.remove_context(item.id(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
is_empty = this.context().is_empty();
|
is_empty = this.context().is_empty();
|
||||||
@@ -475,8 +474,8 @@ impl Render for ContextStrip {
|
|||||||
Some({
|
Some({
|
||||||
let context_store = self.context_store.clone();
|
let context_store = self.context_store.clone();
|
||||||
Rc::new(cx.listener(move |_this, _event, _window, cx| {
|
Rc::new(cx.listener(move |_this, _event, _window, cx| {
|
||||||
context_store.update(cx, |this, _cx| {
|
context_store.update(cx, |this, cx| {
|
||||||
this.remove_context(id);
|
this.remove_context(id, cx);
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use gpui::{Entity, prelude::*};
|
|||||||
|
|
||||||
use crate::thread_store::{SerializedThreadMetadata, ThreadStore};
|
use crate::thread_store::{SerializedThreadMetadata, ThreadStore};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum HistoryEntry {
|
pub enum HistoryEntry {
|
||||||
Thread(SerializedThreadMetadata),
|
Thread(SerializedThreadMetadata),
|
||||||
Context(SavedContextMetadata),
|
Context(SavedContextMetadata),
|
||||||
@@ -21,25 +22,27 @@ impl HistoryEntry {
|
|||||||
pub struct HistoryStore {
|
pub struct HistoryStore {
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||||
|
_subscriptions: Vec<gpui::Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryStore {
|
impl HistoryStore {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||||
_cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let subscriptions = vec![
|
||||||
|
cx.observe(&thread_store, |_, _, cx| cx.notify()),
|
||||||
|
cx.observe(&context_store, |_, _, cx| cx.notify()),
|
||||||
|
];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
thread_store,
|
thread_store,
|
||||||
context_store,
|
context_store,
|
||||||
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of history entries.
|
|
||||||
pub fn entry_count(&self, cx: &mut Context<Self>) -> usize {
|
|
||||||
self.entries(cx).len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
||||||
let mut history_entries = Vec::new();
|
let mut history_entries = Vec::new();
|
||||||
|
|
||||||
|
|||||||
@@ -24,21 +24,23 @@ use gpui::{
|
|||||||
WeakEntity, Window, point,
|
WeakEntity, Window, point,
|
||||||
};
|
};
|
||||||
use language::{Buffer, Point, Selection, TransactionId};
|
use language::{Buffer, Point, Selection, TransactionId};
|
||||||
|
use language_model::ConfiguredModel;
|
||||||
use language_model::{LanguageModelRegistry, report_assistant_event};
|
use language_model::{LanguageModelRegistry, report_assistant_event};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::LspAction;
|
use project::LspAction;
|
||||||
|
use project::Project;
|
||||||
use project::{CodeAction, ProjectTransaction};
|
use project::{CodeAction, ProjectTransaction};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||||
use text::{OffsetRangeExt, ToPoint as _};
|
use text::{OffsetRangeExt, ToPoint as _};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::RangeExt;
|
use util::RangeExt;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{ItemHandle, Toast, Workspace, notifications::NotificationId};
|
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
|
||||||
use workspace::{ShowConfiguration, dock::Panel};
|
use zed_actions::agent::OpenConfiguration;
|
||||||
|
|
||||||
use crate::AssistantPanel;
|
use crate::AssistantPanel;
|
||||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||||
@@ -239,8 +241,8 @@ impl InlineAssistant {
|
|||||||
|
|
||||||
let is_authenticated = || {
|
let is_authenticated = || {
|
||||||
LanguageModelRegistry::read_global(cx)
|
LanguageModelRegistry::read_global(cx)
|
||||||
.active_provider()
|
.inline_assistant_model()
|
||||||
.map_or(false, |provider| provider.is_authenticated(cx))
|
.map_or(false, |model| model.provider.is_authenticated(cx))
|
||||||
};
|
};
|
||||||
|
|
||||||
let thread_store = workspace
|
let thread_store = workspace
|
||||||
@@ -254,6 +256,7 @@ impl InlineAssistant {
|
|||||||
assistant.assist(
|
assistant.assist(
|
||||||
&active_editor,
|
&active_editor,
|
||||||
cx.entity().downgrade(),
|
cx.entity().downgrade(),
|
||||||
|
workspace.project().downgrade(),
|
||||||
thread_store,
|
thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -265,6 +268,7 @@ impl InlineAssistant {
|
|||||||
assistant.assist(
|
assistant.assist(
|
||||||
&active_terminal,
|
&active_terminal,
|
||||||
cx.entity().downgrade(),
|
cx.entity().downgrade(),
|
||||||
|
workspace.project().downgrade(),
|
||||||
thread_store,
|
thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -279,8 +283,8 @@ impl InlineAssistant {
|
|||||||
cx.spawn_in(window, async move |_workspace, cx| {
|
cx.spawn_in(window, async move |_workspace, cx| {
|
||||||
let Some(task) = cx.update(|_, cx| {
|
let Some(task) = cx.update(|_, cx| {
|
||||||
LanguageModelRegistry::read_global(cx)
|
LanguageModelRegistry::read_global(cx)
|
||||||
.active_provider()
|
.inline_assistant_model()
|
||||||
.map_or(None, |provider| Some(provider.authenticate(cx)))
|
.map_or(None, |model| Some(model.provider.authenticate(cx)))
|
||||||
})?
|
})?
|
||||||
else {
|
else {
|
||||||
let answer = cx
|
let answer = cx
|
||||||
@@ -295,7 +299,7 @@ impl InlineAssistant {
|
|||||||
if let Some(answer) = answer {
|
if let Some(answer) = answer {
|
||||||
if answer == 0 {
|
if answer == 0 {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
window.dispatch_action(Box::new(ShowConfiguration), cx)
|
window.dispatch_action(Box::new(OpenConfiguration), cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -318,6 +322,7 @@ impl InlineAssistant {
|
|||||||
&mut self,
|
&mut self,
|
||||||
editor: &Entity<Editor>,
|
editor: &Entity<Editor>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
project: WeakEntity<Project>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
@@ -401,14 +406,14 @@ impl InlineAssistant {
|
|||||||
|
|
||||||
codegen_ranges.push(anchor_range);
|
codegen_ranges.push(anchor_range);
|
||||||
|
|
||||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {
|
||||||
self.telemetry.report_assistant_event(AssistantEvent {
|
self.telemetry.report_assistant_event(AssistantEventData {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
kind: AssistantKind::Inline,
|
kind: AssistantKind::Inline,
|
||||||
phase: AssistantPhase::Invoked,
|
phase: AssistantPhase::Invoked,
|
||||||
message_id: None,
|
message_id: None,
|
||||||
model: model.telemetry_id(),
|
model: model.model.telemetry_id(),
|
||||||
model_provider: model.provider_id().to_string(),
|
model_provider: model.provider.id().to_string(),
|
||||||
response_latency: None,
|
response_latency: None,
|
||||||
error_message: None,
|
error_message: None,
|
||||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||||
@@ -425,7 +430,7 @@ impl InlineAssistant {
|
|||||||
for range in codegen_ranges {
|
for range in codegen_ranges {
|
||||||
let assist_id = self.next_assist_id.post_inc();
|
let assist_id = self.next_assist_id.post_inc();
|
||||||
let context_store =
|
let context_store =
|
||||||
cx.new(|_cx| ContextStore::new(workspace.clone(), thread_store.clone()));
|
cx.new(|_cx| ContextStore::new(project.clone(), thread_store.clone()));
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new(|cx| {
|
||||||
BufferCodegen::new(
|
BufferCodegen::new(
|
||||||
editor.read(cx).buffer().clone(),
|
editor.read(cx).buffer().clone(),
|
||||||
@@ -519,7 +524,7 @@ impl InlineAssistant {
|
|||||||
initial_prompt: String,
|
initial_prompt: String,
|
||||||
initial_transaction_id: Option<TransactionId>,
|
initial_transaction_id: Option<TransactionId>,
|
||||||
focus: bool,
|
focus: bool,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: Entity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
@@ -537,8 +542,8 @@ impl InlineAssistant {
|
|||||||
range.end = range.end.bias_right(&snapshot);
|
range.end = range.end.bias_right(&snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
let context_store =
|
let project = workspace.read(cx).project().downgrade();
|
||||||
cx.new(|_cx| ContextStore::new(workspace.clone(), thread_store.clone()));
|
let context_store = cx.new(|_cx| ContextStore::new(project, thread_store.clone()));
|
||||||
|
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new(|cx| {
|
||||||
BufferCodegen::new(
|
BufferCodegen::new(
|
||||||
@@ -562,7 +567,7 @@ impl InlineAssistant {
|
|||||||
codegen.clone(),
|
codegen.clone(),
|
||||||
self.fs.clone(),
|
self.fs.clone(),
|
||||||
context_store,
|
context_store,
|
||||||
workspace.clone(),
|
workspace.downgrade(),
|
||||||
thread_store,
|
thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -589,7 +594,7 @@ impl InlineAssistant {
|
|||||||
end_block_id,
|
end_block_id,
|
||||||
range,
|
range,
|
||||||
codegen.clone(),
|
codegen.clone(),
|
||||||
workspace.clone(),
|
workspace.downgrade(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
@@ -621,14 +626,14 @@ impl InlineAssistant {
|
|||||||
BlockProperties {
|
BlockProperties {
|
||||||
style: BlockStyle::Sticky,
|
style: BlockStyle::Sticky,
|
||||||
placement: BlockPlacement::Above(range.start),
|
placement: BlockPlacement::Above(range.start),
|
||||||
height: prompt_editor_height,
|
height: Some(prompt_editor_height),
|
||||||
render: build_assist_editor_renderer(prompt_editor),
|
render: build_assist_editor_renderer(prompt_editor),
|
||||||
priority: 0,
|
priority: 0,
|
||||||
},
|
},
|
||||||
BlockProperties {
|
BlockProperties {
|
||||||
style: BlockStyle::Sticky,
|
style: BlockStyle::Sticky,
|
||||||
placement: BlockPlacement::Below(range.end),
|
placement: BlockPlacement::Below(range.end),
|
||||||
height: 0,
|
height: None,
|
||||||
render: Arc::new(|cx| {
|
render: Arc::new(|cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.h_full()
|
.h_full()
|
||||||
@@ -976,7 +981,7 @@ impl InlineAssistant {
|
|||||||
let active_alternative = assist.codegen.read(cx).active_alternative().clone();
|
let active_alternative = assist.codegen.read(cx).active_alternative().clone();
|
||||||
let message_id = active_alternative.read(cx).message_id.clone();
|
let message_id = active_alternative.read(cx).message_id.clone();
|
||||||
|
|
||||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {
|
||||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||||
let snapshot = multibuffer.snapshot(cx);
|
let snapshot = multibuffer.snapshot(cx);
|
||||||
@@ -987,7 +992,7 @@ impl InlineAssistant {
|
|||||||
.map(|language| language.name())
|
.map(|language| language.name())
|
||||||
});
|
});
|
||||||
report_assistant_event(
|
report_assistant_event(
|
||||||
AssistantEvent {
|
AssistantEventData {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
kind: AssistantKind::Inline,
|
kind: AssistantKind::Inline,
|
||||||
message_id,
|
message_id,
|
||||||
@@ -996,15 +1001,15 @@ impl InlineAssistant {
|
|||||||
} else {
|
} else {
|
||||||
AssistantPhase::Accepted
|
AssistantPhase::Accepted
|
||||||
},
|
},
|
||||||
model: model.telemetry_id(),
|
model: model.model.telemetry_id(),
|
||||||
model_provider: model.provider_id().to_string(),
|
model_provider: model.model.provider_id().to_string(),
|
||||||
response_latency: None,
|
response_latency: None,
|
||||||
error_message: None,
|
error_message: None,
|
||||||
language_name: language_name.map(|name| name.to_proto()),
|
language_name: language_name.map(|name| name.to_proto()),
|
||||||
},
|
},
|
||||||
Some(self.telemetry.clone()),
|
Some(self.telemetry.clone()),
|
||||||
cx.http_client(),
|
cx.http_client(),
|
||||||
model.api_key(cx),
|
model.model.api_key(cx),
|
||||||
cx.background_executor(),
|
cx.background_executor(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1217,9 +1222,15 @@ impl InlineAssistant {
|
|||||||
self.prompt_history.pop_front();
|
self.prompt_history.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(ConfiguredModel { model, .. }) =
|
||||||
|
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
assist
|
assist
|
||||||
.codegen
|
.codegen
|
||||||
.update(cx, |codegen, cx| codegen.start(user_prompt, cx))
|
.update(cx, |codegen, cx| codegen.start(model, user_prompt, cx))
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1392,7 +1403,7 @@ impl InlineAssistant {
|
|||||||
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
|
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
|
||||||
new_blocks.push(BlockProperties {
|
new_blocks.push(BlockProperties {
|
||||||
placement: BlockPlacement::Above(new_row),
|
placement: BlockPlacement::Above(new_row),
|
||||||
height,
|
height: Some(height),
|
||||||
style: BlockStyle::Flex,
|
style: BlockStyle::Flex,
|
||||||
render: Arc::new(move |cx| {
|
render: Arc::new(move |cx| {
|
||||||
div()
|
div()
|
||||||
@@ -1779,6 +1790,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let thread_store = self.thread_store.clone();
|
let thread_store = self.thread_store.clone();
|
||||||
window.spawn(cx, async move |cx| {
|
window.spawn(cx, async move |cx| {
|
||||||
|
let workspace = workspace.upgrade().context("workspace was released")?;
|
||||||
let editor = editor.upgrade().context("editor was released")?;
|
let editor = editor.upgrade().context("editor was released")?;
|
||||||
let range = editor
|
let range = editor
|
||||||
.update(cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ 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 language_model_selector::{ModelType, ToggleModelSelector};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
@@ -582,7 +582,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
||||||
|
|
||||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
let default_model = model_registry.active_model();
|
let default_model = model_registry.default_model().map(|default| default.model);
|
||||||
let alternative_models = model_registry.inline_alternative_models();
|
let alternative_models = model_registry.inline_alternative_models();
|
||||||
|
|
||||||
let get_model_name = |index: usize| -> String {
|
let get_model_name = |index: usize| -> String {
|
||||||
@@ -890,6 +890,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
fs,
|
fs,
|
||||||
model_selector_menu_handle,
|
model_selector_menu_handle,
|
||||||
prompt_editor.focus_handle(cx),
|
prompt_editor.focus_handle(cx),
|
||||||
|
ModelType::InlineAssistant,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -1042,6 +1043,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
fs,
|
fs,
|
||||||
model_selector_menu_handle.clone(),
|
model_selector_menu_handle.clone(),
|
||||||
prompt_editor.focus_handle(cx),
|
prompt_editor.focus_handle(cx),
|
||||||
|
ModelType::InlineAssistant,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ impl ProfileSelector {
|
|||||||
|
|
||||||
thread_store
|
thread_store
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.load_profile_by_id(&profile_id, cx);
|
this.load_profile_by_id(profile_id.clone(), cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
@@ -130,8 +130,8 @@ impl Render for ProfileSelector {
|
|||||||
|
|
||||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
let supports_tools = model_registry
|
let supports_tools = model_registry
|
||||||
.active_model()
|
.default_model()
|
||||||
.map_or(false, |model| model.supports_tools());
|
.map_or(false, |default| default.model.supports_tools());
|
||||||
|
|
||||||
let icon = match profile_id.as_str() {
|
let icon = match profile_id.as_str() {
|
||||||
"write" => IconName::Pencil,
|
"write" => IconName::Pencil,
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ use crate::inline_prompt_editor::CodegenStatus;
|
|||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use futures::{SinkExt, StreamExt, channel::mpsc};
|
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
||||||
use language_model::{LanguageModelRegistry, LanguageModelRequest, report_assistant_event};
|
use language_model::{
|
||||||
|
ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, report_assistant_event,
|
||||||
|
};
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
use terminal::Terminal;
|
use terminal::Terminal;
|
||||||
|
|
||||||
pub struct TerminalCodegen {
|
pub struct TerminalCodegen {
|
||||||
@@ -31,7 +33,9 @@ impl TerminalCodegen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut Context<Self>) {
|
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut Context<Self>) {
|
||||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
let Some(ConfiguredModel { model, .. }) =
|
||||||
|
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ impl TerminalCodegen {
|
|||||||
|
|
||||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||||
report_assistant_event(
|
report_assistant_event(
|
||||||
AssistantEvent {
|
AssistantEventData {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
kind: AssistantKind::InlineTerminal,
|
kind: AssistantKind::InlineTerminal,
|
||||||
message_id,
|
message_id,
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ use fs::Fs;
|
|||||||
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||||
report_assistant_event,
|
Role, report_assistant_event,
|
||||||
};
|
};
|
||||||
|
use project::Project;
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
use terminal_view::TerminalView;
|
use terminal_view::TerminalView;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
@@ -67,6 +68,7 @@ impl TerminalInlineAssistant {
|
|||||||
&mut self,
|
&mut self,
|
||||||
terminal_view: &Entity<TerminalView>,
|
terminal_view: &Entity<TerminalView>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
project: WeakEntity<Project>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
@@ -75,8 +77,7 @@ impl TerminalInlineAssistant {
|
|||||||
let assist_id = self.next_assist_id.post_inc();
|
let assist_id = self.next_assist_id.post_inc();
|
||||||
let prompt_buffer =
|
let prompt_buffer =
|
||||||
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(String::new(), cx)), cx));
|
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(String::new(), cx)), cx));
|
||||||
let context_store =
|
let context_store = cx.new(|_cx| ContextStore::new(project, thread_store.clone()));
|
||||||
cx.new(|_cx| ContextStore::new(workspace.clone(), thread_store.clone()));
|
|
||||||
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||||
|
|
||||||
let prompt_editor = cx.new(|cx| {
|
let prompt_editor = cx.new(|cx| {
|
||||||
@@ -260,6 +261,8 @@ impl TerminalInlineAssistant {
|
|||||||
request_message.content.push(prompt.into());
|
request_message.content.push(prompt.into());
|
||||||
|
|
||||||
Ok(LanguageModelRequest {
|
Ok(LanguageModelRequest {
|
||||||
|
thread_id: None,
|
||||||
|
prompt_id: None,
|
||||||
messages: vec![request_message],
|
messages: vec![request_message],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
@@ -286,11 +289,13 @@ impl TerminalInlineAssistant {
|
|||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
if let Some(ConfiguredModel { model, .. }) =
|
||||||
|
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||||
|
{
|
||||||
let codegen = assist.codegen.read(cx);
|
let codegen = assist.codegen.read(cx);
|
||||||
let executor = cx.background_executor().clone();
|
let executor = cx.background_executor().clone();
|
||||||
report_assistant_event(
|
report_assistant_event(
|
||||||
AssistantEvent {
|
AssistantEventData {
|
||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
kind: AssistantKind::InlineTerminal,
|
kind: AssistantKind::InlineTerminal,
|
||||||
message_id: codegen.message_id.clone(),
|
message_id: codegen.message_id.clone(),
|
||||||
|
|||||||
@@ -1,52 +1,189 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_context_editor::SavedContextMetadata;
|
use assistant_context_editor::SavedContextMetadata;
|
||||||
|
use editor::{Editor, EditorEvent};
|
||||||
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle, WeakEntity,
|
App, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task, UniformListScrollHandle,
|
||||||
uniform_list,
|
WeakEntity, Window, uniform_list,
|
||||||
};
|
};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{IconButtonShape, ListItem, ListItemSpacing, Tooltip, prelude::*};
|
use ui::{
|
||||||
|
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
|
||||||
|
Tooltip, prelude::*,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
use crate::history_store::{HistoryEntry, HistoryStore};
|
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||||
use crate::thread_store::SerializedThreadMetadata;
|
use crate::thread_store::SerializedThreadMetadata;
|
||||||
use crate::{AssistantPanel, RemoveSelectedThread};
|
use crate::{AssistantPanel, RemoveSelectedThread};
|
||||||
|
|
||||||
pub struct ThreadHistory {
|
pub struct ThreadHistory {
|
||||||
focus_handle: FocusHandle,
|
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
|
search_query: SharedString,
|
||||||
|
search_editor: Entity<Editor>,
|
||||||
|
all_entries: Arc<Vec<HistoryEntry>>,
|
||||||
|
matches: Vec<StringMatch>,
|
||||||
|
_subscriptions: Vec<gpui::Subscription>,
|
||||||
|
_search_task: Option<Task<()>>,
|
||||||
|
scrollbar_visibility: bool,
|
||||||
|
scrollbar_state: ScrollbarState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadHistory {
|
impl ThreadHistory {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let search_editor = cx.new(|cx| {
|
||||||
|
let mut editor = Editor::single_line(window, cx);
|
||||||
|
editor.set_placeholder_text("Search threads...", cx);
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
let search_editor_subscription =
|
||||||
|
cx.subscribe(&search_editor, |this, search_editor, event, cx| {
|
||||||
|
if let EditorEvent::BufferEdited = event {
|
||||||
|
let query = search_editor.read(cx).text(cx);
|
||||||
|
this.search_query = query.into();
|
||||||
|
this.update_search(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let entries: Arc<Vec<_>> = history_store
|
||||||
|
.update(cx, |store, cx| store.entries(cx))
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let history_store_subscription = cx.observe(&history_store, |this, _, cx| {
|
||||||
|
this.update_all_entries(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let scroll_handle = UniformListScrollHandle::default();
|
||||||
|
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
assistant_panel,
|
assistant_panel,
|
||||||
history_store,
|
history_store,
|
||||||
scroll_handle: UniformListScrollHandle::default(),
|
scroll_handle,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
|
search_query: SharedString::new_static(""),
|
||||||
|
all_entries: entries,
|
||||||
|
matches: Vec::new(),
|
||||||
|
search_editor,
|
||||||
|
_subscriptions: vec![search_editor_subscription, history_store_subscription],
|
||||||
|
_search_task: None,
|
||||||
|
scrollbar_visibility: true,
|
||||||
|
scrollbar_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_all_entries(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.all_entries = self
|
||||||
|
.history_store
|
||||||
|
.update(cx, |store, cx| store.entries(cx))
|
||||||
|
.into();
|
||||||
|
self.matches.clear();
|
||||||
|
self.update_search(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_search(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self._search_task.take();
|
||||||
|
|
||||||
|
if self.has_search_query() {
|
||||||
|
self.perform_search(cx);
|
||||||
|
} else {
|
||||||
|
self.matches.clear();
|
||||||
|
self.set_selected_index(0, cx);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_search(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let query = self.search_query.clone();
|
||||||
|
let all_entries = self.all_entries.clone();
|
||||||
|
|
||||||
|
let task = cx.spawn(async move |this, cx| {
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
|
||||||
|
let matches = cx
|
||||||
|
.background_spawn(async move {
|
||||||
|
let mut candidates = Vec::with_capacity(all_entries.len());
|
||||||
|
|
||||||
|
for (idx, entry) in all_entries.iter().enumerate() {
|
||||||
|
match entry {
|
||||||
|
HistoryEntry::Thread(thread) => {
|
||||||
|
candidates.push(StringMatchCandidate::new(idx, &thread.summary));
|
||||||
|
}
|
||||||
|
HistoryEntry::Context(context) => {
|
||||||
|
candidates.push(StringMatchCandidate::new(idx, &context.title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_MATCHES: usize = 100;
|
||||||
|
|
||||||
|
fuzzy::match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
MAX_MATCHES,
|
||||||
|
&Default::default(),
|
||||||
|
executor,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.matches = matches;
|
||||||
|
this.set_selected_index(0, cx);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
self._search_task = Some(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_search_query(&self) -> bool {
|
||||||
|
!self.search_query.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matched_count(&self) -> usize {
|
||||||
|
if self.has_search_query() {
|
||||||
|
self.matches.len()
|
||||||
|
} else {
|
||||||
|
self.all_entries.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_match(&self, ix: usize) -> Option<&HistoryEntry> {
|
||||||
|
if self.has_search_query() {
|
||||||
|
self.matches
|
||||||
|
.get(ix)
|
||||||
|
.and_then(|m| self.all_entries.get(m.candidate_id))
|
||||||
|
} else {
|
||||||
|
self.all_entries.get(ix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_previous(
|
pub fn select_previous(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &menu::SelectPrevious,
|
_: &menu::SelectPrevious,
|
||||||
window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let count = self
|
let count = self.matched_count();
|
||||||
.history_store
|
|
||||||
.update(cx, |this, cx| this.entry_count(cx));
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if self.selected_index == 0 {
|
if self.selected_index == 0 {
|
||||||
self.set_selected_index(count - 1, window, cx);
|
self.set_selected_index(count - 1, cx);
|
||||||
} else {
|
} else {
|
||||||
self.set_selected_index(self.selected_index - 1, window, cx);
|
self.set_selected_index(self.selected_index - 1, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,64 +191,98 @@ impl ThreadHistory {
|
|||||||
pub fn select_next(
|
pub fn select_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &menu::SelectNext,
|
_: &menu::SelectNext,
|
||||||
window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let count = self
|
let count = self.matched_count();
|
||||||
.history_store
|
|
||||||
.update(cx, |this, cx| this.entry_count(cx));
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if self.selected_index == count - 1 {
|
if self.selected_index == count - 1 {
|
||||||
self.set_selected_index(0, window, cx);
|
self.set_selected_index(0, cx);
|
||||||
} else {
|
} else {
|
||||||
self.set_selected_index(self.selected_index + 1, window, cx);
|
self.set_selected_index(self.selected_index + 1, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_first(
|
||||||
let count = self
|
&mut self,
|
||||||
.history_store
|
_: &menu::SelectFirst,
|
||||||
.update(cx, |this, cx| this.entry_count(cx));
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let count = self.matched_count();
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
self.set_selected_index(0, window, cx);
|
self.set_selected_index(0, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let count = self
|
let count = self.matched_count();
|
||||||
.history_store
|
|
||||||
.update(cx, |this, cx| this.entry_count(cx));
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
self.set_selected_index(count - 1, window, cx);
|
self.set_selected_index(count - 1, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(&mut self, index: usize, _window: &mut Window, cx: &mut Context<Self>) {
|
fn set_selected_index(&mut self, index: usize, cx: &mut Context<Self>) {
|
||||||
self.selected_index = index;
|
self.selected_index = index;
|
||||||
self.scroll_handle
|
self.scroll_handle
|
||||||
.scroll_to_item(index, ScrollStrategy::Top);
|
.scroll_to_item(index, ScrollStrategy::Top);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
||||||
let entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(entry) = entries.get(self.selected_index) {
|
Some(
|
||||||
match entry {
|
div()
|
||||||
HistoryEntry::Thread(thread) => {
|
.occlude()
|
||||||
self.assistant_panel
|
.id("thread-history-scroll")
|
||||||
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
.h_full()
|
||||||
.ok();
|
.bg(cx.theme().colors().panel_background.opacity(0.8))
|
||||||
}
|
.border_l_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.absolute()
|
||||||
|
.right_1()
|
||||||
|
.top_0()
|
||||||
|
.bottom_0()
|
||||||
|
.w_4()
|
||||||
|
.pl_1()
|
||||||
|
.cursor_default()
|
||||||
|
.on_mouse_move(cx.listener(|_, _, _window, cx| {
|
||||||
|
cx.notify();
|
||||||
|
cx.stop_propagation()
|
||||||
|
}))
|
||||||
|
.on_hover(|_, _window, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_any_mouse_down(|_, _window, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
|
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some(entry) = self.get_match(self.selected_index) {
|
||||||
|
let task_result = match entry {
|
||||||
|
HistoryEntry::Thread(thread) => self
|
||||||
|
.assistant_panel
|
||||||
|
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx)),
|
||||||
HistoryEntry::Context(context) => {
|
HistoryEntry::Context(context) => {
|
||||||
self.assistant_panel
|
self.assistant_panel.update(cx, move |this, cx| {
|
||||||
.update(cx, move |this, cx| {
|
this.open_saved_prompt_editor(context.path.clone(), window, cx)
|
||||||
this.open_saved_prompt_editor(context.path.clone(), window, cx)
|
})
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
if let Some(task) = task_result.log_err() {
|
||||||
|
task.detach_and_log_err(cx);
|
||||||
|
};
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -123,25 +294,19 @@ impl ThreadHistory {
|
|||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
if let Some(entry) = self.get_match(self.selected_index) {
|
||||||
|
let task_result = match entry {
|
||||||
|
HistoryEntry::Thread(thread) => self
|
||||||
|
.assistant_panel
|
||||||
|
.update(cx, |this, cx| this.delete_thread(&thread.id, cx)),
|
||||||
|
HistoryEntry::Context(context) => self
|
||||||
|
.assistant_panel
|
||||||
|
.update(cx, |this, cx| this.delete_context(context.path.clone(), cx)),
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(entry) = entries.get(self.selected_index) {
|
if let Some(task) = task_result.log_err() {
|
||||||
match entry {
|
task.detach_and_log_err(cx);
|
||||||
HistoryEntry::Thread(thread) => {
|
};
|
||||||
self.assistant_panel
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.delete_thread(&thread.id, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
HistoryEntry::Context(context) => {
|
|
||||||
self.assistant_panel
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.delete_context(context.path.clone(), cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -149,71 +314,129 @@ impl ThreadHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for ThreadHistory {
|
impl Focusable for ThreadHistory {
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.search_editor.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ThreadHistory {
|
impl Render for ThreadHistory {
|
||||||
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 history_entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
|
||||||
let selected_index = self.selected_index;
|
let selected_index = self.selected_index;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("thread-history-container")
|
|
||||||
.key_context("ThreadHistory")
|
.key_context("ThreadHistory")
|
||||||
.track_focus(&self.focus_handle)
|
|
||||||
.overflow_y_scroll()
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.p_1()
|
|
||||||
.on_action(cx.listener(Self::select_previous))
|
.on_action(cx.listener(Self::select_previous))
|
||||||
.on_action(cx.listener(Self::select_next))
|
.on_action(cx.listener(Self::select_next))
|
||||||
.on_action(cx.listener(Self::select_first))
|
.on_action(cx.listener(Self::select_first))
|
||||||
.on_action(cx.listener(Self::select_last))
|
.on_action(cx.listener(Self::select_last))
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
.on_action(cx.listener(Self::remove_selected_thread))
|
.on_action(cx.listener(Self::remove_selected_thread))
|
||||||
.map(|history| {
|
.when(!self.all_entries.is_empty(), |parent| {
|
||||||
if history_entries.is_empty() {
|
parent.child(
|
||||||
history
|
h_flex()
|
||||||
.justify_center()
|
.h(px(41.)) // Match the toolbar perfectly
|
||||||
|
.w_full()
|
||||||
|
.py_1()
|
||||||
|
.px_2()
|
||||||
|
.gap_2()
|
||||||
|
.justify_between()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::MagnifyingGlass)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(IconSize::Small),
|
||||||
|
)
|
||||||
|
.child(self.search_editor.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child({
|
||||||
|
let view = v_flex()
|
||||||
|
.id("list-container")
|
||||||
|
.relative()
|
||||||
|
.overflow_hidden()
|
||||||
|
.flex_grow();
|
||||||
|
|
||||||
|
if self.all_entries.is_empty() {
|
||||||
|
view.justify_center()
|
||||||
.child(
|
.child(
|
||||||
h_flex().w_full().justify_center().child(
|
h_flex().w_full().justify_center().child(
|
||||||
Label::new("You don't have any past threads yet.")
|
Label::new("You don't have any past threads yet.")
|
||||||
.size(LabelSize::Small),
|
.size(LabelSize::Small),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
} else if self.has_search_query() && self.matches.is_empty() {
|
||||||
|
view.justify_center().child(
|
||||||
|
h_flex().w_full().justify_center().child(
|
||||||
|
Label::new("No threads match your search.").size(LabelSize::Small),
|
||||||
|
),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
history.child(
|
view.pr_5()
|
||||||
uniform_list(
|
.child(
|
||||||
cx.entity().clone(),
|
uniform_list(
|
||||||
"thread-history",
|
cx.entity().clone(),
|
||||||
history_entries.len(),
|
"thread-history",
|
||||||
move |history, range, _window, _cx| {
|
self.matched_count(),
|
||||||
history_entries[range]
|
move |history, range, _window, _cx| {
|
||||||
.iter()
|
let range_start = range.start;
|
||||||
.enumerate()
|
let assistant_panel = history.assistant_panel.clone();
|
||||||
.map(|(index, entry)| {
|
|
||||||
|
let render_item = |index: usize,
|
||||||
|
entry: &HistoryEntry,
|
||||||
|
highlight_positions: Vec<usize>|
|
||||||
|
-> Div {
|
||||||
h_flex().w_full().pb_1().child(match entry {
|
h_flex().w_full().pb_1().child(match entry {
|
||||||
HistoryEntry::Thread(thread) => PastThread::new(
|
HistoryEntry::Thread(thread) => PastThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
history.assistant_panel.clone(),
|
assistant_panel.clone(),
|
||||||
selected_index == index,
|
selected_index == index + range_start,
|
||||||
|
highlight_positions,
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
HistoryEntry::Context(context) => PastContext::new(
|
HistoryEntry::Context(context) => PastContext::new(
|
||||||
context.clone(),
|
context.clone(),
|
||||||
history.assistant_panel.clone(),
|
assistant_panel.clone(),
|
||||||
selected_index == index,
|
selected_index == index + range_start,
|
||||||
|
highlight_positions,
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
})
|
})
|
||||||
})
|
};
|
||||||
.collect()
|
|
||||||
},
|
if history.has_search_query() {
|
||||||
|
history.matches[range]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, m)| {
|
||||||
|
history.all_entries.get(m.candidate_id).map(
|
||||||
|
|entry| {
|
||||||
|
render_item(
|
||||||
|
index,
|
||||||
|
entry,
|
||||||
|
m.positions.clone(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
history.all_entries[range]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, entry)| render_item(index, entry, vec![]))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.p_1()
|
||||||
|
.track_scroll(self.scroll_handle.clone())
|
||||||
|
.flex_grow(),
|
||||||
)
|
)
|
||||||
.track_scroll(self.scroll_handle.clone())
|
.when_some(self.render_scrollbar(cx), |div, scrollbar| {
|
||||||
.flex_grow(),
|
div.child(scrollbar)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -224,6 +447,7 @@ pub struct PastThread {
|
|||||||
thread: SerializedThreadMetadata,
|
thread: SerializedThreadMetadata,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
highlight_positions: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PastThread {
|
impl PastThread {
|
||||||
@@ -231,11 +455,13 @@ impl PastThread {
|
|||||||
thread: SerializedThreadMetadata,
|
thread: SerializedThreadMetadata,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
highlight_positions: Vec<usize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
thread,
|
thread,
|
||||||
assistant_panel,
|
assistant_panel,
|
||||||
selected,
|
selected,
|
||||||
|
highlight_positions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,24 +484,15 @@ impl RenderOnce for PastThread {
|
|||||||
.toggle_state(self.selected)
|
.toggle_state(self.selected)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.start_slot(
|
.start_slot(
|
||||||
div()
|
div().max_w_4_5().child(
|
||||||
.max_w_4_5()
|
HighlightedLabel::new(summary, self.highlight_positions)
|
||||||
.child(Label::new(summary).size(LabelSize::Small).truncate()),
|
.size(LabelSize::Small)
|
||||||
|
.truncate(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.end_slot(
|
.end_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.child(
|
|
||||||
Label::new("Thread")
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(LabelSize::XSmall),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.size(px(3.))
|
|
||||||
.rounded_full()
|
|
||||||
.bg(cx.theme().colors().text_disabled),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
Label::new(thread_timestamp)
|
Label::new(thread_timestamp)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
@@ -285,14 +502,17 @@ impl RenderOnce for PastThread {
|
|||||||
IconButton::new("delete", IconName::TrashAlt)
|
IconButton::new("delete", IconName::TrashAlt)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.tooltip(Tooltip::text("Delete Thread"))
|
.icon_color(Color::Muted)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
|
||||||
|
})
|
||||||
.on_click({
|
.on_click({
|
||||||
let assistant_panel = self.assistant_panel.clone();
|
let assistant_panel = self.assistant_panel.clone();
|
||||||
let id = self.thread.id.clone();
|
let id = self.thread.id.clone();
|
||||||
move |_event, _window, cx| {
|
move |_event, _window, cx| {
|
||||||
assistant_panel
|
assistant_panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.delete_thread(&id, cx);
|
this.delete_thread(&id, cx).detach_and_log_err(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -318,6 +538,7 @@ pub struct PastContext {
|
|||||||
context: SavedContextMetadata,
|
context: SavedContextMetadata,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
highlight_positions: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PastContext {
|
impl PastContext {
|
||||||
@@ -325,11 +546,13 @@ impl PastContext {
|
|||||||
context: SavedContextMetadata,
|
context: SavedContextMetadata,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
highlight_positions: Vec<usize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context,
|
context,
|
||||||
assistant_panel,
|
assistant_panel,
|
||||||
selected,
|
selected,
|
||||||
|
highlight_positions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +560,6 @@ impl PastContext {
|
|||||||
impl RenderOnce for PastContext {
|
impl RenderOnce for PastContext {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let summary = self.context.title;
|
let summary = self.context.title;
|
||||||
|
|
||||||
let context_timestamp = time_format::format_localized_timestamp(
|
let context_timestamp = time_format::format_localized_timestamp(
|
||||||
OffsetDateTime::from_unix_timestamp(self.context.mtime.timestamp()).unwrap(),
|
OffsetDateTime::from_unix_timestamp(self.context.mtime.timestamp()).unwrap(),
|
||||||
OffsetDateTime::now_utc(),
|
OffsetDateTime::now_utc(),
|
||||||
@@ -354,24 +576,15 @@ impl RenderOnce for PastContext {
|
|||||||
.toggle_state(self.selected)
|
.toggle_state(self.selected)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.start_slot(
|
.start_slot(
|
||||||
div()
|
div().max_w_4_5().child(
|
||||||
.max_w_4_5()
|
HighlightedLabel::new(summary, self.highlight_positions)
|
||||||
.child(Label::new(summary).size(LabelSize::Small).truncate()),
|
.size(LabelSize::Small)
|
||||||
|
.truncate(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.end_slot(
|
.end_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.child(
|
|
||||||
Label::new("Prompt Editor")
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(LabelSize::XSmall),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.size(px(3.))
|
|
||||||
.rounded_full()
|
|
||||||
.bg(cx.theme().colors().text_disabled),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
Label::new(context_timestamp)
|
Label::new(context_timestamp)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
@@ -381,14 +594,18 @@ impl RenderOnce for PastContext {
|
|||||||
IconButton::new("delete", IconName::TrashAlt)
|
IconButton::new("delete", IconName::TrashAlt)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.tooltip(Tooltip::text("Delete Prompt Editor"))
|
.icon_color(Color::Muted)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
|
||||||
|
})
|
||||||
.on_click({
|
.on_click({
|
||||||
let assistant_panel = self.assistant_panel.clone();
|
let assistant_panel = self.assistant_panel.clone();
|
||||||
let path = self.context.path.clone();
|
let path = self.context.path.clone();
|
||||||
move |_event, _window, cx| {
|
move |_event, _window, cx| {
|
||||||
assistant_panel
|
assistant_panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.delete_context(path.clone(), cx);
|
this.delete_context(path.clone(), cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,376 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::PathBuf;
|
use std::cell::{Ref, RefCell};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
|
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
|
||||||
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;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::ContextServerManager;
|
||||||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||||
use futures::FutureExt as _;
|
use fs::Fs;
|
||||||
|
use futures::channel::{mpsc, oneshot};
|
||||||
use futures::future::{self, BoxFuture, Shared};
|
use futures::future::{self, BoxFuture, Shared};
|
||||||
|
use futures::{FutureExt as _, StreamExt as _};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, BackgroundExecutor, Context, Entity, Global, ReadGlobal, SharedString, Subscription, Task,
|
App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
|
||||||
prelude::*,
|
Subscription, Task, prelude::*,
|
||||||
};
|
};
|
||||||
use heed::Database;
|
use heed::Database;
|
||||||
use heed::types::SerdeBincode;
|
use heed::types::SerdeBincode;
|
||||||
use language_model::{LanguageModelToolUseId, Role, TokenUsage};
|
use language_model::{LanguageModelToolUseId, Role, TokenUsage};
|
||||||
use project::Project;
|
use project::{Project, Worktree};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::{
|
||||||
|
ProjectContext, PromptBuilder, PromptId, PromptMetadata, PromptStore, PromptsUpdatedEvent,
|
||||||
|
RulesFileContext, UserPromptId, UserRulesContext, WorktreeContext,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings as _, SettingsStore};
|
use settings::{Settings as _, SettingsStore};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
use crate::thread::{
|
use crate::thread::{
|
||||||
DetailedSummaryState, MessageId, ProjectSnapshot, Thread, ThreadEvent, ThreadId,
|
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RULES_FILE_NAMES: [&'static str; 6] = [
|
||||||
|
".rules",
|
||||||
|
".cursorrules",
|
||||||
|
".windsurfrules",
|
||||||
|
".clinerules",
|
||||||
|
".github/copilot-instructions.md",
|
||||||
|
"CLAUDE.md",
|
||||||
|
];
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
ThreadsDatabase::init(cx);
|
ThreadsDatabase::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A system prompt shared by all threads created by this ThreadStore
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct SharedProjectContext(Rc<RefCell<Option<ProjectContext>>>);
|
||||||
|
|
||||||
|
impl SharedProjectContext {
|
||||||
|
pub fn borrow(&self) -> Ref<Option<ProjectContext>> {
|
||||||
|
self.0.borrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ThreadStore {
|
pub struct ThreadStore {
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||||
threads: Vec<SerializedThreadMetadata>,
|
threads: Vec<SerializedThreadMetadata>,
|
||||||
|
project_context: SharedProjectContext,
|
||||||
|
reload_system_prompt_tx: mpsc::Sender<()>,
|
||||||
|
_reload_system_prompt_task: Task<()>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RulesLoadingError {
|
||||||
|
pub message: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<RulesLoadingError> for ThreadStore {}
|
||||||
|
|
||||||
impl ThreadStore {
|
impl ThreadStore {
|
||||||
pub fn new(
|
pub fn load(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<Entity<Self>> {
|
) -> Task<Result<Entity<Self>>> {
|
||||||
let this = cx.new(|cx| {
|
let prompt_store = PromptStore::global(cx);
|
||||||
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
|
cx.spawn(async move |cx| {
|
||||||
let context_server_manager = cx.new(|cx| {
|
let prompt_store = prompt_store.await.ok();
|
||||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
let (thread_store, ready_rx) = cx.update(|cx| {
|
||||||
});
|
let mut option_ready_rx = None;
|
||||||
let settings_subscription =
|
let thread_store = cx.new(|cx| {
|
||||||
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
|
let (thread_store, ready_rx) =
|
||||||
this.load_default_profile(cx);
|
Self::new(project, tools, prompt_builder, prompt_store, cx);
|
||||||
|
option_ready_rx = Some(ready_rx);
|
||||||
|
thread_store
|
||||||
});
|
});
|
||||||
|
(thread_store, option_ready_rx.take().unwrap())
|
||||||
|
})?;
|
||||||
|
ready_rx.await?;
|
||||||
|
Ok(thread_store)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let this = Self {
|
fn new(
|
||||||
project,
|
project: Entity<Project>,
|
||||||
tools,
|
tools: Entity<ToolWorkingSet>,
|
||||||
prompt_builder,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
context_server_manager,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
context_server_tool_ids: HashMap::default(),
|
cx: &mut Context<Self>,
|
||||||
threads: Vec::new(),
|
) -> (Self, oneshot::Receiver<()>) {
|
||||||
_subscriptions: vec![settings_subscription],
|
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
|
||||||
};
|
let context_server_manager = cx.new(|cx| {
|
||||||
this.load_default_profile(cx);
|
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||||
this.register_context_server_handlers(cx);
|
|
||||||
this.reload(cx).detach_and_log_err(cx);
|
|
||||||
|
|
||||||
this
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(this)
|
let mut subscriptions = vec![
|
||||||
|
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
|
||||||
|
this.load_default_profile(cx);
|
||||||
|
}),
|
||||||
|
cx.subscribe(&project, Self::handle_project_event),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(prompt_store) = prompt_store.as_ref() {
|
||||||
|
subscriptions.push(cx.subscribe(
|
||||||
|
prompt_store,
|
||||||
|
|this, _prompt_store, PromptsUpdatedEvent, _cx| {
|
||||||
|
this.enqueue_system_prompt_reload();
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This channel and task prevent concurrent and redundant loading of the system prompt.
|
||||||
|
let (reload_system_prompt_tx, mut reload_system_prompt_rx) = mpsc::channel(1);
|
||||||
|
let (ready_tx, ready_rx) = oneshot::channel();
|
||||||
|
let mut ready_tx = Some(ready_tx);
|
||||||
|
let reload_system_prompt_task = cx.spawn({
|
||||||
|
let prompt_store = prompt_store.clone();
|
||||||
|
async move |thread_store, cx| {
|
||||||
|
loop {
|
||||||
|
let Some(reload_task) = thread_store
|
||||||
|
.update(cx, |thread_store, cx| {
|
||||||
|
thread_store.reload_system_prompt(prompt_store.clone(), cx)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
reload_task.await;
|
||||||
|
if let Some(ready_tx) = ready_tx.take() {
|
||||||
|
ready_tx.send(()).ok();
|
||||||
|
}
|
||||||
|
reload_system_prompt_rx.next().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let this = Self {
|
||||||
|
project,
|
||||||
|
tools,
|
||||||
|
prompt_builder,
|
||||||
|
prompt_store,
|
||||||
|
context_server_manager,
|
||||||
|
context_server_tool_ids: HashMap::default(),
|
||||||
|
threads: Vec::new(),
|
||||||
|
project_context: SharedProjectContext::default(),
|
||||||
|
reload_system_prompt_tx,
|
||||||
|
_reload_system_prompt_task: reload_system_prompt_task,
|
||||||
|
_subscriptions: subscriptions,
|
||||||
|
};
|
||||||
|
this.load_default_profile(cx);
|
||||||
|
this.register_context_server_handlers(cx);
|
||||||
|
this.reload(cx).detach_and_log_err(cx);
|
||||||
|
(this, ready_rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_project_event(
|
||||||
|
&mut self,
|
||||||
|
_project: Entity<Project>,
|
||||||
|
event: &project::Event,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
project::Event::WorktreeAdded(_) | project::Event::WorktreeRemoved(_) => {
|
||||||
|
self.enqueue_system_prompt_reload();
|
||||||
|
}
|
||||||
|
project::Event::WorktreeUpdatedEntries(_, items) => {
|
||||||
|
if items.iter().any(|(path, _, _)| {
|
||||||
|
RULES_FILE_NAMES
|
||||||
|
.iter()
|
||||||
|
.any(|name| path.as_ref() == Path::new(name))
|
||||||
|
}) {
|
||||||
|
self.enqueue_system_prompt_reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enqueue_system_prompt_reload(&mut self) {
|
||||||
|
self.reload_system_prompt_tx.try_send(()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this should only be called from `reload_system_prompt_task`.
|
||||||
|
fn reload_system_prompt(
|
||||||
|
&self,
|
||||||
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<()> {
|
||||||
|
let project = self.project.read(cx);
|
||||||
|
let worktree_tasks = project
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.map(|worktree| {
|
||||||
|
Self::load_worktree_info_for_system_prompt(
|
||||||
|
project.fs().clone(),
|
||||||
|
worktree.read(cx),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let default_user_rules_task = match prompt_store {
|
||||||
|
None => Task::ready(vec![]),
|
||||||
|
Some(prompt_store) => prompt_store.read_with(cx, |prompt_store, cx| {
|
||||||
|
let prompts = prompt_store.default_prompt_metadata();
|
||||||
|
let load_tasks = prompts.into_iter().map(|prompt_metadata| {
|
||||||
|
let contents = prompt_store.load(prompt_metadata.id, cx);
|
||||||
|
async move { (contents.await, prompt_metadata) }
|
||||||
|
});
|
||||||
|
cx.background_spawn(future::join_all(load_tasks))
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let (worktrees, default_user_rules) =
|
||||||
|
future::join(future::join_all(worktree_tasks), default_user_rules_task).await;
|
||||||
|
|
||||||
|
let worktrees = worktrees
|
||||||
|
.into_iter()
|
||||||
|
.map(|(worktree, rules_error)| {
|
||||||
|
if let Some(rules_error) = rules_error {
|
||||||
|
this.update(cx, |_, cx| cx.emit(rules_error)).ok();
|
||||||
|
}
|
||||||
|
worktree
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let default_user_rules = default_user_rules
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|(contents, prompt_metadata)| match contents {
|
||||||
|
Ok(contents) => Some(UserRulesContext {
|
||||||
|
uuid: match prompt_metadata.id {
|
||||||
|
PromptId::User { uuid } => uuid,
|
||||||
|
PromptId::EditWorkflow => return None,
|
||||||
|
},
|
||||||
|
title: prompt_metadata.title.map(|title| title.to_string()),
|
||||||
|
contents,
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
this.update(cx, |_, cx| {
|
||||||
|
cx.emit(RulesLoadingError {
|
||||||
|
message: format!("{err:?}").into(),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
this.update(cx, |this, _cx| {
|
||||||
|
*this.project_context.0.borrow_mut() =
|
||||||
|
Some(ProjectContext::new(worktrees, default_user_rules));
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_worktree_info_for_system_prompt(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
worktree: &Worktree,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
||||||
|
let root_name = worktree.root_name().into();
|
||||||
|
|
||||||
|
let rules_task = Self::load_worktree_rules_file(fs, worktree, cx);
|
||||||
|
let Some(rules_task) = rules_task else {
|
||||||
|
return Task::ready((
|
||||||
|
WorktreeContext {
|
||||||
|
root_name,
|
||||||
|
rules_file: None,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.spawn(async move |_| {
|
||||||
|
let (rules_file, rules_file_error) = match rules_task.await {
|
||||||
|
Ok(rules_file) => (Some(rules_file), None),
|
||||||
|
Err(err) => (
|
||||||
|
None,
|
||||||
|
Some(RulesLoadingError {
|
||||||
|
message: format!("{err}").into(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let worktree_info = WorktreeContext {
|
||||||
|
root_name,
|
||||||
|
rules_file,
|
||||||
|
};
|
||||||
|
(worktree_info, rules_file_error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_worktree_rules_file(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
worktree: &Worktree,
|
||||||
|
cx: &App,
|
||||||
|
) -> Option<Task<Result<RulesFileContext>>> {
|
||||||
|
let selected_rules_file = RULES_FILE_NAMES
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|name| {
|
||||||
|
worktree
|
||||||
|
.entry_for_path(name)
|
||||||
|
.filter(|entry| entry.is_file())
|
||||||
|
.map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
|
||||||
|
})
|
||||||
|
.next();
|
||||||
|
|
||||||
|
// Note that Cline supports `.clinerules` being a directory, but that is not currently
|
||||||
|
// supported. This doesn't seem to occur often in GitHub repositories.
|
||||||
|
selected_rules_file.map(|(path_in_worktree, abs_path)| {
|
||||||
|
let fs = fs.clone();
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let abs_path = abs_path?;
|
||||||
|
let text = fs.load(&abs_path).await.with_context(|| {
|
||||||
|
format!("Failed to load assistant rules file {:?}", abs_path)
|
||||||
|
})?;
|
||||||
|
anyhow::Ok(RulesFileContext {
|
||||||
|
path_in_worktree,
|
||||||
|
abs_path: abs_path.into(),
|
||||||
|
text: text.trim().to_string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_server_manager(&self) -> Entity<ContextServerManager> {
|
pub fn context_server_manager(&self) -> Entity<ContextServerManager> {
|
||||||
self.context_server_manager.clone()
|
self.context_server_manager.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tools(&self) -> Arc<ToolWorkingSet> {
|
pub fn prompt_store(&self) -> Option<Entity<PromptStore>> {
|
||||||
|
self.prompt_store.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_rules(
|
||||||
|
&self,
|
||||||
|
prompt_id: UserPromptId,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Result<(PromptMetadata, String)>> {
|
||||||
|
let prompt_id = PromptId::User { uuid: prompt_id };
|
||||||
|
let Some(prompt_store) = self.prompt_store.as_ref() else {
|
||||||
|
return Task::ready(Err(anyhow!("Prompt store unexpectedly missing.")));
|
||||||
|
};
|
||||||
|
let prompt_store = prompt_store.read(cx);
|
||||||
|
let Some(metadata) = prompt_store.metadata(prompt_id) else {
|
||||||
|
return Task::ready(Err(anyhow!("User rules not found in library.")));
|
||||||
|
};
|
||||||
|
let text_task = prompt_store.load(prompt_id, cx);
|
||||||
|
cx.background_spawn(async move { Ok((metadata, text_task.await?)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tools(&self) -> Entity<ToolWorkingSet> {
|
||||||
self.tools.clone()
|
self.tools.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +395,7 @@ impl ThreadStore {
|
|||||||
self.project.clone(),
|
self.project.clone(),
|
||||||
self.tools.clone(),
|
self.tools.clone(),
|
||||||
self.prompt_builder.clone(),
|
self.prompt_builder.clone(),
|
||||||
|
self.project_context.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -134,21 +423,12 @@ impl ThreadStore {
|
|||||||
this.project.clone(),
|
this.project.clone(),
|
||||||
this.tools.clone(),
|
this.tools.clone(),
|
||||||
this.prompt_builder.clone(),
|
this.prompt_builder.clone(),
|
||||||
|
this.project_context.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (system_prompt_context, load_error) = thread
|
|
||||||
.update(cx, |thread, cx| thread.load_system_prompt_context(cx))?
|
|
||||||
.await;
|
|
||||||
thread.update(cx, |thread, cx| {
|
|
||||||
thread.set_system_prompt_context(system_prompt_context);
|
|
||||||
if let Some(load_error) = load_error {
|
|
||||||
cx.emit(ThreadEvent::ShowError(load_error));
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(thread)
|
Ok(thread)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -174,8 +454,9 @@ impl ThreadStore {
|
|||||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||||
database.delete_thread(id.clone()).await?;
|
database.delete_thread(id.clone()).await?;
|
||||||
|
|
||||||
this.update(cx, |this, _cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.threads.retain(|thread| thread.id != id)
|
this.threads.retain(|thread| thread.id != id);
|
||||||
|
cx.notify();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -196,52 +477,60 @@ impl ThreadStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_default_profile(&self, cx: &Context<Self>) {
|
fn load_default_profile(&self, cx: &mut Context<Self>) {
|
||||||
let assistant_settings = AssistantSettings::get_global(cx);
|
let assistant_settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
self.load_profile_by_id(&assistant_settings.default_profile, cx);
|
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_profile_by_id(&self, profile_id: &AgentProfileId, cx: &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 = AssistantSettings::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, cx);
|
self.load_profile(profile.clone(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_profile(&self, profile: &AgentProfile, cx: &Context<Self>) {
|
pub fn load_profile(&self, profile: AgentProfile, cx: &mut Context<Self>) {
|
||||||
self.tools.disable_all_tools();
|
self.tools.update(cx, |tools, cx| {
|
||||||
self.tools.enable(
|
tools.disable_all_tools(cx);
|
||||||
ToolSource::Native,
|
tools.enable(
|
||||||
&profile
|
ToolSource::Native,
|
||||||
.tools
|
&profile
|
||||||
.iter()
|
.tools
|
||||||
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
.iter()
|
||||||
.collect::<Vec<_>>(),
|
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
||||||
);
|
.collect::<Vec<_>>(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
if profile.enable_all_context_servers {
|
if profile.enable_all_context_servers {
|
||||||
for context_server in self.context_server_manager.read(cx).all_servers() {
|
for context_server in self.context_server_manager.read(cx).all_servers() {
|
||||||
self.tools.enable_source(
|
self.tools.update(cx, |tools, cx| {
|
||||||
ToolSource::ContextServer {
|
tools.enable_source(
|
||||||
id: context_server.id().into(),
|
ToolSource::ContextServer {
|
||||||
},
|
id: context_server.id().into(),
|
||||||
cx,
|
},
|
||||||
);
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (context_server_id, preset) in &profile.context_servers {
|
for (context_server_id, preset) in &profile.context_servers {
|
||||||
self.tools.enable(
|
self.tools.update(cx, |tools, cx| {
|
||||||
ToolSource::ContextServer {
|
tools.enable(
|
||||||
id: context_server_id.clone().into(),
|
ToolSource::ContextServer {
|
||||||
},
|
id: context_server_id.clone().into(),
|
||||||
&preset
|
},
|
||||||
.tools
|
&preset
|
||||||
.iter()
|
.tools
|
||||||
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
.iter()
|
||||||
.collect::<Vec<_>>(),
|
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
||||||
)
|
.collect::<Vec<_>>(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,29 +564,36 @@ impl ThreadStore {
|
|||||||
|
|
||||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||||
let tool_ids = tools
|
let tool_ids = tool_working_set
|
||||||
.tools
|
.update(cx, |tool_working_set, _| {
|
||||||
.into_iter()
|
tools
|
||||||
.map(|tool| {
|
.tools
|
||||||
log::info!(
|
.into_iter()
|
||||||
"registering context server tool: {:?}",
|
.map(|tool| {
|
||||||
tool.name
|
log::info!(
|
||||||
);
|
"registering context server tool: {:?}",
|
||||||
tool_working_set.insert(Arc::new(
|
tool.name
|
||||||
ContextServerTool::new(
|
);
|
||||||
context_server_manager.clone(),
|
tool_working_set.insert(Arc::new(
|
||||||
server.id(),
|
ContextServerTool::new(
|
||||||
tool,
|
context_server_manager.clone(),
|
||||||
),
|
server.id(),
|
||||||
))
|
tool,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.log_err();
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
if let Some(tool_ids) = tool_ids {
|
||||||
this.context_server_tool_ids.insert(server_id, tool_ids);
|
this.update(cx, |this, cx| {
|
||||||
this.load_default_profile(cx);
|
this.context_server_tool_ids
|
||||||
})
|
.insert(server_id, tool_ids);
|
||||||
.log_err();
|
this.load_default_profile(cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,7 +603,9 @@ impl ThreadStore {
|
|||||||
}
|
}
|
||||||
context_server::manager::Event::ServerStopped { server_id } => {
|
context_server::manager::Event::ServerStopped { server_id } => {
|
||||||
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
||||||
tool_working_set.remove(&tool_ids);
|
tool_working_set.update(cx, |tool_working_set, _| {
|
||||||
|
tool_working_set.remove(&tool_ids);
|
||||||
|
});
|
||||||
self.load_default_profile(cx);
|
self.load_default_profile(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +631,11 @@ pub struct SerializedThread {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub cumulative_token_usage: TokenUsage,
|
pub cumulative_token_usage: TokenUsage,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub request_token_usage: Vec<TokenUsage>,
|
||||||
|
#[serde(default)]
|
||||||
pub detailed_summary_state: DetailedSummaryState,
|
pub detailed_summary_state: DetailedSummaryState,
|
||||||
|
#[serde(default)]
|
||||||
|
pub exceeded_window_error: Option<ExceededWindowError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerializedThread {
|
impl SerializedThread {
|
||||||
@@ -374,15 +676,26 @@ pub struct SerializedMessage {
|
|||||||
pub tool_uses: Vec<SerializedToolUse>,
|
pub tool_uses: Vec<SerializedToolUse>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tool_results: Vec<SerializedToolResult>,
|
pub tool_results: Vec<SerializedToolResult>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub context: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum SerializedMessageSegment {
|
pub enum SerializedMessageSegment {
|
||||||
#[serde(rename = "text")]
|
#[serde(rename = "text")]
|
||||||
Text { text: String },
|
Text {
|
||||||
|
text: String,
|
||||||
|
},
|
||||||
#[serde(rename = "thinking")]
|
#[serde(rename = "thinking")]
|
||||||
Thinking { text: String },
|
Thinking {
|
||||||
|
text: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
signature: Option<String>,
|
||||||
|
},
|
||||||
|
RedactedThinking {
|
||||||
|
data: Vec<u8>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -417,7 +730,9 @@ impl LegacySerializedThread {
|
|||||||
messages: self.messages.into_iter().map(|msg| msg.upgrade()).collect(),
|
messages: self.messages.into_iter().map(|msg| msg.upgrade()).collect(),
|
||||||
initial_project_snapshot: self.initial_project_snapshot,
|
initial_project_snapshot: self.initial_project_snapshot,
|
||||||
cumulative_token_usage: TokenUsage::default(),
|
cumulative_token_usage: TokenUsage::default(),
|
||||||
|
request_token_usage: Vec::new(),
|
||||||
detailed_summary_state: DetailedSummaryState::default(),
|
detailed_summary_state: DetailedSummaryState::default(),
|
||||||
|
exceeded_window_error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,6 +756,7 @@ impl LegacySerializedMessage {
|
|||||||
segments: vec![SerializedMessageSegment::Text { text: self.text }],
|
segments: vec![SerializedMessageSegment::Text { text: self.text }],
|
||||||
tool_uses: self.tool_uses,
|
tool_uses: self.tool_uses,
|
||||||
tool_results: self.tool_results,
|
tool_results: self.tool_results,
|
||||||
|
context: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,7 +803,7 @@ impl ThreadsDatabase {
|
|||||||
let database_future = executor
|
let database_future = executor
|
||||||
.spawn({
|
.spawn({
|
||||||
let executor = executor.clone();
|
let executor = executor.clone();
|
||||||
let database_path = paths::support_dir().join("threads/threads-db.1.mdb");
|
let database_path = paths::data_dir().join("threads/threads-db.1.mdb");
|
||||||
async move { ThreadsDatabase::new(database_path, executor) }
|
async move { ThreadsDatabase::new(database_path, 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)))
|
||||||
|
|||||||
94
crates/agent/src/tool_compatibility.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use assistant_tool::{Tool, ToolSource, ToolWorkingSet, ToolWorkingSetEvent};
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
|
||||||
|
use language_model::{LanguageModel, LanguageModelToolSchemaFormat};
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
pub struct IncompatibleToolsState {
|
||||||
|
cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
|
||||||
|
tool_working_set: Entity<ToolWorkingSet>,
|
||||||
|
_tool_working_set_subscription: Subscription,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncompatibleToolsState {
|
||||||
|
pub fn new(tool_working_set: Entity<ToolWorkingSet>, cx: &mut Context<Self>) -> Self {
|
||||||
|
let _tool_working_set_subscription =
|
||||||
|
cx.subscribe(&tool_working_set, |this, _, event, _| match event {
|
||||||
|
ToolWorkingSetEvent::EnabledToolsChanged => {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
cache: HashMap::default(),
|
||||||
|
tool_working_set,
|
||||||
|
_tool_working_set_subscription,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn incompatible_tools(
|
||||||
|
&mut self,
|
||||||
|
model: &Arc<dyn LanguageModel>,
|
||||||
|
cx: &App,
|
||||||
|
) -> &[Arc<dyn Tool>] {
|
||||||
|
self.cache
|
||||||
|
.entry(model.tool_input_format())
|
||||||
|
.or_insert_with(|| {
|
||||||
|
self.tool_working_set
|
||||||
|
.read(cx)
|
||||||
|
.enabled_tools(cx)
|
||||||
|
.iter()
|
||||||
|
.filter(|tool| tool.input_schema(model.tool_input_format()).is_err())
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IncompatibleToolsTooltip {
|
||||||
|
pub incompatible_tools: Vec<Arc<dyn Tool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for IncompatibleToolsTooltip {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
ui::tooltip_container(window, cx, |container, _, cx| {
|
||||||
|
container
|
||||||
|
.w_72()
|
||||||
|
.child(Label::new("Incompatible Tools").size(LabelSize::Small))
|
||||||
|
.child(
|
||||||
|
Label::new(
|
||||||
|
"This model is incompatible with the following tools from your MCPs:",
|
||||||
|
)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.my_1p5()
|
||||||
|
.py_0p5()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.children(
|
||||||
|
self.incompatible_tools
|
||||||
|
.iter()
|
||||||
|
.map(|tool| h_flex().gap_4().child(Label::new(tool.name()).size(LabelSize::Small)).map(|parent|
|
||||||
|
match tool.source() {
|
||||||
|
ToolSource::Native => parent,
|
||||||
|
ToolSource::ContextServer { id } => parent.child(Label::new(id).size(LabelSize::Small).color(Color::Muted)),
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(Label::new("What To Do Instead").size(LabelSize::Small))
|
||||||
|
.child(
|
||||||
|
Label::new(
|
||||||
|
"Every other tool continues to work with this model, but to specifically use those, switch to another model.",
|
||||||
|
)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_tool::{Tool, ToolWorkingSet};
|
use assistant_tool::{AnyToolCard, Tool, ToolUseStatus, ToolWorkingSet};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::FutureExt as _;
|
use futures::FutureExt as _;
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use gpui::{App, SharedString, Task};
|
use gpui::{App, Entity, SharedString, Task};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
LanguageModel, LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolResult,
|
||||||
LanguageModelToolUseId, MessageContent, Role,
|
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
|
||||||
};
|
};
|
||||||
use ui::IconName;
|
use ui::IconName;
|
||||||
|
use util::truncate_lines_to_byte_limit;
|
||||||
|
|
||||||
use crate::thread::MessageId;
|
use crate::thread::{MessageId, PromptId, ThreadId};
|
||||||
use crate::thread_store::SerializedMessage;
|
use crate::thread_store::SerializedMessage;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -26,31 +27,26 @@ pub struct ToolUse {
|
|||||||
pub needs_confirmation: bool,
|
pub needs_confirmation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ToolUseStatus {
|
|
||||||
NeedsConfirmation,
|
|
||||||
Pending,
|
|
||||||
Running,
|
|
||||||
Finished(SharedString),
|
|
||||||
Error(SharedString),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ToolUseState {
|
pub struct ToolUseState {
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||||
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
|
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
|
||||||
tool_results: HashMap<LanguageModelToolUseId, LanguageModelToolResult>,
|
tool_results: HashMap<LanguageModelToolUseId, LanguageModelToolResult>,
|
||||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||||
|
tool_result_cards: HashMap<LanguageModelToolUseId, AnyToolCard>,
|
||||||
|
tool_use_metadata_by_id: HashMap<LanguageModelToolUseId, ToolUseMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolUseState {
|
impl ToolUseState {
|
||||||
pub fn new(tools: Arc<ToolWorkingSet>) -> Self {
|
pub fn new(tools: Entity<ToolWorkingSet>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tools,
|
tools,
|
||||||
tool_uses_by_assistant_message: HashMap::default(),
|
tool_uses_by_assistant_message: HashMap::default(),
|
||||||
tool_uses_by_user_message: HashMap::default(),
|
tool_uses_by_user_message: HashMap::default(),
|
||||||
tool_results: HashMap::default(),
|
tool_results: HashMap::default(),
|
||||||
pending_tool_uses_by_id: HashMap::default(),
|
pending_tool_uses_by_id: HashMap::default(),
|
||||||
|
tool_result_cards: HashMap::default(),
|
||||||
|
tool_use_metadata_by_id: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +54,7 @@ impl ToolUseState {
|
|||||||
///
|
///
|
||||||
/// Accepts a function to filter the tools that should be used to populate the state.
|
/// Accepts a function to filter the tools that should be used to populate the state.
|
||||||
pub fn from_serialized_messages(
|
pub fn from_serialized_messages(
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
messages: &[SerializedMessage],
|
messages: &[SerializedMessage],
|
||||||
mut filter_by_tool_name: impl FnMut(&str) -> bool,
|
mut filter_by_tool_name: impl FnMut(&str) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -77,6 +73,7 @@ impl ToolUseState {
|
|||||||
id: tool_use.id.clone(),
|
id: tool_use.id.clone(),
|
||||||
name: tool_use.name.clone().into(),
|
name: tool_use.name.clone().into(),
|
||||||
input: tool_use.input.clone(),
|
input: tool_use.input.clone(),
|
||||||
|
is_input_complete: true,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -178,23 +175,31 @@ impl ToolUseState {
|
|||||||
PendingToolUseStatus::Error(ref err) => {
|
PendingToolUseStatus::Error(ref err) => {
|
||||||
ToolUseStatus::Error(err.clone().into())
|
ToolUseStatus::Error(err.clone().into())
|
||||||
}
|
}
|
||||||
|
PendingToolUseStatus::InputStillStreaming => {
|
||||||
|
ToolUseStatus::InputStillStreaming
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ToolUseStatus::Pending
|
ToolUseStatus::Pending
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let (icon, needs_confirmation) = if let Some(tool) = self.tools.tool(&tool_use.name, cx)
|
let (icon, needs_confirmation) =
|
||||||
{
|
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
|
||||||
(tool.icon(), tool.needs_confirmation())
|
(tool.icon(), tool.needs_confirmation(&tool_use.input, cx))
|
||||||
} else {
|
} else {
|
||||||
(IconName::Cog, false)
|
(IconName::Cog, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
tool_uses.push(ToolUse {
|
tool_uses.push(ToolUse {
|
||||||
id: tool_use.id.clone(),
|
id: tool_use.id.clone(),
|
||||||
name: tool_use.name.clone().into(),
|
name: tool_use.name.clone().into(),
|
||||||
ui_text: self.tool_ui_label(&tool_use.name, &tool_use.input, cx),
|
ui_text: self.tool_ui_label(
|
||||||
|
&tool_use.name,
|
||||||
|
&tool_use.input,
|
||||||
|
tool_use.is_input_complete,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
input: tool_use.input.clone(),
|
input: tool_use.input.clone(),
|
||||||
status,
|
status,
|
||||||
icon,
|
icon,
|
||||||
@@ -209,10 +214,15 @@ impl ToolUseState {
|
|||||||
&self,
|
&self,
|
||||||
tool_name: &str,
|
tool_name: &str,
|
||||||
input: &serde_json::Value,
|
input: &serde_json::Value,
|
||||||
|
is_input_complete: bool,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> SharedString {
|
) -> SharedString {
|
||||||
if let Some(tool) = self.tools.tool(tool_name, cx) {
|
if let Some(tool) = self.tools.read(cx).tool(tool_name, cx) {
|
||||||
tool.ui_text(input).into()
|
if is_input_complete {
|
||||||
|
tool.ui_text(input).into()
|
||||||
|
} else {
|
||||||
|
tool.still_streaming_ui_text(input).into()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("Unknown tool {tool_name:?}").into()
|
format!("Unknown tool {tool_name:?}").into()
|
||||||
}
|
}
|
||||||
@@ -242,24 +252,68 @@ impl ToolUseState {
|
|||||||
self.tool_results.get(tool_use_id)
|
self.tool_results.get(tool_use_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tool_result_card(&self, tool_use_id: &LanguageModelToolUseId) -> Option<&AnyToolCard> {
|
||||||
|
self.tool_result_cards.get(tool_use_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_tool_result_card(
|
||||||
|
&mut self,
|
||||||
|
tool_use_id: LanguageModelToolUseId,
|
||||||
|
card: AnyToolCard,
|
||||||
|
) {
|
||||||
|
self.tool_result_cards.insert(tool_use_id, card);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn request_tool_use(
|
pub fn request_tool_use(
|
||||||
&mut self,
|
&mut self,
|
||||||
assistant_message_id: MessageId,
|
assistant_message_id: MessageId,
|
||||||
tool_use: LanguageModelToolUse,
|
tool_use: LanguageModelToolUse,
|
||||||
|
metadata: ToolUseMetadata,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) {
|
) -> Arc<str> {
|
||||||
self.tool_uses_by_assistant_message
|
let tool_uses = self
|
||||||
|
.tool_uses_by_assistant_message
|
||||||
.entry(assistant_message_id)
|
.entry(assistant_message_id)
|
||||||
.or_default()
|
.or_default();
|
||||||
.push(tool_use.clone());
|
|
||||||
|
|
||||||
// The tool use is being requested by the Assistant, so we want to
|
let mut existing_tool_use_found = false;
|
||||||
// attach the tool results to the next user message.
|
|
||||||
let next_user_message_id = MessageId(assistant_message_id.0 + 1);
|
for existing_tool_use in tool_uses.iter_mut() {
|
||||||
self.tool_uses_by_user_message
|
if existing_tool_use.id == tool_use.id {
|
||||||
.entry(next_user_message_id)
|
*existing_tool_use = tool_use.clone();
|
||||||
.or_default()
|
existing_tool_use_found = true;
|
||||||
.push(tool_use.id.clone());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existing_tool_use_found {
|
||||||
|
tool_uses.push(tool_use.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = if tool_use.is_input_complete {
|
||||||
|
self.tool_use_metadata_by_id
|
||||||
|
.insert(tool_use.id.clone(), metadata);
|
||||||
|
|
||||||
|
// The tool use is being requested by the Assistant, so we want to
|
||||||
|
// attach the tool results to the next user message.
|
||||||
|
let next_user_message_id = MessageId(assistant_message_id.0 + 1);
|
||||||
|
self.tool_uses_by_user_message
|
||||||
|
.entry(next_user_message_id)
|
||||||
|
.or_default()
|
||||||
|
.push(tool_use.id.clone());
|
||||||
|
|
||||||
|
PendingToolUseStatus::Idle
|
||||||
|
} else {
|
||||||
|
PendingToolUseStatus::InputStillStreaming
|
||||||
|
};
|
||||||
|
|
||||||
|
let ui_text: Arc<str> = self
|
||||||
|
.tool_ui_label(
|
||||||
|
&tool_use.name,
|
||||||
|
&tool_use.input,
|
||||||
|
tool_use.is_input_complete,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
self.pending_tool_uses_by_id.insert(
|
self.pending_tool_uses_by_id.insert(
|
||||||
tool_use.id.clone(),
|
tool_use.id.clone(),
|
||||||
@@ -267,13 +321,13 @@ impl ToolUseState {
|
|||||||
assistant_message_id,
|
assistant_message_id,
|
||||||
id: tool_use.id,
|
id: tool_use.id,
|
||||||
name: tool_use.name.clone(),
|
name: tool_use.name.clone(),
|
||||||
ui_text: self
|
ui_text: ui_text.clone(),
|
||||||
.tool_ui_label(&tool_use.name, &tool_use.input, cx)
|
|
||||||
.into(),
|
|
||||||
input: tool_use.input,
|
input: tool_use.input,
|
||||||
status: PendingToolUseStatus::Idle,
|
status,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ui_text
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_pending_tool(
|
pub fn run_pending_tool(
|
||||||
@@ -317,9 +371,48 @@ impl ToolUseState {
|
|||||||
tool_use_id: LanguageModelToolUseId,
|
tool_use_id: LanguageModelToolUseId,
|
||||||
tool_name: Arc<str>,
|
tool_name: Arc<str>,
|
||||||
output: Result<String>,
|
output: Result<String>,
|
||||||
|
cx: &App,
|
||||||
) -> Option<PendingToolUse> {
|
) -> Option<PendingToolUse> {
|
||||||
|
let metadata = self.tool_use_metadata_by_id.remove(&tool_use_id);
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Agent Tool Finished",
|
||||||
|
model = metadata
|
||||||
|
.as_ref()
|
||||||
|
.map(|metadata| metadata.model.telemetry_id()),
|
||||||
|
model_provider = metadata
|
||||||
|
.as_ref()
|
||||||
|
.map(|metadata| metadata.model.provider_id().to_string()),
|
||||||
|
thread_id = metadata.as_ref().map(|metadata| metadata.thread_id.clone()),
|
||||||
|
prompt_id = metadata.as_ref().map(|metadata| metadata.prompt_id.clone()),
|
||||||
|
tool_name,
|
||||||
|
success = output.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(tool_result) => {
|
Ok(tool_result) => {
|
||||||
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
|
|
||||||
|
const BYTES_PER_TOKEN_ESTIMATE: usize = 3;
|
||||||
|
|
||||||
|
// Protect from clearly large output
|
||||||
|
let tool_output_limit = model_registry
|
||||||
|
.default_model()
|
||||||
|
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
|
||||||
|
.unwrap_or(usize::MAX);
|
||||||
|
|
||||||
|
let tool_result = if tool_result.len() <= tool_output_limit {
|
||||||
|
tool_result
|
||||||
|
} else {
|
||||||
|
let truncated = truncate_lines_to_byte_limit(&tool_result, tool_output_limit);
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"Tool result too long. The first {} bytes:\n\n{}",
|
||||||
|
truncated.len(),
|
||||||
|
truncated
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
self.tool_results.insert(
|
self.tool_results.insert(
|
||||||
tool_use_id.clone(),
|
tool_use_id.clone(),
|
||||||
LanguageModelToolResult {
|
LanguageModelToolResult {
|
||||||
@@ -424,6 +517,7 @@ pub struct Confirmation {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PendingToolUseStatus {
|
pub enum PendingToolUseStatus {
|
||||||
|
InputStillStreaming,
|
||||||
Idle,
|
Idle,
|
||||||
NeedsConfirmation(Arc<Confirmation>),
|
NeedsConfirmation(Arc<Confirmation>),
|
||||||
Running { _task: Shared<Task<()>> },
|
Running { _task: Shared<Task<()>> },
|
||||||
@@ -443,3 +537,10 @@ impl PendingToolUseStatus {
|
|||||||
matches!(self, PendingToolUseStatus::NeedsConfirmation { .. })
|
matches!(self, PendingToolUseStatus::NeedsConfirmation { .. })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToolUseMetadata {
|
||||||
|
pub model: Arc<dyn LanguageModel>,
|
||||||
|
pub thread_id: ThreadId,
|
||||||
|
pub prompt_id: PromptId,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
mod agent_notification;
|
mod agent_notification;
|
||||||
|
mod animated_label;
|
||||||
mod context_pill;
|
mod context_pill;
|
||||||
|
mod usage_banner;
|
||||||
|
|
||||||
pub use agent_notification::*;
|
pub use agent_notification::*;
|
||||||
|
pub use animated_label::*;
|
||||||
pub use context_pill::*;
|
pub use context_pill::*;
|
||||||
|
pub use usage_banner::*;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub struct AgentNotification {
|
|||||||
title: SharedString,
|
title: SharedString,
|
||||||
caption: SharedString,
|
caption: SharedString,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
|
project_name: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentNotification {
|
impl AgentNotification {
|
||||||
@@ -19,11 +20,13 @@ impl AgentNotification {
|
|||||||
title: impl Into<SharedString>,
|
title: impl Into<SharedString>,
|
||||||
caption: impl Into<SharedString>,
|
caption: impl Into<SharedString>,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
|
project_name: Option<impl Into<SharedString>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: title.into(),
|
title: title.into(),
|
||||||
caption: caption.into(),
|
caption: caption.into(),
|
||||||
icon,
|
icon,
|
||||||
|
project_name: project_name.map(|name| name.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,11 +133,34 @@ impl Render for AgentNotification {
|
|||||||
.child(gradient_overflow()),
|
.child(gradient_overflow()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
h_flex()
|
||||||
.relative()
|
.relative()
|
||||||
|
.gap_1p5()
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.text_color(cx.theme().colors().text_muted)
|
||||||
.truncate()
|
.truncate()
|
||||||
|
.when_some(
|
||||||
|
self.project_name.clone(),
|
||||||
|
|description, project_name| {
|
||||||
|
description.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.max_w_16()
|
||||||
|
.truncate()
|
||||||
|
.child(project_name),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().size(px(3.)).rounded_full().bg(cx
|
||||||
|
.theme()
|
||||||
|
.colors()
|
||||||
|
.text
|
||||||
|
.opacity(0.5)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
.child(self.caption.clone())
|
.child(self.caption.clone())
|
||||||
.child(gradient_overflow()),
|
.child(gradient_overflow()),
|
||||||
),
|
),
|
||||||
|
|||||||
116
crates/agent/src/ui/animated_label.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use gpui::{Animation, AnimationExt, FontWeight, pulsating_between};
|
||||||
|
use std::time::Duration;
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct AnimatedLabel {
|
||||||
|
base: Label,
|
||||||
|
text: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedLabel {
|
||||||
|
pub fn new(text: impl Into<SharedString>) -> Self {
|
||||||
|
let text = text.into();
|
||||||
|
AnimatedLabel {
|
||||||
|
base: Label::new(text.clone()),
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabelCommon for AnimatedLabel {
|
||||||
|
fn size(mut self, size: LabelSize) -> Self {
|
||||||
|
self.base = self.base.size(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn weight(mut self, weight: FontWeight) -> Self {
|
||||||
|
self.base = self.base.weight(weight);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
|
||||||
|
self.base = self.base.line_height_style(line_height_style);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(mut self, color: Color) -> Self {
|
||||||
|
self.base = self.base.color(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strikethrough(mut self) -> Self {
|
||||||
|
self.base = self.base.strikethrough();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn italic(mut self) -> Self {
|
||||||
|
self.base = self.base.italic();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alpha(mut self, alpha: f32) -> Self {
|
||||||
|
self.base = self.base.alpha(alpha);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn underline(mut self) -> Self {
|
||||||
|
self.base = self.base.underline();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(mut self) -> Self {
|
||||||
|
self.base = self.base.truncate();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn single_line(mut self) -> Self {
|
||||||
|
self.base = self.base.single_line();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_font(mut self, cx: &App) -> Self {
|
||||||
|
self.base = self.base.buffer_font(cx);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for AnimatedLabel {
|
||||||
|
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let text = self.text.clone();
|
||||||
|
|
||||||
|
self.base
|
||||||
|
.color(Color::Muted)
|
||||||
|
.with_animations(
|
||||||
|
"animated-label",
|
||||||
|
vec![
|
||||||
|
Animation::new(Duration::from_secs(1)),
|
||||||
|
Animation::new(Duration::from_secs(1)).repeat(),
|
||||||
|
],
|
||||||
|
move |mut label, animation_ix, delta| {
|
||||||
|
match animation_ix {
|
||||||
|
0 => {
|
||||||
|
let chars_to_show = (delta * text.len() as f32).ceil() as usize;
|
||||||
|
let text = SharedString::from(text[0..chars_to_show].to_string());
|
||||||
|
label.set_text(text);
|
||||||
|
}
|
||||||
|
1 => match delta {
|
||||||
|
d if d < 0.25 => label.set_text(text.clone()),
|
||||||
|
d if d < 0.5 => label.set_text(format!("{}.", text)),
|
||||||
|
d if d < 0.75 => label.set_text(format!("{}..", text)),
|
||||||
|
_ => label.set_text(format!("{}...", text)),
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
label
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_animation(
|
||||||
|
"pulsating-label",
|
||||||
|
Animation::new(Duration::from_secs(2))
|
||||||
|
.repeat()
|
||||||
|
.with_easing(pulsating_between(0.6, 1.)),
|
||||||
|
|label, delta| label.map_element(|label| label.alpha(delta)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
use std::{rc::Rc, time::Duration};
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::ClickEvent;
|
use futures::FutureExt;
|
||||||
use gpui::{Animation, AnimationExt as _, pulsating_between};
|
use gpui::{Animation, AnimationExt as _, Image, MouseButton, pulsating_between};
|
||||||
use ui::{IconButtonShape, Tooltip, prelude::*};
|
use gpui::{ClickEvent, Task};
|
||||||
|
use language_model::LanguageModelImage;
|
||||||
|
use ui::{IconButtonShape, Tooltip, prelude::*, tooltip_container};
|
||||||
|
|
||||||
use crate::context::{AssistantContext, ContextId, ContextKind};
|
use crate::context::{AssistantContext, ContextId, ContextKind, ImageContext};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub enum ContextPill {
|
pub enum ContextPill {
|
||||||
@@ -120,117 +123,127 @@ impl RenderOnce for ContextPill {
|
|||||||
on_remove,
|
on_remove,
|
||||||
focused,
|
focused,
|
||||||
on_click,
|
on_click,
|
||||||
} => base_pill
|
} => {
|
||||||
.bg(color.element_background)
|
let status_is_error = matches!(context.status, ContextStatus::Error { .. });
|
||||||
.border_color(if *focused {
|
|
||||||
color.border_focused
|
base_pill
|
||||||
} else {
|
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
|
||||||
color.border.opacity(0.5)
|
.map(|pill| {
|
||||||
})
|
if status_is_error {
|
||||||
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
|
pill.bg(cx.theme().status().error_background)
|
||||||
.child(
|
.border_color(cx.theme().status().error_border)
|
||||||
h_flex()
|
} else if *focused {
|
||||||
.id("context-data")
|
pill.bg(color.element_background)
|
||||||
.gap_1()
|
.border_color(color.border_focused)
|
||||||
.child(
|
} else {
|
||||||
div().max_w_64().child(
|
pill.bg(color.element_background)
|
||||||
Label::new(context.name.clone())
|
.border_color(color.border.opacity(0.5))
|
||||||
.size(LabelSize::Small)
|
}
|
||||||
.truncate(),
|
})
|
||||||
),
|
.child(
|
||||||
)
|
h_flex()
|
||||||
.when_some(context.parent.as_ref(), |element, parent_name| {
|
.id("context-data")
|
||||||
if *dupe_name {
|
.gap_1()
|
||||||
element.child(
|
.child(
|
||||||
Label::new(parent_name.clone())
|
div().max_w_64().child(
|
||||||
.size(LabelSize::XSmall)
|
Label::new(context.name.clone())
|
||||||
.color(Color::Muted),
|
.size(LabelSize::Small)
|
||||||
)
|
.truncate(),
|
||||||
} else {
|
),
|
||||||
element
|
)
|
||||||
}
|
.when_some(context.parent.as_ref(), |element, parent_name| {
|
||||||
})
|
if *dupe_name {
|
||||||
.when_some(context.tooltip.as_ref(), |element, tooltip| {
|
element.child(
|
||||||
element.tooltip(Tooltip::text(tooltip.clone()))
|
Label::new(parent_name.clone())
|
||||||
}),
|
.size(LabelSize::XSmall)
|
||||||
)
|
.color(Color::Muted),
|
||||||
.when_some(on_remove.as_ref(), |element, on_remove| {
|
)
|
||||||
element.child(
|
} else {
|
||||||
IconButton::new(("remove", context.id.0), IconName::Close)
|
element
|
||||||
.shape(IconButtonShape::Square)
|
}
|
||||||
.icon_size(IconSize::XSmall)
|
})
|
||||||
.tooltip(Tooltip::text("Remove Context"))
|
.when_some(context.tooltip.as_ref(), |element, tooltip| {
|
||||||
.on_click({
|
element.tooltip(Tooltip::text(tooltip.clone()))
|
||||||
let on_remove = on_remove.clone();
|
})
|
||||||
move |event, window, cx| on_remove(event, window, cx)
|
.map(|element| match &context.status {
|
||||||
|
ContextStatus::Ready => element
|
||||||
|
.when_some(
|
||||||
|
context.render_preview.as_ref(),
|
||||||
|
|element, render_preview| {
|
||||||
|
element.hoverable_tooltip({
|
||||||
|
let render_preview = render_preview.clone();
|
||||||
|
move |_, cx| {
|
||||||
|
cx.new(|_| ContextPillPreview {
|
||||||
|
render_preview: render_preview.clone(),
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_any(),
|
||||||
|
ContextStatus::Loading { message } => element
|
||||||
|
.tooltip(ui::Tooltip::text(message.clone()))
|
||||||
|
.with_animation(
|
||||||
|
"pulsating-ctx-pill",
|
||||||
|
Animation::new(Duration::from_secs(2))
|
||||||
|
.repeat()
|
||||||
|
.with_easing(pulsating_between(0.4, 0.8)),
|
||||||
|
|label, delta| label.opacity(delta),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
ContextStatus::Error { message } => element
|
||||||
|
.tooltip(ui::Tooltip::text(message.clone()))
|
||||||
|
.into_any_element(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
.when_some(on_remove.as_ref(), |element, on_remove| {
|
||||||
.when_some(on_click.as_ref(), |element, on_click| {
|
element.child(
|
||||||
let on_click = on_click.clone();
|
IconButton::new(("remove", context.id.0), IconName::Close)
|
||||||
element
|
.shape(IconButtonShape::Square)
|
||||||
.cursor_pointer()
|
.icon_size(IconSize::XSmall)
|
||||||
.on_click(move |event, window, cx| on_click(event, window, cx))
|
.tooltip(Tooltip::text("Remove Context"))
|
||||||
})
|
.on_click({
|
||||||
.map(|element| {
|
let on_remove = on_remove.clone();
|
||||||
if context.summarizing {
|
move |event, window, cx| on_remove(event, window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(on_click.as_ref(), |element, on_click| {
|
||||||
|
let on_click = on_click.clone();
|
||||||
element
|
element
|
||||||
.tooltip(ui::Tooltip::text("Summarizing..."))
|
.cursor_pointer()
|
||||||
.with_animation(
|
.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||||
"pulsating-ctx-pill",
|
})
|
||||||
Animation::new(Duration::from_secs(2))
|
.into_any_element()
|
||||||
.repeat()
|
}
|
||||||
.with_easing(pulsating_between(0.4, 0.8)),
|
|
||||||
|label, delta| label.opacity(delta),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
} else {
|
|
||||||
element.into_any()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
ContextPill::Suggested {
|
ContextPill::Suggested {
|
||||||
name,
|
name,
|
||||||
icon_path: _,
|
icon_path: _,
|
||||||
kind,
|
kind: _,
|
||||||
focused,
|
focused,
|
||||||
on_click,
|
on_click,
|
||||||
} => base_pill
|
} => base_pill
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.pr_1()
|
.pr_1()
|
||||||
.when(*focused, |this| {
|
|
||||||
this.bg(color.element_background.opacity(0.5))
|
|
||||||
})
|
|
||||||
.border_dashed()
|
.border_dashed()
|
||||||
.border_color(if *focused {
|
.map(|pill| {
|
||||||
color.border_focused
|
if *focused {
|
||||||
} else {
|
pill.border_color(color.border_focused)
|
||||||
color.border
|
.bg(color.element_background.opacity(0.5))
|
||||||
|
} else {
|
||||||
|
pill.border_color(color.border)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.hover(|style| style.bg(color.element_hover.opacity(0.5)))
|
.hover(|style| style.bg(color.element_hover.opacity(0.5)))
|
||||||
.child(
|
.child(
|
||||||
div().px_0p5().max_w_64().child(
|
div().max_w_64().child(
|
||||||
Label::new(name.clone())
|
Label::new(name.clone())
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.truncate(),
|
.truncate(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
Label::new(match kind {
|
|
||||||
ContextKind::File => "Active Tab",
|
|
||||||
ContextKind::Thread
|
|
||||||
| ContextKind::Directory
|
|
||||||
| ContextKind::FetchedUrl
|
|
||||||
| ContextKind::Symbol => "Active",
|
|
||||||
})
|
|
||||||
.size(LabelSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::Plus)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|window, cx| {
|
||||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
|
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
|
||||||
})
|
})
|
||||||
@@ -243,6 +256,13 @@ impl RenderOnce for ContextPill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ContextStatus {
|
||||||
|
Ready,
|
||||||
|
Loading { message: SharedString },
|
||||||
|
Error { message: SharedString },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RegisterComponent)]
|
||||||
pub struct AddedContext {
|
pub struct AddedContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
pub kind: ContextKind,
|
pub kind: ContextKind,
|
||||||
@@ -250,14 +270,15 @@ pub struct AddedContext {
|
|||||||
pub parent: Option<SharedString>,
|
pub parent: Option<SharedString>,
|
||||||
pub tooltip: Option<SharedString>,
|
pub tooltip: Option<SharedString>,
|
||||||
pub icon_path: Option<SharedString>,
|
pub icon_path: Option<SharedString>,
|
||||||
pub summarizing: bool,
|
pub status: ContextStatus,
|
||||||
|
pub render_preview: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddedContext {
|
impl AddedContext {
|
||||||
pub fn new(context: &AssistantContext, cx: &App) -> AddedContext {
|
pub fn new(context: &AssistantContext, cx: &App) -> AddedContext {
|
||||||
match context {
|
match context {
|
||||||
AssistantContext::File(file_context) => {
|
AssistantContext::File(file_context) => {
|
||||||
let full_path = file_context.context_buffer.file.full_path(cx);
|
let full_path = file_context.context_buffer.full_path(cx);
|
||||||
let full_path_string: SharedString =
|
let full_path_string: SharedString =
|
||||||
full_path.to_string_lossy().into_owned().into();
|
full_path.to_string_lossy().into_owned().into();
|
||||||
let name = full_path
|
let name = full_path
|
||||||
@@ -275,14 +296,20 @@ impl AddedContext {
|
|||||||
parent,
|
parent,
|
||||||
tooltip: Some(full_path_string),
|
tooltip: Some(full_path_string),
|
||||||
icon_path: FileIcons::get_icon(&full_path, cx),
|
icon_path: FileIcons::get_icon(&full_path, cx),
|
||||||
summarizing: false,
|
status: ContextStatus::Ready,
|
||||||
|
render_preview: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AssistantContext::Directory(directory_context) => {
|
AssistantContext::Directory(directory_context) => {
|
||||||
// TODO: handle worktree disambiguation. Maybe by storing an `Arc<dyn File>` to also
|
let worktree = directory_context.worktree.read(cx);
|
||||||
// handle renames?
|
// If the directory no longer exists, use its last known path.
|
||||||
let full_path = &directory_context.project_path.path;
|
let full_path = worktree
|
||||||
|
.entry_for_id(directory_context.entry_id)
|
||||||
|
.map_or_else(
|
||||||
|
|| directory_context.last_path.clone(),
|
||||||
|
|entry| worktree.full_path(&entry.path).into(),
|
||||||
|
);
|
||||||
let full_path_string: SharedString =
|
let full_path_string: SharedString =
|
||||||
full_path.to_string_lossy().into_owned().into();
|
full_path.to_string_lossy().into_owned().into();
|
||||||
let name = full_path
|
let name = full_path
|
||||||
@@ -300,7 +327,8 @@ impl AddedContext {
|
|||||||
parent,
|
parent,
|
||||||
tooltip: Some(full_path_string),
|
tooltip: Some(full_path_string),
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
summarizing: false,
|
status: ContextStatus::Ready,
|
||||||
|
render_preview: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +339,55 @@ impl AddedContext {
|
|||||||
parent: None,
|
parent: None,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
summarizing: false,
|
status: ContextStatus::Ready,
|
||||||
|
render_preview: None,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AssistantContext::Selection(selection_context) => {
|
||||||
|
let full_path = selection_context.context_buffer.full_path(cx);
|
||||||
|
let mut full_path_string = full_path.to_string_lossy().into_owned();
|
||||||
|
let mut name = full_path
|
||||||
|
.file_name()
|
||||||
|
.map(|n| n.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|| full_path_string.clone());
|
||||||
|
|
||||||
|
let line_range_text = format!(
|
||||||
|
" ({}-{})",
|
||||||
|
selection_context.line_range.start.row + 1,
|
||||||
|
selection_context.line_range.end.row + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
full_path_string.push_str(&line_range_text);
|
||||||
|
name.push_str(&line_range_text);
|
||||||
|
|
||||||
|
let parent = full_path
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.file_name())
|
||||||
|
.map(|n| n.to_string_lossy().into_owned().into());
|
||||||
|
|
||||||
|
AddedContext {
|
||||||
|
id: selection_context.id,
|
||||||
|
kind: ContextKind::Selection,
|
||||||
|
name: name.into(),
|
||||||
|
parent,
|
||||||
|
tooltip: None,
|
||||||
|
icon_path: FileIcons::get_icon(&full_path, cx),
|
||||||
|
status: ContextStatus::Ready,
|
||||||
|
render_preview: Some(Rc::new({
|
||||||
|
let content = selection_context.context_buffer.text.clone();
|
||||||
|
move |_, cx| {
|
||||||
|
div()
|
||||||
|
.id("context-pill-selection-preview")
|
||||||
|
.overflow_scroll()
|
||||||
|
.max_w_128()
|
||||||
|
.max_h_96()
|
||||||
|
.child(Label::new(content.clone()).buffer_font(cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AssistantContext::FetchedUrl(fetched_url_context) => AddedContext {
|
AssistantContext::FetchedUrl(fetched_url_context) => AddedContext {
|
||||||
id: fetched_url_context.id,
|
id: fetched_url_context.id,
|
||||||
kind: ContextKind::FetchedUrl,
|
kind: ContextKind::FetchedUrl,
|
||||||
@@ -321,7 +395,8 @@ impl AddedContext {
|
|||||||
parent: None,
|
parent: None,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
summarizing: false,
|
status: ContextStatus::Ready,
|
||||||
|
render_preview: None,
|
||||||
},
|
},
|
||||||
|
|
||||||
AssistantContext::Thread(thread_context) => AddedContext {
|
AssistantContext::Thread(thread_context) => AddedContext {
|
||||||
@@ -331,11 +406,143 @@ impl AddedContext {
|
|||||||
parent: None,
|
parent: None,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
summarizing: thread_context
|
status: if thread_context
|
||||||
.thread
|
.thread
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.is_generating_detailed_summary(),
|
.is_generating_detailed_summary()
|
||||||
|
{
|
||||||
|
ContextStatus::Loading {
|
||||||
|
message: "Summarizing…".into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ContextStatus::Ready
|
||||||
|
},
|
||||||
|
render_preview: None,
|
||||||
|
},
|
||||||
|
|
||||||
|
AssistantContext::Rules(user_rules_context) => AddedContext {
|
||||||
|
id: user_rules_context.id,
|
||||||
|
kind: ContextKind::Rules,
|
||||||
|
name: user_rules_context.title.clone(),
|
||||||
|
parent: None,
|
||||||
|
tooltip: None,
|
||||||
|
icon_path: None,
|
||||||
|
status: ContextStatus::Ready,
|
||||||
|
render_preview: None,
|
||||||
|
},
|
||||||
|
|
||||||
|
AssistantContext::Image(image_context) => AddedContext {
|
||||||
|
id: image_context.id,
|
||||||
|
kind: ContextKind::Image,
|
||||||
|
name: "Image".into(),
|
||||||
|
parent: None,
|
||||||
|
tooltip: None,
|
||||||
|
icon_path: None,
|
||||||
|
status: if image_context.is_loading() {
|
||||||
|
ContextStatus::Loading {
|
||||||
|
message: "Loading…".into(),
|
||||||
|
}
|
||||||
|
} else if image_context.is_error() {
|
||||||
|
ContextStatus::Error {
|
||||||
|
message: "Failed to load image".into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ContextStatus::Ready
|
||||||
|
},
|
||||||
|
render_preview: Some(Rc::new({
|
||||||
|
let image = image_context.original_image.clone();
|
||||||
|
move |_, _| {
|
||||||
|
gpui::img(image.clone())
|
||||||
|
.max_w_96()
|
||||||
|
.max_h_96()
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ContextPillPreview {
|
||||||
|
render_preview: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ContextPillPreview {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
tooltip_container(window, cx, move |this, window, cx| {
|
||||||
|
this.occlude()
|
||||||
|
.on_mouse_move(|_, _, cx| cx.stop_propagation())
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||||
|
.child((self.render_preview)(window, cx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for AddedContext {
|
||||||
|
fn scope() -> ComponentScope {
|
||||||
|
ComponentScope::Agent
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_name() -> &'static str {
|
||||||
|
"AddedContext"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||||
|
let image_ready = (
|
||||||
|
"Ready",
|
||||||
|
AddedContext::new(
|
||||||
|
&AssistantContext::Image(ImageContext {
|
||||||
|
id: ContextId(0),
|
||||||
|
original_image: Arc::new(Image::empty()),
|
||||||
|
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let image_loading = (
|
||||||
|
"Loading",
|
||||||
|
AddedContext::new(
|
||||||
|
&AssistantContext::Image(ImageContext {
|
||||||
|
id: ContextId(1),
|
||||||
|
original_image: Arc::new(Image::empty()),
|
||||||
|
image_task: cx
|
||||||
|
.background_spawn(async move {
|
||||||
|
smol::Timer::after(Duration::from_secs(60 * 5)).await;
|
||||||
|
Some(LanguageModelImage::empty())
|
||||||
|
})
|
||||||
|
.shared(),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let image_error = (
|
||||||
|
"Error",
|
||||||
|
AddedContext::new(
|
||||||
|
&AssistantContext::Image(ImageContext {
|
||||||
|
id: ContextId(2),
|
||||||
|
original_image: Arc::new(Image::empty()),
|
||||||
|
image_task: Task::ready(None).shared(),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.children(
|
||||||
|
vec![image_ready, image_loading, image_error]
|
||||||
|
.into_iter()
|
||||||
|
.map(|(text, context)| {
|
||||||
|
single_example(
|
||||||
|
text,
|
||||||
|
ContextPill::added(context, false, false, None).into_any_element(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_any(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
258
crates/agent/src/ui/usage_banner.rs
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
use client::zed_urls;
|
||||||
|
use language_model::RequestUsage;
|
||||||
|
use ui::{Banner, ProgressBar, Severity, prelude::*};
|
||||||
|
use zed_llm_client::{Plan, UsageLimit};
|
||||||
|
|
||||||
|
#[derive(IntoElement, RegisterComponent)]
|
||||||
|
pub struct UsageBanner {
|
||||||
|
plan: Plan,
|
||||||
|
usage: RequestUsage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsageBanner {
|
||||||
|
pub fn new(plan: Plan, usage: RequestUsage) -> Self {
|
||||||
|
Self { plan, usage }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for UsageBanner {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let used_percentage = match self.usage.limit {
|
||||||
|
UsageLimit::Limited(limit) => Some((self.usage.amount as f32 / limit as f32) * 100.),
|
||||||
|
UsageLimit::Unlimited => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (severity, message) = match self.usage.limit {
|
||||||
|
UsageLimit::Limited(limit) => {
|
||||||
|
if self.usage.amount >= limit {
|
||||||
|
let message = match self.plan {
|
||||||
|
Plan::ZedPro => "Monthly request limit reached",
|
||||||
|
Plan::ZedProTrial => "Trial request limit reached",
|
||||||
|
Plan::Free => "Free tier request limit reached",
|
||||||
|
};
|
||||||
|
|
||||||
|
(Severity::Error, message)
|
||||||
|
} else if (self.usage.amount as f32 / limit as f32) >= 0.9 {
|
||||||
|
(Severity::Warning, "Approaching request limit")
|
||||||
|
} else {
|
||||||
|
let message = match self.plan {
|
||||||
|
Plan::ZedPro => "Zed Pro",
|
||||||
|
Plan::ZedProTrial => "Zed Pro (Trial)",
|
||||||
|
Plan::Free => "Zed Free",
|
||||||
|
};
|
||||||
|
|
||||||
|
(Severity::Info, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UsageLimit::Unlimited => {
|
||||||
|
let message = match self.plan {
|
||||||
|
Plan::ZedPro => "Zed Pro",
|
||||||
|
Plan::ZedProTrial => "Zed Pro (Trial)",
|
||||||
|
Plan::Free => "Zed Free",
|
||||||
|
};
|
||||||
|
|
||||||
|
(Severity::Info, message)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let action = match self.plan {
|
||||||
|
Plan::ZedProTrial | Plan::Free => {
|
||||||
|
Button::new("upgrade", "Upgrade").on_click(|_, _window, cx| {
|
||||||
|
cx.open_url(&zed_urls::account_url(cx));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Plan::ZedPro => Button::new("manage", "Manage").on_click(|_, _window, cx| {
|
||||||
|
cx.open_url(&zed_urls::account_url(cx));
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
Banner::new().severity(severity).children(
|
||||||
|
h_flex().flex_1().gap_1().child(Label::new(message)).child(
|
||||||
|
h_flex()
|
||||||
|
.flex_1()
|
||||||
|
.justify_end()
|
||||||
|
.gap_1p5()
|
||||||
|
.children(used_percentage.map(|percent| {
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.w_full()
|
||||||
|
.max_w(px(180.))
|
||||||
|
.child(ProgressBar::new("usage", percent, 100., cx))
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
Label::new(match self.usage.limit {
|
||||||
|
UsageLimit::Limited(limit) => {
|
||||||
|
format!("{} / {limit}", self.usage.amount)
|
||||||
|
}
|
||||||
|
UsageLimit::Unlimited => format!("{} / ∞", self.usage.amount),
|
||||||
|
})
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
// Note: This should go in the banner's `action_slot`, but doing that messes with the size of the
|
||||||
|
// progress bar.
|
||||||
|
.child(action),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for UsageBanner {
|
||||||
|
fn sort_name() -> &'static str {
|
||||||
|
"AgentUsageBanner"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||||
|
let trial_limit = Plan::ZedProTrial.model_requests_limit();
|
||||||
|
let trial_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Zed Pro Trial - New User",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::ZedProTrial,
|
||||||
|
RequestUsage {
|
||||||
|
limit: trial_limit,
|
||||||
|
amount: 10,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Zed Pro Trial - Approaching Limit",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::ZedProTrial,
|
||||||
|
RequestUsage {
|
||||||
|
limit: trial_limit,
|
||||||
|
amount: 135,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Zed Pro Trial - Request Limit Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::ZedProTrial,
|
||||||
|
RequestUsage {
|
||||||
|
limit: trial_limit,
|
||||||
|
amount: 150,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let free_limit = Plan::Free.model_requests_limit();
|
||||||
|
let free_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Free - Normal Usage",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::Free,
|
||||||
|
RequestUsage {
|
||||||
|
limit: free_limit,
|
||||||
|
amount: 25,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Free - Approaching Limit",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::Free,
|
||||||
|
RequestUsage {
|
||||||
|
limit: free_limit,
|
||||||
|
amount: 45,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Free - Request Limit Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::Free,
|
||||||
|
RequestUsage {
|
||||||
|
limit: free_limit,
|
||||||
|
amount: 50,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let zed_pro_limit = Plan::ZedPro.model_requests_limit();
|
||||||
|
let zed_pro_examples = vec![
|
||||||
|
single_example(
|
||||||
|
"Zed Pro - Normal Usage",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::ZedPro,
|
||||||
|
RequestUsage {
|
||||||
|
limit: zed_pro_limit,
|
||||||
|
amount: 250,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Zed Pro - Approaching Limit",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::ZedPro,
|
||||||
|
RequestUsage {
|
||||||
|
limit: zed_pro_limit,
|
||||||
|
amount: 450,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Zed Pro - Request Limit Reached",
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(UsageBanner::new(
|
||||||
|
Plan::ZedPro,
|
||||||
|
RequestUsage {
|
||||||
|
limit: zed_pro_limit,
|
||||||
|
amount: 500,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Some(
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.p_4()
|
||||||
|
.children(vec![
|
||||||
|
Label::new("Trial Plan")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(trial_examples).vertical().into_any_element(),
|
||||||
|
Label::new("Free Plan")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(free_examples).vertical().into_any_element(),
|
||||||
|
Label::new("Pro Plan")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
example_group(zed_pro_examples)
|
||||||
|
.vertical()
|
||||||
|
.into_any_element(),
|
||||||
|
])
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,4 +25,4 @@ serde.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
util.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
mod supported_countries;
|
mod supported_countries;
|
||||||
|
|
||||||
use std::{pin::Pin, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use futures::{AsyncBufReadExt, AsyncReadExt, Stream, StreamExt, io::BufReader, stream::BoxStream};
|
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
|
||||||
use http_client::http::{HeaderMap, HeaderValue};
|
use http_client::http::{HeaderMap, HeaderValue};
|
||||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumIter, EnumString};
|
use strum::{EnumIter, EnumString};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use util::ResultExt as _;
|
|
||||||
|
|
||||||
pub use supported_countries::*;
|
pub use supported_countries::*;
|
||||||
|
|
||||||
@@ -37,9 +36,9 @@ pub enum AnthropicModelMode {
|
|||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||||
pub enum Model {
|
pub enum Model {
|
||||||
#[default]
|
|
||||||
#[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(
|
||||||
@@ -75,6 +74,10 @@ pub enum Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
|
pub fn default_fast() -> Self {
|
||||||
|
Self::Claude3_5Haiku
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_id(id: &str) -> Result<Self> {
|
pub fn from_id(id: &str) -> Result<Self> {
|
||||||
if id.starts_with("claude-3-5-sonnet") {
|
if id.starts_with("claude-3-5-sonnet") {
|
||||||
Ok(Self::Claude3_5Sonnet)
|
Ok(Self::Claude3_5Sonnet)
|
||||||
@@ -220,16 +223,23 @@ impl Model {
|
|||||||
.map(|header| header.to_string())
|
.map(|header| header.to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if let Self::Custom {
|
match self {
|
||||||
extra_beta_headers, ..
|
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => {
|
||||||
} = self
|
// Try beta token-efficient tool use (supported in Claude 3.7 Sonnet only)
|
||||||
{
|
// https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
|
||||||
headers.extend(
|
headers.push("token-efficient-tools-2025-02-19".to_string());
|
||||||
extra_beta_headers
|
}
|
||||||
.iter()
|
Self::Custom {
|
||||||
.filter(|header| !header.trim().is_empty())
|
extra_beta_headers, ..
|
||||||
.cloned(),
|
} => {
|
||||||
);
|
headers.extend(
|
||||||
|
extra_beta_headers
|
||||||
|
.iter()
|
||||||
|
.filter(|header| !header.trim().is_empty())
|
||||||
|
.cloned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.join(",")
|
headers.join(",")
|
||||||
@@ -314,38 +324,68 @@ pub async fn stream_completion(
|
|||||||
.map(|output| output.0)
|
.map(|output| output.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An individual rate limit.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RateLimit {
|
||||||
|
pub limit: usize,
|
||||||
|
pub remaining: usize,
|
||||||
|
pub reset: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateLimit {
|
||||||
|
fn from_headers(resource: &str, headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||||
|
let limit =
|
||||||
|
get_header(&format!("anthropic-ratelimit-{resource}-limit"), headers)?.parse()?;
|
||||||
|
let remaining = get_header(
|
||||||
|
&format!("anthropic-ratelimit-{resource}-remaining"),
|
||||||
|
headers,
|
||||||
|
)?
|
||||||
|
.parse()?;
|
||||||
|
let reset = DateTime::parse_from_rfc3339(get_header(
|
||||||
|
&format!("anthropic-ratelimit-{resource}-reset"),
|
||||||
|
headers,
|
||||||
|
)?)?
|
||||||
|
.to_utc();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
limit,
|
||||||
|
remaining,
|
||||||
|
reset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
|
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RateLimitInfo {
|
pub struct RateLimitInfo {
|
||||||
pub requests_limit: usize,
|
pub requests: Option<RateLimit>,
|
||||||
pub requests_remaining: usize,
|
pub tokens: Option<RateLimit>,
|
||||||
pub requests_reset: DateTime<Utc>,
|
pub input_tokens: Option<RateLimit>,
|
||||||
pub tokens_limit: usize,
|
pub output_tokens: Option<RateLimit>,
|
||||||
pub tokens_remaining: usize,
|
|
||||||
pub tokens_reset: DateTime<Utc>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RateLimitInfo {
|
impl RateLimitInfo {
|
||||||
fn from_headers(headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
fn from_headers(headers: &HeaderMap<HeaderValue>) -> Self {
|
||||||
let tokens_limit = get_header("anthropic-ratelimit-tokens-limit", headers)?.parse()?;
|
// Check if any rate limit headers exist
|
||||||
let requests_limit = get_header("anthropic-ratelimit-requests-limit", headers)?.parse()?;
|
let has_rate_limit_headers = headers
|
||||||
let tokens_remaining =
|
.keys()
|
||||||
get_header("anthropic-ratelimit-tokens-remaining", headers)?.parse()?;
|
.any(|k| k.as_str().starts_with("anthropic-ratelimit-"));
|
||||||
let requests_remaining =
|
|
||||||
get_header("anthropic-ratelimit-requests-remaining", headers)?.parse()?;
|
|
||||||
let requests_reset = get_header("anthropic-ratelimit-requests-reset", headers)?;
|
|
||||||
let tokens_reset = get_header("anthropic-ratelimit-tokens-reset", headers)?;
|
|
||||||
let requests_reset = DateTime::parse_from_rfc3339(requests_reset)?.to_utc();
|
|
||||||
let tokens_reset = DateTime::parse_from_rfc3339(tokens_reset)?.to_utc();
|
|
||||||
|
|
||||||
Ok(Self {
|
if !has_rate_limit_headers {
|
||||||
requests_limit,
|
return Self {
|
||||||
tokens_limit,
|
requests: None,
|
||||||
requests_remaining,
|
tokens: None,
|
||||||
tokens_remaining,
|
input_tokens: None,
|
||||||
requests_reset,
|
output_tokens: None,
|
||||||
tokens_reset,
|
};
|
||||||
})
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
requests: RateLimit::from_headers("requests", headers).ok(),
|
||||||
|
tokens: RateLimit::from_headers("tokens", headers).ok(),
|
||||||
|
input_tokens: RateLimit::from_headers("input-tokens", headers).ok(),
|
||||||
|
output_tokens: RateLimit::from_headers("output-tokens", headers).ok(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +451,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.boxed();
|
.boxed();
|
||||||
Ok((stream, rate_limits.log_err()))
|
Ok((stream, Some(rate_limits)))
|
||||||
} else {
|
} else {
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
response
|
response
|
||||||
@@ -437,50 +477,6 @@ pub async fn stream_completion_with_rate_limit_info(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn extract_tool_args_from_events(
|
|
||||||
tool_name: String,
|
|
||||||
mut events: Pin<Box<dyn Send + Stream<Item = Result<Event>>>>,
|
|
||||||
) -> Result<impl Send + Stream<Item = Result<String>>> {
|
|
||||||
let mut tool_use_index = None;
|
|
||||||
while let Some(event) = events.next().await {
|
|
||||||
if let Event::ContentBlockStart {
|
|
||||||
index,
|
|
||||||
content_block: ResponseContent::ToolUse { name, .. },
|
|
||||||
} = event?
|
|
||||||
{
|
|
||||||
if name == tool_name {
|
|
||||||
tool_use_index = Some(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(tool_use_index) = tool_use_index else {
|
|
||||||
return Err(anyhow!("tool not used"));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(events.filter_map(move |event| {
|
|
||||||
let result = match event {
|
|
||||||
Err(error) => Some(Err(error)),
|
|
||||||
Ok(Event::ContentBlockDelta { index, delta }) => match delta {
|
|
||||||
ContentDelta::TextDelta { .. } => None,
|
|
||||||
ContentDelta::ThinkingDelta { .. } => None,
|
|
||||||
ContentDelta::SignatureDelta { .. } => None,
|
|
||||||
ContentDelta::InputJsonDelta { partial_json } => {
|
|
||||||
if index == tool_use_index {
|
|
||||||
Some(Ok(partial_json))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
async move { result }
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum CacheControlType {
|
pub enum CacheControlType {
|
||||||
@@ -515,6 +511,15 @@ pub enum RequestContent {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
cache_control: Option<CacheControl>,
|
cache_control: Option<CacheControl>,
|
||||||
},
|
},
|
||||||
|
#[serde(rename = "thinking")]
|
||||||
|
Thinking {
|
||||||
|
thinking: String,
|
||||||
|
signature: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
cache_control: Option<CacheControl>,
|
||||||
|
},
|
||||||
|
#[serde(rename = "redacted_thinking")]
|
||||||
|
RedactedThinking { data: String },
|
||||||
#[serde(rename = "image")]
|
#[serde(rename = "image")]
|
||||||
Image {
|
Image {
|
||||||
source: ImageSource,
|
source: ImageSource,
|
||||||
@@ -745,4 +750,54 @@ impl ApiError {
|
|||||||
pub fn is_rate_limit_error(&self) -> bool {
|
pub fn is_rate_limit_error(&self) -> bool {
|
||||||
matches!(self.error_type.as_str(), "rate_limit_error")
|
matches!(self.error_type.as_str(), "rate_limit_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn match_window_exceeded(&self) -> Option<usize> {
|
||||||
|
let Some(ApiErrorCode::InvalidRequestError) = self.code() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_prompt_too_long(&self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_prompt_too_long(message: &str) -> Option<usize> {
|
||||||
|
message
|
||||||
|
.strip_prefix("prompt is too long: ")?
|
||||||
|
.split_once(" tokens")?
|
||||||
|
.0
|
||||||
|
.parse::<usize>()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_match_window_exceeded() {
|
||||||
|
let error = ApiError {
|
||||||
|
error_type: "invalid_request_error".to_string(),
|
||||||
|
message: "prompt is too long: 220000 tokens > 200000".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(error.match_window_exceeded(), Some(220_000));
|
||||||
|
|
||||||
|
let error = ApiError {
|
||||||
|
error_type: "invalid_request_error".to_string(),
|
||||||
|
message: "prompt is too long: 1234953 tokens".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(error.match_window_exceeded(), Some(1234953));
|
||||||
|
|
||||||
|
let error = ApiError {
|
||||||
|
error_type: "invalid_request_error".to_string(),
|
||||||
|
message: "not a prompt length error".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(error.match_window_exceeded(), None);
|
||||||
|
|
||||||
|
let error = ApiError {
|
||||||
|
error_type: "rate_limit_error".to_string(),
|
||||||
|
message: "prompt is too long: 12345 tokens".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(error.match_window_exceeded(), None);
|
||||||
|
|
||||||
|
let error = ApiError {
|
||||||
|
error_type: "invalid_request_error".to_string(),
|
||||||
|
message: "prompt is too long: invalid tokens".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(error.match_window_exceeded(), None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ gpui.workspace = true
|
|||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
which.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|||||||