Compare commits
729 Commits
debug-view
...
test-winge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
923e880150 | ||
|
|
de8dd9bea5 | ||
|
|
c77cc9b0eb | ||
|
|
3950f5af29 | ||
|
|
1ee6ef5e1a | ||
|
|
87adc96e0f | ||
|
|
fba7f4d8cc | ||
|
|
ae25baad02 | ||
|
|
cf49194819 | ||
|
|
ea6853d35c | ||
|
|
c37a2f885a | ||
|
|
9c70ba7dcc | ||
|
|
5c4f1e6b85 | ||
|
|
86ce4ef3ab | ||
|
|
9948778e96 | ||
|
|
c2ace408d9 | ||
|
|
e016c05959 | ||
|
|
f2e8d0cc08 | ||
|
|
e406ac6db9 | ||
|
|
546715634c | ||
|
|
db4b86e0c8 | ||
|
|
35f5eb1fe7 | ||
|
|
5939cae6fa | ||
|
|
83f9f9d9e3 | ||
|
|
43baa5d8b8 | ||
|
|
f4609c04eb | ||
|
|
28e14a361d | ||
|
|
77933f83e5 | ||
|
|
186237bb1a | ||
|
|
500acc9511 | ||
|
|
49acfd2602 | ||
|
|
ecb016081a | ||
|
|
bb0cc1059c | ||
|
|
3e2680d650 | ||
|
|
35595fe3c2 | ||
|
|
ce2259ce51 | ||
|
|
6f97d74ff9 | ||
|
|
d6c9d00a4c | ||
|
|
85c2dc909d | ||
|
|
c814b99fcb | ||
|
|
07ccff217a | ||
|
|
8ab52f3491 | ||
|
|
ecf410e57d | ||
|
|
ec0eeaf69d | ||
|
|
376335496d | ||
|
|
4f656cedfa | ||
|
|
0e9ee3cb55 | ||
|
|
bbe764794d | ||
|
|
3882323f79 | ||
|
|
b0b83ef5aa | ||
|
|
7beae757b8 | ||
|
|
a6e99c1c16 | ||
|
|
6d8d2e2989 | ||
|
|
877790a105 | ||
|
|
0c08bbca05 | ||
|
|
ba0b68779d | ||
|
|
45af5e4239 | ||
|
|
01f9b1e9b4 | ||
|
|
635b71c486 | ||
|
|
c4a7552a04 | ||
|
|
918aee550c | ||
|
|
5c194f7cdc | ||
|
|
54df5812d9 | ||
|
|
0d84651f14 | ||
|
|
06af052e6d | ||
|
|
f1786b3b5f | ||
|
|
f348240a8c | ||
|
|
762fa9b3c7 | ||
|
|
1bd34e0db0 | ||
|
|
ce696c18ed | ||
|
|
9d23527663 | ||
|
|
fc2b3b2e45 | ||
|
|
8c7fb26af0 | ||
|
|
867b5df070 | ||
|
|
c5bbd556ea | ||
|
|
4a84b78093 | ||
|
|
fd63d432e9 | ||
|
|
ab70555a8a | ||
|
|
474eb8db77 | ||
|
|
da5f25d9b0 | ||
|
|
83ba05eb32 | ||
|
|
da583e5943 | ||
|
|
9ad6196150 | ||
|
|
d4cc4f8ca7 | ||
|
|
c61429e166 | ||
|
|
4c70d55546 | ||
|
|
025938b4a5 | ||
|
|
cc9af8d036 | ||
|
|
ee60d5855c | ||
|
|
97f398e677 | ||
|
|
6a2bad4e11 | ||
|
|
ad4a53c71c | ||
|
|
160fca029c | ||
|
|
6a1648825c | ||
|
|
f0d097c66a | ||
|
|
a3bcf6fe21 | ||
|
|
ac8e2f0576 | ||
|
|
5c4649bd37 | ||
|
|
bd13c90acc | ||
|
|
997f6c6a19 | ||
|
|
8dfbafd345 | ||
|
|
677d6acc9d | ||
|
|
96add6c9de | ||
|
|
f76eecd758 | ||
|
|
bec2bfeb8b | ||
|
|
9edf1f8f04 | ||
|
|
23fe74ebc5 | ||
|
|
46fff9979d | ||
|
|
e7b19ab0b1 | ||
|
|
ce8d5e41a5 | ||
|
|
dac5725246 | ||
|
|
f1db1f3a3c | ||
|
|
02bdba80a4 | ||
|
|
af0cd30a9c | ||
|
|
3ea4b30e8d | ||
|
|
fdf801d90f | ||
|
|
ff50f48980 | ||
|
|
af52cbacf9 | ||
|
|
785cb41565 | ||
|
|
ce20e71abf | ||
|
|
237474a889 | ||
|
|
f6630ed736 | ||
|
|
81cd435e08 | ||
|
|
47a66c938f | ||
|
|
1ca2f9871e | ||
|
|
52cc71e380 | ||
|
|
7a8a328d3c | ||
|
|
eeaf0b5fec | ||
|
|
95780e5baf | ||
|
|
92e765b5d2 | ||
|
|
abc1e67221 | ||
|
|
68bda24bc1 | ||
|
|
3f3d894c8b | ||
|
|
83f0a36733 | ||
|
|
bbb6783fb8 | ||
|
|
998fece3af | ||
|
|
abe1fd5e16 | ||
|
|
deef58bef7 | ||
|
|
e11e39f9b4 | ||
|
|
6dc3e643b4 | ||
|
|
d4b5bb9f17 | ||
|
|
74d92fd733 | ||
|
|
7d260bf4ef | ||
|
|
89bb2de450 | ||
|
|
3d4d8ef6a8 | ||
|
|
42365df12f | ||
|
|
201124e13f | ||
|
|
3ba4b84107 | ||
|
|
f7e7a304e0 | ||
|
|
65a38a27a9 | ||
|
|
d6becab3be | ||
|
|
924e7e61a5 | ||
|
|
18405dece8 | ||
|
|
120faadef8 | ||
|
|
6a9639f62f | ||
|
|
a696e829ac | ||
|
|
eb8510cb39 | ||
|
|
a54cf3c74e | ||
|
|
41cac5e032 | ||
|
|
59c109f77f | ||
|
|
5e78fb0f94 | ||
|
|
63032f6c66 | ||
|
|
5f857ffbb1 | ||
|
|
a78b560b8b | ||
|
|
b9a6660b93 | ||
|
|
a693d44553 | ||
|
|
41ee92e5f2 | ||
|
|
a9eb480f3c | ||
|
|
5698636c92 | ||
|
|
bbd735905f | ||
|
|
3d5ddcccf0 | ||
|
|
4dae3a15cc | ||
|
|
c6373cc26d | ||
|
|
a4ec693e34 | ||
|
|
08a2b6898b | ||
|
|
13b17b3a85 | ||
|
|
e4f0fbbf80 | ||
|
|
98d4c34199 | ||
|
|
c24f365b69 | ||
|
|
2dfde55367 | ||
|
|
e946a06efe | ||
|
|
75067c94ad | ||
|
|
d7143009fc | ||
|
|
a22c29c5f9 | ||
|
|
c543709d5f | ||
|
|
c58931ac04 | ||
|
|
dd5da592f0 | ||
|
|
f1d17fcfbe | ||
|
|
ccfc1ce387 | ||
|
|
3d4f488d46 | ||
|
|
ba2337ffb9 | ||
|
|
37d676e2c6 | ||
|
|
1bb6752e3e | ||
|
|
8c9b42dda8 | ||
|
|
15c4aadb57 | ||
|
|
3d200a5466 | ||
|
|
e077b63915 | ||
|
|
ef839cc207 | ||
|
|
3d0312f4c7 | ||
|
|
c1e3958c26 | ||
|
|
ba937d16e7 | ||
|
|
4dbd186485 | ||
|
|
88887fd292 | ||
|
|
31e75b2235 | ||
|
|
681c19899f | ||
|
|
439add3d23 | ||
|
|
81b98cdd4d | ||
|
|
ca89a40df2 | ||
|
|
f5884e99d0 | ||
|
|
fce931144e | ||
|
|
ef423148fc | ||
|
|
cd656485c8 | ||
|
|
1e149b755f | ||
|
|
e0eeda11ed | ||
|
|
bcef3b5010 | ||
|
|
5fd187769d | ||
|
|
096930817b | ||
|
|
c7d5afedc5 | ||
|
|
d6b1801fb3 | ||
|
|
7c55f7181d | ||
|
|
4684d6b50e | ||
|
|
578e7e4cbd | ||
|
|
a960db6a43 | ||
|
|
5a0f796a44 | ||
|
|
604d56659d | ||
|
|
1d1c799b4b | ||
|
|
70af11ef2a | ||
|
|
5fa4b3bfe8 | ||
|
|
93a5dffea1 | ||
|
|
9ac010043c | ||
|
|
dd3b65f707 | ||
|
|
057b7b1543 | ||
|
|
a9455eb947 | ||
|
|
db3c186af0 | ||
|
|
71856706c7 | ||
|
|
4ec24ebe01 | ||
|
|
4152942a8e | ||
|
|
bbf4bfad6f | ||
|
|
989d172cfc | ||
|
|
1265b229a9 | ||
|
|
294ca25f44 | ||
|
|
5c7907ad2f | ||
|
|
f652c3a14d | ||
|
|
69ac003bc9 | ||
|
|
d615525771 | ||
|
|
8bf37dd130 | ||
|
|
8cb67ec91c | ||
|
|
cd67941598 | ||
|
|
669db62e33 | ||
|
|
41f1835bbe | ||
|
|
791ba9ce4c | ||
|
|
e60a61f7e7 | ||
|
|
b8a6180b82 | ||
|
|
dfce57c7f8 | ||
|
|
15580a867b | ||
|
|
f7bb22fb83 | ||
|
|
7db7ad93a2 | ||
|
|
642643de01 | ||
|
|
391e304c9f | ||
|
|
3106472bf3 | ||
|
|
d04ac864b8 | ||
|
|
f9a2724a8b | ||
|
|
ded73c9d56 | ||
|
|
41cf114d8a | ||
|
|
e765818487 | ||
|
|
84f488879c | ||
|
|
85985fe960 | ||
|
|
3bec885536 | ||
|
|
9a5034ea6d | ||
|
|
64eec67a81 | ||
|
|
ffff56f7fe | ||
|
|
b02b130b7c | ||
|
|
41ac6a8764 | ||
|
|
963204c99d | ||
|
|
f6f11eb544 | ||
|
|
c1e917165d | ||
|
|
a2a7bd139a | ||
|
|
4de13e06ec | ||
|
|
e680dfb0a0 | ||
|
|
31544d294d | ||
|
|
4e932297a4 | ||
|
|
b2f0b1b168 | ||
|
|
94f1faffa7 | ||
|
|
075104a529 | ||
|
|
c80d213227 | ||
|
|
fe9895d112 | ||
|
|
24bc52a15a | ||
|
|
a65a8bea43 | ||
|
|
ea60a7b172 | ||
|
|
a67a55d81a | ||
|
|
1a9f9ccc29 | ||
|
|
6da5945cd2 | ||
|
|
354cc65daa | ||
|
|
2c6a8634cc | ||
|
|
84ec865c44 | ||
|
|
80727a03bf | ||
|
|
e7339fbd42 | ||
|
|
db5b1a31b5 | ||
|
|
bc39ed2575 | ||
|
|
1764337a5d | ||
|
|
3707102702 | ||
|
|
5263f51432 | ||
|
|
93cd10aaa8 | ||
|
|
2c1cc01b81 | ||
|
|
81ada92306 | ||
|
|
4bd7ef8bad | ||
|
|
d1e2a1f20c | ||
|
|
79a8986cb7 | ||
|
|
d2b91eb2bc | ||
|
|
c26937a848 | ||
|
|
da82eec4cb | ||
|
|
2bfcd60b88 | ||
|
|
9c7369f54d | ||
|
|
5160510ed0 | ||
|
|
ee557fb7ea | ||
|
|
f9919f9214 | ||
|
|
0f0974f105 | ||
|
|
e317d98915 | ||
|
|
dada318be7 | ||
|
|
b53f9c8863 | ||
|
|
5b0a2f1ab6 | ||
|
|
d5a4890142 | ||
|
|
cd61bfbd42 | ||
|
|
469ecfbe13 | ||
|
|
46b6adadf9 | ||
|
|
1a9e9c5faa | ||
|
|
eb64ca8758 | ||
|
|
68e6d55596 | ||
|
|
bcd2d269e2 | ||
|
|
b32075cdcb | ||
|
|
21e75b8221 | ||
|
|
978951b79a | ||
|
|
6b980ecad3 | ||
|
|
d9c7f44b0b | ||
|
|
55e68553a4 | ||
|
|
9fe46dc8d2 | ||
|
|
aced13bc9f | ||
|
|
2859cbdba9 | ||
|
|
4443f61c16 | ||
|
|
f0f0beb42f | ||
|
|
6707ff3b50 | ||
|
|
93770e8314 | ||
|
|
f8c617303a | ||
|
|
e5f05a21ce | ||
|
|
f499504b13 | ||
|
|
504216cbbf | ||
|
|
3bf71c690f | ||
|
|
456ba32ea7 | ||
|
|
9aeb617a89 | ||
|
|
fd8bae9b72 | ||
|
|
f71c9122ca | ||
|
|
8441aa49b2 | ||
|
|
7b96e1cf1a | ||
|
|
86322a186f | ||
|
|
1b94d74dc3 | ||
|
|
db825c1141 | ||
|
|
f3abd1dab5 | ||
|
|
662ec9977f | ||
|
|
3ab5103de1 | ||
|
|
39bd03b92d | ||
|
|
1fffcb99ba | ||
|
|
e4f90b5da2 | ||
|
|
dc6fad9659 | ||
|
|
64c289a9a2 | ||
|
|
a08897ff30 | ||
|
|
d359a814f8 | ||
|
|
4c35274b6e | ||
|
|
bf48a95344 | ||
|
|
7c3a21f732 | ||
|
|
af630be7ca | ||
|
|
dbd8efe129 | ||
|
|
3afbe836a1 | ||
|
|
d8709f2107 | ||
|
|
df7bc8200d | ||
|
|
8575972a07 | ||
|
|
40c417f9c3 | ||
|
|
7c2cf86dd9 | ||
|
|
126ed6fbdd | ||
|
|
6f4381b39d | ||
|
|
6fbbdb3512 | ||
|
|
179fb21778 | ||
|
|
6584fb23e3 | ||
|
|
d8698dffe3 | ||
|
|
bf44dc5ff5 | ||
|
|
d85b6a1544 | ||
|
|
702e618bba | ||
|
|
1029d3c301 | ||
|
|
97f552876c | ||
|
|
63c081d456 | ||
|
|
6970ab2040 | ||
|
|
e42dfb4387 | ||
|
|
ec202a26c8 | ||
|
|
f17096879c | ||
|
|
fb343a7743 | ||
|
|
a49b2d5bf8 | ||
|
|
b5d57598b6 | ||
|
|
b9d9602074 | ||
|
|
cc19f66ee1 | ||
|
|
62f90fec77 | ||
|
|
86ebb1890d | ||
|
|
dd5099ac28 | ||
|
|
c95b88d546 | ||
|
|
c217f6bd36 | ||
|
|
3314de8175 | ||
|
|
6b907bd102 | ||
|
|
3cb933ddb1 | ||
|
|
cf5362ffd1 | ||
|
|
74ac5ece6a | ||
|
|
f107708de3 | ||
|
|
4940e53d23 | ||
|
|
ab79fa440d | ||
|
|
c9b7df4113 | ||
|
|
f2df49764e | ||
|
|
77cc55656e | ||
|
|
1c85995ed7 | ||
|
|
d1543f75b6 | ||
|
|
fc0b249136 | ||
|
|
01dbc68f82 | ||
|
|
e111acad33 | ||
|
|
c61409e577 | ||
|
|
1659fb81e7 | ||
|
|
dd6c653fe9 | ||
|
|
a13e84a108 | ||
|
|
1cac3e3e40 | ||
|
|
9abe5811a5 | ||
|
|
97bd2846e9 | ||
|
|
e9244d50a7 | ||
|
|
83e5a3033e | ||
|
|
94a4c0c352 | ||
|
|
0f8693386a | ||
|
|
ed269b4467 | ||
|
|
34ddf5466f | ||
|
|
a701388cb7 | ||
|
|
29afc0412e | ||
|
|
e65a9291ef | ||
|
|
a53faff412 | ||
|
|
074cb88036 | ||
|
|
67ebb1f795 | ||
|
|
ace617037f | ||
|
|
43061b6b16 | ||
|
|
e23e976e58 | ||
|
|
0266a995aa | ||
|
|
9741e9ab8b | ||
|
|
3f31fc2874 | ||
|
|
6c50fd6de9 | ||
|
|
df43a2d3b1 | ||
|
|
35749e99e5 | ||
|
|
e965c43703 | ||
|
|
14fc726cae | ||
|
|
4f95186b53 | ||
|
|
33f44009de | ||
|
|
9d895c5ea7 | ||
|
|
0811d48a7a | ||
|
|
d8cafdf937 | ||
|
|
95190a2034 | ||
|
|
49335d54be | ||
|
|
624e448492 | ||
|
|
bf9dd6bbef | ||
|
|
6af385235d | ||
|
|
cc19387853 | ||
|
|
5922f4adce | ||
|
|
cac920d992 | ||
|
|
773850f477 | ||
|
|
9c60bc3837 | ||
|
|
fbb4dcf2b1 | ||
|
|
2ccadc7f65 | ||
|
|
80989d6767 | ||
|
|
719013dae6 | ||
|
|
8af3f583c2 | ||
|
|
f1d80b715a | ||
|
|
42ef3e5d3d | ||
|
|
90ea252c82 | ||
|
|
6e5ff6d091 | ||
|
|
04216a88f3 | ||
|
|
3ae65153db | ||
|
|
ffc9060607 | ||
|
|
4fc4707cfc | ||
|
|
8662025d12 | ||
|
|
ceddd5752a | ||
|
|
20166727a6 | ||
|
|
6e80fca0d5 | ||
|
|
778ca84f85 | ||
|
|
ebdc0572c6 | ||
|
|
cda48a3a1c | ||
|
|
b7f9fd7d74 | ||
|
|
98ab118526 | ||
|
|
1e70a1a4ce | ||
|
|
163219af35 | ||
|
|
f96fd928d7 | ||
|
|
9aa5817b85 | ||
|
|
28cc39ad56 | ||
|
|
0da3f9ffda | ||
|
|
f2efe78feb | ||
|
|
ed7217ff46 | ||
|
|
f9fb389f86 | ||
|
|
632e569c5f | ||
|
|
0c71aa9f01 | ||
|
|
92a09ecf25 | ||
|
|
bad96776cd | ||
|
|
aa14980523 | ||
|
|
12aba6193e | ||
|
|
720971e47b | ||
|
|
0a10e3e264 | ||
|
|
77854f4627 | ||
|
|
5ce7eda8d2 | ||
|
|
6d7a4c441b | ||
|
|
cc85a48de5 | ||
|
|
4cd839e352 | ||
|
|
78098f6809 | ||
|
|
4d2ff6c899 | ||
|
|
6f5d1522cb | ||
|
|
682cf023ca | ||
|
|
72948e14ee | ||
|
|
a063a70cfb | ||
|
|
687e22b4c3 | ||
|
|
e13b88e4bd | ||
|
|
e1e9f78dc3 | ||
|
|
0fe696bc7c | ||
|
|
ead38fd1be | ||
|
|
fbdf5d4df4 | ||
|
|
837f282f1e | ||
|
|
bd3cccea15 | ||
|
|
d437bbaa0a | ||
|
|
114791e1a8 | ||
|
|
d6fcd404af | ||
|
|
7ad9ca9bcc | ||
|
|
a55dff7834 | ||
|
|
6db621a1ed | ||
|
|
948b4379df | ||
|
|
8db24dd8ad | ||
|
|
4aac5642c1 | ||
|
|
30b49cfbf5 | ||
|
|
c69912c76a | ||
|
|
7f14ab26dd | ||
|
|
5ee73d3e3c | ||
|
|
d5aa81a5b2 | ||
|
|
21855c15e4 | ||
|
|
1f9279a56f | ||
|
|
da71465437 | ||
|
|
bcc8149263 | ||
|
|
b1528601cc | ||
|
|
ee357e8987 | ||
|
|
0891a7142d | ||
|
|
94fe862fb6 | ||
|
|
4f91fab190 | ||
|
|
0e0f48d8e1 | ||
|
|
7980dbdaea | ||
|
|
a5683f3541 | ||
|
|
67984d5e49 | ||
|
|
d83d7d35cb | ||
|
|
6470443271 | ||
|
|
5b72dfff87 | ||
|
|
495a7b0a84 | ||
|
|
301e976465 | ||
|
|
daebc4052d | ||
|
|
ecc35fcd9a | ||
|
|
236006b6b3 | ||
|
|
39c4480841 | ||
|
|
48aac2a746 | ||
|
|
ae036f8ead | ||
|
|
de1de25712 | ||
|
|
18fc951135 | ||
|
|
40138e12a4 | ||
|
|
e7a5c81b07 | ||
|
|
d98175c0a6 | ||
|
|
bc7d804a42 | ||
|
|
50bb8a4ae6 | ||
|
|
b2b90b003d | ||
|
|
c0f56f500e | ||
|
|
a9fe18f4cb | ||
|
|
3c5e683fbe | ||
|
|
783ba389f7 | ||
|
|
e72021a26b | ||
|
|
f25ace6be0 | ||
|
|
c627543b46 | ||
|
|
f303a461c4 | ||
|
|
a9def8128f | ||
|
|
6580eac077 | ||
|
|
5c3c79d667 | ||
|
|
16fccb5c76 | ||
|
|
a25504edaf | ||
|
|
bc11844b2e | ||
|
|
10b99c6f55 | ||
|
|
17dea24533 | ||
|
|
17e55daf6f | ||
|
|
6b968e0118 | ||
|
|
0f66310192 | ||
|
|
26adc70ae6 | ||
|
|
a5fb290252 | ||
|
|
8fc7bd9ae8 | ||
|
|
7167be5889 | ||
|
|
d321cf93ba | ||
|
|
ce7b02e3a1 | ||
|
|
03f9cf4414 | ||
|
|
3c626f3758 | ||
|
|
4a1bab52f3 | ||
|
|
91b0f42382 | ||
|
|
523c042930 | ||
|
|
ed7bd5a8ed | ||
|
|
8ebe4fa149 | ||
|
|
6b646e3a14 | ||
|
|
e653cc90c5 | ||
|
|
0794de71e3 | ||
|
|
2b283e7c53 | ||
|
|
45a4277026 | ||
|
|
fa76b6ce06 | ||
|
|
a13e3a8af3 | ||
|
|
39370bceb2 | ||
|
|
53885c00d3 | ||
|
|
6f3e66d027 | ||
|
|
b3f9be6e9c | ||
|
|
4353b61155 | ||
|
|
e1b57f00a0 | ||
|
|
c5219e8fd2 | ||
|
|
5612a961b0 | ||
|
|
c53e5ba397 | ||
|
|
d5a99d079e | ||
|
|
9418a2f4bc | ||
|
|
880fff471c | ||
|
|
5f6ae2361f | ||
|
|
5d89b2ea26 | ||
|
|
0f7dbf57f5 | ||
|
|
b60f19f71e | ||
|
|
0a261ad8d0 | ||
|
|
28ed08340c | ||
|
|
74fe3b17f7 | ||
|
|
9112554262 | ||
|
|
3b79490e8f | ||
|
|
52c467ea3a | ||
|
|
831de8e48f | ||
|
|
bc528411df | ||
|
|
9ac511e47c | ||
|
|
afaed3af62 | ||
|
|
f78699eb71 | ||
|
|
3646aa6bba | ||
|
|
dc20a41e0d | ||
|
|
6a24ad7d39 | ||
|
|
8fefd793f0 | ||
|
|
f6e2a2a808 | ||
|
|
3cf6fa8f61 | ||
|
|
2759f541da | ||
|
|
809d3bfe00 | ||
|
|
0aad47493e | ||
|
|
271d67f7ad | ||
|
|
2e87387e53 | ||
|
|
15e75bdf04 | ||
|
|
3ac14e15bb | ||
|
|
9e7302520e | ||
|
|
1bf8332333 | ||
|
|
d8048f46ee | ||
|
|
edb804de5a | ||
|
|
691bfe71db | ||
|
|
1d5da68560 | ||
|
|
f07bc12aed | ||
|
|
4532765ae8 | ||
|
|
25a1827456 | ||
|
|
98865a3ff2 | ||
|
|
681a4adc42 | ||
|
|
5e502a32fb | ||
|
|
e9fbcf5abf | ||
|
|
c9e3b32366 | ||
|
|
e9abd5b28b | ||
|
|
a90abb1009 | ||
|
|
46d19d8a47 | ||
|
|
e484f49ee8 | ||
|
|
80dcabe95c | ||
|
|
e602cfadd3 | ||
|
|
d4adb51553 | ||
|
|
a0514af589 | ||
|
|
c88fdaf02d | ||
|
|
003163eb4f | ||
|
|
9e64b7b911 | ||
|
|
d4fd59f0a2 | ||
|
|
4e6e424fd7 | ||
|
|
dccbb47fbc | ||
|
|
b97843ea02 | ||
|
|
fbe06238e4 | ||
|
|
e0028fbef2 | ||
|
|
1bbf98aea6 | ||
|
|
8bac1bee7a | ||
|
|
55dc9ff7ca | ||
|
|
50bd8bc255 | ||
|
|
a2c71d3d20 | ||
|
|
79620454d0 | ||
|
|
271771c742 | ||
|
|
891a06c294 | ||
|
|
11041ef3b0 | ||
|
|
839c216620 | ||
|
|
18df6a81b4 | ||
|
|
f5c2e4b49e | ||
|
|
1d1bbf01a9 | ||
|
|
ffa23d25e3 | ||
|
|
782058647d | ||
|
|
be77682a3f | ||
|
|
8df616e28b | ||
|
|
89520ea221 | ||
|
|
de75e2d9f6 | ||
|
|
4e316c683b | ||
|
|
1afbfcb832 | ||
|
|
be7575536e | ||
|
|
30a29ab34e | ||
|
|
b9188e0fd3 | ||
|
|
df6f0bc2a7 | ||
|
|
4743fe8415 | ||
|
|
0f4bdca9e9 | ||
|
|
154b01c5fe | ||
|
|
b6944d0bae | ||
|
|
94fcbb400b | ||
|
|
2e97ef32c4 | ||
|
|
aa5b99dc11 | ||
|
|
3217bcb83e | ||
|
|
a3da66cec0 | ||
|
|
9e6f1d5a6e | ||
|
|
430ac5175f | ||
|
|
5f728efccf | ||
|
|
194a13ffb5 | ||
|
|
66f2fda625 | ||
|
|
e62dd2a0e5 | ||
|
|
c826ce6fc6 | ||
|
|
e5e308ba78 | ||
|
|
166b2352f3 | ||
|
|
f18b19a73e | ||
|
|
b09764c54a | ||
|
|
5f4f0a873e | ||
|
|
82e1e5b7ac | ||
|
|
530225a06a | ||
|
|
11212b80f9 | ||
|
|
e3e0522e32 | ||
|
|
fc0eb882f7 |
@@ -10,3 +10,15 @@
|
|||||||
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
|
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
|
||||||
[target.'cfg(all())']
|
[target.'cfg(all())']
|
||||||
rustflags = ["-D", "warnings"]
|
rustflags = ["-D", "warnings"]
|
||||||
|
|
||||||
|
# Use Mold on Linux, because it's faster than GNU ld and LLD.
|
||||||
|
#
|
||||||
|
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which
|
||||||
|
# is faster than Mold, in their own ~/.cargo/config.toml.
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
linker = "clang"
|
||||||
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
linker = "clang"
|
||||||
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
|
|||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
xtask = "run --package xtask --"
|
xtask = "run --package xtask --"
|
||||||
|
perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--all-features", "--config", "target.'cfg(true)'.runner='cargo run -p perf --release'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"]
|
||||||
[target.x86_64-unknown-linux-gnu]
|
# Keep similar flags here to share some ccache
|
||||||
linker = "clang"
|
perf-compare = ["run", "--profile", "release-fast", "-p", "perf", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]", "--", "compare"]
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
||||||
|
|
||||||
[target.aarch64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")']
|
[target.'cfg(target_os = "windows")']
|
||||||
rustflags = [
|
rustflags = [
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ workspace-members = [
|
|||||||
third-party = [
|
third-party = [
|
||||||
{ name = "reqwest", version = "0.11.27" },
|
{ name = "reqwest", version = "0.11.27" },
|
||||||
# build of remote_server should not include scap / its x11 dependency
|
# build of remote_server should not include scap / its x11 dependency
|
||||||
{ name = "scap", git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7" },
|
{ name = "zed-scap", git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", version = "0.0.8-zed" },
|
||||||
# build of remote_server should not need to include on libalsa through rodio
|
# build of remote_server should not need to include on libalsa through rodio
|
||||||
{ name = "rodio", git = "https://github.com/RustAudio/rodio", branch = "better_wav_output"},
|
{ name = "rodio", git = "https://github.com/RustAudio/rodio" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[final-excludes]
|
[final-excludes]
|
||||||
@@ -37,8 +37,6 @@ workspace-members = [
|
|||||||
"zed_glsl",
|
"zed_glsl",
|
||||||
"zed_html",
|
"zed_html",
|
||||||
"zed_proto",
|
"zed_proto",
|
||||||
"zed_ruff",
|
|
||||||
"slash_commands_example",
|
"slash_commands_example",
|
||||||
"zed_snippets",
|
|
||||||
"zed_test_extension",
|
"zed_test_extension",
|
||||||
]
|
]
|
||||||
|
|||||||
35
.github/ISSUE_TEMPLATE/07_bug_windows.yml
vendored
Normal file
35
.github/ISSUE_TEMPLATE/07_bug_windows.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Bug Report (Windows)
|
||||||
|
description: Zed Windows Related Bugs
|
||||||
|
type: "Bug"
|
||||||
|
labels: ["windows"]
|
||||||
|
title: "Windows: <a short description of the Windows 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.
|
||||||
|
|
||||||
|
**Expected Behavior**:
|
||||||
|
**Actual 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
|
||||||
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -20,4 +20,4 @@ runs:
|
|||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
run: cargo nextest run --workspace --no-fail-fast
|
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
|
||||||
|
|||||||
2
.github/actions/run_tests_windows/action.yml
vendored
2
.github/actions/run_tests_windows/action.yml
vendored
@@ -24,4 +24,4 @@ runs:
|
|||||||
shell: powershell
|
shell: powershell
|
||||||
working-directory: ${{ inputs.working-directory }}
|
working-directory: ${{ inputs.working-directory }}
|
||||||
run: |
|
run: |
|
||||||
cargo nextest run --workspace --no-fail-fast
|
cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
|
||||||
|
|||||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -826,8 +826,9 @@ jobs:
|
|||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
name: Create a Windows installer
|
name: Create a Windows installer
|
||||||
runs-on: [self-32vcpu-windows-2022]
|
runs-on: [self-32vcpu-windows-2022]
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
if: |
|
||||||
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
( startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||||
needs: [windows_tests]
|
needs: [windows_tests]
|
||||||
env:
|
env:
|
||||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||||
@@ -865,13 +866,12 @@ jobs:
|
|||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
with:
|
with:
|
||||||
name: ZedEditorUserSetup-x64-${{ github.event.pull_request.head.sha || github.sha }}.exe
|
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe
|
||||||
path: ${{ env.SETUP_PATH }}
|
path: ${{ env.SETUP_PATH }}
|
||||||
|
|
||||||
- name: Upload Artifacts to release
|
- name: Upload Artifacts to release
|
||||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||||
# Re-enable when we are ready to publish windows preview releases
|
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
|
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||||
|
|||||||
48
.github/workflows/community_champion_auto_labeler.yml
vendored
Normal file
48
.github/workflows/community_champion_auto_labeler.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Community Champion Auto Labeler
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label_community_champion:
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check if author is a community champion and apply label
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const communityChampionBody = `${{ secrets.COMMUNITY_CHAMPIONS }}`;
|
||||||
|
|
||||||
|
const communityChampions = communityChampionBody
|
||||||
|
.split('\n')
|
||||||
|
.map(handle => handle.trim().toLowerCase());
|
||||||
|
|
||||||
|
let author;
|
||||||
|
if (context.eventName === 'issues') {
|
||||||
|
author = context.payload.issue.user.login;
|
||||||
|
} else if (context.eventName === 'pull_request_target') {
|
||||||
|
author = context.payload.pull_request.user.login;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!author || !communityChampions.includes(author.toLowerCase())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
labels: ['community champion']
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Applied 'community champion' label to #${issueNumber} by ${author}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to apply label: ${error.message}`);
|
||||||
|
}
|
||||||
22
.github/workflows/community_release_actions.yml
vendored
22
.github/workflows/community_release_actions.yml
vendored
@@ -38,8 +38,28 @@ jobs:
|
|||||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
|
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
|
||||||
content: ${{ steps.get-content.outputs.string }}
|
content: ${{ steps.get-content.outputs.string }}
|
||||||
|
|
||||||
|
publish-winget:
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set Package Name
|
||||||
|
id: set-package-name
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||||
|
PACKAGE_NAME=ZedIndustries.Zed.Preview
|
||||||
|
else
|
||||||
|
PACKAGE_NAME=ZedIndustries.Zed
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
|
||||||
|
- uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # v2
|
||||||
|
with:
|
||||||
|
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
|
||||||
|
max-versions-to-keep: 5
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
|
||||||
send_release_notes_email:
|
send_release_notes_email:
|
||||||
if: github.repository_owner == 'zed-industries' && !github.event.release.prerelease
|
if: false && github.repository_owner == 'zed-industries' && !github.event.release.prerelease
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|||||||
33
.github/workflows/issue_response.yml
vendored
33
.github/workflows/issue_response.yml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: Issue Response
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 12 * * 2"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
issue-response:
|
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
cache: "pnpm"
|
|
||||||
cache-dependency-path: "script/issue_response/pnpm-lock.yaml"
|
|
||||||
|
|
||||||
- run: pnpm install --dir script/issue_response
|
|
||||||
|
|
||||||
- name: Run Issue Response
|
|
||||||
run: pnpm run --dir script/issue_response start
|
|
||||||
env:
|
|
||||||
ISSUE_RESPONSE_GITHUB_TOKEN: ${{ secrets.ISSUE_RESPONSE_GITHUB_TOKEN }}
|
|
||||||
SLACK_ISSUE_RESPONSE_WEBHOOK_URL: ${{ secrets.SLACK_ISSUE_RESPONSE_WEBHOOK_URL }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@
|
|||||||
.venv
|
.venv
|
||||||
.vscode
|
.vscode
|
||||||
.wrangler
|
.wrangler
|
||||||
|
.perf-runs
|
||||||
/assets/*licenses.*
|
/assets/*licenses.*
|
||||||
/crates/collab/seed.json
|
/crates/collab/seed.json
|
||||||
/crates/theme/schemas/theme.json
|
/crates/theme/schemas/theme.json
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ Although there are few hard and fast rules, typically we don't merge:
|
|||||||
- New file icons. Zed's default icon theme consists of icons that are hand-designed to fit together in a cohesive manner, please don't submit PRs with off-the-shelf SVGs.
|
- New file icons. Zed's default icon theme consists of icons that are hand-designed to fit together in a cohesive manner, please don't submit PRs with off-the-shelf SVGs.
|
||||||
- Giant refactorings.
|
- Giant refactorings.
|
||||||
- Non-trivial changes with no tests.
|
- Non-trivial changes with no tests.
|
||||||
|
- Stylistic code changes that do not alter any app logic. Reducing allocations, removing `.unwrap()`s, fixing typos is great; making code "more readable" — maybe not so much.
|
||||||
- Features where (in our subjective opinion) the extra complexity isn't worth it for the number of people who will benefit.
|
- Features where (in our subjective opinion) the extra complexity isn't worth it for the number of people who will benefit.
|
||||||
- Anything that seems completely AI generated.
|
- Anything that seems completely AI generated.
|
||||||
|
|
||||||
|
|||||||
3061
Cargo.lock
generated
3061
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
71
Cargo.toml
71
Cargo.toml
@@ -35,6 +35,7 @@ members = [
|
|||||||
"crates/cloud_api_client",
|
"crates/cloud_api_client",
|
||||||
"crates/cloud_api_types",
|
"crates/cloud_api_types",
|
||||||
"crates/cloud_llm_client",
|
"crates/cloud_llm_client",
|
||||||
|
"crates/cloud_zeta2_prompt",
|
||||||
"crates/collab",
|
"crates/collab",
|
||||||
"crates/collab_ui",
|
"crates/collab_ui",
|
||||||
"crates/collections",
|
"crates/collections",
|
||||||
@@ -58,7 +59,7 @@ members = [
|
|||||||
"crates/edit_prediction",
|
"crates/edit_prediction",
|
||||||
"crates/edit_prediction_button",
|
"crates/edit_prediction_button",
|
||||||
"crates/edit_prediction_context",
|
"crates/edit_prediction_context",
|
||||||
"crates/edit_prediction_tools",
|
"crates/zeta2_tools",
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
"crates/eval",
|
"crates/eval",
|
||||||
"crates/explorer_command_injector",
|
"crates/explorer_command_injector",
|
||||||
@@ -89,9 +90,8 @@ members = [
|
|||||||
"crates/image_viewer",
|
"crates/image_viewer",
|
||||||
"crates/inspector_ui",
|
"crates/inspector_ui",
|
||||||
"crates/install_cli",
|
"crates/install_cli",
|
||||||
"crates/jj",
|
|
||||||
"crates/jj_ui",
|
|
||||||
"crates/journal",
|
"crates/journal",
|
||||||
|
"crates/json_schema_store",
|
||||||
"crates/keymap_editor",
|
"crates/keymap_editor",
|
||||||
"crates/language",
|
"crates/language",
|
||||||
"crates/language_extension",
|
"crates/language_extension",
|
||||||
@@ -150,8 +150,9 @@ members = [
|
|||||||
"crates/semantic_version",
|
"crates/semantic_version",
|
||||||
"crates/session",
|
"crates/session",
|
||||||
"crates/settings",
|
"crates/settings",
|
||||||
|
"crates/settings_macros",
|
||||||
"crates/settings_profile_selector",
|
"crates/settings_profile_selector",
|
||||||
"crates/settings_ui_macros",
|
"crates/settings_ui",
|
||||||
"crates/snippet",
|
"crates/snippet",
|
||||||
"crates/snippet_provider",
|
"crates/snippet_provider",
|
||||||
"crates/snippets_ui",
|
"crates/snippets_ui",
|
||||||
@@ -163,6 +164,7 @@ members = [
|
|||||||
"crates/sum_tree",
|
"crates/sum_tree",
|
||||||
"crates/supermaven",
|
"crates/supermaven",
|
||||||
"crates/supermaven_api",
|
"crates/supermaven_api",
|
||||||
|
"crates/codestral",
|
||||||
"crates/svg_preview",
|
"crates/svg_preview",
|
||||||
"crates/system_specs",
|
"crates/system_specs",
|
||||||
"crates/tab_switcher",
|
"crates/tab_switcher",
|
||||||
@@ -199,6 +201,7 @@ members = [
|
|||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
"crates/zed_env_vars",
|
"crates/zed_env_vars",
|
||||||
"crates/zeta",
|
"crates/zeta",
|
||||||
|
"crates/zeta2",
|
||||||
"crates/zeta_cli",
|
"crates/zeta_cli",
|
||||||
"crates/zlog",
|
"crates/zlog",
|
||||||
"crates/zlog_settings",
|
"crates/zlog_settings",
|
||||||
@@ -210,17 +213,16 @@ members = [
|
|||||||
"extensions/glsl",
|
"extensions/glsl",
|
||||||
"extensions/html",
|
"extensions/html",
|
||||||
"extensions/proto",
|
"extensions/proto",
|
||||||
"extensions/ruff",
|
|
||||||
"extensions/slash-commands-example",
|
"extensions/slash-commands-example",
|
||||||
"extensions/snippets",
|
|
||||||
"extensions/test-extension",
|
"extensions/test-extension",
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tooling
|
# Tooling
|
||||||
#
|
#
|
||||||
|
|
||||||
|
"tooling/perf",
|
||||||
"tooling/workspace-hack",
|
"tooling/workspace-hack",
|
||||||
"tooling/xtask",
|
"tooling/xtask", "crates/fs_benchmarks", "crates/worktree_benchmarks",
|
||||||
]
|
]
|
||||||
default-members = ["crates/zed"]
|
default-members = ["crates/zed"]
|
||||||
|
|
||||||
@@ -269,9 +271,10 @@ clock = { path = "crates/clock" }
|
|||||||
cloud_api_client = { path = "crates/cloud_api_client" }
|
cloud_api_client = { path = "crates/cloud_api_client" }
|
||||||
cloud_api_types = { path = "crates/cloud_api_types" }
|
cloud_api_types = { path = "crates/cloud_api_types" }
|
||||||
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
||||||
|
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
|
||||||
collab = { path = "crates/collab" }
|
collab = { path = "crates/collab" }
|
||||||
collab_ui = { path = "crates/collab_ui" }
|
collab_ui = { path = "crates/collab_ui" }
|
||||||
collections = { path = "crates/collections" }
|
collections = { path = "crates/collections", version = "0.1.0" }
|
||||||
command_palette = { path = "crates/command_palette" }
|
command_palette = { path = "crates/command_palette" }
|
||||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||||
component = { path = "crates/component" }
|
component = { path = "crates/component" }
|
||||||
@@ -287,6 +290,7 @@ debug_adapter_extension = { path = "crates/debug_adapter_extension" }
|
|||||||
debugger_tools = { path = "crates/debugger_tools" }
|
debugger_tools = { path = "crates/debugger_tools" }
|
||||||
debugger_ui = { path = "crates/debugger_ui" }
|
debugger_ui = { path = "crates/debugger_ui" }
|
||||||
deepseek = { path = "crates/deepseek" }
|
deepseek = { path = "crates/deepseek" }
|
||||||
|
derive_refineable = { path = "crates/refineable/derive_refineable" }
|
||||||
diagnostics = { path = "crates/diagnostics" }
|
diagnostics = { path = "crates/diagnostics" }
|
||||||
editor = { path = "crates/editor" }
|
editor = { path = "crates/editor" }
|
||||||
extension = { path = "crates/extension" }
|
extension = { path = "crates/extension" }
|
||||||
@@ -315,12 +319,11 @@ image_viewer = { path = "crates/image_viewer" }
|
|||||||
edit_prediction = { path = "crates/edit_prediction" }
|
edit_prediction = { path = "crates/edit_prediction" }
|
||||||
edit_prediction_button = { path = "crates/edit_prediction_button" }
|
edit_prediction_button = { path = "crates/edit_prediction_button" }
|
||||||
edit_prediction_context = { path = "crates/edit_prediction_context" }
|
edit_prediction_context = { path = "crates/edit_prediction_context" }
|
||||||
edit_prediction_tools = { path = "crates/edit_prediction_tools" }
|
zeta2_tools = { path = "crates/zeta2_tools" }
|
||||||
inspector_ui = { path = "crates/inspector_ui" }
|
inspector_ui = { path = "crates/inspector_ui" }
|
||||||
install_cli = { path = "crates/install_cli" }
|
install_cli = { path = "crates/install_cli" }
|
||||||
jj = { path = "crates/jj" }
|
|
||||||
jj_ui = { path = "crates/jj_ui" }
|
|
||||||
journal = { path = "crates/journal" }
|
journal = { path = "crates/journal" }
|
||||||
|
json_schema_store = { path = "crates/json_schema_store" }
|
||||||
keymap_editor = { path = "crates/keymap_editor" }
|
keymap_editor = { path = "crates/keymap_editor" }
|
||||||
language = { path = "crates/language" }
|
language = { path = "crates/language" }
|
||||||
language_extension = { path = "crates/language_extension" }
|
language_extension = { path = "crates/language_extension" }
|
||||||
@@ -355,6 +358,7 @@ outline = { path = "crates/outline" }
|
|||||||
outline_panel = { path = "crates/outline_panel" }
|
outline_panel = { path = "crates/outline_panel" }
|
||||||
panel = { path = "crates/panel" }
|
panel = { path = "crates/panel" }
|
||||||
paths = { path = "crates/paths" }
|
paths = { path = "crates/paths" }
|
||||||
|
perf = { path = "tooling/perf" }
|
||||||
picker = { path = "crates/picker" }
|
picker = { path = "crates/picker" }
|
||||||
plugin = { path = "crates/plugin" }
|
plugin = { path = "crates/plugin" }
|
||||||
plugin_macros = { path = "crates/plugin_macros" }
|
plugin_macros = { path = "crates/plugin_macros" }
|
||||||
@@ -374,7 +378,7 @@ remote_server = { path = "crates/remote_server" }
|
|||||||
repl = { path = "crates/repl" }
|
repl = { path = "crates/repl" }
|
||||||
reqwest_client = { path = "crates/reqwest_client" }
|
reqwest_client = { path = "crates/reqwest_client" }
|
||||||
rich_text = { path = "crates/rich_text" }
|
rich_text = { path = "crates/rich_text" }
|
||||||
rodio = { git = "https://github.com/RustAudio/rodio", branch = "better_wav_output"}
|
rodio = { git = "https://github.com/RustAudio/rodio" }
|
||||||
rope = { path = "crates/rope" }
|
rope = { path = "crates/rope" }
|
||||||
rpc = { path = "crates/rpc" }
|
rpc = { path = "crates/rpc" }
|
||||||
rules_library = { path = "crates/rules_library" }
|
rules_library = { path = "crates/rules_library" }
|
||||||
@@ -382,8 +386,8 @@ search = { path = "crates/search" }
|
|||||||
semantic_version = { path = "crates/semantic_version" }
|
semantic_version = { path = "crates/semantic_version" }
|
||||||
session = { path = "crates/session" }
|
session = { path = "crates/session" }
|
||||||
settings = { path = "crates/settings" }
|
settings = { path = "crates/settings" }
|
||||||
|
settings_macros = { path = "crates/settings_macros" }
|
||||||
settings_ui = { path = "crates/settings_ui" }
|
settings_ui = { path = "crates/settings_ui" }
|
||||||
settings_ui_macros = { path = "crates/settings_ui_macros" }
|
|
||||||
snippet = { path = "crates/snippet" }
|
snippet = { path = "crates/snippet" }
|
||||||
snippet_provider = { path = "crates/snippet_provider" }
|
snippet_provider = { path = "crates/snippet_provider" }
|
||||||
snippets_ui = { path = "crates/snippets_ui" }
|
snippets_ui = { path = "crates/snippets_ui" }
|
||||||
@@ -395,6 +399,7 @@ streaming_diff = { path = "crates/streaming_diff" }
|
|||||||
sum_tree = { path = "crates/sum_tree" }
|
sum_tree = { path = "crates/sum_tree" }
|
||||||
supermaven = { path = "crates/supermaven" }
|
supermaven = { path = "crates/supermaven" }
|
||||||
supermaven_api = { path = "crates/supermaven_api" }
|
supermaven_api = { path = "crates/supermaven_api" }
|
||||||
|
codestral = { path = "crates/codestral" }
|
||||||
system_specs = { path = "crates/system_specs" }
|
system_specs = { path = "crates/system_specs" }
|
||||||
tab_switcher = { path = "crates/tab_switcher" }
|
tab_switcher = { path = "crates/tab_switcher" }
|
||||||
task = { path = "crates/task" }
|
task = { path = "crates/task" }
|
||||||
@@ -431,6 +436,7 @@ zed = { path = "crates/zed" }
|
|||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
zed_env_vars = { path = "crates/zed_env_vars" }
|
zed_env_vars = { path = "crates/zed_env_vars" }
|
||||||
zeta = { path = "crates/zeta" }
|
zeta = { path = "crates/zeta" }
|
||||||
|
zeta2 = { path = "crates/zeta2" }
|
||||||
zlog = { path = "crates/zlog" }
|
zlog = { path = "crates/zlog" }
|
||||||
zlog_settings = { path = "crates/zlog_settings" }
|
zlog_settings = { path = "crates/zlog_settings" }
|
||||||
|
|
||||||
@@ -438,9 +444,9 @@ zlog_settings = { path = "crates/zlog_settings" }
|
|||||||
# External crates
|
# External crates
|
||||||
#
|
#
|
||||||
|
|
||||||
agent-client-protocol = { version = "0.4.0", features = ["unstable"] }
|
agent-client-protocol = { version = "0.4.3", features = ["unstable"] }
|
||||||
aho-corasick = "1.1"
|
aho-corasick = "1.1"
|
||||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
alacritty_terminal = "0.25.1-rc1"
|
||||||
any_vec = "0.14"
|
any_vec = "0.14"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||||
@@ -449,6 +455,7 @@ async-compat = "0.2.1"
|
|||||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||||
async-dispatcher = "0.1"
|
async-dispatcher = "0.1"
|
||||||
async-fs = "2.1"
|
async-fs = "2.1"
|
||||||
|
async-lock = "2.1"
|
||||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
async-tar = "0.5.0"
|
async-tar = "0.5.0"
|
||||||
@@ -469,10 +476,9 @@ backtrace = "0.3"
|
|||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "bfa594ea697d4b6326ea29f747525c85ecf933b9" }
|
blade-graphics = { version = "0.7.0" }
|
||||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "bfa594ea697d4b6326ea29f747525c85ecf933b9" }
|
blade-macros = { version = "0.3.0" }
|
||||||
blade-util = { git = "https://github.com/kvark/blade", rev = "bfa594ea697d4b6326ea29f747525c85ecf933b9" }
|
blade-util = { version = "0.3.0" }
|
||||||
blake3 = "1.5.3"
|
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
cargo_metadata = "0.19"
|
cargo_metadata = "0.19"
|
||||||
cargo_toml = "0.21"
|
cargo_toml = "0.21"
|
||||||
@@ -509,6 +515,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"
|
||||||
|
hashbrown = "0.15.3"
|
||||||
heck = "0.5"
|
heck = "0.5"
|
||||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
@@ -524,7 +531,6 @@ indexmap = { version = "2.7.0", features = ["serde"] }
|
|||||||
indoc = "2"
|
indoc = "2"
|
||||||
inventory = "0.3.19"
|
inventory = "0.3.19"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
jj-lib = { git = "https://github.com/jj-vcs/jj", rev = "e18eb8e05efaa153fad5ef46576af145bba1807f" }
|
|
||||||
json_dotpath = "1.1"
|
json_dotpath = "1.1"
|
||||||
jsonschema = "0.30.0"
|
jsonschema = "0.30.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
@@ -545,6 +551,7 @@ nanoid = "0.4"
|
|||||||
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
|
num-traits = "0.2"
|
||||||
objc = "0.2"
|
objc = "0.2"
|
||||||
objc2-foundation = { version = "0.3", default-features = false, features = [
|
objc2-foundation = { version = "0.3", default-features = false, features = [
|
||||||
"NSArray",
|
"NSArray",
|
||||||
@@ -601,7 +608,8 @@ rand = "0.9"
|
|||||||
rayon = "1.8"
|
rayon = "1.8"
|
||||||
ref-cast = "1.0.24"
|
ref-cast = "1.0.24"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c770a32f1998d6e999cef3e59e0013e6c4415", default-features = false, features = [
|
# WARNING: If you change this, you must also publish a new version of zed-reqwest to crates.io
|
||||||
|
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [
|
||||||
"charset",
|
"charset",
|
||||||
"http2",
|
"http2",
|
||||||
"macos-system-configuration",
|
"macos-system-configuration",
|
||||||
@@ -609,17 +617,17 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
|
|||||||
"rustls-tls-native-roots",
|
"rustls-tls-native-roots",
|
||||||
"socks",
|
"socks",
|
||||||
"stream",
|
"stream",
|
||||||
] }
|
], package = "zed-reqwest", version = "0.12.15-zed" }
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.6"
|
||||||
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
||||||
"async-dispatcher-runtime",
|
"async-dispatcher-runtime",
|
||||||
] }
|
] }
|
||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||||
rustc-demangle = "0.1.23"
|
|
||||||
rustc-hash = "2.1.0"
|
rustc-hash = "2.1.0"
|
||||||
rustls = { version = "0.23.26" }
|
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 = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7", default-features = false }
|
# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
|
||||||
|
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
|
||||||
schemars = { version = "1.0", features = ["indexmap2"] }
|
schemars = { version = "1.0", features = ["indexmap2"] }
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
serde = { version = "1.0.221", features = ["derive", "rc"] }
|
serde = { version = "1.0.221", features = ["derive", "rc"] }
|
||||||
@@ -645,9 +653,9 @@ streaming-iterator = "0.1"
|
|||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum = { version = "0.27.0", features = ["derive"] }
|
strum = { version = "0.27.0", features = ["derive"] }
|
||||||
subtle = "2.5.0"
|
subtle = "2.5.0"
|
||||||
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
|
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
sysinfo = "0.31.0"
|
sysinfo = "0.37.0"
|
||||||
take-until = "0.2.0"
|
take-until = "0.2.0"
|
||||||
tempfile = "3.20.0"
|
tempfile = "3.20.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
@@ -663,8 +671,9 @@ tiny_http = "0.8"
|
|||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
|
||||||
tower-http = "0.4.4"
|
tower-http = "0.4.4"
|
||||||
tree-sitter = { version = "0.25.6", features = ["wasm"] }
|
tree-sitter = { version = "0.25.10", features = ["wasm"] }
|
||||||
tree-sitter-bash = "0.25.0"
|
tree-sitter-bash = "0.25.0"
|
||||||
tree-sitter-c = "0.23"
|
tree-sitter-c = "0.23"
|
||||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
|
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
|
||||||
@@ -681,11 +690,11 @@ tree-sitter-html = "0.23"
|
|||||||
tree-sitter-jsdoc = "0.23"
|
tree-sitter-jsdoc = "0.23"
|
||||||
tree-sitter-json = "0.24"
|
tree-sitter-json = "0.24"
|
||||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||||
tree-sitter-python = { git = "https://github.com/zed-industries/tree-sitter-python", rev = "218fcbf3fda3d029225f3dec005cb497d111b35e" }
|
tree-sitter-python = "0.25"
|
||||||
tree-sitter-regex = "0.24"
|
tree-sitter-regex = "0.24"
|
||||||
tree-sitter-ruby = "0.23"
|
tree-sitter-ruby = "0.23"
|
||||||
tree-sitter-rust = "0.24"
|
tree-sitter-rust = "0.24"
|
||||||
tree-sitter-typescript = "0.23"
|
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
|
||||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
unicode-script = "0.5.7"
|
unicode-script = "0.5.7"
|
||||||
@@ -712,6 +721,7 @@ windows-core = "0.61"
|
|||||||
wit-component = "0.221"
|
wit-component = "0.221"
|
||||||
workspace-hack = "0.1.0"
|
workspace-hack = "0.1.0"
|
||||||
yawc = "0.2.5"
|
yawc = "0.2.5"
|
||||||
|
zeroize = "1.8"
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
|
|
||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
@@ -738,6 +748,7 @@ features = [
|
|||||||
"Win32_Networking_WinSock",
|
"Win32_Networking_WinSock",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_Security_Credentials",
|
"Win32_Security_Credentials",
|
||||||
|
"Win32_Security_Cryptography",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
"Win32_System_Com_StructuredStorage",
|
"Win32_System_Com_StructuredStorage",
|
||||||
@@ -806,6 +817,7 @@ image_viewer = { codegen-units = 1 }
|
|||||||
edit_prediction_button = { codegen-units = 1 }
|
edit_prediction_button = { codegen-units = 1 }
|
||||||
install_cli = { codegen-units = 1 }
|
install_cli = { codegen-units = 1 }
|
||||||
journal = { codegen-units = 1 }
|
journal = { codegen-units = 1 }
|
||||||
|
json_schema_store = { codegen-units = 1 }
|
||||||
lmstudio = { codegen-units = 1 }
|
lmstudio = { codegen-units = 1 }
|
||||||
menu = { codegen-units = 1 }
|
menu = { codegen-units = 1 }
|
||||||
notifications = { codegen-units = 1 }
|
notifications = { codegen-units = 1 }
|
||||||
@@ -857,6 +869,7 @@ todo = "deny"
|
|||||||
declare_interior_mutable_const = "deny"
|
declare_interior_mutable_const = "deny"
|
||||||
|
|
||||||
redundant_clone = "deny"
|
redundant_clone = "deny"
|
||||||
|
disallowed_methods = "deny"
|
||||||
|
|
||||||
# We currently do not restrict any style rules
|
# We currently do not restrict any style rules
|
||||||
# as it slows down shipping code to Zed.
|
# as it slows down shipping code to Zed.
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[build]
|
|
||||||
dockerfile = "Dockerfile-cross"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax = docker/dockerfile:1.2
|
# syntax = docker/dockerfile:1.2
|
||||||
|
|
||||||
FROM rust:1.89-bookworm as builder
|
FROM rust:1.90-bookworm as builder
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# syntax=docker/dockerfile:1
|
|
||||||
|
|
||||||
ARG CROSS_BASE_IMAGE
|
|
||||||
FROM ${CROSS_BASE_IMAGE}
|
|
||||||
WORKDIR /app
|
|
||||||
ARG TZ=Etc/UTC \
|
|
||||||
LANG=C.UTF-8 \
|
|
||||||
LC_ALL=C.UTF-8 \
|
|
||||||
DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV CARGO_TERM_COLOR=always
|
|
||||||
|
|
||||||
COPY script/install-mold script/
|
|
||||||
RUN ./script/install-mold "2.34.0"
|
|
||||||
COPY script/remote-server script/
|
|
||||||
RUN ./script/remote-server
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
@@ -1,9 +1,3 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path opacity="0.6" d="M3.5 11V5.5L8.5 8L3.5 11Z" fill="black"/>
|
<path d="M13.2806 4.66818L8.26042 1.76982C8.09921 1.67673 7.9003 1.67673 7.73909 1.76982L2.71918 4.66818C2.58367 4.74642 2.5 4.89112 2.5 5.04785V10.8924C2.5 11.0489 2.58367 11.1938 2.71918 11.2721L7.73934 14.1704C7.90054 14.2635 8.09946 14.2635 8.26066 14.1704L13.2808 11.2721C13.4163 11.1938 13.5 11.0491 13.5 10.8924V5.04785C13.5 4.89136 13.4163 4.74642 13.2808 4.66818H13.2806ZM12.9653 5.28212L8.11901 13.676C8.08626 13.7326 7.99977 13.7095 7.99977 13.6439V8.14771C7.99977 8.03788 7.94107 7.9363 7.84586 7.88115L3.08613 5.13317C3.02957 5.10041 3.05266 5.0139 3.11818 5.0139H12.8106C12.9483 5.0139 13.0343 5.1631 12.9655 5.28236H12.9653V5.28212Z" fill="#C4CAD4"/>
|
||||||
<path opacity="0.4" d="M8.5 14L3.5 11L8.5 8V14Z" fill="black"/>
|
|
||||||
<path opacity="0.6" d="M8.5 5.5H3.5L8.5 2.5L8.5 5.5Z" fill="black"/>
|
|
||||||
<path opacity="0.8" d="M8.5 5.5V2.5L13.5 5.5H8.5Z" fill="black"/>
|
|
||||||
<path opacity="0.2" d="M13.5 11L8.5 14L11 9.5L13.5 11Z" fill="black"/>
|
|
||||||
<path opacity="0.5" d="M13.5 11L11 9.5L13.5 5V11Z" fill="black"/>
|
|
||||||
<path d="M3.5 11V5L8.5 2.11325L13.5 5V11L8.5 13.8868L3.5 11Z" stroke="black"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 769 B |
3
assets/icons/paperclip.svg
Normal file
3
assets/icons/paperclip.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.1645 4.45825L5.20344 9.52074C4.98225 9.74193 4.85798 10.0419 4.85798 10.3548C4.85798 10.6676 4.98225 10.9676 5.20344 11.1888C5.42464 11.41 5.72464 11.5342 6.03746 11.5342C6.35028 11.5342 6.65028 11.41 6.87148 11.1888L11.8326 6.12629C12.2749 5.68397 12.5234 5.08407 12.5234 4.45854C12.5234 3.83302 12.2749 3.23311 11.8326 2.7908C11.3902 2.34849 10.7903 2.1 10.1648 2.1C9.53928 2.1 8.93938 2.34849 8.49707 2.7908L3.55663 7.83265C3.22373 8.16017 2.95897 8.55037 2.77762 8.98072C2.59628 9.41108 2.50193 9.87308 2.50003 10.3401C2.49813 10.8071 2.58871 11.2698 2.76654 11.7017C2.94438 12.1335 3.20595 12.5258 3.53618 12.856C3.8664 13.1863 4.25873 13.4478 4.69055 13.6257C5.12237 13.8035 5.58513 13.8941 6.05213 13.8922C6.51913 13.8903 6.98114 13.7959 7.41149 13.6146C7.84185 13.4332 8.23204 13.1685 8.55957 12.8356L13.5 7.79373" stroke="#C4CAD4" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.6 5v3.6h3.6"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13.4 11A5.4 5.4 0 0 0 8 5.6a5.4 5.4 0 0 0-3.6 1.38L2.6 8.6"/></svg>
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.125 9.25001L3 6.125L6.125 3" stroke="#C4CAD4" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 6.125H9.56251C10.0139 6.125 10.4609 6.21391 10.878 6.38666C11.295 6.55942 11.674 6.81262 11.9932 7.13182C12.3124 7.45102 12.5656 7.82997 12.7383 8.24703C12.9111 8.66408 13 9.11108 13 9.5625C13 10.0139 12.9111 10.4609 12.7383 10.878C12.5656 11.295 12.3124 11.674 11.9932 11.9932C11.674 12.3124 11.295 12.5656 10.878 12.7383C10.4609 12.9111 10.0139 13 9.56251 13H7.375" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 692 B |
@@ -31,6 +31,7 @@
|
|||||||
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||||
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||||
"ctrl-,": "zed::OpenSettings",
|
"ctrl-,": "zed::OpenSettings",
|
||||||
|
"ctrl-alt-,": "zed::OpenSettingsFile",
|
||||||
"ctrl-q": "zed::Quit",
|
"ctrl-q": "zed::Quit",
|
||||||
"f4": "debugger::Start",
|
"f4": "debugger::Start",
|
||||||
"shift-f5": "debugger::Stop",
|
"shift-f5": "debugger::Stop",
|
||||||
@@ -250,7 +251,7 @@
|
|||||||
"alt-enter": "agent::ContinueWithBurnMode",
|
"alt-enter": "agent::ContinueWithBurnMode",
|
||||||
"ctrl-y": "agent::AllowOnce",
|
"ctrl-y": "agent::AllowOnce",
|
||||||
"ctrl-alt-y": "agent::AllowAlways",
|
"ctrl-alt-y": "agent::AllowAlways",
|
||||||
"ctrl-d": "agent::RejectOnce"
|
"ctrl-alt-z": "agent::RejectOnce"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -369,7 +370,8 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"new": "rules_library::NewRule",
|
"new": "rules_library::NewRule",
|
||||||
"ctrl-n": "rules_library::NewRule",
|
"ctrl-n": "rules_library::NewRule",
|
||||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
|
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
|
||||||
|
"ctrl-w": "workspace::CloseWindow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -489,8 +491,8 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-[": "editor::Outdent",
|
"ctrl-[": "editor::Outdent",
|
||||||
"ctrl-]": "editor::Indent",
|
"ctrl-]": "editor::Indent",
|
||||||
"shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
|
"shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
|
||||||
"shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
|
"shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
|
||||||
"ctrl-shift-k": "editor::DeleteLine",
|
"ctrl-shift-k": "editor::DeleteLine",
|
||||||
"alt-up": "editor::MoveLineUp",
|
"alt-up": "editor::MoveLineUp",
|
||||||
"alt-down": "editor::MoveLineDown",
|
"alt-down": "editor::MoveLineDown",
|
||||||
@@ -525,19 +527,19 @@
|
|||||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||||
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
|
"ctrl-k ctrl-1": "editor::FoldAtLevel_1",
|
||||||
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
|
"ctrl-k ctrl-2": "editor::FoldAtLevel_2",
|
||||||
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
|
"ctrl-k ctrl-3": "editor::FoldAtLevel_3",
|
||||||
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
|
"ctrl-k ctrl-4": "editor::FoldAtLevel_4",
|
||||||
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
|
"ctrl-k ctrl-5": "editor::FoldAtLevel_5",
|
||||||
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
|
"ctrl-k ctrl-6": "editor::FoldAtLevel_6",
|
||||||
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
|
"ctrl-k ctrl-7": "editor::FoldAtLevel_7",
|
||||||
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
|
"ctrl-k ctrl-8": "editor::FoldAtLevel_8",
|
||||||
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
|
"ctrl-k ctrl-9": "editor::FoldAtLevel_9",
|
||||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
"ctrl-shift-space": "editor::ShowSignatureHelp",
|
||||||
"ctrl-.": "editor::ToggleCodeActions",
|
"ctrl-.": "editor::ToggleCodeActions",
|
||||||
"ctrl-k r": "editor::RevealInFileManager",
|
"ctrl-k r": "editor::RevealInFileManager",
|
||||||
"ctrl-k p": "editor::CopyPath",
|
"ctrl-k p": "editor::CopyPath",
|
||||||
@@ -619,7 +621,7 @@
|
|||||||
"ctrl-shift-f": "pane::DeploySearch",
|
"ctrl-shift-f": "pane::DeploySearch",
|
||||||
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||||
"ctrl-shift-t": "pane::ReopenClosedItem",
|
"ctrl-shift-t": "pane::ReopenClosedItem",
|
||||||
"ctrl-k ctrl-s": "zed::OpenKeymapEditor",
|
"ctrl-k ctrl-s": "zed::OpenKeymap",
|
||||||
"ctrl-k ctrl-t": "theme_selector::Toggle",
|
"ctrl-k ctrl-t": "theme_selector::Toggle",
|
||||||
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
||||||
"ctrl-t": "project_symbols::Toggle",
|
"ctrl-t": "project_symbols::Toggle",
|
||||||
@@ -797,7 +799,7 @@
|
|||||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||||
"ctrl-f8": "editor::GoToHunk",
|
"ctrl-f8": "editor::GoToHunk",
|
||||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"ctrl-i": "assistant::InlineAssist",
|
||||||
"ctrl-:": "editor::ToggleInlayHints"
|
"ctrl-:": "editor::ToggleInlayHints"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1091,7 +1093,7 @@
|
|||||||
"paste": "terminal::Paste",
|
"paste": "terminal::Paste",
|
||||||
"shift-insert": "terminal::Paste",
|
"shift-insert": "terminal::Paste",
|
||||||
"ctrl-shift-v": "terminal::Paste",
|
"ctrl-shift-v": "terminal::Paste",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"ctrl-i": "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."],
|
"alt-.": ["terminal::SendText", "\u001b."],
|
||||||
@@ -1227,9 +1229,6 @@
|
|||||||
"context": "Onboarding",
|
"context": "Onboarding",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-1": "onboarding::ActivateBasicsPage",
|
|
||||||
"ctrl-2": "onboarding::ActivateEditingPage",
|
|
||||||
"ctrl-3": "onboarding::ActivateAISetupPage",
|
|
||||||
"ctrl-enter": "onboarding::Finish",
|
"ctrl-enter": "onboarding::Finish",
|
||||||
"alt-shift-l": "onboarding::SignIn",
|
"alt-shift-l": "onboarding::SignIn",
|
||||||
"alt-shift-a": "onboarding::OpenAccount"
|
"alt-shift-a": "onboarding::OpenAccount"
|
||||||
@@ -1241,5 +1240,44 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "SettingsWindow",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-w": "workspace::CloseWindow",
|
||||||
|
"escape": "workspace::CloseWindow",
|
||||||
|
"ctrl-m": "settings_editor::Minimize",
|
||||||
|
"ctrl-f": "search::FocusSearch",
|
||||||
|
"left": "settings_editor::ToggleFocusNav",
|
||||||
|
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
|
||||||
|
// todo(settings_ui): cut this down based on the max files and overflow UI
|
||||||
|
"ctrl-1": ["settings_editor::FocusFile", 0],
|
||||||
|
"ctrl-2": ["settings_editor::FocusFile", 1],
|
||||||
|
"ctrl-3": ["settings_editor::FocusFile", 2],
|
||||||
|
"ctrl-4": ["settings_editor::FocusFile", 3],
|
||||||
|
"ctrl-5": ["settings_editor::FocusFile", 4],
|
||||||
|
"ctrl-6": ["settings_editor::FocusFile", 5],
|
||||||
|
"ctrl-7": ["settings_editor::FocusFile", 6],
|
||||||
|
"ctrl-8": ["settings_editor::FocusFile", 7],
|
||||||
|
"ctrl-9": ["settings_editor::FocusFile", 8],
|
||||||
|
"ctrl-0": ["settings_editor::FocusFile", 9],
|
||||||
|
"ctrl-pageup": "settings_editor::FocusPreviousFile",
|
||||||
|
"ctrl-pagedown": "settings_editor::FocusNextFile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "SettingsWindow > NavigationMenu",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"up": "settings_editor::FocusPreviousNavEntry",
|
||||||
|
"down": "settings_editor::FocusNextNavEntry",
|
||||||
|
"right": "settings_editor::ExpandNavEntry",
|
||||||
|
"left": "settings_editor::CollapseNavEntry",
|
||||||
|
"pageup": "settings_editor::FocusPreviousRootNavEntry",
|
||||||
|
"pagedown": "settings_editor::FocusNextRootNavEntry",
|
||||||
|
"home": "settings_editor::FocusFirstNavEntry",
|
||||||
|
"end": "settings_editor::FocusLastNavEntry"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||||
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||||
"cmd-,": "zed::OpenSettings",
|
"cmd-,": "zed::OpenSettings",
|
||||||
|
"cmd-alt-,": "zed::OpenSettingsFile",
|
||||||
"cmd-q": "zed::Quit",
|
"cmd-q": "zed::Quit",
|
||||||
"cmd-h": "zed::Hide",
|
"cmd-h": "zed::Hide",
|
||||||
"alt-cmd-h": "zed::HideOthers",
|
"alt-cmd-h": "zed::HideOthers",
|
||||||
@@ -141,7 +142,7 @@
|
|||||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||||
"cmd-alt-g b": "git::Blame",
|
"cmd-alt-g b": "git::Blame",
|
||||||
"cmd-alt-g m": "git::OpenModifiedFiles",
|
"cmd-alt-g m": "git::OpenModifiedFiles",
|
||||||
"cmd-i": "editor::ShowSignatureHelp",
|
"cmd-shift-space": "editor::ShowSignatureHelp",
|
||||||
"f9": "editor::ToggleBreakpoint",
|
"f9": "editor::ToggleBreakpoint",
|
||||||
"shift-f9": "editor::EditLogBreakpoint",
|
"shift-f9": "editor::EditLogBreakpoint",
|
||||||
"ctrl-f12": "editor::GoToDeclaration",
|
"ctrl-f12": "editor::GoToDeclaration",
|
||||||
@@ -289,7 +290,7 @@
|
|||||||
"alt-enter": "agent::ContinueWithBurnMode",
|
"alt-enter": "agent::ContinueWithBurnMode",
|
||||||
"cmd-y": "agent::AllowOnce",
|
"cmd-y": "agent::AllowOnce",
|
||||||
"cmd-alt-y": "agent::AllowAlways",
|
"cmd-alt-y": "agent::AllowAlways",
|
||||||
"cmd-d": "agent::RejectOnce"
|
"cmd-alt-z": "agent::RejectOnce"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -538,10 +539,10 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-[": "editor::Outdent",
|
"cmd-[": "editor::Outdent",
|
||||||
"cmd-]": "editor::Indent",
|
"cmd-]": "editor::Indent",
|
||||||
"cmd-ctrl-p": "editor::AddSelectionAbove", // Insert cursor above
|
"cmd-ctrl-p": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }], // Insert cursor above
|
||||||
"cmd-alt-up": "editor::AddSelectionAbove",
|
"cmd-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
|
||||||
"cmd-ctrl-n": "editor::AddSelectionBelow", // Insert cursor below
|
"cmd-ctrl-n": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }], // Insert cursor below
|
||||||
"cmd-alt-down": "editor::AddSelectionBelow",
|
"cmd-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
|
||||||
"cmd-shift-k": "editor::DeleteLine",
|
"cmd-shift-k": "editor::DeleteLine",
|
||||||
"alt-up": "editor::MoveLineUp",
|
"alt-up": "editor::MoveLineUp",
|
||||||
"alt-down": "editor::MoveLineDown",
|
"alt-down": "editor::MoveLineDown",
|
||||||
@@ -550,6 +551,8 @@
|
|||||||
"cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
"cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
||||||
"cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
"cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
||||||
"cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up
|
"cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up
|
||||||
|
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
|
||||||
|
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
|
||||||
"cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down
|
"cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down
|
||||||
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
"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
|
||||||
@@ -579,15 +582,15 @@
|
|||||||
"cmd-k cmd-l": "editor::ToggleFold",
|
"cmd-k cmd-l": "editor::ToggleFold",
|
||||||
"cmd-k cmd-[": "editor::FoldRecursive",
|
"cmd-k cmd-[": "editor::FoldRecursive",
|
||||||
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
||||||
"cmd-k cmd-1": ["editor::FoldAtLevel", 1],
|
"cmd-k cmd-1": "editor::FoldAtLevel_1",
|
||||||
"cmd-k cmd-2": ["editor::FoldAtLevel", 2],
|
"cmd-k cmd-2": "editor::FoldAtLevel_2",
|
||||||
"cmd-k cmd-3": ["editor::FoldAtLevel", 3],
|
"cmd-k cmd-3": "editor::FoldAtLevel_3",
|
||||||
"cmd-k cmd-4": ["editor::FoldAtLevel", 4],
|
"cmd-k cmd-4": "editor::FoldAtLevel_4",
|
||||||
"cmd-k cmd-5": ["editor::FoldAtLevel", 5],
|
"cmd-k cmd-5": "editor::FoldAtLevel_5",
|
||||||
"cmd-k cmd-6": ["editor::FoldAtLevel", 6],
|
"cmd-k cmd-6": "editor::FoldAtLevel_6",
|
||||||
"cmd-k cmd-7": ["editor::FoldAtLevel", 7],
|
"cmd-k cmd-7": "editor::FoldAtLevel_7",
|
||||||
"cmd-k cmd-8": ["editor::FoldAtLevel", 8],
|
"cmd-k cmd-8": "editor::FoldAtLevel_8",
|
||||||
"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` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
|
// Using `ctrl-space` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
|
||||||
@@ -687,7 +690,7 @@
|
|||||||
"cmd-shift-f": "pane::DeploySearch",
|
"cmd-shift-f": "pane::DeploySearch",
|
||||||
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||||
"cmd-shift-t": "pane::ReopenClosedItem",
|
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||||
"cmd-k cmd-s": "zed::OpenKeymapEditor",
|
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||||
"ctrl-alt-cmd-p": "settings_profile_selector::Toggle",
|
"ctrl-alt-cmd-p": "settings_profile_selector::Toggle",
|
||||||
"cmd-t": "project_symbols::Toggle",
|
"cmd-t": "project_symbols::Toggle",
|
||||||
@@ -861,7 +864,7 @@
|
|||||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||||
"cmd-f8": "editor::GoToHunk",
|
"cmd-f8": "editor::GoToHunk",
|
||||||
"cmd-shift-f8": "editor::GoToPreviousHunk",
|
"cmd-shift-f8": "editor::GoToPreviousHunk",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"cmd-i": "assistant::InlineAssist",
|
||||||
"ctrl-:": "editor::ToggleInlayHints"
|
"ctrl-:": "editor::ToggleInlayHints"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1164,7 +1167,7 @@
|
|||||||
"cmd-a": "editor::SelectAll",
|
"cmd-a": "editor::SelectAll",
|
||||||
"cmd-k": "terminal::Clear",
|
"cmd-k": "terminal::Clear",
|
||||||
"cmd-n": "workspace::NewTerminal",
|
"cmd-n": "workspace::NewTerminal",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"cmd-i": "assistant::InlineAssist",
|
||||||
"ctrl-_": null, // emacs undo
|
"ctrl-_": null, // emacs undo
|
||||||
// Some nice conveniences
|
// Some nice conveniences
|
||||||
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
|
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
|
||||||
@@ -1331,10 +1334,7 @@
|
|||||||
"context": "Onboarding",
|
"context": "Onboarding",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-1": "onboarding::ActivateBasicsPage",
|
"cmd-enter": "onboarding::Finish",
|
||||||
"cmd-2": "onboarding::ActivateEditingPage",
|
|
||||||
"cmd-3": "onboarding::ActivateAISetupPage",
|
|
||||||
"cmd-escape": "onboarding::Finish",
|
|
||||||
"alt-tab": "onboarding::SignIn",
|
"alt-tab": "onboarding::SignIn",
|
||||||
"alt-shift-a": "onboarding::OpenAccount"
|
"alt-shift-a": "onboarding::OpenAccount"
|
||||||
}
|
}
|
||||||
@@ -1345,5 +1345,44 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "SettingsWindow",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-w": "workspace::CloseWindow",
|
||||||
|
"escape": "workspace::CloseWindow",
|
||||||
|
"cmd-m": "settings_editor::Minimize",
|
||||||
|
"cmd-f": "search::FocusSearch",
|
||||||
|
"left": "settings_editor::ToggleFocusNav",
|
||||||
|
"cmd-shift-e": "settings_editor::ToggleFocusNav",
|
||||||
|
// todo(settings_ui): cut this down based on the max files and overflow UI
|
||||||
|
"ctrl-1": ["settings_editor::FocusFile", 0],
|
||||||
|
"ctrl-2": ["settings_editor::FocusFile", 1],
|
||||||
|
"ctrl-3": ["settings_editor::FocusFile", 2],
|
||||||
|
"ctrl-4": ["settings_editor::FocusFile", 3],
|
||||||
|
"ctrl-5": ["settings_editor::FocusFile", 4],
|
||||||
|
"ctrl-6": ["settings_editor::FocusFile", 5],
|
||||||
|
"ctrl-7": ["settings_editor::FocusFile", 6],
|
||||||
|
"ctrl-8": ["settings_editor::FocusFile", 7],
|
||||||
|
"ctrl-9": ["settings_editor::FocusFile", 8],
|
||||||
|
"ctrl-0": ["settings_editor::FocusFile", 9],
|
||||||
|
"cmd-{": "settings_editor::FocusPreviousFile",
|
||||||
|
"cmd-}": "settings_editor::FocusNextFile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "SettingsWindow > NavigationMenu",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"up": "settings_editor::FocusPreviousNavEntry",
|
||||||
|
"down": "settings_editor::FocusNextNavEntry",
|
||||||
|
"right": "settings_editor::ExpandNavEntry",
|
||||||
|
"left": "settings_editor::CollapseNavEntry",
|
||||||
|
"pageup": "settings_editor::FocusPreviousRootNavEntry",
|
||||||
|
"pagedown": "settings_editor::FocusNextRootNavEntry",
|
||||||
|
"home": "settings_editor::FocusFirstNavEntry",
|
||||||
|
"end": "settings_editor::FocusLastNavEntry"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
"up": "menu::SelectPrevious",
|
"up": "menu::SelectPrevious",
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
"ctrl-enter": "menu::SecondaryConfirm",
|
"ctrl-enter": "menu::SecondaryConfirm",
|
||||||
"ctrl-escape": "menu::Cancel",
|
|
||||||
"ctrl-c": "menu::Cancel",
|
"ctrl-c": "menu::Cancel",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"shift-alt-enter": "menu::Restart",
|
"shift-alt-enter": "menu::Restart",
|
||||||
@@ -31,6 +30,7 @@
|
|||||||
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||||
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||||
"ctrl-,": "zed::OpenSettings",
|
"ctrl-,": "zed::OpenSettings",
|
||||||
|
"ctrl-alt-,": "zed::OpenSettingsFile",
|
||||||
"ctrl-q": "zed::Quit",
|
"ctrl-q": "zed::Quit",
|
||||||
"f4": "debugger::Start",
|
"f4": "debugger::Start",
|
||||||
"shift-f5": "debugger::Stop",
|
"shift-f5": "debugger::Stop",
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||||
"ctrl-f": "buffer_search::Deploy",
|
"ctrl-f": "buffer_search::Deploy",
|
||||||
"ctrl-h": "buffer_search::DeployReplace",
|
"ctrl-h": "buffer_search::DeployReplace",
|
||||||
"ctrl-shift-.": "assistant::QuoteSelection",
|
"ctrl-shift-.": "agent::QuoteSelection",
|
||||||
"ctrl-shift-,": "assistant::InsertIntoEditor",
|
"ctrl-shift-,": "assistant::InsertIntoEditor",
|
||||||
"shift-alt-e": "editor::SelectEnclosingSymbol",
|
"shift-alt-e": "editor::SelectEnclosingSymbol",
|
||||||
"ctrl-shift-backspace": "editor::GoToPreviousChange",
|
"ctrl-shift-backspace": "editor::GoToPreviousChange",
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||||
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
|
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
|
||||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||||
"ctrl-shift-.": "assistant::QuoteSelection",
|
"ctrl-shift-.": "agent::QuoteSelection",
|
||||||
"shift-alt-e": "agent::RemoveAllContext",
|
"shift-alt-e": "agent::RemoveAllContext",
|
||||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||||
"ctrl-shift-enter": "agent::ContinueThread",
|
"ctrl-shift-enter": "agent::ContinueThread",
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"alt-enter": "agent::ContinueWithBurnMode",
|
"alt-enter": "agent::ContinueWithBurnMode",
|
||||||
"ctrl-y": "agent::AllowOnce",
|
"ctrl-y": "agent::AllowOnce",
|
||||||
"ctrl-alt-y": "agent::AllowAlways",
|
"ctrl-alt-y": "agent::AllowAlways",
|
||||||
"ctrl-d": "agent::RejectOnce"
|
"ctrl-alt-z": "agent::RejectOnce"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -346,7 +346,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "AcpThread > Editor",
|
"context": "AcpThread > Editor && !use_modifier_to_send",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "agent::Chat",
|
"enter": "agent::Chat",
|
||||||
@@ -356,6 +356,17 @@
|
|||||||
"shift-tab": "agent::CycleModeSelector"
|
"shift-tab": "agent::CycleModeSelector"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "AcpThread > Editor && use_modifier_to_send",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-enter": "agent::Chat",
|
||||||
|
"ctrl-shift-r": "agent::OpenAgentDiff",
|
||||||
|
"ctrl-shift-y": "agent::KeepAll",
|
||||||
|
"ctrl-shift-n": "agent::RejectAll",
|
||||||
|
"shift-tab": "agent::CycleModeSelector"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ThreadHistory",
|
"context": "ThreadHistory",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -368,7 +379,8 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-n": "rules_library::NewRule",
|
"ctrl-n": "rules_library::NewRule",
|
||||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
|
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
|
||||||
|
"ctrl-w": "workspace::CloseWindow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -465,8 +477,8 @@
|
|||||||
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
|
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
|
||||||
"back": "pane::GoBack",
|
"back": "pane::GoBack",
|
||||||
"alt--": "pane::GoBack",
|
"alt--": "pane::GoBack",
|
||||||
"alt-=": "pane::GoForward",
|
|
||||||
"forward": "pane::GoForward",
|
"forward": "pane::GoForward",
|
||||||
|
"alt-=": "pane::GoForward",
|
||||||
"f3": "search::SelectNextMatch",
|
"f3": "search::SelectNextMatch",
|
||||||
"shift-f3": "search::SelectPreviousMatch",
|
"shift-f3": "search::SelectPreviousMatch",
|
||||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||||
@@ -488,8 +500,8 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-[": "editor::Outdent",
|
"ctrl-[": "editor::Outdent",
|
||||||
"ctrl-]": "editor::Indent",
|
"ctrl-]": "editor::Indent",
|
||||||
"ctrl-shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
|
"ctrl-shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
|
||||||
"ctrl-shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
|
"ctrl-shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
|
||||||
"ctrl-shift-k": "editor::DeleteLine",
|
"ctrl-shift-k": "editor::DeleteLine",
|
||||||
"alt-up": "editor::MoveLineUp",
|
"alt-up": "editor::MoveLineUp",
|
||||||
"alt-down": "editor::MoveLineDown",
|
"alt-down": "editor::MoveLineDown",
|
||||||
@@ -497,8 +509,6 @@
|
|||||||
"shift-alt-down": "editor::DuplicateLineDown",
|
"shift-alt-down": "editor::DuplicateLineDown",
|
||||||
"shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
"shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
||||||
"shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
"shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
||||||
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
|
|
||||||
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
|
|
||||||
"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 }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||||
@@ -526,19 +536,19 @@
|
|||||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||||
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
|
"ctrl-k ctrl-1": "editor::FoldAtLevel_1",
|
||||||
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
|
"ctrl-k ctrl-2": "editor::FoldAtLevel_2",
|
||||||
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
|
"ctrl-k ctrl-3": "editor::FoldAtLevel_3",
|
||||||
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
|
"ctrl-k ctrl-4": "editor::FoldAtLevel_4",
|
||||||
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
|
"ctrl-k ctrl-5": "editor::FoldAtLevel_5",
|
||||||
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
|
"ctrl-k ctrl-6": "editor::FoldAtLevel_6",
|
||||||
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
|
"ctrl-k ctrl-7": "editor::FoldAtLevel_7",
|
||||||
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
|
"ctrl-k ctrl-8": "editor::FoldAtLevel_8",
|
||||||
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
|
"ctrl-k ctrl-9": "editor::FoldAtLevel_9",
|
||||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
"ctrl-shift-space": "editor::ShowSignatureHelp",
|
||||||
"ctrl-.": "editor::ToggleCodeActions",
|
"ctrl-.": "editor::ToggleCodeActions",
|
||||||
"ctrl-k r": "editor::RevealInFileManager",
|
"ctrl-k r": "editor::RevealInFileManager",
|
||||||
"ctrl-k p": "editor::CopyPath",
|
"ctrl-k p": "editor::CopyPath",
|
||||||
@@ -610,12 +620,10 @@
|
|||||||
"shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
"shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||||
"shift-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
|
"shift-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
|
||||||
"shift-alt-0": "workspace::ResetOpenDocksSize",
|
"shift-alt-0": "workspace::ResetOpenDocksSize",
|
||||||
"ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }],
|
|
||||||
"ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }],
|
|
||||||
"ctrl-shift-f": "pane::DeploySearch",
|
"ctrl-shift-f": "pane::DeploySearch",
|
||||||
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||||
"ctrl-shift-t": "pane::ReopenClosedItem",
|
"ctrl-shift-t": "pane::ReopenClosedItem",
|
||||||
"ctrl-k ctrl-s": "zed::OpenKeymapEditor",
|
"ctrl-k ctrl-s": "zed::OpenKeymap",
|
||||||
"ctrl-k ctrl-t": "theme_selector::Toggle",
|
"ctrl-k ctrl-t": "theme_selector::Toggle",
|
||||||
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
||||||
"ctrl-t": "project_symbols::Toggle",
|
"ctrl-t": "project_symbols::Toggle",
|
||||||
@@ -804,7 +812,7 @@
|
|||||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||||
"ctrl-f8": "editor::GoToHunk",
|
"ctrl-f8": "editor::GoToHunk",
|
||||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"ctrl-i": "assistant::InlineAssist",
|
||||||
"ctrl-shift-;": "editor::ToggleInlayHints"
|
"ctrl-shift-;": "editor::ToggleInlayHints"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1110,11 +1118,12 @@
|
|||||||
"ctrl-shift-c": "terminal::Copy",
|
"ctrl-shift-c": "terminal::Copy",
|
||||||
"shift-insert": "terminal::Paste",
|
"shift-insert": "terminal::Paste",
|
||||||
"ctrl-shift-v": "terminal::Paste",
|
"ctrl-shift-v": "terminal::Paste",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"ctrl-i": "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."],
|
"alt-.": ["terminal::SendText", "\u001b."],
|
||||||
"ctrl-delete": ["terminal::SendText", "\u001bd"],
|
"ctrl-delete": ["terminal::SendText", "\u001bd"],
|
||||||
|
"ctrl-n": "workspace::NewTerminal",
|
||||||
// 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"],
|
||||||
@@ -1248,12 +1257,48 @@
|
|||||||
"context": "Onboarding",
|
"context": "Onboarding",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-1": "onboarding::ActivateBasicsPage",
|
"ctrl-enter": "onboarding::Finish",
|
||||||
"ctrl-2": "onboarding::ActivateEditingPage",
|
"alt-shift-l": "onboarding::SignIn",
|
||||||
"ctrl-3": "onboarding::ActivateAISetupPage",
|
|
||||||
"ctrl-escape": "onboarding::Finish",
|
|
||||||
"alt-tab": "onboarding::SignIn",
|
|
||||||
"shift-alt-a": "onboarding::OpenAccount"
|
"shift-alt-a": "onboarding::OpenAccount"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "SettingsWindow",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-w": "workspace::CloseWindow",
|
||||||
|
"escape": "workspace::CloseWindow",
|
||||||
|
"ctrl-m": "settings_editor::Minimize",
|
||||||
|
"ctrl-f": "search::FocusSearch",
|
||||||
|
"left": "settings_editor::ToggleFocusNav",
|
||||||
|
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
|
||||||
|
// todo(settings_ui): cut this down based on the max files and overflow UI
|
||||||
|
"ctrl-1": ["settings_editor::FocusFile", 0],
|
||||||
|
"ctrl-2": ["settings_editor::FocusFile", 1],
|
||||||
|
"ctrl-3": ["settings_editor::FocusFile", 2],
|
||||||
|
"ctrl-4": ["settings_editor::FocusFile", 3],
|
||||||
|
"ctrl-5": ["settings_editor::FocusFile", 4],
|
||||||
|
"ctrl-6": ["settings_editor::FocusFile", 5],
|
||||||
|
"ctrl-7": ["settings_editor::FocusFile", 6],
|
||||||
|
"ctrl-8": ["settings_editor::FocusFile", 7],
|
||||||
|
"ctrl-9": ["settings_editor::FocusFile", 8],
|
||||||
|
"ctrl-0": ["settings_editor::FocusFile", 9],
|
||||||
|
"ctrl-pageup": "settings_editor::FocusPreviousFile",
|
||||||
|
"ctrl-pagedown": "settings_editor::FocusNextFile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "SettingsWindow > NavigationMenu",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"up": "settings_editor::FocusPreviousNavEntry",
|
||||||
|
"down": "settings_editor::FocusNextNavEntry",
|
||||||
|
"right": "settings_editor::ExpandNavEntry",
|
||||||
|
"left": "settings_editor::CollapseNavEntry",
|
||||||
|
"pageup": "settings_editor::FocusPreviousRootNavEntry",
|
||||||
|
"pagedown": "settings_editor::FocusNextRootNavEntry",
|
||||||
|
"home": "settings_editor::FocusFirstNavEntry",
|
||||||
|
"end": "settings_editor::FocusLastNavEntry"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
"ctrl-<": "editor::ScrollCursorCenter", // editor:scroll-to-cursor
|
"ctrl-<": "editor::ScrollCursorCenter", // editor:scroll-to-cursor
|
||||||
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
||||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||||
"alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
|
"alt-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // editor:add-selection-below
|
||||||
"alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
|
"alt-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // editor:add-selection-above
|
||||||
"ctrl-j": "editor::JoinLines", // editor:join-lines
|
"ctrl-j": "editor::JoinLines", // editor:join-lines
|
||||||
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
|
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
|
||||||
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
|
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-s": "zed::OpenSettings",
|
"ctrl-alt-s": "zed::OpenSettingsFile",
|
||||||
"ctrl-{": "pane::ActivatePreviousItem",
|
"ctrl-{": "pane::ActivatePreviousItem",
|
||||||
"ctrl-}": "pane::ActivateNextItem",
|
"ctrl-}": "pane::ActivateNextItem",
|
||||||
"shift-escape": null, // Unmap workspace::zoom
|
"shift-escape": null, // Unmap workspace::zoom
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-up": "editor::AddSelectionAbove",
|
"ctrl-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
|
||||||
"ctrl-alt-down": "editor::AddSelectionBelow",
|
"ctrl-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
|
||||||
"ctrl-shift-up": "editor::MoveLineUp",
|
"ctrl-shift-up": "editor::MoveLineUp",
|
||||||
"ctrl-shift-down": "editor::MoveLineDown",
|
"ctrl-shift-down": "editor::MoveLineDown",
|
||||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
"cmd-<": "editor::ScrollCursorCenter",
|
"cmd-<": "editor::ScrollCursorCenter",
|
||||||
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
||||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
"ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
|
||||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
"ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
|
||||||
"alt-enter": "editor::Newline",
|
"alt-enter": "editor::Newline",
|
||||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||||
"ctrl-cmd-up": "editor::MoveLineUp",
|
"ctrl-cmd-up": "editor::MoveLineUp",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// from the command palette.
|
// from the command palette.
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"context": "!GitPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-g": "menu::Cancel"
|
"ctrl-g": "menu::Cancel"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
"ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
|
||||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
"ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
|
||||||
"cmd-ctrl-up": "editor::MoveLineUp",
|
"cmd-ctrl-up": "editor::MoveLineUp",
|
||||||
"cmd-ctrl-down": "editor::MoveLineDown",
|
"cmd-ctrl-down": "editor::MoveLineDown",
|
||||||
"cmd-shift-space": "editor::SelectAll",
|
"cmd-shift-space": "editor::SelectAll",
|
||||||
|
|||||||
@@ -95,8 +95,6 @@
|
|||||||
"g g": "vim::StartOfDocument",
|
"g g": "vim::StartOfDocument",
|
||||||
"g h": "editor::Hover",
|
"g h": "editor::Hover",
|
||||||
"g B": "editor::BlameHover",
|
"g B": "editor::BlameHover",
|
||||||
"g t": "pane::ActivateNextItem",
|
|
||||||
"g shift-t": "pane::ActivatePreviousItem",
|
|
||||||
"g d": "editor::GoToDefinition",
|
"g d": "editor::GoToDefinition",
|
||||||
"g shift-d": "editor::GoToDeclaration",
|
"g shift-d": "editor::GoToDeclaration",
|
||||||
"g y": "editor::GoToTypeDefinition",
|
"g y": "editor::GoToTypeDefinition",
|
||||||
@@ -240,6 +238,7 @@
|
|||||||
"delete": "vim::DeleteRight",
|
"delete": "vim::DeleteRight",
|
||||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||||
"y": "vim::PushYank",
|
"y": "vim::PushYank",
|
||||||
|
"shift-y": "vim::YankLine",
|
||||||
"x": "vim::DeleteRight",
|
"x": "vim::DeleteRight",
|
||||||
"shift-x": "vim::DeleteLeft",
|
"shift-x": "vim::DeleteLeft",
|
||||||
"ctrl-a": "vim::Increment",
|
"ctrl-a": "vim::Increment",
|
||||||
@@ -426,6 +425,7 @@
|
|||||||
";": "vim::HelixCollapseSelection",
|
";": "vim::HelixCollapseSelection",
|
||||||
":": "command_palette::Toggle",
|
":": "command_palette::Toggle",
|
||||||
"m": "vim::PushHelixMatch",
|
"m": "vim::PushHelixMatch",
|
||||||
|
"s": "vim::HelixSelectRegex",
|
||||||
"]": ["vim::PushHelixNext", { "around": true }],
|
"]": ["vim::PushHelixNext", { "around": true }],
|
||||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||||
"left": "vim::WrappingLeft",
|
"left": "vim::WrappingLeft",
|
||||||
@@ -433,6 +433,8 @@
|
|||||||
"h": "vim::WrappingLeft",
|
"h": "vim::WrappingLeft",
|
||||||
"l": "vim::WrappingRight",
|
"l": "vim::WrappingRight",
|
||||||
"y": "vim::HelixYank",
|
"y": "vim::HelixYank",
|
||||||
|
"p": "vim::HelixPaste",
|
||||||
|
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||||
"alt-;": "vim::OtherEnd",
|
"alt-;": "vim::OtherEnd",
|
||||||
"ctrl-r": "vim::Redo",
|
"ctrl-r": "vim::Redo",
|
||||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||||
@@ -496,8 +498,8 @@
|
|||||||
"ctrl-c": "editor::ToggleComments",
|
"ctrl-c": "editor::ToggleComments",
|
||||||
"d": "vim::HelixDelete",
|
"d": "vim::HelixDelete",
|
||||||
"c": "vim::Substitute",
|
"c": "vim::Substitute",
|
||||||
"shift-c": "editor::AddSelectionBelow",
|
"shift-c": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
|
||||||
"alt-shift-c": "editor::AddSelectionAbove"
|
"alt-shift-c": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -576,18 +578,18 @@
|
|||||||
// "q": "vim::AnyQuotes",
|
// "q": "vim::AnyQuotes",
|
||||||
"q": "vim::MiniQuotes",
|
"q": "vim::MiniQuotes",
|
||||||
"|": "vim::VerticalBars",
|
"|": "vim::VerticalBars",
|
||||||
"(": "vim::Parentheses",
|
"(": ["vim::Parentheses", { "opening": true }],
|
||||||
")": "vim::Parentheses",
|
")": "vim::Parentheses",
|
||||||
"b": "vim::Parentheses",
|
"b": "vim::Parentheses",
|
||||||
// "b": "vim::AnyBrackets",
|
// "b": "vim::AnyBrackets",
|
||||||
// "b": "vim::MiniBrackets",
|
// "b": "vim::MiniBrackets",
|
||||||
"[": "vim::SquareBrackets",
|
"[": ["vim::SquareBrackets", { "opening": true }],
|
||||||
"]": "vim::SquareBrackets",
|
"]": "vim::SquareBrackets",
|
||||||
"r": "vim::SquareBrackets",
|
"r": "vim::SquareBrackets",
|
||||||
"{": "vim::CurlyBrackets",
|
"{": ["vim::CurlyBrackets", { "opening": true }],
|
||||||
"}": "vim::CurlyBrackets",
|
"}": "vim::CurlyBrackets",
|
||||||
"shift-b": "vim::CurlyBrackets",
|
"shift-b": "vim::CurlyBrackets",
|
||||||
"<": "vim::AngleBrackets",
|
"<": ["vim::AngleBrackets", { "opening": true }],
|
||||||
">": "vim::AngleBrackets",
|
">": "vim::AngleBrackets",
|
||||||
"a": "vim::Argument",
|
"a": "vim::Argument",
|
||||||
"i": "vim::IndentObj",
|
"i": "vim::IndentObj",
|
||||||
@@ -807,7 +809,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "VimControl || !Editor && !Terminal",
|
"context": "VimControl && !menu || !Editor && !Terminal",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// window related commands (ctrl-w X)
|
// window related commands (ctrl-w X)
|
||||||
"ctrl-w": null,
|
"ctrl-w": null,
|
||||||
@@ -861,7 +863,9 @@
|
|||||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||||
"ctrl-w n": "workspace::NewFileSplitHorizontal"
|
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||||
|
"g t": "vim::GoToTab",
|
||||||
|
"g shift-t": "vim::GoToPreviousTab"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -880,10 +884,12 @@
|
|||||||
"/": "project_panel::NewSearchInDirectory",
|
"/": "project_panel::NewSearchInDirectory",
|
||||||
"d": "project_panel::NewDirectory",
|
"d": "project_panel::NewDirectory",
|
||||||
"enter": "project_panel::OpenPermanent",
|
"enter": "project_panel::OpenPermanent",
|
||||||
"escape": "project_panel::ToggleFocus",
|
"escape": "vim::ToggleProjectPanelFocus",
|
||||||
"h": "project_panel::CollapseSelectedEntry",
|
"h": "project_panel::CollapseSelectedEntry",
|
||||||
"j": "menu::SelectNext",
|
"j": "vim::MenuSelectNext",
|
||||||
"k": "menu::SelectPrevious",
|
"k": "vim::MenuSelectPrevious",
|
||||||
|
"down": "vim::MenuSelectNext",
|
||||||
|
"up": "vim::MenuSelectPrevious",
|
||||||
"l": "project_panel::ExpandSelectedEntry",
|
"l": "project_panel::ExpandSelectedEntry",
|
||||||
"shift-d": "project_panel::Delete",
|
"shift-d": "project_panel::Delete",
|
||||||
"shift-r": "project_panel::Rename",
|
"shift-r": "project_panel::Rename",
|
||||||
@@ -902,7 +908,22 @@
|
|||||||
"{": "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-u": "project_panel::ScrollUp",
|
||||||
|
"ctrl-d": "project_panel::ScrollDown",
|
||||||
|
"z t": "project_panel::ScrollCursorTop",
|
||||||
|
"z z": "project_panel::ScrollCursorCenter",
|
||||||
|
"z b": "project_panel::ScrollCursorBottom",
|
||||||
|
"0": ["vim::Number", 0],
|
||||||
|
"1": ["vim::Number", 1],
|
||||||
|
"2": ["vim::Number", 2],
|
||||||
|
"3": ["vim::Number", 3],
|
||||||
|
"4": ["vim::Number", 4],
|
||||||
|
"5": ["vim::Number", 5],
|
||||||
|
"6": ["vim::Number", 6],
|
||||||
|
"7": ["vim::Number", 7],
|
||||||
|
"8": ["vim::Number", 8],
|
||||||
|
"9": ["vim::Number", 9]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ Generate {{content_type}} based on the following prompt:
|
|||||||
|
|
||||||
Match the indentation in the original file in the inserted {{content_type}}, don't include any indentation on blank lines.
|
Match the indentation in the original file in the inserted {{content_type}}, don't include any indentation on blank lines.
|
||||||
|
|
||||||
Immediately start with the following format with no remarks:
|
Return ONLY the {{content_type}} to insert. Do NOT include any XML tags like <document>, <insert_here>, or any surrounding markup from the input.
|
||||||
|
|
||||||
|
Respond with a code block containing the {{content_type}} to insert. Replace \{{INSERTED_CODE}} with your actual {{content_type}}:
|
||||||
|
|
||||||
```
|
```
|
||||||
\{{INSERTED_CODE}}
|
\{{INSERTED_CODE}}
|
||||||
@@ -66,7 +68,9 @@ Only make changes that are necessary to fulfill the prompt, leave everything els
|
|||||||
|
|
||||||
Start at the indentation level in the original file in the rewritten {{content_type}}. Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.
|
Start at the indentation level in the original file in the rewritten {{content_type}}. Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.
|
||||||
|
|
||||||
Immediately start with the following format with no remarks:
|
Return ONLY the rewritten {{content_type}}. Do NOT include any XML tags like <document>, <rewrite_this>, or any surrounding markup from the input.
|
||||||
|
|
||||||
|
Respond with a code block containing the rewritten {{content_type}}. Replace \{{REWRITTEN_CODE}} with your actual rewritten {{content_type}}:
|
||||||
|
|
||||||
```
|
```
|
||||||
\{{REWRITTEN_CODE}}
|
\{{REWRITTEN_CODE}}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"project_name": null,
|
"$schema": "zed://schemas/settings",
|
||||||
|
/// The displayed name of this project. If not set or empty, the root directory name
|
||||||
|
/// will be displayed.
|
||||||
|
"project_name": "",
|
||||||
// The name of the Zed theme to use for the UI.
|
// The name of the Zed theme to use for the UI.
|
||||||
//
|
//
|
||||||
// `mode` is one of:
|
// `mode` is one of:
|
||||||
@@ -72,8 +75,10 @@
|
|||||||
"ui_font_weight": 400,
|
"ui_font_weight": 400,
|
||||||
// The default font size for text in the UI
|
// The default font size for text in the UI
|
||||||
"ui_font_size": 16,
|
"ui_font_size": 16,
|
||||||
// The default font size for text in the agent panel. Falls back to the UI font size if unset.
|
// The default font size for agent responses in the agent panel. Falls back to the UI font size if unset.
|
||||||
"agent_font_size": null,
|
"agent_ui_font_size": null,
|
||||||
|
// The default font size for user messages in the agent panel.
|
||||||
|
"agent_buffer_font_size": 12,
|
||||||
// How much to fade out unused code.
|
// How much to fade out unused code.
|
||||||
"unnecessary_code_fade": 0.3,
|
"unnecessary_code_fade": 0.3,
|
||||||
// Active pane styling settings.
|
// Active pane styling settings.
|
||||||
@@ -115,6 +120,7 @@
|
|||||||
// Whether to enable vim modes and key bindings.
|
// Whether to enable vim modes and key bindings.
|
||||||
"vim_mode": false,
|
"vim_mode": false,
|
||||||
// Whether to enable helix mode and key bindings.
|
// Whether to enable helix mode and key bindings.
|
||||||
|
// Enabling this mode will automatically enable vim mode.
|
||||||
"helix_mode": false,
|
"helix_mode": false,
|
||||||
// Whether to show the informational hover box when moving the mouse
|
// Whether to show the informational hover box when moving the mouse
|
||||||
// over symbols in the editor.
|
// over symbols in the editor.
|
||||||
@@ -391,8 +397,6 @@
|
|||||||
"use_system_window_tabs": false,
|
"use_system_window_tabs": false,
|
||||||
// Titlebar related settings
|
// Titlebar related settings
|
||||||
"title_bar": {
|
"title_bar": {
|
||||||
// When to show the title bar: "always" | "never" | "hide_in_full_screen".
|
|
||||||
"show": "always",
|
|
||||||
// Whether to show the branch icon beside branch switcher in the titlebar.
|
// Whether to show the branch icon beside branch switcher in the titlebar.
|
||||||
"show_branch_icon": false,
|
"show_branch_icon": false,
|
||||||
// Whether to show the branch name button in the titlebar.
|
// Whether to show the branch name button in the titlebar.
|
||||||
@@ -413,15 +417,33 @@
|
|||||||
"experimental.rodio_audio": false,
|
"experimental.rodio_audio": false,
|
||||||
// Requires 'rodio_audio: true'
|
// Requires 'rodio_audio: true'
|
||||||
//
|
//
|
||||||
// Use the new audio systems automatic gain control for your microphone.
|
// Automatically increase or decrease you microphone's volume. This affects how
|
||||||
// This affects how loud you sound to others.
|
// loud you sound to others.
|
||||||
"experimental.control_input_volume": false,
|
//
|
||||||
|
// Recommended: off (default)
|
||||||
|
// Microphones are too quite in zed, until everyone is on experimental
|
||||||
|
// audio and has auto speaker volume on this will make you very loud
|
||||||
|
// compared to other speakers.
|
||||||
|
"experimental.auto_microphone_volume": false,
|
||||||
// Requires 'rodio_audio: true'
|
// Requires 'rodio_audio: true'
|
||||||
//
|
//
|
||||||
// Use the new audio systems automatic gain control on everyone in the
|
// Automatically increate or decrease the volume of other call members.
|
||||||
// call. This makes call members who are too quite louder and those who are
|
// This only affects how things sound for you.
|
||||||
// too loud quieter. This only affects how things sound for you.
|
"experimental.auto_speaker_volume": true,
|
||||||
"experimental.control_output_volume": false
|
// Requires 'rodio_audio: true'
|
||||||
|
//
|
||||||
|
// Remove background noises. Works great for typing, cars, dogs, AC. Does
|
||||||
|
// not work well on music.
|
||||||
|
"experimental.denoise": true,
|
||||||
|
// Requires 'rodio_audio: true'
|
||||||
|
//
|
||||||
|
// Use audio parameters compatible with the previous versions of
|
||||||
|
// experimental audio and non-experimental audio. When this is false you
|
||||||
|
// will sound strange to anyone not on the latest experimental audio. In
|
||||||
|
// the future we will migrate by setting this to false
|
||||||
|
//
|
||||||
|
// You need to rejoin a call for this setting to apply
|
||||||
|
"experimental.legacy_audio_compatible": true
|
||||||
},
|
},
|
||||||
// Scrollbar related settings
|
// Scrollbar related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
@@ -700,7 +722,11 @@
|
|||||||
// Whether to enable drag-and-drop operations in the project panel.
|
// Whether to enable drag-and-drop operations in the project panel.
|
||||||
"drag_and_drop": true,
|
"drag_and_drop": true,
|
||||||
// Whether to hide the root entry when only one folder is open in the window.
|
// Whether to hide the root entry when only one folder is open in the window.
|
||||||
"hide_root": false
|
"hide_root": false,
|
||||||
|
// Whether to hide the hidden entries in the project panel.
|
||||||
|
"hide_hidden": false,
|
||||||
|
// Whether to automatically open files when pasting them in the project panel.
|
||||||
|
"open_file_on_paste": true
|
||||||
},
|
},
|
||||||
"outline_panel": {
|
"outline_panel": {
|
||||||
// Whether to show the outline panel button in the status bar
|
// Whether to show the outline panel button in the status bar
|
||||||
@@ -882,6 +908,7 @@
|
|||||||
"now": true,
|
"now": true,
|
||||||
"find_path": true,
|
"find_path": true,
|
||||||
"read_file": true,
|
"read_file": true,
|
||||||
|
"open": true,
|
||||||
"grep": true,
|
"grep": true,
|
||||||
"terminal": true,
|
"terminal": true,
|
||||||
"thinking": true,
|
"thinking": true,
|
||||||
@@ -893,7 +920,6 @@
|
|||||||
// 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": true,
|
||||||
@@ -1080,25 +1106,31 @@
|
|||||||
// Removes any lines containing only whitespace at the end of the file and
|
// Removes any lines containing only whitespace at the end of the file and
|
||||||
// ensures just one newline at the end.
|
// ensures just one newline at the end.
|
||||||
"ensure_final_newline_on_save": true,
|
"ensure_final_newline_on_save": true,
|
||||||
// Whether or not to perform a buffer format before saving: [on, off, prettier, language_server]
|
// Whether or not to perform a buffer format before saving: [on, off]
|
||||||
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
|
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
|
||||||
"format_on_save": "on",
|
"format_on_save": "on",
|
||||||
// How to perform a buffer format. This setting can take 4 values:
|
// How to perform a buffer format. This setting can take multiple values:
|
||||||
//
|
//
|
||||||
// 1. Format code using the current language server:
|
// 1. Default. Format files using Zed's Prettier integration (if applicable),
|
||||||
|
// or falling back to formatting via language server:
|
||||||
|
// "formatter": "auto"
|
||||||
|
// 2. Format code using the current language server:
|
||||||
// "formatter": "language_server"
|
// "formatter": "language_server"
|
||||||
// 2. Format code using an external command:
|
// 3. Format code using a specific language server:
|
||||||
|
// "formatter": {"language_server": {"name": "ruff"}}
|
||||||
|
// 4. Format code using an external command:
|
||||||
// "formatter": {
|
// "formatter": {
|
||||||
// "external": {
|
// "external": {
|
||||||
// "command": "prettier",
|
// "command": "prettier",
|
||||||
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// 3. Format code using Zed's Prettier integration:
|
// 5. Format code using Zed's Prettier integration:
|
||||||
// "formatter": "prettier"
|
// "formatter": "prettier"
|
||||||
// 4. Default. Format files using Zed's Prettier integration (if applicable),
|
// 6. Format code using a code action
|
||||||
// or falling back to formatting via language server:
|
// "formatter": {"code_action": "source.fixAll.eslint"}
|
||||||
// "formatter": "auto"
|
// 7. An array of any format step specified above to apply in order
|
||||||
|
// "formatter": [{"code_action": "source.fixAll.eslint"}, "prettier"]
|
||||||
"formatter": "auto",
|
"formatter": "auto",
|
||||||
// How to soft-wrap long lines of text.
|
// How to soft-wrap long lines of text.
|
||||||
// Possible values:
|
// Possible values:
|
||||||
@@ -1210,6 +1242,10 @@
|
|||||||
// 2. Hide the gutter
|
// 2. Hide the gutter
|
||||||
// "git_gutter": "hide"
|
// "git_gutter": "hide"
|
||||||
"git_gutter": "tracked_files",
|
"git_gutter": "tracked_files",
|
||||||
|
/// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
|
||||||
|
///
|
||||||
|
/// Default: 0
|
||||||
|
"gutter_debounce": 0,
|
||||||
// Control whether the git blame information is shown inline,
|
// Control whether the git blame information is shown inline,
|
||||||
// in the currently focused line.
|
// in the currently focused line.
|
||||||
"inline_blame": {
|
"inline_blame": {
|
||||||
@@ -1225,6 +1261,9 @@
|
|||||||
// The minimum column number to show the inline blame information at
|
// The minimum column number to show the inline blame information at
|
||||||
"min_column": 0
|
"min_column": 0
|
||||||
},
|
},
|
||||||
|
"blame": {
|
||||||
|
"show_avatar": true
|
||||||
|
},
|
||||||
// Control which information is shown in the branch picker.
|
// Control which information is shown in the branch picker.
|
||||||
"branch_picker": {
|
"branch_picker": {
|
||||||
"show_author_name": true
|
"show_author_name": true
|
||||||
@@ -1283,15 +1322,18 @@
|
|||||||
// "proxy": "",
|
// "proxy": "",
|
||||||
// "proxy_no_verify": false
|
// "proxy_no_verify": false
|
||||||
// },
|
// },
|
||||||
// Whether edit predictions are enabled when editing text threads.
|
|
||||||
// This setting has no effect if globally disabled.
|
|
||||||
"enabled_in_text_threads": true,
|
|
||||||
|
|
||||||
"copilot": {
|
"copilot": {
|
||||||
"enterprise_uri": null,
|
"enterprise_uri": null,
|
||||||
"proxy": null,
|
"proxy": null,
|
||||||
"proxy_no_verify": null
|
"proxy_no_verify": null
|
||||||
}
|
},
|
||||||
|
"codestral": {
|
||||||
|
"model": null,
|
||||||
|
"max_tokens": null
|
||||||
|
},
|
||||||
|
// Whether edit predictions are enabled when editing text threads.
|
||||||
|
// This setting has no effect if globally disabled.
|
||||||
|
"enabled_in_text_threads": true
|
||||||
},
|
},
|
||||||
// Settings specific to journaling
|
// Settings specific to journaling
|
||||||
"journal": {
|
"journal": {
|
||||||
@@ -1305,6 +1347,8 @@
|
|||||||
},
|
},
|
||||||
// Status bar-related settings.
|
// Status bar-related settings.
|
||||||
"status_bar": {
|
"status_bar": {
|
||||||
|
// Whether to show the status bar.
|
||||||
|
"experimental.show": true,
|
||||||
// Whether to show the active language button in the status bar.
|
// Whether to show the active language button in the status bar.
|
||||||
"active_language_button": true,
|
"active_language_button": true,
|
||||||
// Whether to show the cursor position button in the status bar.
|
// Whether to show the cursor position button in the status bar.
|
||||||
@@ -1371,8 +1415,8 @@
|
|||||||
// 4. A box drawn around the following character
|
// 4. A box drawn around the following character
|
||||||
// "hollow"
|
// "hollow"
|
||||||
//
|
//
|
||||||
// Default: not set, defaults to "block"
|
// Default: "block"
|
||||||
"cursor_shape": null,
|
"cursor_shape": "block",
|
||||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||||
// presses when in the alternate screen (e.g. when running applications
|
// presses when in the alternate screen (e.g. when running applications
|
||||||
@@ -1394,8 +1438,8 @@
|
|||||||
// Whether or not selecting text in the terminal will automatically
|
// Whether or not selecting text in the terminal will automatically
|
||||||
// copy to the system clipboard.
|
// copy to the system clipboard.
|
||||||
"copy_on_select": false,
|
"copy_on_select": false,
|
||||||
// Whether to keep the text selection after copying it to the clipboard
|
// Whether to keep the text selection after copying it to the clipboard.
|
||||||
"keep_selection_on_copy": false,
|
"keep_selection_on_copy": true,
|
||||||
// Whether to show the terminal button in the status bar
|
// Whether to show the terminal button in the status bar
|
||||||
"button": true,
|
"button": true,
|
||||||
// Any key-value pairs added to this list will be added to the terminal's
|
// Any key-value pairs added to this list will be added to the terminal's
|
||||||
@@ -1414,7 +1458,7 @@
|
|||||||
// "line_height": {
|
// "line_height": {
|
||||||
// "custom": 2
|
// "custom": 2
|
||||||
// },
|
// },
|
||||||
"line_height": "comfortable",
|
"line_height": "standard",
|
||||||
// Activate the python virtual environment, if one is found, in the
|
// Activate the python virtual environment, if one is found, in the
|
||||||
// terminal's working directory (as resolved by the working_directory
|
// terminal's working directory (as resolved by the working_directory
|
||||||
// setting). Set this to "off" to disable this behavior.
|
// setting). Set this to "off" to disable this behavior.
|
||||||
@@ -1434,7 +1478,7 @@
|
|||||||
//
|
//
|
||||||
// The shell running in the terminal needs to be configured to emit the title.
|
// The shell running in the terminal needs to be configured to emit the title.
|
||||||
// Example: `echo -e "\e]2;New Title\007";`
|
// Example: `echo -e "\e]2;New Title\007";`
|
||||||
"breadcrumbs": true
|
"breadcrumbs": false
|
||||||
},
|
},
|
||||||
// Scrollbar-related settings
|
// Scrollbar-related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
@@ -1485,7 +1529,6 @@
|
|||||||
// A value of 45 preserves colorful themes while ensuring legibility.
|
// A value of 45 preserves colorful themes while ensuring legibility.
|
||||||
"minimum_contrast": 45
|
"minimum_contrast": 45
|
||||||
},
|
},
|
||||||
"code_actions_on_format": {},
|
|
||||||
// Settings related to running tasks.
|
// Settings related to running tasks.
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"variables": {},
|
"variables": {},
|
||||||
@@ -1514,7 +1557,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
"file_types": {
|
"file_types": {
|
||||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json", "tsconfig*.json"],
|
||||||
"Shell Script": [".env.*"]
|
"Shell Script": [".env.*"]
|
||||||
},
|
},
|
||||||
// Settings for which version of Node.js and NPM to use when installing
|
// Settings for which version of Node.js and NPM to use when installing
|
||||||
@@ -1540,6 +1583,14 @@
|
|||||||
"auto_install_extensions": {
|
"auto_install_extensions": {
|
||||||
"html": true
|
"html": true
|
||||||
},
|
},
|
||||||
|
// The capabilities granted to extensions.
|
||||||
|
//
|
||||||
|
// This list can be customized to restrict what extensions are able to do.
|
||||||
|
"granted_extension_capabilities": [
|
||||||
|
{ "kind": "process:exec", "command": "*", "args": ["**"] },
|
||||||
|
{ "kind": "download_file", "host": "*", "path": ["**"] },
|
||||||
|
{ "kind": "npm:install", "package": "*" }
|
||||||
|
],
|
||||||
// Controls how completions are processed for this language.
|
// Controls how completions are processed for this language.
|
||||||
"completions": {
|
"completions": {
|
||||||
// Controls how words are completed.
|
// Controls how words are completed.
|
||||||
@@ -1647,9 +1698,7 @@
|
|||||||
"preferred_line_length": 72
|
"preferred_line_length": 72
|
||||||
},
|
},
|
||||||
"Go": {
|
"Go": {
|
||||||
"code_actions_on_format": {
|
"formatter": [{ "code_action": "source.organizeImports" }, "language_server"],
|
||||||
"source.organizeImports": true
|
|
||||||
},
|
|
||||||
"debuggers": ["Delve"]
|
"debuggers": ["Delve"]
|
||||||
},
|
},
|
||||||
"GraphQL": {
|
"GraphQL": {
|
||||||
@@ -1838,21 +1887,19 @@
|
|||||||
// Allows to enable/disable formatting with Prettier
|
// Allows to enable/disable formatting with Prettier
|
||||||
// and configure default Prettier, used when no project-level Prettier installation is found.
|
// and configure default Prettier, used when no project-level Prettier installation is found.
|
||||||
"prettier": {
|
"prettier": {
|
||||||
// // Whether to consider prettier formatter or not when attempting to format a file.
|
// Enables or disables formatting with Prettier for any given language.
|
||||||
"allowed": false
|
"allowed": false,
|
||||||
//
|
// Forces Prettier integration to use a specific parser name when formatting files with the language.
|
||||||
// // Use regular Prettier json configuration.
|
"plugins": [],
|
||||||
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
|
// Default Prettier options, in the format as in package.json section for Prettier.
|
||||||
// // the project has no other Prettier installed.
|
// If project installs Prettier via its package.json, these options will be ignored.
|
||||||
// "plugins": [],
|
|
||||||
//
|
|
||||||
// // Use regular Prettier json configuration.
|
|
||||||
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
|
|
||||||
// // the project has no other Prettier installed.
|
|
||||||
// "trailingComma": "es5",
|
// "trailingComma": "es5",
|
||||||
// "tabWidth": 4,
|
// "tabWidth": 4,
|
||||||
// "semi": false,
|
// "semi": false,
|
||||||
// "singleQuote": true
|
// "singleQuote": true
|
||||||
|
// Forces Prettier integration to use a specific parser name when formatting files with the language
|
||||||
|
// when set to a non-empty string.
|
||||||
|
"parser": ""
|
||||||
},
|
},
|
||||||
// Settings for auto-closing of JSX tags.
|
// Settings for auto-closing of JSX tags.
|
||||||
"jsx_tag_auto_close": {
|
"jsx_tag_auto_close": {
|
||||||
@@ -2002,7 +2049,7 @@
|
|||||||
// Examples:
|
// Examples:
|
||||||
// "profiles": {
|
// "profiles": {
|
||||||
// "Presenting": {
|
// "Presenting": {
|
||||||
// "agent_font_size": 20.0,
|
// "agent_ui_font_size": 20.0,
|
||||||
// "buffer_font_size": 20.0,
|
// "buffer_font_size": 20.0,
|
||||||
// "theme": "One Light",
|
// "theme": "One Light",
|
||||||
// "ui_font_size": 20.0
|
// "ui_font_size": 20.0
|
||||||
@@ -2015,7 +2062,7 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
"profiles": [],
|
"profiles": {},
|
||||||
|
|
||||||
// A map of log scopes to the desired log level.
|
// A map of log scopes to the desired log level.
|
||||||
// Useful for filtering out noisy logs or enabling more verbose logging.
|
// Useful for filtering out noisy logs or enabling more verbose logging.
|
||||||
|
|||||||
@@ -43,7 +43,11 @@
|
|||||||
// "args": ["--login"]
|
// "args": ["--login"]
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
"shell": "system"
|
"shell": "system",
|
||||||
|
// Whether to show the task line in the output of the spawned task, defaults to `true`.
|
||||||
|
"show_summary": true,
|
||||||
|
// Whether to show the command line in the output of the spawned task, defaults to `true`.
|
||||||
|
"show_command": true
|
||||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||||
// "tags": []
|
// "tags": []
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/sounds/guest_joined_call.wav
Normal file
BIN
assets/sounds/guest_joined_call.wav
Normal file
Binary file not shown.
@@ -192,7 +192,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"color": "#abb5be8c",
|
"color": "#5c6773ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#628b80ff",
|
"color": "#628b80ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#ff8f3fff",
|
"color": "#ff8f3fff",
|
||||||
@@ -583,7 +583,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"color": "#787b8099",
|
"color": "#abb0b6ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -630,7 +630,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#8ca7c2ff",
|
"color": "#8ca7c2ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#fa8d3eff",
|
"color": "#fa8d3eff",
|
||||||
@@ -974,7 +974,7 @@
|
|||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"color": "#b8cfe680",
|
"color": "#5c6773ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
@@ -1021,7 +1021,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#7399a3ff",
|
"color": "#7399a3ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#ffad65ff",
|
"color": "#ffad65ff",
|
||||||
|
|||||||
@@ -248,7 +248,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#8c957dff",
|
"color": "#8c957dff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#fb4833ff",
|
"color": "#fb4833ff",
|
||||||
@@ -653,7 +653,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#8c957dff",
|
"color": "#8c957dff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#fb4833ff",
|
"color": "#fb4833ff",
|
||||||
@@ -1058,7 +1058,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#8c957dff",
|
"color": "#8c957dff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#fb4833ff",
|
"color": "#fb4833ff",
|
||||||
@@ -1463,7 +1463,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#677562ff",
|
"color": "#677562ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#9d0006ff",
|
"color": "#9d0006ff",
|
||||||
@@ -1868,7 +1868,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#677562ff",
|
"color": "#677562ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#9d0006ff",
|
"color": "#9d0006ff",
|
||||||
@@ -2273,7 +2273,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#677562ff",
|
"color": "#677562ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#9d0006ff",
|
"color": "#9d0006ff",
|
||||||
|
|||||||
@@ -244,7 +244,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#788ca6ff",
|
"color": "#788ca6ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#b477cfff",
|
"color": "#b477cfff",
|
||||||
@@ -643,7 +643,7 @@
|
|||||||
"hint": {
|
"hint": {
|
||||||
"color": "#7274a7ff",
|
"color": "#7274a7ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": 700
|
"font_weight": null
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
"color": "#a449abff",
|
"color": "#a449abff",
|
||||||
|
|||||||
13
clippy.toml
13
clippy.toml
@@ -5,3 +5,16 @@ ignore-interior-mutability = [
|
|||||||
# and Hash impls do not use fields with interior mutability.
|
# and Hash impls do not use fields with interior mutability.
|
||||||
"agent::context::AgentContextKey"
|
"agent::context::AgentContextKey"
|
||||||
]
|
]
|
||||||
|
disallowed-methods = [
|
||||||
|
{ path = "std::process::Command::spawn", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::spawn" },
|
||||||
|
{ path = "std::process::Command::output", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::output" },
|
||||||
|
{ path = "std::process::Command::status", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::status" },
|
||||||
|
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
|
||||||
|
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
|
||||||
|
]
|
||||||
|
disallowed-types = [
|
||||||
|
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },
|
||||||
|
# { path = "std::collections::HashSet", replacement = "collections::HashSet" },
|
||||||
|
# { path = "indexmap::IndexSet", replacement = "collections::IndexSet" },
|
||||||
|
# { path = "indexmap::IndexMap", replacement = "collections::IndexMap" },
|
||||||
|
]
|
||||||
|
|||||||
10
compose.yml
10
compose.yml
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15
|
image: docker.io/library/postgres:15
|
||||||
container_name: zed_postgres
|
container_name: zed_postgres
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
@@ -23,7 +23,7 @@ services:
|
|||||||
- ./.blob_store:/data
|
- ./.blob_store:/data
|
||||||
|
|
||||||
livekit_server:
|
livekit_server:
|
||||||
image: livekit/livekit-server
|
image: docker.io/livekit/livekit-server
|
||||||
container_name: livekit_server
|
container_name: livekit_server
|
||||||
entrypoint: /livekit-server --config /livekit.yaml
|
entrypoint: /livekit-server --config /livekit.yaml
|
||||||
ports:
|
ports:
|
||||||
@@ -34,7 +34,7 @@ services:
|
|||||||
- ./livekit.yaml:/livekit.yaml
|
- ./livekit.yaml:/livekit.yaml
|
||||||
|
|
||||||
postgrest_app:
|
postgrest_app:
|
||||||
image: postgrest/postgrest
|
image: docker.io/postgrest/postgrest
|
||||||
container_name: postgrest_app
|
container_name: postgrest_app
|
||||||
ports:
|
ports:
|
||||||
- 8081:8081
|
- 8081:8081
|
||||||
@@ -47,7 +47,7 @@ services:
|
|||||||
- postgres
|
- postgres
|
||||||
|
|
||||||
postgrest_llm:
|
postgrest_llm:
|
||||||
image: postgrest/postgrest
|
image: docker.io/postgrest/postgrest
|
||||||
container_name: postgrest_llm
|
container_name: postgrest_llm
|
||||||
ports:
|
ports:
|
||||||
- 8082:8082
|
- 8082:8082
|
||||||
@@ -60,7 +60,7 @@ services:
|
|||||||
- postgres
|
- postgres
|
||||||
|
|
||||||
stripe-mock:
|
stripe-mock:
|
||||||
image: stripe/stripe-mock:v0.178.0
|
image: docker.io/stripe/stripe-mock:v0.178.0
|
||||||
ports:
|
ports:
|
||||||
- 12111:12111
|
- 12111:12111
|
||||||
- 12112:12112
|
- 12112:12112
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod diff;
|
|||||||
mod mention;
|
mod mention;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
|
|
||||||
|
use ::terminal::terminal_settings::TerminalSettings;
|
||||||
use agent_settings::AgentSettings;
|
use agent_settings::AgentSettings;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
pub use connection::*;
|
pub use connection::*;
|
||||||
@@ -11,7 +12,7 @@ use language::language_settings::FormatOnSave;
|
|||||||
pub use mention::*;
|
pub use mention::*;
|
||||||
use project::lsp_store::{FormatTrigger, LspFormatTarget};
|
use project::lsp_store::{FormatTrigger, LspFormatTarget};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
use settings::{Settings as _, SettingsLocation};
|
||||||
use task::{Shell, ShellBuilder};
|
use task::{Shell, ShellBuilder};
|
||||||
pub use terminal::*;
|
pub use terminal::*;
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ use std::rc::Rc;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
|
use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
|
||||||
use ui::App;
|
use ui::App;
|
||||||
use util::{ResultExt, get_default_system_shell};
|
use util::{ResultExt, get_default_system_shell_preferring_bash};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -573,7 +574,7 @@ impl ToolCallContent {
|
|||||||
))),
|
))),
|
||||||
acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
|
acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
|
||||||
Diff::finalized(
|
Diff::finalized(
|
||||||
diff.path,
|
diff.path.to_string_lossy().into_owned(),
|
||||||
diff.old_text,
|
diff.old_text,
|
||||||
diff.new_text,
|
diff.new_text,
|
||||||
language_registry,
|
language_registry,
|
||||||
@@ -787,6 +788,8 @@ pub struct AcpThread {
|
|||||||
prompt_capabilities: acp::PromptCapabilities,
|
prompt_capabilities: acp::PromptCapabilities,
|
||||||
_observe_prompt_capabilities: Task<anyhow::Result<()>>,
|
_observe_prompt_capabilities: Task<anyhow::Result<()>>,
|
||||||
terminals: HashMap<acp::TerminalId, Entity<Terminal>>,
|
terminals: HashMap<acp::TerminalId, Entity<Terminal>>,
|
||||||
|
pending_terminal_output: HashMap<acp::TerminalId, Vec<Vec<u8>>>,
|
||||||
|
pending_terminal_exit: HashMap<acp::TerminalId, acp::TerminalExitStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -809,6 +812,126 @@ pub enum AcpThreadEvent {
|
|||||||
|
|
||||||
impl EventEmitter<AcpThreadEvent> for AcpThread {}
|
impl EventEmitter<AcpThreadEvent> for AcpThread {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TerminalProviderEvent {
|
||||||
|
Created {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
label: String,
|
||||||
|
cwd: Option<PathBuf>,
|
||||||
|
output_byte_limit: Option<u64>,
|
||||||
|
terminal: Entity<::terminal::Terminal>,
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
data: Vec<u8>,
|
||||||
|
},
|
||||||
|
TitleChanged {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
title: String,
|
||||||
|
},
|
||||||
|
Exit {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
status: acp::TerminalExitStatus,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TerminalProviderCommand {
|
||||||
|
WriteInput {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
},
|
||||||
|
Resize {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
cols: u16,
|
||||||
|
rows: u16,
|
||||||
|
},
|
||||||
|
Close {
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcpThread {
|
||||||
|
pub fn on_terminal_provider_event(
|
||||||
|
&mut self,
|
||||||
|
event: TerminalProviderEvent,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
TerminalProviderEvent::Created {
|
||||||
|
terminal_id,
|
||||||
|
label,
|
||||||
|
cwd,
|
||||||
|
output_byte_limit,
|
||||||
|
terminal,
|
||||||
|
} => {
|
||||||
|
let entity = self.register_terminal_created(
|
||||||
|
terminal_id.clone(),
|
||||||
|
label,
|
||||||
|
cwd,
|
||||||
|
output_byte_limit,
|
||||||
|
terminal,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(mut chunks) = self.pending_terminal_output.remove(&terminal_id) {
|
||||||
|
for data in chunks.drain(..) {
|
||||||
|
entity.update(cx, |term, cx| {
|
||||||
|
term.inner().update(cx, |inner, cx| {
|
||||||
|
inner.write_output(&data, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(_status) = self.pending_terminal_exit.remove(&terminal_id) {
|
||||||
|
entity.update(cx, |_term, cx| {
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
TerminalProviderEvent::Output { terminal_id, data } => {
|
||||||
|
if let Some(entity) = self.terminals.get(&terminal_id) {
|
||||||
|
entity.update(cx, |term, cx| {
|
||||||
|
term.inner().update(cx, |inner, cx| {
|
||||||
|
inner.write_output(&data, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.pending_terminal_output
|
||||||
|
.entry(terminal_id)
|
||||||
|
.or_default()
|
||||||
|
.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TerminalProviderEvent::TitleChanged { terminal_id, title } => {
|
||||||
|
if let Some(entity) = self.terminals.get(&terminal_id) {
|
||||||
|
entity.update(cx, |term, cx| {
|
||||||
|
term.inner().update(cx, |inner, cx| {
|
||||||
|
inner.breadcrumb_text = title;
|
||||||
|
cx.emit(::terminal::Event::BreadcrumbsChanged);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TerminalProviderEvent::Exit {
|
||||||
|
terminal_id,
|
||||||
|
status,
|
||||||
|
} => {
|
||||||
|
if let Some(entity) = self.terminals.get(&terminal_id) {
|
||||||
|
entity.update(cx, |_term, cx| {
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.pending_terminal_exit.insert(terminal_id, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum ThreadStatus {
|
pub enum ThreadStatus {
|
||||||
Idle,
|
Idle,
|
||||||
@@ -886,6 +1009,8 @@ impl AcpThread {
|
|||||||
prompt_capabilities,
|
prompt_capabilities,
|
||||||
_observe_prompt_capabilities: task,
|
_observe_prompt_capabilities: task,
|
||||||
terminals: HashMap::default(),
|
terminals: HashMap::default(),
|
||||||
|
pending_terminal_output: HashMap::default(),
|
||||||
|
pending_terminal_exit: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1780,20 +1905,26 @@ impl AcpThread {
|
|||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
reuse_shared_snapshot: bool,
|
reuse_shared_snapshot: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<String>> {
|
) -> Task<Result<String, acp::Error>> {
|
||||||
// Args are 1-based, move to 0-based
|
// Args are 1-based, move to 0-based
|
||||||
let line = line.unwrap_or_default().saturating_sub(1);
|
let line = line.unwrap_or_default().saturating_sub(1);
|
||||||
let limit = limit.unwrap_or(u32::MAX);
|
let limit = limit.unwrap_or(u32::MAX);
|
||||||
let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
let action_log = self.action_log.clone();
|
let action_log = self.action_log.clone();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let load = project.update(cx, |project, cx| {
|
let load = project
|
||||||
let path = project
|
.update(cx, |project, cx| {
|
||||||
.project_path_for_absolute_path(&path, cx)
|
let path = project
|
||||||
.context("invalid path")?;
|
.project_path_for_absolute_path(&path, cx)
|
||||||
anyhow::Ok(project.open_buffer(path, cx))
|
.ok_or_else(|| {
|
||||||
});
|
acp::Error::resource_not_found(Some(path.display().to_string()))
|
||||||
let buffer = load??.await?;
|
})?;
|
||||||
|
Ok(project.open_buffer(path, cx))
|
||||||
|
})
|
||||||
|
.map_err(|e| acp::Error::internal_error().with_data(e.to_string()))
|
||||||
|
.flatten()?;
|
||||||
|
|
||||||
|
let buffer = load.await?;
|
||||||
|
|
||||||
let snapshot = if reuse_shared_snapshot {
|
let snapshot = if reuse_shared_snapshot {
|
||||||
this.read_with(cx, |this, _| {
|
this.read_with(cx, |this, _| {
|
||||||
@@ -1820,15 +1951,17 @@ impl AcpThread {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let max_point = snapshot.max_point();
|
let max_point = snapshot.max_point();
|
||||||
if line >= max_point.row {
|
let start_position = Point::new(line, 0);
|
||||||
anyhow::bail!(
|
|
||||||
|
if start_position > max_point {
|
||||||
|
return Err(acp::Error::invalid_params().with_data(format!(
|
||||||
"Attempting to read beyond the end of the file, line {}:{}",
|
"Attempting to read beyond the end of the file, line {}:{}",
|
||||||
max_point.row + 1,
|
max_point.row + 1,
|
||||||
max_point.column
|
max_point.column
|
||||||
);
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = snapshot.anchor_before(Point::new(line, 0));
|
let start = snapshot.anchor_before(start_position);
|
||||||
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
|
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
|
||||||
|
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
@@ -1953,16 +2086,24 @@ impl AcpThread {
|
|||||||
) -> Task<Result<Entity<Terminal>>> {
|
) -> Task<Result<Entity<Terminal>>> {
|
||||||
let env = match &cwd {
|
let env = match &cwd {
|
||||||
Some(dir) => self.project.update(cx, |project, cx| {
|
Some(dir) => self.project.update(cx, |project, cx| {
|
||||||
project.directory_environment(dir.as_path().into(), cx)
|
let worktree = project.find_worktree(dir.as_path(), cx);
|
||||||
|
let shell = TerminalSettings::get(
|
||||||
|
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
|
||||||
|
worktree_id: worktree.read(cx).id(),
|
||||||
|
path: &path,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.shell
|
||||||
|
.clone();
|
||||||
|
project.directory_environment(&shell, dir.as_path().into(), cx)
|
||||||
}),
|
}),
|
||||||
None => Task::ready(None).shared(),
|
None => Task::ready(None).shared(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let env = cx.spawn(async move |_, _| {
|
let env = cx.spawn(async move |_, _| {
|
||||||
let mut env = env.await.unwrap_or_default();
|
let mut env = env.await.unwrap_or_default();
|
||||||
if cfg!(unix) {
|
// Disables paging for `git` and hopefully other commands
|
||||||
env.insert("PAGER".into(), "cat".into());
|
env.insert("PAGER".into(), "".into());
|
||||||
}
|
|
||||||
for var in extra_env {
|
for var in extra_env {
|
||||||
env.insert(var.name, var.value);
|
env.insert(var.name, var.value);
|
||||||
}
|
}
|
||||||
@@ -1971,30 +2112,30 @@ impl AcpThread {
|
|||||||
|
|
||||||
let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
let language_registry = project.read(cx).languages().clone();
|
let language_registry = project.read(cx).languages().clone();
|
||||||
|
let is_windows = project.read(cx).path_style(cx).is_windows();
|
||||||
|
|
||||||
let terminal_id = acp::TerminalId(Uuid::new_v4().to_string().into());
|
let terminal_id = acp::TerminalId(Uuid::new_v4().to_string().into());
|
||||||
let terminal_task = cx.spawn({
|
let terminal_task = cx.spawn({
|
||||||
let terminal_id = terminal_id.clone();
|
let terminal_id = terminal_id.clone();
|
||||||
async move |_this, cx| {
|
async move |_this, cx| {
|
||||||
let env = env.await;
|
let env = env.await;
|
||||||
let (command, args) = ShellBuilder::new(
|
let shell = project
|
||||||
project
|
.update(cx, |project, cx| {
|
||||||
.update(cx, |project, cx| {
|
project
|
||||||
project
|
.remote_client()
|
||||||
.remote_client()
|
.and_then(|r| r.read(cx).default_system_shell())
|
||||||
.and_then(|r| r.read(cx).default_system_shell())
|
})?
|
||||||
})?
|
.unwrap_or_else(|| get_default_system_shell_preferring_bash());
|
||||||
.as_deref(),
|
let (task_command, task_args) =
|
||||||
&Shell::Program(get_default_system_shell()),
|
ShellBuilder::new(&Shell::Program(shell), is_windows)
|
||||||
)
|
.redirect_stdin_to_dev_null()
|
||||||
.redirect_stdin_to_dev_null()
|
.build(Some(command.clone()), &args);
|
||||||
.build(Some(command), &args);
|
|
||||||
let terminal = project
|
let terminal = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.create_terminal_task(
|
project.create_terminal_task(
|
||||||
task::SpawnInTerminal {
|
task::SpawnInTerminal {
|
||||||
command: Some(command.clone()),
|
command: Some(task_command),
|
||||||
args: args.clone(),
|
args: task_args,
|
||||||
cwd: cwd.clone(),
|
cwd: cwd.clone(),
|
||||||
env,
|
env,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -2071,6 +2212,32 @@ impl AcpThread {
|
|||||||
pub fn emit_load_error(&mut self, error: LoadError, cx: &mut Context<Self>) {
|
pub fn emit_load_error(&mut self, error: LoadError, cx: &mut Context<Self>) {
|
||||||
cx.emit(AcpThreadEvent::LoadError(error));
|
cx.emit(AcpThreadEvent::LoadError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_terminal_created(
|
||||||
|
&mut self,
|
||||||
|
terminal_id: acp::TerminalId,
|
||||||
|
command_label: String,
|
||||||
|
working_dir: Option<PathBuf>,
|
||||||
|
output_byte_limit: Option<u64>,
|
||||||
|
terminal: Entity<::terminal::Terminal>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Entity<Terminal> {
|
||||||
|
let language_registry = self.project.read(cx).languages().clone();
|
||||||
|
|
||||||
|
let entity = cx.new(|cx| {
|
||||||
|
Terminal::new(
|
||||||
|
terminal_id.clone(),
|
||||||
|
&command_label,
|
||||||
|
working_dir.clone(),
|
||||||
|
output_byte_limit.map(|l| l as usize),
|
||||||
|
terminal,
|
||||||
|
language_registry,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
self.terminals.insert(terminal_id.clone(), entity.clone());
|
||||||
|
entity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn markdown_for_raw_output(
|
fn markdown_for_raw_output(
|
||||||
@@ -2147,6 +2314,145 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_terminal_output_buffered_before_created_renders(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
let project = Project::test(fs, [], cx).await;
|
||||||
|
let connection = Rc::new(FakeAgentConnection::new());
|
||||||
|
let thread = cx
|
||||||
|
.update(|cx| connection.new_thread(project, std::path::Path::new(path!("/test")), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
|
||||||
|
|
||||||
|
// Send Output BEFORE Created - should be buffered by acp_thread
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Output {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
data: b"hello buffered".to_vec(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a display-only terminal and then send Created
|
||||||
|
let lower = cx.new(|cx| {
|
||||||
|
let builder = ::terminal::TerminalBuilder::new_display_only(
|
||||||
|
::terminal::terminal_settings::CursorShape::default(),
|
||||||
|
::terminal::terminal_settings::AlternateScroll::On,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
builder.subscribe(cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Created {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
label: "Buffered Test".to_string(),
|
||||||
|
cwd: None,
|
||||||
|
output_byte_limit: None,
|
||||||
|
terminal: lower.clone(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// After Created, buffered Output should have been flushed into the renderer
|
||||||
|
let content = thread.read_with(cx, |thread, cx| {
|
||||||
|
let term = thread.terminal(terminal_id.clone()).unwrap();
|
||||||
|
term.read_with(cx, |t, cx| t.inner().read(cx).get_content())
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
content.contains("hello buffered"),
|
||||||
|
"expected buffered output to render, got: {content}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_terminal_output_and_exit_buffered_before_created(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
let project = Project::test(fs, [], cx).await;
|
||||||
|
let connection = Rc::new(FakeAgentConnection::new());
|
||||||
|
let thread = cx
|
||||||
|
.update(|cx| connection.new_thread(project, std::path::Path::new(path!("/test")), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
|
||||||
|
|
||||||
|
// Send Output BEFORE Created
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Output {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
data: b"pre-exit data".to_vec(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send Exit BEFORE Created
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Exit {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
status: acp::TerminalExitStatus {
|
||||||
|
exit_code: Some(0),
|
||||||
|
signal: None,
|
||||||
|
meta: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now create a display-only lower-level terminal and send Created
|
||||||
|
let lower = cx.new(|cx| {
|
||||||
|
let builder = ::terminal::TerminalBuilder::new_display_only(
|
||||||
|
::terminal::terminal_settings::CursorShape::default(),
|
||||||
|
::terminal::terminal_settings::AlternateScroll::On,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
builder.subscribe(cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.update(cx, |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Created {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
label: "Buffered Exit Test".to_string(),
|
||||||
|
cwd: None,
|
||||||
|
output_byte_limit: None,
|
||||||
|
terminal: lower.clone(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Output should be present after Created (flushed from buffer)
|
||||||
|
let content = thread.read_with(cx, |thread, cx| {
|
||||||
|
let term = thread.terminal(terminal_id.clone()).unwrap();
|
||||||
|
term.read_with(cx, |t, cx| t.inner().read(cx).get_content())
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
content.contains("pre-exit data"),
|
||||||
|
"expected pre-exit data to render, got: {content}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_push_user_content_block(cx: &mut gpui::TestAppContext) {
|
async fn test_push_user_content_block(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
@@ -2449,6 +2755,81 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(content, "two\nthree\n");
|
assert_eq!(content, "two\nthree\n");
|
||||||
|
|
||||||
|
// Invalid
|
||||||
|
let err = thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.read_text_file(path!("/tmp/foo").into(), Some(6), Some(2), false, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"Invalid params: \"Attempting to read beyond the end of the file, line 5:0\""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_reading_empty_file(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(path!("/tmp"), json!({"foo": ""})).await;
|
||||||
|
let project = Project::test(fs.clone(), [], cx).await;
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let connection = Rc::new(FakeAgentConnection::new());
|
||||||
|
|
||||||
|
let thread = cx
|
||||||
|
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Whole file
|
||||||
|
let content = thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.read_text_file(path!("/tmp/foo").into(), None, None, false, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(content, "");
|
||||||
|
|
||||||
|
// Only start line
|
||||||
|
let content = thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.read_text_file(path!("/tmp/foo").into(), Some(1), None, false, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(content, "");
|
||||||
|
|
||||||
|
// Only limit
|
||||||
|
let content = thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.read_text_file(path!("/tmp/foo").into(), None, Some(2), false, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(content, "");
|
||||||
|
|
||||||
|
// Range
|
||||||
|
let content = thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.read_text_file(path!("/tmp/foo").into(), Some(1), Some(1), false, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(content, "");
|
||||||
|
|
||||||
// Invalid
|
// Invalid
|
||||||
let err = thread
|
let err = thread
|
||||||
.update(cx, |thread, cx| {
|
.update(cx, |thread, cx| {
|
||||||
@@ -2459,9 +2840,40 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
"Attempting to read beyond the end of the file, line 5:0"
|
"Invalid params: \"Attempting to read beyond the end of the file, line 1:0\""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_reading_non_existing_file(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(path!("/tmp"), json!({})).await;
|
||||||
|
let project = Project::test(fs.clone(), [], cx).await;
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_worktree(path!("/tmp"), true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let connection = Rc::new(FakeAgentConnection::new());
|
||||||
|
|
||||||
|
let thread = cx
|
||||||
|
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Out of project file
|
||||||
|
let err = thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
thread.read_text_file(path!("/foo").into(), None, None, false, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(err.code, acp::ErrorCode::RESOURCE_NOT_FOUND.code);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
|
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub trait AgentConnection {
|
|||||||
///
|
///
|
||||||
/// If the agent does not support model selection, returns [None].
|
/// If the agent does not support model selection, returns [None].
|
||||||
/// This allows sharing the selector in UI components.
|
/// This allows sharing the selector in UI components.
|
||||||
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
|
fn model_selector(&self, _session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,61 +177,48 @@ pub trait AgentModelSelector: 'static {
|
|||||||
/// If the session doesn't exist or the model is invalid, it returns an error.
|
/// If the session doesn't exist or the model is invalid, it returns an error.
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - `session_id`: The ID of the session (thread) to apply the model to.
|
|
||||||
/// - `model`: The model to select (should be one from [list_models]).
|
/// - `model`: The model to select (should be one from [list_models]).
|
||||||
/// - `cx`: The GPUI app context.
|
/// - `cx`: The GPUI app context.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A task resolving to `Ok(())` on success or an error.
|
/// A task resolving to `Ok(())` on success or an error.
|
||||||
fn select_model(
|
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>>;
|
||||||
&self,
|
|
||||||
session_id: acp::SessionId,
|
|
||||||
model_id: AgentModelId,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<()>>;
|
|
||||||
|
|
||||||
/// Retrieves the currently selected model for a specific session (thread).
|
/// Retrieves the currently selected model for a specific session (thread).
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - `session_id`: The ID of the session (thread) to query.
|
|
||||||
/// - `cx`: The GPUI app context.
|
/// - `cx`: The GPUI app context.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A task resolving to the selected model (always set) or an error (e.g., session not found).
|
/// A task resolving to the selected model (always set) or an error (e.g., session not found).
|
||||||
fn selected_model(
|
fn selected_model(&self, cx: &mut App) -> Task<Result<AgentModelInfo>>;
|
||||||
&self,
|
|
||||||
session_id: &acp::SessionId,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<AgentModelInfo>>;
|
|
||||||
|
|
||||||
/// Whenever the model list is updated the receiver will be notified.
|
/// Whenever the model list is updated the receiver will be notified.
|
||||||
fn watch(&self, cx: &mut App) -> watch::Receiver<()>;
|
/// Optional for agents that don't update their model list.
|
||||||
}
|
fn watch(&self, _cx: &mut App) -> Option<watch::Receiver<()>> {
|
||||||
|
None
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct AgentModelId(pub SharedString);
|
|
||||||
|
|
||||||
impl std::ops::Deref for AgentModelId {
|
|
||||||
type Target = SharedString;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for AgentModelId {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AgentModelInfo {
|
pub struct AgentModelInfo {
|
||||||
pub id: AgentModelId,
|
pub id: acp::ModelId,
|
||||||
pub name: SharedString,
|
pub name: SharedString,
|
||||||
|
pub description: Option<SharedString>,
|
||||||
pub icon: Option<IconName>,
|
pub icon: Option<IconName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<acp::ModelInfo> for AgentModelInfo {
|
||||||
|
fn from(info: acp::ModelInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
id: info.model_id,
|
||||||
|
name: info.name.into(),
|
||||||
|
description: info.description.map(|desc| desc.into()),
|
||||||
|
icon: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct AgentModelGroupName(pub SharedString);
|
pub struct AgentModelGroupName(pub SharedString);
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ use itertools::Itertools;
|
|||||||
use language::{
|
use language::{
|
||||||
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer,
|
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||||
cmp::Reverse,
|
|
||||||
ops::Range,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub enum Diff {
|
pub enum Diff {
|
||||||
@@ -21,7 +16,7 @@ pub enum Diff {
|
|||||||
|
|
||||||
impl Diff {
|
impl Diff {
|
||||||
pub fn finalized(
|
pub fn finalized(
|
||||||
path: PathBuf,
|
path: String,
|
||||||
old_text: Option<String>,
|
old_text: Option<String>,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
@@ -36,7 +31,7 @@ impl Diff {
|
|||||||
let buffer = new_buffer.clone();
|
let buffer = new_buffer.clone();
|
||||||
async move |_, cx| {
|
async move |_, cx| {
|
||||||
let language = language_registry
|
let language = language_registry
|
||||||
.language_for_file_path(&path)
|
.load_language_for_file_path(Path::new(&path))
|
||||||
.await
|
.await
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
@@ -152,12 +147,15 @@ impl Diff {
|
|||||||
let path = match self {
|
let path = match self {
|
||||||
Diff::Pending(PendingDiff {
|
Diff::Pending(PendingDiff {
|
||||||
new_buffer: buffer, ..
|
new_buffer: buffer, ..
|
||||||
}) => buffer.read(cx).file().map(|file| file.path().as_ref()),
|
}) => buffer
|
||||||
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()),
|
.read(cx)
|
||||||
|
.file()
|
||||||
|
.map(|file| file.path().display(file.path_style(cx))),
|
||||||
|
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
"Diff: {}\n```\n{}\n```\n",
|
"Diff: {}\n```\n{}\n```\n",
|
||||||
path.unwrap_or(Path::new("untitled")).display(),
|
path.unwrap_or("untitled".into()),
|
||||||
buffer_text
|
buffer_text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -244,8 +242,8 @@ impl PendingDiff {
|
|||||||
.new_buffer
|
.new_buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
.map(|file| file.path().as_ref())
|
.map(|file| file.path().display(file.path_style(cx)))
|
||||||
.unwrap_or(Path::new("untitled"))
|
.unwrap_or("untitled".into())
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
// Replace the buffer in the multibuffer with the snapshot
|
// Replace the buffer in the multibuffer with the snapshot
|
||||||
@@ -348,7 +346,7 @@ impl PendingDiff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FinalizedDiff {
|
pub struct FinalizedDiff {
|
||||||
path: PathBuf,
|
path: String,
|
||||||
base_text: Arc<String>,
|
base_text: Arc<String>,
|
||||||
new_buffer: Entity<Buffer>,
|
new_buffer: Entity<Buffer>,
|
||||||
multibuffer: Entity<MultiBuffer>,
|
multibuffer: Entity<MultiBuffer>,
|
||||||
|
|||||||
@@ -126,6 +126,39 @@ impl MentionUri {
|
|||||||
abs_path: None,
|
abs_path: None,
|
||||||
line_range,
|
line_range,
|
||||||
})
|
})
|
||||||
|
} else if let Some(name) = path.strip_prefix("/agent/symbol/") {
|
||||||
|
let fragment = url
|
||||||
|
.fragment()
|
||||||
|
.context("Missing fragment for untitled buffer selection")?;
|
||||||
|
let line_range = parse_line_range(fragment)?;
|
||||||
|
let path =
|
||||||
|
single_query_param(&url, "path")?.context("Missing path for symbol")?;
|
||||||
|
Ok(Self::Symbol {
|
||||||
|
name: name.to_string(),
|
||||||
|
abs_path: path.into(),
|
||||||
|
line_range,
|
||||||
|
})
|
||||||
|
} else if path.starts_with("/agent/file") {
|
||||||
|
let path =
|
||||||
|
single_query_param(&url, "path")?.context("Missing path for file")?;
|
||||||
|
Ok(Self::File {
|
||||||
|
abs_path: path.into(),
|
||||||
|
})
|
||||||
|
} else if path.starts_with("/agent/directory") {
|
||||||
|
let path =
|
||||||
|
single_query_param(&url, "path")?.context("Missing path for directory")?;
|
||||||
|
Ok(Self::Directory {
|
||||||
|
abs_path: path.into(),
|
||||||
|
})
|
||||||
|
} else if path.starts_with("/agent/selection") {
|
||||||
|
let fragment = url.fragment().context("Missing fragment for selection")?;
|
||||||
|
let line_range = parse_line_range(fragment)?;
|
||||||
|
let path =
|
||||||
|
single_query_param(&url, "path")?.context("Missing path for selection")?;
|
||||||
|
Ok(Self::Selection {
|
||||||
|
abs_path: Some(path.into()),
|
||||||
|
line_range,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
bail!("invalid zed url: {:?}", input);
|
bail!("invalid zed url: {:?}", input);
|
||||||
}
|
}
|
||||||
@@ -180,20 +213,29 @@ impl MentionUri {
|
|||||||
pub fn to_uri(&self) -> Url {
|
pub fn to_uri(&self) -> Url {
|
||||||
match self {
|
match self {
|
||||||
MentionUri::File { abs_path } => {
|
MentionUri::File { abs_path } => {
|
||||||
Url::from_file_path(abs_path).expect("mention path should be absolute")
|
let mut url = Url::parse("zed:///").unwrap();
|
||||||
|
url.set_path("/agent/file");
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair("path", &abs_path.to_string_lossy());
|
||||||
|
url
|
||||||
}
|
}
|
||||||
MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(),
|
MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(),
|
||||||
MentionUri::Directory { abs_path } => {
|
MentionUri::Directory { abs_path } => {
|
||||||
Url::from_directory_path(abs_path).expect("mention path should be absolute")
|
let mut url = Url::parse("zed:///").unwrap();
|
||||||
|
url.set_path("/agent/directory");
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair("path", &abs_path.to_string_lossy());
|
||||||
|
url
|
||||||
}
|
}
|
||||||
MentionUri::Symbol {
|
MentionUri::Symbol {
|
||||||
abs_path,
|
abs_path,
|
||||||
name,
|
name,
|
||||||
line_range,
|
line_range,
|
||||||
} => {
|
} => {
|
||||||
let mut url =
|
let mut url = Url::parse("zed:///").unwrap();
|
||||||
Url::from_file_path(abs_path).expect("mention path should be absolute");
|
url.set_path(&format!("/agent/symbol/{name}"));
|
||||||
url.query_pairs_mut().append_pair("symbol", name);
|
url.query_pairs_mut()
|
||||||
|
.append_pair("path", &abs_path.to_string_lossy());
|
||||||
url.set_fragment(Some(&format!(
|
url.set_fragment(Some(&format!(
|
||||||
"L{}:{}",
|
"L{}:{}",
|
||||||
line_range.start() + 1,
|
line_range.start() + 1,
|
||||||
@@ -202,15 +244,16 @@ impl MentionUri {
|
|||||||
url
|
url
|
||||||
}
|
}
|
||||||
MentionUri::Selection {
|
MentionUri::Selection {
|
||||||
abs_path: path,
|
abs_path,
|
||||||
line_range,
|
line_range,
|
||||||
} => {
|
} => {
|
||||||
let mut url = if let Some(path) = path {
|
let mut url = Url::parse("zed:///").unwrap();
|
||||||
Url::from_file_path(path).expect("mention path should be absolute")
|
if let Some(abs_path) = abs_path {
|
||||||
|
url.set_path("/agent/selection");
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair("path", &abs_path.to_string_lossy());
|
||||||
} else {
|
} else {
|
||||||
let mut url = Url::parse("zed:///").unwrap();
|
|
||||||
url.set_path("/agent/untitled-buffer");
|
url.set_path("/agent/untitled-buffer");
|
||||||
url
|
|
||||||
};
|
};
|
||||||
url.set_fragment(Some(&format!(
|
url.set_fragment(Some(&format!(
|
||||||
"L{}:{}",
|
"L{}:{}",
|
||||||
@@ -295,37 +338,32 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_file_uri() {
|
fn test_parse_file_uri() {
|
||||||
let file_uri = uri!("file:///path/to/file.rs");
|
let old_uri = uri!("file:///path/to/file.rs");
|
||||||
let parsed = MentionUri::parse(file_uri).unwrap();
|
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||||
match &parsed {
|
match &parsed {
|
||||||
MentionUri::File { abs_path } => {
|
MentionUri::File { abs_path } => {
|
||||||
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs"));
|
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected File variant"),
|
_ => panic!("Expected File variant"),
|
||||||
}
|
}
|
||||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
let new_uri = parsed.to_uri().to_string();
|
||||||
|
assert!(new_uri.starts_with("zed:///agent/file"));
|
||||||
|
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_directory_uri() {
|
fn test_parse_directory_uri() {
|
||||||
let file_uri = uri!("file:///path/to/dir/");
|
let old_uri = uri!("file:///path/to/dir/");
|
||||||
let parsed = MentionUri::parse(file_uri).unwrap();
|
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||||
match &parsed {
|
match &parsed {
|
||||||
MentionUri::Directory { abs_path } => {
|
MentionUri::Directory { abs_path } => {
|
||||||
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/"));
|
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected Directory variant"),
|
_ => panic!("Expected Directory variant"),
|
||||||
}
|
}
|
||||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
let new_uri = parsed.to_uri().to_string();
|
||||||
}
|
assert!(new_uri.starts_with("zed:///agent/directory"));
|
||||||
|
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||||
#[test]
|
|
||||||
fn test_to_directory_uri_with_slash() {
|
|
||||||
let uri = MentionUri::Directory {
|
|
||||||
abs_path: PathBuf::from(path!("/path/to/dir/")),
|
|
||||||
};
|
|
||||||
let expected = uri!("file:///path/to/dir/");
|
|
||||||
assert_eq!(uri.to_uri().to_string(), expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -333,14 +371,15 @@ mod tests {
|
|||||||
let uri = MentionUri::Directory {
|
let uri = MentionUri::Directory {
|
||||||
abs_path: PathBuf::from(path!("/path/to/dir")),
|
abs_path: PathBuf::from(path!("/path/to/dir")),
|
||||||
};
|
};
|
||||||
let expected = uri!("file:///path/to/dir/");
|
let uri_string = uri.to_uri().to_string();
|
||||||
assert_eq!(uri.to_uri().to_string(), expected);
|
assert!(uri_string.starts_with("zed:///agent/directory"));
|
||||||
|
assert_eq!(MentionUri::parse(&uri_string).unwrap(), uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_symbol_uri() {
|
fn test_parse_symbol_uri() {
|
||||||
let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
|
let old_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
|
||||||
let parsed = MentionUri::parse(symbol_uri).unwrap();
|
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||||
match &parsed {
|
match &parsed {
|
||||||
MentionUri::Symbol {
|
MentionUri::Symbol {
|
||||||
abs_path: path,
|
abs_path: path,
|
||||||
@@ -354,13 +393,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
_ => panic!("Expected Symbol variant"),
|
_ => panic!("Expected Symbol variant"),
|
||||||
}
|
}
|
||||||
assert_eq!(parsed.to_uri().to_string(), symbol_uri);
|
let new_uri = parsed.to_uri().to_string();
|
||||||
|
assert!(new_uri.starts_with("zed:///agent/symbol/MySymbol"));
|
||||||
|
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_selection_uri() {
|
fn test_parse_selection_uri() {
|
||||||
let selection_uri = uri!("file:///path/to/file.rs#L5:15");
|
let old_uri = uri!("file:///path/to/file.rs#L5:15");
|
||||||
let parsed = MentionUri::parse(selection_uri).unwrap();
|
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||||
match &parsed {
|
match &parsed {
|
||||||
MentionUri::Selection {
|
MentionUri::Selection {
|
||||||
abs_path: path,
|
abs_path: path,
|
||||||
@@ -375,7 +416,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
_ => panic!("Expected Selection variant"),
|
_ => panic!("Expected Selection variant"),
|
||||||
}
|
}
|
||||||
assert_eq!(parsed.to_uri().to_string(), selection_uri);
|
let new_uri = parsed.to_uri().to_string();
|
||||||
|
assert!(new_uri.starts_with("zed:///agent/selection"));
|
||||||
|
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -4,22 +4,26 @@ use std::{
|
|||||||
fmt::Display,
|
fmt::Display,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment, ListState,
|
App, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment,
|
||||||
StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list, prelude::*,
|
ListState, StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list,
|
||||||
|
prelude::*,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
|
use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::prelude::*;
|
use ui::{Tooltip, prelude::*};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{Item, Workspace};
|
use workspace::{
|
||||||
|
Item, ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
actions!(dev, [OpenAcpLogs]);
|
actions!(dev, [OpenAcpLogs]);
|
||||||
|
|
||||||
@@ -227,6 +231,34 @@ impl AcpTools {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_observed_messages(&self) -> Option<String> {
|
||||||
|
let connection = self.watched_connection.as_ref()?;
|
||||||
|
|
||||||
|
let messages: Vec<serde_json::Value> = connection
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.filter_map(|message| {
|
||||||
|
let params = match &message.params {
|
||||||
|
Ok(Some(params)) => params.clone(),
|
||||||
|
Ok(None) => serde_json::Value::Null,
|
||||||
|
Err(err) => serde_json::to_value(err).ok()?,
|
||||||
|
};
|
||||||
|
Some(serde_json::json!({
|
||||||
|
"_direction": match message.direction {
|
||||||
|
acp::StreamMessageDirection::Incoming => "incoming",
|
||||||
|
acp::StreamMessageDirection::Outgoing => "outgoing",
|
||||||
|
},
|
||||||
|
"_type": message.message_type.to_string().to_lowercase(),
|
||||||
|
"id": message.request_id,
|
||||||
|
"method": message.name.to_string(),
|
||||||
|
"params": params,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
serde_json::to_string_pretty(&messages).ok()
|
||||||
|
}
|
||||||
|
|
||||||
fn render_message(
|
fn render_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
index: usize,
|
index: usize,
|
||||||
@@ -492,3 +524,92 @@ impl Render for AcpTools {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AcpToolsToolbarItemView {
|
||||||
|
acp_tools: Option<Entity<AcpTools>>,
|
||||||
|
just_copied: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcpToolsToolbarItemView {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
acp_tools: None,
|
||||||
|
just_copied: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for AcpToolsToolbarItemView {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let Some(acp_tools) = self.acp_tools.as_ref() else {
|
||||||
|
return Empty.into_any_element();
|
||||||
|
};
|
||||||
|
|
||||||
|
let acp_tools = acp_tools.clone();
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"copy_all_messages",
|
||||||
|
if self.just_copied {
|
||||||
|
IconName::Check
|
||||||
|
} else {
|
||||||
|
IconName::Copy
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.tooltip(Tooltip::text(if self.just_copied {
|
||||||
|
"Copied!"
|
||||||
|
} else {
|
||||||
|
"Copy All Messages"
|
||||||
|
}))
|
||||||
|
.disabled(
|
||||||
|
acp_tools
|
||||||
|
.read(cx)
|
||||||
|
.watched_connection
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|connection| connection.messages.is_empty()),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||||
|
if let Some(content) = acp_tools.read(cx).serialize_observed_messages() {
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(content));
|
||||||
|
|
||||||
|
this.just_copied = true;
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.just_copied = false;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ToolbarItemEvent> for AcpToolsToolbarItemView {}
|
||||||
|
|
||||||
|
impl ToolbarItemView for AcpToolsToolbarItemView {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> ToolbarItemLocation {
|
||||||
|
if let Some(item) = active_pane_item
|
||||||
|
&& let Some(acp_tools) = item.downcast::<AcpTools>()
|
||||||
|
{
|
||||||
|
self.acp_tools = Some(acp_tools);
|
||||||
|
cx.notify();
|
||||||
|
return ToolbarItemLocation::PrimaryRight;
|
||||||
|
}
|
||||||
|
if self.acp_tools.take().is_some() {
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
|
|||||||
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
|
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
|
||||||
use std::{cmp, ops::Range, sync::Arc};
|
use std::{cmp, ops::Range, sync::Arc};
|
||||||
use text::{Edit, Patch, Rope};
|
use text::{Edit, Patch, Rope};
|
||||||
use util::{
|
use util::{RangeExt, ResultExt as _};
|
||||||
RangeExt, ResultExt as _,
|
|
||||||
paths::{PathStyle, RemotePathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Tracks actions performed by tools in a thread
|
/// Tracks actions performed by tools in a thread
|
||||||
pub struct ActionLog {
|
pub struct ActionLog {
|
||||||
@@ -62,7 +59,13 @@ impl ActionLog {
|
|||||||
let file_path = buffer
|
let file_path = buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
.map(|file| RemotePathBuf::new(file.full_path(cx), PathStyle::Posix).to_proto())
|
.map(|file| {
|
||||||
|
let mut path = file.full_path(cx).to_string_lossy().into_owned();
|
||||||
|
if file.path_style(cx).is_windows() {
|
||||||
|
path = path.replace('\\', "/");
|
||||||
|
}
|
||||||
|
path
|
||||||
|
})
|
||||||
.unwrap_or_else(|| format!("buffer_{}", buffer.entity_id()));
|
.unwrap_or_else(|| format!("buffer_{}", buffer.entity_id()));
|
||||||
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
@@ -2301,7 +2304,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
fs.set_head_for_repo(
|
fs.set_head_for_repo(
|
||||||
path!("/project/.git").as_ref(),
|
path!("/project/.git").as_ref(),
|
||||||
&[("file.txt".into(), "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())],
|
&[("file.txt", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())],
|
||||||
"0000000",
|
"0000000",
|
||||||
);
|
);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
@@ -2384,7 +2387,7 @@ mod tests {
|
|||||||
// - Ignores the last line edit (j stays as j)
|
// - Ignores the last line edit (j stays as j)
|
||||||
fs.set_head_for_repo(
|
fs.set_head_for_repo(
|
||||||
path!("/project/.git").as_ref(),
|
path!("/project/.git").as_ref(),
|
||||||
&[("file.txt".into(), "A\nb\nc\nf\nG\nh\ni\nj".into())],
|
&[("file.txt", "A\nb\nc\nf\nG\nh\ni\nj".into())],
|
||||||
"0000001",
|
"0000001",
|
||||||
);
|
);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
@@ -2415,10 +2418,7 @@ mod tests {
|
|||||||
// Make another commit that accepts the NEW line but with different content
|
// Make another commit that accepts the NEW line but with different content
|
||||||
fs.set_head_for_repo(
|
fs.set_head_for_repo(
|
||||||
path!("/project/.git").as_ref(),
|
path!("/project/.git").as_ref(),
|
||||||
&[(
|
&[("file.txt", "A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into())],
|
||||||
"file.txt".into(),
|
|
||||||
"A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into(),
|
|
||||||
)],
|
|
||||||
"0000002",
|
"0000002",
|
||||||
);
|
);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
@@ -2444,7 +2444,7 @@ mod tests {
|
|||||||
// Final commit that accepts all remaining edits
|
// Final commit that accepts all remaining edits
|
||||||
fs.set_head_for_repo(
|
fs.set_head_for_repo(
|
||||||
path!("/project/.git").as_ref(),
|
path!("/project/.git").as_ref(),
|
||||||
&[("file.txt".into(), "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())],
|
&[("file.txt", "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())],
|
||||||
"0000003",
|
"0000003",
|
||||||
);
|
);
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use std::{
|
|||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
path::Path,
|
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@@ -328,17 +327,13 @@ impl ActivityIndicator {
|
|||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_environment_errors<'a>(
|
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
|
||||||
&'a self,
|
self.project.read(cx).peek_environment_error(cx)
|
||||||
cx: &'a App,
|
|
||||||
) -> impl Iterator<Item = (&'a Arc<Path>, &'a EnvironmentErrorMessage)> {
|
|
||||||
self.project.read(cx).shell_environment_errors(cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
|
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
|
||||||
// Show if any direnv calls failed
|
// Show if any direnv calls failed
|
||||||
if let Some((abs_path, error)) = self.pending_environment_errors(cx).next() {
|
if let Some(error) = self.pending_environment_error(cx) {
|
||||||
let abs_path = abs_path.clone();
|
|
||||||
return Some(Content {
|
return Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
Icon::new(IconName::Warning)
|
Icon::new(IconName::Warning)
|
||||||
@@ -348,7 +343,7 @@ impl ActivityIndicator {
|
|||||||
message: error.0.clone(),
|
message: error.0.clone(),
|
||||||
on_click: Some(Arc::new(move |this, window, cx| {
|
on_click: Some(Arc::new(move |this, window, cx| {
|
||||||
this.project.update(cx, |project, cx| {
|
this.project.update(cx, |project, cx| {
|
||||||
project.remove_environment_error(&abs_path, cx);
|
project.pop_environment_error(cx);
|
||||||
});
|
});
|
||||||
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ heed.workspace = true
|
|||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
icons.workspace = true
|
icons.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
itertools.workspace = true
|
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ pub mod agent_profile;
|
|||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod context_server_tool;
|
pub mod context_server_tool;
|
||||||
pub mod context_store;
|
pub mod context_store;
|
||||||
pub mod history_store;
|
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub mod thread_store;
|
pub mod thread_store;
|
||||||
pub mod tool_use;
|
pub mod tool_use;
|
||||||
|
|
||||||
pub use context::{AgentContext, ContextId, ContextLoadResult};
|
pub use context::{AgentContext, ContextId, ContextLoadResult};
|
||||||
pub use context_store::ContextStore;
|
pub use context_store::ContextStore;
|
||||||
|
use fs::Fs;
|
||||||
|
use std::sync::Arc;
|
||||||
pub use thread::{
|
pub use thread::{
|
||||||
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
||||||
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
|
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
|
||||||
};
|
};
|
||||||
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||||
|
|
||||||
pub fn init(cx: &mut gpui::App) {
|
pub fn init(fs: Arc<dyn Fs>, cx: &mut gpui::App) {
|
||||||
thread_store::init(cx);
|
thread_store::init(fs, cx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use std::path::PathBuf;
|
|||||||
use std::{ops::Range, path::Path, sync::Arc};
|
use std::{ops::Range, path::Path, sync::Arc};
|
||||||
use text::{Anchor, OffsetRangeExt as _};
|
use text::{Anchor, OffsetRangeExt as _};
|
||||||
use util::markdown::MarkdownCodeBlock;
|
use util::markdown::MarkdownCodeBlock;
|
||||||
|
use util::rel_path::RelPath;
|
||||||
use util::{ResultExt as _, post_inc};
|
use util::{ResultExt as _, post_inc};
|
||||||
|
|
||||||
pub const RULES_ICON: IconName = IconName::Reader;
|
pub const RULES_ICON: IconName = IconName::Reader;
|
||||||
@@ -158,7 +159,7 @@ pub struct FileContextHandle {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FileContext {
|
pub struct FileContext {
|
||||||
pub handle: FileContextHandle,
|
pub handle: FileContextHandle,
|
||||||
pub full_path: Arc<Path>,
|
pub full_path: String,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
pub is_outline: bool,
|
pub is_outline: bool,
|
||||||
}
|
}
|
||||||
@@ -186,7 +187,7 @@ impl FileContextHandle {
|
|||||||
log::error!("file context missing path");
|
log::error!("file context missing path");
|
||||||
return Task::ready(None);
|
return Task::ready(None);
|
||||||
};
|
};
|
||||||
let full_path: Arc<Path> = file.full_path(cx).into();
|
let full_path = file.full_path(cx).to_string_lossy().into_owned();
|
||||||
let rope = buffer_ref.as_rope().clone();
|
let rope = buffer_ref.as_rope().clone();
|
||||||
let buffer = self.buffer.clone();
|
let buffer = self.buffer.clone();
|
||||||
|
|
||||||
@@ -235,14 +236,14 @@ pub struct DirectoryContextHandle {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DirectoryContext {
|
pub struct DirectoryContext {
|
||||||
pub handle: DirectoryContextHandle,
|
pub handle: DirectoryContextHandle,
|
||||||
pub full_path: Arc<Path>,
|
pub full_path: String,
|
||||||
pub descendants: Vec<DirectoryContextDescendant>,
|
pub descendants: Vec<DirectoryContextDescendant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DirectoryContextDescendant {
|
pub struct DirectoryContextDescendant {
|
||||||
/// Path within the directory.
|
/// Path within the directory.
|
||||||
pub rel_path: Arc<Path>,
|
pub rel_path: Arc<RelPath>,
|
||||||
pub fenced_codeblock: SharedString,
|
pub fenced_codeblock: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,13 +274,16 @@ impl DirectoryContextHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let directory_path = entry.path.clone();
|
let directory_path = entry.path.clone();
|
||||||
let directory_full_path = worktree_ref.full_path(&directory_path).into();
|
let directory_full_path = worktree_ref
|
||||||
|
.full_path(&directory_path)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let file_paths = collect_files_in_path(worktree_ref, &directory_path);
|
let file_paths = collect_files_in_path(worktree_ref, &directory_path);
|
||||||
let descendants_future = future::join_all(file_paths.into_iter().map(|path| {
|
let descendants_future = future::join_all(file_paths.into_iter().map(|path| {
|
||||||
let worktree_ref = worktree.read(cx);
|
let worktree_ref = worktree.read(cx);
|
||||||
let worktree_id = worktree_ref.id();
|
let worktree_id = worktree_ref.id();
|
||||||
let full_path = worktree_ref.full_path(&path);
|
let full_path = worktree_ref.full_path(&path).to_string_lossy().into_owned();
|
||||||
|
|
||||||
let rel_path = path
|
let rel_path = path
|
||||||
.strip_prefix(&directory_path)
|
.strip_prefix(&directory_path)
|
||||||
@@ -360,7 +364,7 @@ pub struct SymbolContextHandle {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SymbolContext {
|
pub struct SymbolContext {
|
||||||
pub handle: SymbolContextHandle,
|
pub handle: SymbolContextHandle,
|
||||||
pub full_path: Arc<Path>,
|
pub full_path: String,
|
||||||
pub line_range: Range<Point>,
|
pub line_range: Range<Point>,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
@@ -399,7 +403,7 @@ impl SymbolContextHandle {
|
|||||||
log::error!("symbol context's file has no path");
|
log::error!("symbol context's file has no path");
|
||||||
return Task::ready(None);
|
return Task::ready(None);
|
||||||
};
|
};
|
||||||
let full_path = file.full_path(cx).into();
|
let full_path = file.full_path(cx).to_string_lossy().into_owned();
|
||||||
let line_range = self.enclosing_range.to_point(&buffer_ref.snapshot());
|
let line_range = self.enclosing_range.to_point(&buffer_ref.snapshot());
|
||||||
let text = self.text(cx);
|
let text = self.text(cx);
|
||||||
let buffer = self.buffer.clone();
|
let buffer = self.buffer.clone();
|
||||||
@@ -433,7 +437,7 @@ pub struct SelectionContextHandle {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SelectionContext {
|
pub struct SelectionContext {
|
||||||
pub handle: SelectionContextHandle,
|
pub handle: SelectionContextHandle,
|
||||||
pub full_path: Arc<Path>,
|
pub full_path: String,
|
||||||
pub line_range: Range<Point>,
|
pub line_range: Range<Point>,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
@@ -472,7 +476,7 @@ impl SelectionContextHandle {
|
|||||||
let text = self.text(cx);
|
let text = self.text(cx);
|
||||||
let buffer = self.buffer.clone();
|
let buffer = self.buffer.clone();
|
||||||
let context = AgentContext::Selection(SelectionContext {
|
let context = AgentContext::Selection(SelectionContext {
|
||||||
full_path: full_path.into(),
|
full_path: full_path.to_string_lossy().into_owned(),
|
||||||
line_range: self.line_range(cx),
|
line_range: self.line_range(cx),
|
||||||
text,
|
text,
|
||||||
handle: self,
|
handle: self,
|
||||||
@@ -702,7 +706,7 @@ impl Display for RulesContext {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ImageContext {
|
pub struct ImageContext {
|
||||||
pub project_path: Option<ProjectPath>,
|
pub project_path: Option<ProjectPath>,
|
||||||
pub full_path: Option<Arc<Path>>,
|
pub full_path: Option<String>,
|
||||||
pub original_image: Arc<gpui::Image>,
|
pub original_image: Arc<gpui::Image>,
|
||||||
// TODO: handle this elsewhere and remove `ignore-interior-mutability` opt-out in clippy.toml
|
// TODO: handle this elsewhere and remove `ignore-interior-mutability` opt-out in clippy.toml
|
||||||
// needed due to a false positive of `clippy::mutable_key_type`.
|
// needed due to a false positive of `clippy::mutable_key_type`.
|
||||||
@@ -968,7 +972,7 @@ pub fn load_context(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<Arc<RelPath>> {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
|
|
||||||
for entry in worktree.child_entries(path) {
|
for entry in worktree.child_entries(path) {
|
||||||
@@ -982,14 +986,17 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
|||||||
files
|
files
|
||||||
}
|
}
|
||||||
|
|
||||||
fn codeblock_tag(full_path: &Path, line_range: Option<Range<Point>>) -> String {
|
fn codeblock_tag(full_path: &str, line_range: Option<Range<Point>>) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
|
|
||||||
if let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) {
|
if let Some(extension) = Path::new(full_path)
|
||||||
|
.extension()
|
||||||
|
.and_then(|ext| ext.to_str())
|
||||||
|
{
|
||||||
let _ = write!(result, "{} ", extension);
|
let _ = write!(result, "{} ", extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = write!(result, "{}", full_path.display());
|
let _ = write!(result, "{}", full_path);
|
||||||
|
|
||||||
if let Some(range) = line_range {
|
if let Some(range) = line_range {
|
||||||
if range.start.row == range.end.row {
|
if range.start.row == range.end.row {
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ use futures::{self, FutureExt};
|
|||||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||||
use language::{Buffer, File as _};
|
use language::{Buffer, File as _};
|
||||||
use language_model::LanguageModelImage;
|
use language_model::LanguageModelImage;
|
||||||
use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file};
|
use project::{
|
||||||
|
Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file,
|
||||||
|
lsp_store::SymbolLocation,
|
||||||
|
};
|
||||||
use prompt_store::UserPromptId;
|
use prompt_store::UserPromptId;
|
||||||
use ref_cast::RefCast as _;
|
use ref_cast::RefCast as _;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -309,7 +312,7 @@ impl ContextStore {
|
|||||||
let item = image_item.read(cx);
|
let item = image_item.read(cx);
|
||||||
this.insert_image(
|
this.insert_image(
|
||||||
Some(item.project_path(cx)),
|
Some(item.project_path(cx)),
|
||||||
Some(item.file.full_path(cx).into()),
|
Some(item.file.full_path(cx).to_string_lossy().into_owned()),
|
||||||
item.image.clone(),
|
item.image.clone(),
|
||||||
remove_if_exists,
|
remove_if_exists,
|
||||||
cx,
|
cx,
|
||||||
@@ -325,7 +328,7 @@ impl ContextStore {
|
|||||||
fn insert_image(
|
fn insert_image(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: Option<ProjectPath>,
|
project_path: Option<ProjectPath>,
|
||||||
full_path: Option<Arc<Path>>,
|
full_path: Option<String>,
|
||||||
image: Arc<Image>,
|
image: Arc<Image>,
|
||||||
remove_if_exists: bool,
|
remove_if_exists: bool,
|
||||||
cx: &mut Context<ContextStore>,
|
cx: &mut Context<ContextStore>,
|
||||||
@@ -500,7 +503,7 @@ impl ContextStore {
|
|||||||
let Some(context_path) = buffer.project_path(cx) else {
|
let Some(context_path) = buffer.project_path(cx) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if context_path != symbol.path {
|
if symbol.path != SymbolLocation::InProject(context_path) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let context_range = context.range.to_point_utf16(&buffer.snapshot());
|
let context_range = context.range.to_point_utf16(&buffer.snapshot());
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
use crate::{ThreadId, thread_store::SerializedThreadMetadata};
|
|
||||||
use anyhow::{Context as _, Result};
|
|
||||||
use assistant_context::SavedContextMetadata;
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use paths::contexts_dir;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration};
|
|
||||||
use util::ResultExt as _;
|
|
||||||
|
|
||||||
const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
|
|
||||||
const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
|
|
||||||
const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum HistoryEntry {
|
|
||||||
Thread(SerializedThreadMetadata),
|
|
||||||
Context(SavedContextMetadata),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HistoryEntry {
|
|
||||||
pub fn updated_at(&self) -> DateTime<Utc> {
|
|
||||||
match self {
|
|
||||||
HistoryEntry::Thread(thread) => thread.updated_at,
|
|
||||||
HistoryEntry::Context(context) => context.mtime.to_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> HistoryEntryId {
|
|
||||||
match self {
|
|
||||||
HistoryEntry::Thread(thread) => HistoryEntryId::Thread(thread.id.clone()),
|
|
||||||
HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title(&self) -> &SharedString {
|
|
||||||
match self {
|
|
||||||
HistoryEntry::Thread(thread) => &thread.summary,
|
|
||||||
HistoryEntry::Context(context) => &context.title,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic identifier for a history entry.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub enum HistoryEntryId {
|
|
||||||
Thread(ThreadId),
|
|
||||||
Context(Arc<Path>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
enum SerializedRecentOpen {
|
|
||||||
Thread(String),
|
|
||||||
ContextName(String),
|
|
||||||
/// Old format which stores the full path
|
|
||||||
Context(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HistoryStore {
|
|
||||||
context_store: Entity<assistant_context::ContextStore>,
|
|
||||||
recently_opened_entries: VecDeque<HistoryEntryId>,
|
|
||||||
_subscriptions: Vec<gpui::Subscription>,
|
|
||||||
_save_recently_opened_entries_task: Task<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HistoryStore {
|
|
||||||
pub fn new(
|
|
||||||
context_store: Entity<assistant_context::ContextStore>,
|
|
||||||
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let subscriptions = vec![cx.observe(&context_store, |_, _, cx| cx.notify())];
|
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
|
|
||||||
this.update(cx, |this, _| {
|
|
||||||
this.recently_opened_entries
|
|
||||||
.extend(
|
|
||||||
entries.into_iter().take(
|
|
||||||
MAX_RECENTLY_OPENED_ENTRIES
|
|
||||||
.saturating_sub(this.recently_opened_entries.len()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
context_store,
|
|
||||||
recently_opened_entries: initial_recent_entries.into_iter().collect(),
|
|
||||||
_subscriptions: subscriptions,
|
|
||||||
_save_recently_opened_entries_task: Task::ready(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
|
||||||
let mut history_entries = Vec::new();
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
|
||||||
return history_entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
history_entries.extend(
|
|
||||||
self.context_store
|
|
||||||
.read(cx)
|
|
||||||
.unordered_contexts()
|
|
||||||
.cloned()
|
|
||||||
.map(HistoryEntry::Context),
|
|
||||||
);
|
|
||||||
|
|
||||||
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
|
|
||||||
history_entries
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recent_entries(&self, limit: usize, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
|
||||||
self.entries(cx).into_iter().take(limit).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let context_entries =
|
|
||||||
self.context_store
|
|
||||||
.read(cx)
|
|
||||||
.unordered_contexts()
|
|
||||||
.flat_map(|context| {
|
|
||||||
self.recently_opened_entries
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(index, entry)| match entry {
|
|
||||||
HistoryEntryId::Context(path) if &context.path == path => {
|
|
||||||
Some((index, HistoryEntry::Context(context.clone())))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
context_entries
|
|
||||||
// optimization to halt iteration early
|
|
||||||
.take(self.recently_opened_entries.len())
|
|
||||||
.sorted_unstable_by_key(|(index, _)| *index)
|
|
||||||
.map(|(_, entry)| entry)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
|
|
||||||
let serialized_entries = self
|
|
||||||
.recently_opened_entries
|
|
||||||
.iter()
|
|
||||||
.filter_map(|entry| match entry {
|
|
||||||
HistoryEntryId::Context(path) => path.file_name().map(|file| {
|
|
||||||
SerializedRecentOpen::ContextName(file.to_string_lossy().to_string())
|
|
||||||
}),
|
|
||||||
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
self._save_recently_opened_entries_task = cx.spawn(async move |_, cx| {
|
|
||||||
cx.background_executor()
|
|
||||||
.timer(SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE)
|
|
||||||
.await;
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
|
||||||
let content = serde_json::to_string(&serialized_entries)?;
|
|
||||||
std::fs::write(path, content)?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> {
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
|
||||||
let contents = match smol::fs::read_to_string(path).await {
|
|
||||||
Ok(it) => it,
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(e)
|
|
||||||
.context("deserializing persisted agent panel navigation history");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
|
|
||||||
.context("deserializing persisted agent panel navigation history")?
|
|
||||||
.into_iter()
|
|
||||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
|
||||||
.flat_map(|entry| match entry {
|
|
||||||
SerializedRecentOpen::Thread(id) => {
|
|
||||||
Some(HistoryEntryId::Thread(id.as_str().into()))
|
|
||||||
}
|
|
||||||
SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
|
|
||||||
contexts_dir().join(file_name).into(),
|
|
||||||
)),
|
|
||||||
SerializedRecentOpen::Context(path) => {
|
|
||||||
Path::new(&path).file_name().map(|file_name| {
|
|
||||||
HistoryEntryId::Context(contexts_dir().join(file_name).into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(entries)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
|
|
||||||
self.recently_opened_entries
|
|
||||||
.retain(|old_entry| old_entry != &entry);
|
|
||||||
self.recently_opened_entries.push_front(entry);
|
|
||||||
self.recently_opened_entries
|
|
||||||
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
|
|
||||||
self.save_recently_opened_entries(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
|
|
||||||
self.recently_opened_entries.retain(
|
|
||||||
|entry| !matches!(entry, HistoryEntryId::Thread(thread_id) if thread_id == &id),
|
|
||||||
);
|
|
||||||
self.save_recently_opened_entries(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_recently_opened_text_thread(
|
|
||||||
&mut self,
|
|
||||||
old_path: &Path,
|
|
||||||
new_path: &Arc<Path>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
for entry in &mut self.recently_opened_entries {
|
|
||||||
match entry {
|
|
||||||
HistoryEntryId::Context(path) if path.as_ref() == old_path => {
|
|
||||||
*entry = HistoryEntryId::Context(new_path.clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.save_recently_opened_entries(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
|
|
||||||
self.recently_opened_entries
|
|
||||||
.retain(|old_entry| old_entry != entry);
|
|
||||||
self.save_recently_opened_entries(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -234,7 +234,6 @@ impl MessageSegment {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct ProjectSnapshot {
|
pub struct ProjectSnapshot {
|
||||||
pub worktree_snapshots: Vec<WorktreeSnapshot>,
|
pub worktree_snapshots: Vec<WorktreeSnapshot>,
|
||||||
pub unsaved_buffer_paths: Vec<String>,
|
|
||||||
pub timestamp: DateTime<Utc>,
|
pub timestamp: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1277,62 +1276,6 @@ impl Thread {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retry_last_completion(
|
|
||||||
&mut self,
|
|
||||||
window: Option<AnyWindowHandle>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
// Clear any existing error state
|
|
||||||
self.retry_state = None;
|
|
||||||
|
|
||||||
// Use the last error context if available, otherwise fall back to configured model
|
|
||||||
let (model, intent) = if let Some((model, intent)) = self.last_error_context.take() {
|
|
||||||
(model, intent)
|
|
||||||
} else if let Some(configured_model) = self.configured_model.as_ref() {
|
|
||||||
let model = configured_model.model.clone();
|
|
||||||
let intent = if self.has_pending_tool_uses() {
|
|
||||||
CompletionIntent::ToolResults
|
|
||||||
} else {
|
|
||||||
CompletionIntent::UserPrompt
|
|
||||||
};
|
|
||||||
(model, intent)
|
|
||||||
} else if let Some(configured_model) = self.get_or_init_configured_model(cx) {
|
|
||||||
let model = configured_model.model.clone();
|
|
||||||
let intent = if self.has_pending_tool_uses() {
|
|
||||||
CompletionIntent::ToolResults
|
|
||||||
} else {
|
|
||||||
CompletionIntent::UserPrompt
|
|
||||||
};
|
|
||||||
(model, intent)
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.send_to_model(model, intent, window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_burn_mode_and_retry(
|
|
||||||
&mut self,
|
|
||||||
window: Option<AnyWindowHandle>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.completion_mode = CompletionMode::Burn;
|
|
||||||
cx.emit(ThreadEvent::ProfileChanged);
|
|
||||||
self.retry_last_completion(window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn used_tools_since_last_user_message(&self) -> bool {
|
|
||||||
for message in self.messages.iter().rev() {
|
|
||||||
if self.tool_use.message_has_tool_results(message.id) {
|
|
||||||
return true;
|
|
||||||
} else if message.role == Role::User {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
@@ -2857,27 +2800,11 @@ impl Thread {
|
|||||||
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, _| {
|
||||||
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
||||||
|
|
||||||
let mut unsaved_buffers = Vec::new();
|
|
||||||
cx.update(|app_cx| {
|
|
||||||
let buffer_store = project.read(app_cx).buffer_store();
|
|
||||||
for buffer_handle in buffer_store.read(app_cx).buffers() {
|
|
||||||
let buffer = buffer_handle.read(app_cx);
|
|
||||||
if buffer.is_dirty()
|
|
||||||
&& let Some(file) = buffer.file()
|
|
||||||
{
|
|
||||||
let path = file.path().to_string_lossy().to_string();
|
|
||||||
unsaved_buffers.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Arc::new(ProjectSnapshot {
|
Arc::new(ProjectSnapshot {
|
||||||
worktree_snapshots,
|
worktree_snapshots,
|
||||||
unsaved_buffer_paths: unsaved_buffers,
|
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -2892,7 +2819,7 @@ impl Thread {
|
|||||||
// Get worktree path and snapshot
|
// Get worktree path and snapshot
|
||||||
let worktree_info = cx.update(|app_cx| {
|
let worktree_info = cx.update(|app_cx| {
|
||||||
let worktree = worktree.read(app_cx);
|
let worktree = worktree.read(app_cx);
|
||||||
let path = worktree.abs_path().to_string_lossy().to_string();
|
let path = worktree.abs_path().to_string_lossy().into_owned();
|
||||||
let snapshot = worktree.snapshot();
|
let snapshot = worktree.snapshot();
|
||||||
(path, snapshot)
|
(path, snapshot)
|
||||||
});
|
});
|
||||||
@@ -3275,6 +3202,7 @@ mod tests {
|
|||||||
use agent_settings::{AgentProfileId, AgentSettings};
|
use agent_settings::{AgentProfileId, AgentSettings};
|
||||||
use assistant_tool::ToolRegistry;
|
use assistant_tool::ToolRegistry;
|
||||||
use assistant_tools;
|
use assistant_tools;
|
||||||
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::stream::BoxStream;
|
use futures::stream::BoxStream;
|
||||||
@@ -3292,15 +3220,15 @@ mod tests {
|
|||||||
use settings::{LanguageModelParameters, Settings, SettingsStore};
|
use settings::{LanguageModelParameters, Settings, SettingsStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use theme::ThemeSettings;
|
|
||||||
use util::path;
|
use util::path;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_message_with_context(cx: &mut TestAppContext) {
|
async fn test_message_with_context(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
)
|
)
|
||||||
@@ -3375,9 +3303,10 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_only_include_new_contexts(cx: &mut TestAppContext) {
|
async fn test_only_include_new_contexts(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({
|
json!({
|
||||||
"file1.rs": "fn function1() {}\n",
|
"file1.rs": "fn function1() {}\n",
|
||||||
@@ -3531,9 +3460,10 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_message_without_files(cx: &mut TestAppContext) {
|
async fn test_message_without_files(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
)
|
)
|
||||||
@@ -3610,9 +3540,10 @@ fn main() {{
|
|||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
#[ignore] // turn this test on when project_notifications tool is re-enabled
|
#[ignore] // turn this test on when project_notifications tool is re-enabled
|
||||||
async fn test_stale_buffer_notification(cx: &mut TestAppContext) {
|
async fn test_stale_buffer_notification(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
)
|
)
|
||||||
@@ -3738,9 +3669,10 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) {
|
async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
)
|
)
|
||||||
@@ -3760,9 +3692,10 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_serializing_thread_profile(cx: &mut TestAppContext) {
|
async fn test_serializing_thread_profile(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
)
|
)
|
||||||
@@ -3803,9 +3736,10 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_temperature_setting(cx: &mut TestAppContext) {
|
async fn test_temperature_setting(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(
|
let project = create_test_project(
|
||||||
|
&fs,
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
|
||||||
)
|
)
|
||||||
@@ -3897,9 +3831,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_thread_summary(cx: &mut TestAppContext) {
|
async fn test_thread_summary(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
|
|
||||||
let (_, _thread_store, thread, _context_store, model) =
|
let (_, _thread_store, thread, _context_store, model) =
|
||||||
setup_test_environment(cx, project.clone()).await;
|
setup_test_environment(cx, project.clone()).await;
|
||||||
@@ -3982,9 +3916,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_thread_summary_error_set_manually(cx: &mut TestAppContext) {
|
async fn test_thread_summary_error_set_manually(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
|
|
||||||
let (_, _thread_store, thread, _context_store, model) =
|
let (_, _thread_store, thread, _context_store, model) =
|
||||||
setup_test_environment(cx, project.clone()).await;
|
setup_test_environment(cx, project.clone()).await;
|
||||||
@@ -4004,9 +3938,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
|
async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
|
|
||||||
let (_, _thread_store, thread, _context_store, model) =
|
let (_, _thread_store, thread, _context_store, model) =
|
||||||
setup_test_environment(cx, project.clone()).await;
|
setup_test_environment(cx, project.clone()).await;
|
||||||
@@ -4158,9 +4092,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_retry_on_overloaded_error(cx: &mut TestAppContext) {
|
async fn test_retry_on_overloaded_error(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -4236,9 +4170,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_retry_on_internal_server_error(cx: &mut TestAppContext) {
|
async fn test_retry_on_internal_server_error(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -4318,9 +4252,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_exponential_backoff_on_retries(cx: &mut TestAppContext) {
|
async fn test_exponential_backoff_on_retries(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -4438,9 +4372,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_max_retries_exceeded(cx: &mut TestAppContext) {
|
async fn test_max_retries_exceeded(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -4529,9 +4463,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_retry_message_removed_on_retry(cx: &mut TestAppContext) {
|
async fn test_retry_message_removed_on_retry(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -4702,9 +4636,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_successful_completion_clears_retry_state(cx: &mut TestAppContext) {
|
async fn test_successful_completion_clears_retry_state(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -4868,9 +4802,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_rate_limit_retry_single_attempt(cx: &mut TestAppContext) {
|
async fn test_rate_limit_retry_single_attempt(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -5053,9 +4987,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_ui_only_messages_not_sent_to_model(cx: &mut TestAppContext) {
|
async fn test_ui_only_messages_not_sent_to_model(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Insert a regular user message
|
// Insert a regular user message
|
||||||
@@ -5153,9 +5087,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_no_retry_without_burn_mode(cx: &mut TestAppContext) {
|
async fn test_no_retry_without_burn_mode(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Ensure we're in Normal mode (not Burn mode)
|
// Ensure we're in Normal mode (not Burn mode)
|
||||||
@@ -5226,9 +5160,9 @@ fn main() {{
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_retry_canceled_on_stop(cx: &mut TestAppContext) {
|
async fn test_retry_canceled_on_stop(cx: &mut TestAppContext) {
|
||||||
init_test_settings(cx);
|
let fs = init_test_settings(cx);
|
||||||
|
|
||||||
let project = create_test_project(cx, json!({})).await;
|
let project = create_test_project(&fs, cx, json!({})).await;
|
||||||
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
|
||||||
|
|
||||||
// Enable Burn Mode to allow retries
|
// Enable Burn Mode to allow retries
|
||||||
@@ -5334,7 +5268,8 @@ fn main() {{
|
|||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_test_settings(cx: &mut TestAppContext) {
|
fn init_test_settings(cx: &mut TestAppContext) -> Arc<dyn Fs> {
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
@@ -5342,10 +5277,10 @@ fn main() {{
|
|||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
AgentSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(fs.clone(), cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
language_model::init_settings(cx);
|
language_model::init_settings(cx);
|
||||||
ThemeSettings::register(cx);
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
ToolRegistry::default_global(cx);
|
ToolRegistry::default_global(cx);
|
||||||
assistant_tool::init(cx);
|
assistant_tool::init(cx);
|
||||||
|
|
||||||
@@ -5356,16 +5291,17 @@ fn main() {{
|
|||||||
));
|
));
|
||||||
assistant_tools::init(http_client, cx);
|
assistant_tools::init(http_client, cx);
|
||||||
});
|
});
|
||||||
|
fs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to create a test project with test files
|
// Helper to create a test project with test files
|
||||||
async fn create_test_project(
|
async fn create_test_project(
|
||||||
|
fs: &Arc<dyn Fs>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
files: serde_json::Value,
|
files: serde_json::Value,
|
||||||
) -> Entity<Project> {
|
) -> Entity<Project> {
|
||||||
let fs = FakeFs::new(cx.executor());
|
fs.as_fake().insert_tree(path!("/test"), files).await;
|
||||||
fs.insert_tree(path!("/test"), files).await;
|
Project::test(fs.clone(), [path!("/test").as_ref()], cx).await
|
||||||
Project::test(fs, [path!("/test").as_ref()], cx).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_test_environment(
|
async fn setup_test_environment(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use assistant_tool::{Tool, ToolId, ToolWorkingSet};
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::ContextServerId;
|
use context_server::ContextServerId;
|
||||||
|
use fs::{Fs, RemoveOptions};
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt as _, StreamExt as _,
|
FutureExt as _, StreamExt as _,
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
@@ -37,9 +38,9 @@ use std::{
|
|||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, LazyLock, Mutex},
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::{ResultExt as _, rel_path::RelPath};
|
||||||
|
|
||||||
use zed_env_vars::ZED_STATELESS;
|
use zed_env_vars::ZED_STATELESS;
|
||||||
|
|
||||||
@@ -73,20 +74,22 @@ impl Column for DataType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const RULES_FILE_NAMES: [&str; 9] = [
|
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
|
||||||
".rules",
|
[
|
||||||
".cursorrules",
|
RelPath::unix(".rules").unwrap(),
|
||||||
".windsurfrules",
|
RelPath::unix(".cursorrules").unwrap(),
|
||||||
".clinerules",
|
RelPath::unix(".windsurfrules").unwrap(),
|
||||||
".github/copilot-instructions.md",
|
RelPath::unix(".clinerules").unwrap(),
|
||||||
"CLAUDE.md",
|
RelPath::unix(".github/copilot-instructions.md").unwrap(),
|
||||||
"AGENT.md",
|
RelPath::unix("CLAUDE.md").unwrap(),
|
||||||
"AGENTS.md",
|
RelPath::unix("AGENT.md").unwrap(),
|
||||||
"GEMINI.md",
|
RelPath::unix("AGENTS.md").unwrap(),
|
||||||
];
|
RelPath::unix("GEMINI.md").unwrap(),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||||
ThreadsDatabase::init(cx);
|
ThreadsDatabase::init(fs, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A system prompt shared by all threads created by this ThreadStore
|
/// A system prompt shared by all threads created by this ThreadStore
|
||||||
@@ -231,11 +234,10 @@ impl ThreadStore {
|
|||||||
self.enqueue_system_prompt_reload();
|
self.enqueue_system_prompt_reload();
|
||||||
}
|
}
|
||||||
project::Event::WorktreeUpdatedEntries(_, items) => {
|
project::Event::WorktreeUpdatedEntries(_, items) => {
|
||||||
if items.iter().any(|(path, _, _)| {
|
if items
|
||||||
RULES_FILE_NAMES
|
.iter()
|
||||||
.iter()
|
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
|
||||||
.any(|name| path.as_ref() == Path::new(name))
|
{
|
||||||
}) {
|
|
||||||
self.enqueue_system_prompt_reload();
|
self.enqueue_system_prompt_reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +329,7 @@ impl ThreadStore {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
||||||
let tree = worktree.read(cx);
|
let tree = worktree.read(cx);
|
||||||
let root_name = tree.root_name().into();
|
let root_name = tree.root_name_str().into();
|
||||||
let abs_path = tree.abs_path();
|
let abs_path = tree.abs_path();
|
||||||
|
|
||||||
let mut context = WorktreeContext {
|
let mut context = WorktreeContext {
|
||||||
@@ -869,13 +871,13 @@ impl ThreadsDatabase {
|
|||||||
GlobalThreadsDatabase::global(cx).0.clone()
|
GlobalThreadsDatabase::global(cx).0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(cx: &mut App) {
|
fn init(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||||
let executor = cx.background_executor().clone();
|
let executor = cx.background_executor().clone();
|
||||||
let database_future = executor
|
let database_future = executor
|
||||||
.spawn({
|
.spawn({
|
||||||
let executor = executor.clone();
|
let executor = executor.clone();
|
||||||
let threads_dir = paths::data_dir().join("threads");
|
let threads_dir = paths::data_dir().join("threads");
|
||||||
async move { ThreadsDatabase::new(threads_dir, executor) }
|
async move { ThreadsDatabase::new(fs, threads_dir, executor).await }
|
||||||
})
|
})
|
||||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -884,13 +886,17 @@ impl ThreadsDatabase {
|
|||||||
cx.set_global(GlobalThreadsDatabase(database_future));
|
cx.set_global(GlobalThreadsDatabase(database_future));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
|
pub async fn new(
|
||||||
std::fs::create_dir_all(&threads_dir)?;
|
fs: Arc<dyn Fs>,
|
||||||
|
threads_dir: PathBuf,
|
||||||
|
executor: BackgroundExecutor,
|
||||||
|
) -> Result<Self> {
|
||||||
|
fs.create_dir(&threads_dir).await?;
|
||||||
|
|
||||||
let sqlite_path = threads_dir.join("threads.db");
|
let sqlite_path = threads_dir.join("threads.db");
|
||||||
let mdb_path = threads_dir.join("threads-db.1.mdb");
|
let mdb_path = threads_dir.join("threads-db.1.mdb");
|
||||||
|
|
||||||
let needs_migration_from_heed = mdb_path.exists();
|
let needs_migration_from_heed = fs.is_file(&mdb_path).await;
|
||||||
|
|
||||||
let connection = if *ZED_STATELESS {
|
let connection = if *ZED_STATELESS {
|
||||||
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
|
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
|
||||||
@@ -932,7 +938,14 @@ impl ThreadsDatabase {
|
|||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
log::info!("Starting threads.db migration");
|
log::info!("Starting threads.db migration");
|
||||||
Self::migrate_from_heed(&mdb_path, db_connection, executor_clone)?;
|
Self::migrate_from_heed(&mdb_path, db_connection, executor_clone)?;
|
||||||
std::fs::remove_dir_all(mdb_path)?;
|
fs.remove_dir(
|
||||||
|
&mdb_path,
|
||||||
|
RemoveOptions {
|
||||||
|
recursive: true,
|
||||||
|
ignore_if_not_exists: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
log::info!("threads.db migrated to sqlite");
|
log::info!("threads.db migrated to sqlite");
|
||||||
Ok::<(), anyhow::Error>(())
|
Ok::<(), anyhow::Error>(())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
use util::rel_path::RelPath;
|
||||||
|
|
||||||
const RULES_FILE_NAMES: [&str; 9] = [
|
const RULES_FILE_NAMES: [&str; 9] = [
|
||||||
".rules",
|
".rules",
|
||||||
@@ -56,7 +57,7 @@ struct Session {
|
|||||||
|
|
||||||
pub struct LanguageModels {
|
pub struct LanguageModels {
|
||||||
/// Access language model by ID
|
/// Access language model by ID
|
||||||
models: HashMap<acp_thread::AgentModelId, Arc<dyn LanguageModel>>,
|
models: HashMap<acp::ModelId, Arc<dyn LanguageModel>>,
|
||||||
/// Cached list for returning language model information
|
/// Cached list for returning language model information
|
||||||
model_list: acp_thread::AgentModelList,
|
model_list: acp_thread::AgentModelList,
|
||||||
refresh_models_rx: watch::Receiver<()>,
|
refresh_models_rx: watch::Receiver<()>,
|
||||||
@@ -132,10 +133,7 @@ impl LanguageModels {
|
|||||||
self.refresh_models_rx.clone()
|
self.refresh_models_rx.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn model_from_id(
|
pub fn model_from_id(&self, model_id: &acp::ModelId) -> Option<Arc<dyn LanguageModel>> {
|
||||||
&self,
|
|
||||||
model_id: &acp_thread::AgentModelId,
|
|
||||||
) -> Option<Arc<dyn LanguageModel>> {
|
|
||||||
self.models.get(model_id).cloned()
|
self.models.get(model_id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +144,13 @@ impl LanguageModels {
|
|||||||
acp_thread::AgentModelInfo {
|
acp_thread::AgentModelInfo {
|
||||||
id: Self::model_id(model),
|
id: Self::model_id(model),
|
||||||
name: model.name().0,
|
name: model.name().0,
|
||||||
|
description: None,
|
||||||
icon: Some(provider.icon()),
|
icon: Some(provider.icon()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn model_id(model: &Arc<dyn LanguageModel>) -> acp_thread::AgentModelId {
|
fn model_id(model: &Arc<dyn LanguageModel>) -> acp::ModelId {
|
||||||
acp_thread::AgentModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
|
acp::ModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
|
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
|
||||||
@@ -436,7 +435,7 @@ impl NativeAgent {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
||||||
let tree = worktree.read(cx);
|
let tree = worktree.read(cx);
|
||||||
let root_name = tree.root_name().into();
|
let root_name = tree.root_name_str().into();
|
||||||
let abs_path = tree.abs_path();
|
let abs_path = tree.abs_path();
|
||||||
|
|
||||||
let mut context = WorktreeContext {
|
let mut context = WorktreeContext {
|
||||||
@@ -476,7 +475,7 @@ impl NativeAgent {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|name| {
|
.filter_map(|name| {
|
||||||
worktree
|
worktree
|
||||||
.entry_for_path(name)
|
.entry_for_path(RelPath::unix(name).unwrap())
|
||||||
.filter(|entry| entry.is_file())
|
.filter(|entry| entry.is_file())
|
||||||
.map(|entry| entry.path.clone())
|
.map(|entry| entry.path.clone())
|
||||||
})
|
})
|
||||||
@@ -560,7 +559,7 @@ impl NativeAgent {
|
|||||||
if items.iter().any(|(path, _, _)| {
|
if items.iter().any(|(path, _, _)| {
|
||||||
RULES_FILE_NAMES
|
RULES_FILE_NAMES
|
||||||
.iter()
|
.iter()
|
||||||
.any(|name| path.as_ref() == Path::new(name))
|
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
|
||||||
}) {
|
}) {
|
||||||
self.project_context_needs_refresh.send(()).ok();
|
self.project_context_needs_refresh.send(()).ok();
|
||||||
}
|
}
|
||||||
@@ -836,10 +835,15 @@ impl NativeAgentConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentModelSelector for NativeAgentConnection {
|
struct NativeAgentModelSelector {
|
||||||
|
session_id: acp::SessionId,
|
||||||
|
connection: NativeAgentConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
||||||
fn list_models(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
|
fn list_models(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
|
||||||
log::debug!("NativeAgentConnection::list_models called");
|
log::debug!("NativeAgentConnection::list_models called");
|
||||||
let list = self.0.read(cx).models.model_list.clone();
|
let list = self.connection.0.read(cx).models.model_list.clone();
|
||||||
Task::ready(if list.is_empty() {
|
Task::ready(if list.is_empty() {
|
||||||
Err(anyhow::anyhow!("No models available"))
|
Err(anyhow::anyhow!("No models available"))
|
||||||
} else {
|
} else {
|
||||||
@@ -847,24 +851,24 @@ impl AgentModelSelector for NativeAgentConnection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_model(
|
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>> {
|
||||||
&self,
|
log::debug!(
|
||||||
session_id: acp::SessionId,
|
"Setting model for session {}: {}",
|
||||||
model_id: acp_thread::AgentModelId,
|
self.session_id,
|
||||||
cx: &mut App,
|
model_id
|
||||||
) -> Task<Result<()>> {
|
);
|
||||||
log::debug!("Setting model for session {}: {}", session_id, model_id);
|
|
||||||
let Some(thread) = self
|
let Some(thread) = self
|
||||||
|
.connection
|
||||||
.0
|
.0
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.sessions
|
.sessions
|
||||||
.get(&session_id)
|
.get(&self.session_id)
|
||||||
.map(|session| session.thread.clone())
|
.map(|session| session.thread.clone())
|
||||||
else {
|
else {
|
||||||
return Task::ready(Err(anyhow!("Session not found")));
|
return Task::ready(Err(anyhow!("Session not found")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(model) = self.0.read(cx).models.model_from_id(&model_id) else {
|
let Some(model) = self.connection.0.read(cx).models.model_from_id(&model_id) else {
|
||||||
return Task::ready(Err(anyhow!("Invalid model ID {}", model_id)));
|
return Task::ready(Err(anyhow!("Invalid model ID {}", model_id)));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -872,33 +876,32 @@ impl AgentModelSelector for NativeAgentConnection {
|
|||||||
thread.set_model(model.clone(), cx);
|
thread.set_model(model.clone(), cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
update_settings_file(self.0.read(cx).fs.clone(), cx, move |settings, _cx| {
|
update_settings_file(
|
||||||
let provider = model.provider_id().0.to_string();
|
self.connection.0.read(cx).fs.clone(),
|
||||||
let model = model.id().0.to_string();
|
cx,
|
||||||
settings
|
move |settings, _cx| {
|
||||||
.agent
|
let provider = model.provider_id().0.to_string();
|
||||||
.get_or_insert_default()
|
let model = model.id().0.to_string();
|
||||||
.set_model(LanguageModelSelection {
|
settings
|
||||||
provider: provider.into(),
|
.agent
|
||||||
model,
|
.get_or_insert_default()
|
||||||
});
|
.set_model(LanguageModelSelection {
|
||||||
});
|
provider: provider.into(),
|
||||||
|
model,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_model(
|
fn selected_model(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelInfo>> {
|
||||||
&self,
|
|
||||||
session_id: &acp::SessionId,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<acp_thread::AgentModelInfo>> {
|
|
||||||
let session_id = session_id.clone();
|
|
||||||
|
|
||||||
let Some(thread) = self
|
let Some(thread) = self
|
||||||
|
.connection
|
||||||
.0
|
.0
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.sessions
|
.sessions
|
||||||
.get(&session_id)
|
.get(&self.session_id)
|
||||||
.map(|session| session.thread.clone())
|
.map(|session| session.thread.clone())
|
||||||
else {
|
else {
|
||||||
return Task::ready(Err(anyhow!("Session not found")));
|
return Task::ready(Err(anyhow!("Session not found")));
|
||||||
@@ -915,8 +918,8 @@ impl AgentModelSelector for NativeAgentConnection {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&self, cx: &mut App) -> watch::Receiver<()> {
|
fn watch(&self, cx: &mut App) -> Option<watch::Receiver<()>> {
|
||||||
self.0.read(cx).models.watch()
|
Some(self.connection.0.read(cx).models.watch())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -972,8 +975,11 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
|
|||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
|
fn model_selector(&self, session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {
|
||||||
Some(Rc::new(self.clone()) as Rc<dyn AgentModelSelector>)
|
Some(Rc::new(NativeAgentModelSelector {
|
||||||
|
session_id: session_id.clone(),
|
||||||
|
connection: self.clone(),
|
||||||
|
}) as Rc<dyn AgentModelSelector>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(
|
fn prompt(
|
||||||
@@ -1196,16 +1202,14 @@ mod tests {
|
|||||||
use crate::HistoryEntryId;
|
use crate::HistoryEntryId;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use acp_thread::{
|
use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelInfo, MentionUri};
|
||||||
AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo, MentionUri,
|
|
||||||
};
|
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use indoc::indoc;
|
use indoc::formatdoc;
|
||||||
use language_model::fake_provider::FakeLanguageModel;
|
use language_model::fake_provider::FakeLanguageModel;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use util::path;
|
use util::{path, rel_path::rel_path};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_maintaining_project_context(cx: &mut TestAppContext) {
|
async fn test_maintaining_project_context(cx: &mut TestAppContext) {
|
||||||
@@ -1255,14 +1259,17 @@ mod tests {
|
|||||||
fs.insert_file("/a/.rules", Vec::new()).await;
|
fs.insert_file("/a/.rules", Vec::new()).await;
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
agent.read_with(cx, |agent, cx| {
|
agent.read_with(cx, |agent, cx| {
|
||||||
let rules_entry = worktree.read(cx).entry_for_path(".rules").unwrap();
|
let rules_entry = worktree
|
||||||
|
.read(cx)
|
||||||
|
.entry_for_path(rel_path(".rules"))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
agent.project_context.read(cx).worktrees,
|
agent.project_context.read(cx).worktrees,
|
||||||
vec![WorktreeContext {
|
vec![WorktreeContext {
|
||||||
root_name: "a".into(),
|
root_name: "a".into(),
|
||||||
abs_path: Path::new("/a").into(),
|
abs_path: Path::new("/a").into(),
|
||||||
rules_file: Some(RulesFileContext {
|
rules_file: Some(RulesFileContext {
|
||||||
path_in_worktree: Path::new(".rules").into(),
|
path_in_worktree: rel_path(".rules").into(),
|
||||||
text: "".into(),
|
text: "".into(),
|
||||||
project_entry_id: rules_entry.id.to_usize()
|
project_entry_id: rules_entry.id.to_usize()
|
||||||
})
|
})
|
||||||
@@ -1292,7 +1299,25 @@ mod tests {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let models = cx.update(|cx| connection.list_models(cx)).await.unwrap();
|
// Create a thread/session
|
||||||
|
let acp_thread = cx
|
||||||
|
.update(|cx| {
|
||||||
|
Rc::new(connection.clone()).new_thread(project.clone(), Path::new("/a"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
|
||||||
|
|
||||||
|
let models = cx
|
||||||
|
.update(|cx| {
|
||||||
|
connection
|
||||||
|
.model_selector(&session_id)
|
||||||
|
.unwrap()
|
||||||
|
.list_models(cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let acp_thread::AgentModelList::Grouped(models) = models else {
|
let acp_thread::AgentModelList::Grouped(models) = models else {
|
||||||
panic!("Unexpected model group");
|
panic!("Unexpected model group");
|
||||||
@@ -1302,8 +1327,9 @@ mod tests {
|
|||||||
IndexMap::from_iter([(
|
IndexMap::from_iter([(
|
||||||
AgentModelGroupName("Fake".into()),
|
AgentModelGroupName("Fake".into()),
|
||||||
vec![AgentModelInfo {
|
vec![AgentModelInfo {
|
||||||
id: AgentModelId("fake/fake".into()),
|
id: acp::ModelId("fake/fake".into()),
|
||||||
name: "Fake".into(),
|
name: "Fake".into(),
|
||||||
|
description: None,
|
||||||
icon: Some(ui::IconName::ZedAssistant),
|
icon: Some(ui::IconName::ZedAssistant),
|
||||||
}]
|
}]
|
||||||
)])
|
)])
|
||||||
@@ -1360,8 +1386,9 @@ mod tests {
|
|||||||
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
|
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
|
||||||
|
|
||||||
// Select a model
|
// Select a model
|
||||||
let model_id = AgentModelId("fake/fake".into());
|
let selector = connection.model_selector(&session_id).unwrap();
|
||||||
cx.update(|cx| connection.select_model(session_id.clone(), model_id.clone(), cx))
|
let model_id = acp::ModelId("fake/fake".into());
|
||||||
|
cx.update(|cx| selector.select_model(model_id.clone(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1391,7 +1418,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
#[cfg_attr(target_os = "windows", ignore)] // TODO: Fix this test on Windows
|
|
||||||
async fn test_save_load_thread(cx: &mut TestAppContext) {
|
async fn test_save_load_thread(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
let fs = FakeFs::new(cx.executor());
|
let fs = FakeFs::new(cx.executor());
|
||||||
@@ -1471,17 +1497,22 @@ mod tests {
|
|||||||
model.send_last_completion_stream_text_chunk("Lorem.");
|
model.send_last_completion_stream_text_chunk("Lorem.");
|
||||||
model.end_last_completion_stream();
|
model.end_last_completion_stream();
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
summary_model.send_last_completion_stream_text_chunk("Explaining /a/b.md");
|
summary_model
|
||||||
|
.send_last_completion_stream_text_chunk(&format!("Explaining {}", path!("/a/b.md")));
|
||||||
summary_model.end_last_completion_stream();
|
summary_model.end_last_completion_stream();
|
||||||
|
|
||||||
send.await.unwrap();
|
send.await.unwrap();
|
||||||
|
let uri = MentionUri::File {
|
||||||
|
abs_path: path!("/a/b.md").into(),
|
||||||
|
}
|
||||||
|
.to_uri();
|
||||||
acp_thread.read_with(cx, |thread, cx| {
|
acp_thread.read_with(cx, |thread, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
thread.to_markdown(cx),
|
thread.to_markdown(cx),
|
||||||
indoc! {"
|
formatdoc! {"
|
||||||
## User
|
## User
|
||||||
|
|
||||||
What does [@b.md](file:///a/b.md) mean?
|
What does [@b.md]({uri}) mean?
|
||||||
|
|
||||||
## Assistant
|
## Assistant
|
||||||
|
|
||||||
@@ -1507,7 +1538,7 @@ mod tests {
|
|||||||
history_entries(&history_store, cx),
|
history_entries(&history_store, cx),
|
||||||
vec![(
|
vec![(
|
||||||
HistoryEntryId::AcpThread(session_id.clone()),
|
HistoryEntryId::AcpThread(session_id.clone()),
|
||||||
"Explaining /a/b.md".into()
|
format!("Explaining {}", path!("/a/b.md"))
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
let acp_thread = agent
|
let acp_thread = agent
|
||||||
@@ -1517,10 +1548,10 @@ mod tests {
|
|||||||
acp_thread.read_with(cx, |thread, cx| {
|
acp_thread.read_with(cx, |thread, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
thread.to_markdown(cx),
|
thread.to_markdown(cx),
|
||||||
indoc! {"
|
formatdoc! {"
|
||||||
## User
|
## User
|
||||||
|
|
||||||
What does [@b.md](file:///a/b.md) mean?
|
What does [@b.md]({uri}) mean?
|
||||||
|
|
||||||
## Assistant
|
## Assistant
|
||||||
|
|
||||||
|
|||||||
@@ -422,17 +422,15 @@ mod tests {
|
|||||||
use agent::MessageSegment;
|
use agent::MessageSegment;
|
||||||
use agent::context::LoadedContext;
|
use agent::context::LoadedContext;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use fs::FakeFs;
|
use fs::{FakeFs, Fs};
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use http_client::FakeHttpClient;
|
use http_client::FakeHttpClient;
|
||||||
use language_model::Role;
|
use language_model::Role;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use serde_json::json;
|
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use util::test::TempTree;
|
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) {
|
fn init_test(fs: Arc<dyn Fs>, cx: &mut TestAppContext) {
|
||||||
env_logger::try_init().ok();
|
env_logger::try_init().ok();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
@@ -443,7 +441,7 @@ mod tests {
|
|||||||
let http_client = FakeHttpClient::with_404_response();
|
let http_client = FakeHttpClient::with_404_response();
|
||||||
let clock = Arc::new(clock::FakeSystemClock::new());
|
let clock = Arc::new(clock::FakeSystemClock::new());
|
||||||
let client = Client::new(clock, http_client, cx);
|
let client = Client::new(clock, http_client, cx);
|
||||||
agent::init(cx);
|
agent::init(fs, cx);
|
||||||
agent_settings::init(cx);
|
agent_settings::init(cx);
|
||||||
language_model::init(client, cx);
|
language_model::init(client, cx);
|
||||||
});
|
});
|
||||||
@@ -451,10 +449,8 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_retrieving_old_thread(cx: &mut TestAppContext) {
|
async fn test_retrieving_old_thread(cx: &mut TestAppContext) {
|
||||||
let tree = TempTree::new(json!({}));
|
|
||||||
util::paths::set_home_dir(tree.path().into());
|
|
||||||
init_test(cx);
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
init_test(fs.clone(), cx);
|
||||||
let project = Project::test(fs, [], cx).await;
|
let project = Project::test(fs, [], cx).await;
|
||||||
|
|
||||||
// Save a thread using the old agent.
|
// Save a thread using the old agent.
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ impl HistoryStore {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| match entry {
|
.filter_map(|entry| match entry {
|
||||||
HistoryEntryId::TextThread(path) => path.file_name().map(|file| {
|
HistoryEntryId::TextThread(path) => path.file_name().map(|file| {
|
||||||
SerializedRecentOpen::TextThread(file.to_string_lossy().to_string())
|
SerializedRecentOpen::TextThread(file.to_string_lossy().into_owned())
|
||||||
}),
|
}),
|
||||||
HistoryEntryId::AcpThread(id) => {
|
HistoryEntryId::AcpThread(id) => {
|
||||||
Some(SerializedRecentOpen::AcpThread(id.to_string()))
|
Some(SerializedRecentOpen::AcpThread(id.to_string()))
|
||||||
|
|||||||
@@ -48,16 +48,15 @@ The one exception to this is if the user references something you don't know abo
|
|||||||
## Code Block Formatting
|
## Code Block Formatting
|
||||||
|
|
||||||
Whenever you mention a code block, you MUST use ONLY use the following format:
|
Whenever you mention a code block, you MUST use ONLY use the following format:
|
||||||
|
|
||||||
```path/to/Something.blah#L123-456
|
```path/to/Something.blah#L123-456
|
||||||
(code goes here)
|
(code goes here)
|
||||||
```
|
```
|
||||||
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah
|
|
||||||
is a path in the project. (If there is no valid path in the project, then you can use
|
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah is a path in the project. (If there is no valid path in the project, then you can use /dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser does not understand the more common ```language syntax, or bare ``` blocks. It only understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
|
||||||
/dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser
|
|
||||||
does not understand the more common ```language syntax, or bare ``` blocks. It only
|
|
||||||
understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
|
|
||||||
Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP!
|
Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP!
|
||||||
You have made a mistake. You can only ever put paths after triple backticks!
|
You have made a mistake. You can only ever put paths after triple backticks!
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
Based on all the information I've gathered, here's a summary of how this system works:
|
Based on all the information I've gathered, here's a summary of how this system works:
|
||||||
1. The README file is loaded into the system.
|
1. The README file is loaded into the system.
|
||||||
@@ -74,6 +73,7 @@ This is the last header in the README.
|
|||||||
```
|
```
|
||||||
4. Finally, it passes this information on to the next process.
|
4. Finally, it passes this information on to the next process.
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
In Markdown, hash marks signify headings. For example:
|
In Markdown, hash marks signify headings. For example:
|
||||||
```/dev/null/example.md#L1-3
|
```/dev/null/example.md#L1-3
|
||||||
@@ -82,6 +82,7 @@ In Markdown, hash marks signify headings. For example:
|
|||||||
### Level 3 heading
|
### Level 3 heading
|
||||||
```
|
```
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
Here are examples of ways you must never render code blocks:
|
Here are examples of ways you must never render code blocks:
|
||||||
<bad_example_do_not_do_this>
|
<bad_example_do_not_do_this>
|
||||||
In Markdown, hash marks signify headings. For example:
|
In Markdown, hash marks signify headings. For example:
|
||||||
@@ -91,7 +92,9 @@ In Markdown, hash marks signify headings. For example:
|
|||||||
### Level 3 heading
|
### Level 3 heading
|
||||||
```
|
```
|
||||||
</bad_example_do_not_do_this>
|
</bad_example_do_not_do_this>
|
||||||
|
|
||||||
This example is unacceptable because it does not include the path.
|
This example is unacceptable because it does not include the path.
|
||||||
|
|
||||||
<bad_example_do_not_do_this>
|
<bad_example_do_not_do_this>
|
||||||
In Markdown, hash marks signify headings. For example:
|
In Markdown, hash marks signify headings. For example:
|
||||||
```markdown
|
```markdown
|
||||||
@@ -101,14 +104,15 @@ In Markdown, hash marks signify headings. For example:
|
|||||||
```
|
```
|
||||||
</bad_example_do_not_do_this>
|
</bad_example_do_not_do_this>
|
||||||
This example is unacceptable because it has the language instead of the path.
|
This example is unacceptable because it has the language instead of the path.
|
||||||
|
|
||||||
<bad_example_do_not_do_this>
|
<bad_example_do_not_do_this>
|
||||||
In Markdown, hash marks signify headings. For example:
|
In Markdown, hash marks signify headings. For example:
|
||||||
# Level 1 heading
|
# Level 1 heading
|
||||||
## Level 2 heading
|
## Level 2 heading
|
||||||
### Level 3 heading
|
### Level 3 heading
|
||||||
</bad_example_do_not_do_this>
|
</bad_example_do_not_do_this>
|
||||||
This example is unacceptable because it uses indentation to mark the code block
|
This example is unacceptable because it uses indentation to mark the code block instead of backticks with a path.
|
||||||
instead of backticks with a path.
|
|
||||||
<bad_example_do_not_do_this>
|
<bad_example_do_not_do_this>
|
||||||
In Markdown, hash marks signify headings. For example:
|
In Markdown, hash marks signify headings. For example:
|
||||||
```markdown
|
```markdown
|
||||||
|
|||||||
@@ -1850,8 +1850,18 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let connection = NativeAgentConnection(agent.clone());
|
let connection = NativeAgentConnection(agent.clone());
|
||||||
|
|
||||||
|
// Create a thread using new_thread
|
||||||
|
let connection_rc = Rc::new(connection.clone());
|
||||||
|
let acp_thread = cx
|
||||||
|
.update(|cx| connection_rc.new_thread(project, cwd, cx))
|
||||||
|
.await
|
||||||
|
.expect("new_thread should succeed");
|
||||||
|
|
||||||
|
// Get the session_id from the AcpThread
|
||||||
|
let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
|
||||||
|
|
||||||
// Test model_selector returns Some
|
// Test model_selector returns Some
|
||||||
let selector_opt = connection.model_selector();
|
let selector_opt = connection.model_selector(&session_id);
|
||||||
assert!(
|
assert!(
|
||||||
selector_opt.is_some(),
|
selector_opt.is_some(),
|
||||||
"agent2 should always support ModelSelector"
|
"agent2 should always support ModelSelector"
|
||||||
@@ -1868,23 +1878,16 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
|
|||||||
};
|
};
|
||||||
assert!(!listed_models.is_empty(), "should have at least one model");
|
assert!(!listed_models.is_empty(), "should have at least one model");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
listed_models[&AgentModelGroupName("Fake".into())][0].id.0,
|
listed_models[&AgentModelGroupName("Fake".into())][0]
|
||||||
|
.id
|
||||||
|
.0
|
||||||
|
.as_ref(),
|
||||||
"fake/fake"
|
"fake/fake"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a thread using new_thread
|
|
||||||
let connection_rc = Rc::new(connection.clone());
|
|
||||||
let acp_thread = cx
|
|
||||||
.update(|cx| connection_rc.new_thread(project, cwd, cx))
|
|
||||||
.await
|
|
||||||
.expect("new_thread should succeed");
|
|
||||||
|
|
||||||
// Get the session_id from the AcpThread
|
|
||||||
let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
|
|
||||||
|
|
||||||
// Test selected_model returns the default
|
// Test selected_model returns the default
|
||||||
let model = cx
|
let model = cx
|
||||||
.update(|cx| selector.selected_model(&session_id, cx))
|
.update(|cx| selector.selected_model(cx))
|
||||||
.await
|
.await
|
||||||
.expect("selected_model should succeed");
|
.expect("selected_model should succeed");
|
||||||
let model = cx
|
let model = cx
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ use agent_settings::{
|
|||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_tool::adapt_schema_to_format;
|
use assistant_tool::adapt_schema_to_format;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use client::{ModelRequestUsage, RequestUsage};
|
use client::{ModelRequestUsage, RequestUsage, UserStore};
|
||||||
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
|
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, Plan, UsageLimit};
|
||||||
use collections::{HashMap, HashSet, IndexMap};
|
use collections::{HashMap, HashSet, IndexMap};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
|
use futures::stream;
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt,
|
FutureExt,
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
@@ -34,7 +35,7 @@ use language_model::{
|
|||||||
LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest,
|
||||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
|
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
|
||||||
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
|
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
|
||||||
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage,
|
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
Project,
|
Project,
|
||||||
@@ -585,6 +586,7 @@ pub struct Thread {
|
|||||||
pending_title_generation: Option<Task<()>>,
|
pending_title_generation: Option<Task<()>>,
|
||||||
summary: Option<SharedString>,
|
summary: Option<SharedString>,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
|
user_store: Entity<UserStore>,
|
||||||
completion_mode: CompletionMode,
|
completion_mode: CompletionMode,
|
||||||
/// Holds the task that handles agent interaction until the end of the turn.
|
/// Holds the task that handles agent interaction until the end of the turn.
|
||||||
/// Survives across multiple requests as the model performs tool calls and
|
/// Survives across multiple requests as the model performs tool calls and
|
||||||
@@ -641,6 +643,7 @@ impl Thread {
|
|||||||
pending_title_generation: None,
|
pending_title_generation: None,
|
||||||
summary: None,
|
summary: None,
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
|
user_store: project.read(cx).user_store(),
|
||||||
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
||||||
running_turn: None,
|
running_turn: None,
|
||||||
pending_message: None,
|
pending_message: None,
|
||||||
@@ -820,6 +823,7 @@ impl Thread {
|
|||||||
pending_title_generation: None,
|
pending_title_generation: None,
|
||||||
summary: db_thread.detailed_summary,
|
summary: db_thread.detailed_summary,
|
||||||
messages: db_thread.messages,
|
messages: db_thread.messages,
|
||||||
|
user_store: project.read(cx).user_store(),
|
||||||
completion_mode: db_thread.completion_mode.unwrap_or_default(),
|
completion_mode: db_thread.completion_mode.unwrap_or_default(),
|
||||||
running_turn: None,
|
running_turn: None,
|
||||||
pending_message: None,
|
pending_message: None,
|
||||||
@@ -879,27 +883,11 @@ impl Thread {
|
|||||||
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, _| {
|
||||||
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
||||||
|
|
||||||
let mut unsaved_buffers = Vec::new();
|
|
||||||
cx.update(|app_cx| {
|
|
||||||
let buffer_store = project.read(app_cx).buffer_store();
|
|
||||||
for buffer_handle in buffer_store.read(app_cx).buffers() {
|
|
||||||
let buffer = buffer_handle.read(app_cx);
|
|
||||||
if buffer.is_dirty()
|
|
||||||
&& let Some(file) = buffer.file()
|
|
||||||
{
|
|
||||||
let path = file.path().to_string_lossy().to_string();
|
|
||||||
unsaved_buffers.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Arc::new(ProjectSnapshot {
|
Arc::new(ProjectSnapshot {
|
||||||
worktree_snapshots,
|
worktree_snapshots,
|
||||||
unsaved_buffer_paths: unsaved_buffers,
|
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -914,7 +902,7 @@ impl Thread {
|
|||||||
// Get worktree path and snapshot
|
// Get worktree path and snapshot
|
||||||
let worktree_info = cx.update(|app_cx| {
|
let worktree_info = cx.update(|app_cx| {
|
||||||
let worktree = worktree.read(app_cx);
|
let worktree = worktree.read(app_cx);
|
||||||
let path = worktree.abs_path().to_string_lossy().to_string();
|
let path = worktree.abs_path().to_string_lossy().into_owned();
|
||||||
let snapshot = worktree.snapshot();
|
let snapshot = worktree.snapshot();
|
||||||
(path, snapshot)
|
(path, snapshot)
|
||||||
});
|
});
|
||||||
@@ -1265,12 +1253,12 @@ impl Thread {
|
|||||||
);
|
);
|
||||||
|
|
||||||
log::debug!("Calling model.stream_completion, attempt {}", attempt);
|
log::debug!("Calling model.stream_completion, attempt {}", attempt);
|
||||||
let mut events = model
|
|
||||||
.stream_completion(request, cx)
|
let (mut events, mut error) = match model.stream_completion(request, cx).await {
|
||||||
.await
|
Ok(events) => (events, None),
|
||||||
.map_err(|error| anyhow!(error))?;
|
Err(err) => (stream::empty().boxed(), Some(err)),
|
||||||
|
};
|
||||||
let mut tool_results = FuturesUnordered::new();
|
let mut tool_results = FuturesUnordered::new();
|
||||||
let mut error = None;
|
|
||||||
while let Some(event) = events.next().await {
|
while let Some(event) = events.next().await {
|
||||||
log::trace!("Received completion event: {:?}", event);
|
log::trace!("Received completion event: {:?}", event);
|
||||||
match event {
|
match event {
|
||||||
@@ -1318,8 +1306,10 @@ impl Thread {
|
|||||||
|
|
||||||
if let Some(error) = error {
|
if let Some(error) = error {
|
||||||
attempt += 1;
|
attempt += 1;
|
||||||
let retry =
|
let retry = this.update(cx, |this, cx| {
|
||||||
this.update(cx, |this, _| this.handle_completion_error(error, attempt))??;
|
let user_store = this.user_store.read(cx);
|
||||||
|
this.handle_completion_error(error, attempt, user_store.plan())
|
||||||
|
})??;
|
||||||
let timer = cx.background_executor().timer(retry.duration);
|
let timer = cx.background_executor().timer(retry.duration);
|
||||||
event_stream.send_retry(retry);
|
event_stream.send_retry(retry);
|
||||||
timer.await;
|
timer.await;
|
||||||
@@ -1346,8 +1336,23 @@ impl Thread {
|
|||||||
&mut self,
|
&mut self,
|
||||||
error: LanguageModelCompletionError,
|
error: LanguageModelCompletionError,
|
||||||
attempt: u8,
|
attempt: u8,
|
||||||
|
plan: Option<Plan>,
|
||||||
) -> Result<acp_thread::RetryStatus> {
|
) -> Result<acp_thread::RetryStatus> {
|
||||||
if self.completion_mode == CompletionMode::Normal {
|
let Some(model) = self.model.as_ref() else {
|
||||||
|
return Err(anyhow!(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
let auto_retry = if model.provider_id() == ZED_CLOUD_PROVIDER_ID {
|
||||||
|
match plan {
|
||||||
|
Some(Plan::V2(_)) => true,
|
||||||
|
Some(Plan::V1(_)) => self.completion_mode == CompletionMode::Burn,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if !auto_retry {
|
||||||
return Err(anyhow!(error));
|
return Err(anyhow!(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ use std::sync::Arc;
|
|||||||
use util::markdown::MarkdownInlineCode;
|
use util::markdown::MarkdownInlineCode;
|
||||||
|
|
||||||
/// Copies a file or directory in the project, and returns confirmation that the copy succeeded.
|
/// Copies a file or directory in the project, and returns confirmation that the copy succeeded.
|
||||||
/// Directory contents will be copied recursively (like `cp -r`).
|
/// Directory contents will be copied recursively.
|
||||||
///
|
///
|
||||||
/// This tool should be used when it's desirable to create a copy of a file or directory without modifying the original.
|
/// This tool should be used when it's desirable to create a copy of a file or directory without modifying the original.
|
||||||
/// It's much more efficient than doing this by separately reading and then writing the file or directory's contents, so this tool should be preferred over that approach whenever copying is the goal.
|
/// It's much more efficient than doing this by separately reading and then writing the file or directory's contents, so this tool should be preferred over that approach whenever copying is the goal.
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct CopyPathToolInput {
|
pub struct CopyPathToolInput {
|
||||||
/// The source path of the file or directory to copy.
|
/// The source path of the file or directory to copy.
|
||||||
/// If a directory is specified, its contents will be copied recursively (like `cp -r`).
|
/// If a directory is specified, its contents will be copied recursively.
|
||||||
///
|
///
|
||||||
/// <example>
|
/// <example>
|
||||||
/// If the project has the following files:
|
/// If the project has the following files:
|
||||||
@@ -84,9 +84,7 @@ impl AgentTool for CopyPathTool {
|
|||||||
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
||||||
{
|
{
|
||||||
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
||||||
Some(project_path) => {
|
Some(project_path) => project.copy_entry(entity.id, project_path, cx),
|
||||||
project.copy_entry(entity.id, None, project_path.path, cx)
|
|
||||||
}
|
|
||||||
None => Task::ready(Err(anyhow!(
|
None => Task::ready(Err(anyhow!(
|
||||||
"Destination path {} was outside the project.",
|
"Destination path {} was outside the project.",
|
||||||
input.destination_path
|
input.destination_path
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{AgentTool, ToolCallEventStream};
|
|||||||
|
|
||||||
/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
|
/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
|
||||||
///
|
///
|
||||||
/// This tool creates a directory and all necessary parent directories (similar to `mkdir -p`). It should be used whenever you need to create new directories within the project.
|
/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project.
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct CreateDirectoryToolInput {
|
pub struct CreateDirectoryToolInput {
|
||||||
/// The path of the new directory.
|
/// The path of the new directory.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use language::{DiagnosticSeverity, OffsetRangeExt};
|
|||||||
use project::Project;
|
use project::Project;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt::Write, path::Path, sync::Arc};
|
use std::{fmt::Write, sync::Arc};
|
||||||
use ui::SharedString;
|
use ui::SharedString;
|
||||||
use util::markdown::MarkdownInlineCode;
|
use util::markdown::MarkdownInlineCode;
|
||||||
|
|
||||||
@@ -147,9 +147,7 @@ impl AgentTool for DiagnosticsTool {
|
|||||||
has_diagnostics = true;
|
has_diagnostics = true;
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
"{}: {} error(s), {} warning(s)\n",
|
"{}: {} error(s), {} warning(s)\n",
|
||||||
Path::new(worktree.read(cx).root_name())
|
worktree.read(cx).absolutize(&project_path.path).display(),
|
||||||
.join(project_path.path)
|
|
||||||
.display(),
|
|
||||||
summary.error_count,
|
summary.error_count,
|
||||||
summary.warning_count
|
summary.warning_count
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ use schemars::JsonSchema;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::stream::StreamExt as _;
|
use smol::stream::StreamExt as _;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::SharedString;
|
use ui::SharedString;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
use util::rel_path::RelPath;
|
||||||
|
|
||||||
const DEFAULT_UI_TEXT: &str = "Editing file";
|
const DEFAULT_UI_TEXT: &str = "Editing file";
|
||||||
|
|
||||||
@@ -148,12 +150,11 @@ impl EditFileTool {
|
|||||||
|
|
||||||
// If any path component matches the local settings folder, then this could affect
|
// If any path component matches the local settings folder, then this could affect
|
||||||
// the editor in ways beyond the project source, so prompt.
|
// the editor in ways beyond the project source, so prompt.
|
||||||
let local_settings_folder = paths::local_settings_folder_relative_path();
|
let local_settings_folder = paths::local_settings_folder_name();
|
||||||
let path = Path::new(&input.path);
|
let path = Path::new(&input.path);
|
||||||
if path
|
if path.components().any(|component| {
|
||||||
.components()
|
component.as_os_str() == <_ as AsRef<OsStr>>::as_ref(&local_settings_folder)
|
||||||
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
|
}) {
|
||||||
{
|
|
||||||
return event_stream.authorize(
|
return event_stream.authorize(
|
||||||
format!("{} (local settings)", input.display_description),
|
format!("{} (local settings)", input.display_description),
|
||||||
cx,
|
cx,
|
||||||
@@ -162,6 +163,7 @@ impl EditFileTool {
|
|||||||
|
|
||||||
// It's also possible that the global config dir is configured to be inside the project,
|
// It's also possible that the global config dir is configured to be inside the project,
|
||||||
// so check for that edge case too.
|
// so check for that edge case too.
|
||||||
|
// TODO this is broken when remoting
|
||||||
if let Ok(canonical_path) = std::fs::canonicalize(&input.path)
|
if let Ok(canonical_path) = std::fs::canonicalize(&input.path)
|
||||||
&& canonical_path.starts_with(paths::config_dir())
|
&& canonical_path.starts_with(paths::config_dir())
|
||||||
{
|
{
|
||||||
@@ -216,9 +218,7 @@ impl AgentTool for EditFileTool {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.short_full_path_for_project_path(&project_path, cx)
|
.short_full_path_for_project_path(&project_path, cx)
|
||||||
})
|
})
|
||||||
.unwrap_or(Path::new(&input.path).into())
|
.unwrap_or(input.path.to_string_lossy().into_owned())
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
.into(),
|
.into(),
|
||||||
Err(raw_input) => {
|
Err(raw_input) => {
|
||||||
if let Some(input) =
|
if let Some(input) =
|
||||||
@@ -235,9 +235,7 @@ impl AgentTool for EditFileTool {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.short_full_path_for_project_path(&project_path, cx)
|
.short_full_path_for_project_path(&project_path, cx)
|
||||||
})
|
})
|
||||||
.unwrap_or(Path::new(&input.path).into())
|
.unwrap_or(input.path)
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +476,7 @@ impl AgentTool for EditFileTool {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
event_stream.update_diff(cx.new(|cx| {
|
event_stream.update_diff(cx.new(|cx| {
|
||||||
Diff::finalized(
|
Diff::finalized(
|
||||||
output.input_path,
|
output.input_path.to_string_lossy().into_owned(),
|
||||||
Some(output.old_text.to_string()),
|
Some(output.old_text.to_string()),
|
||||||
output.new_text,
|
output.new_text,
|
||||||
self.language_registry.clone(),
|
self.language_registry.clone(),
|
||||||
@@ -542,10 +540,12 @@ fn resolve_path(
|
|||||||
let file_name = input
|
let file_name = input
|
||||||
.path
|
.path
|
||||||
.file_name()
|
.file_name()
|
||||||
|
.and_then(|file_name| file_name.to_str())
|
||||||
|
.and_then(|file_name| RelPath::unix(file_name).ok())
|
||||||
.context("Can't create file: invalid filename")?;
|
.context("Can't create file: invalid filename")?;
|
||||||
|
|
||||||
let new_file_path = parent_project_path.map(|parent| ProjectPath {
|
let new_file_path = parent_project_path.map(|parent| ProjectPath {
|
||||||
path: Arc::from(parent.path.join(file_name)),
|
path: parent.path.join(file_name),
|
||||||
..parent
|
..parent
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -565,7 +565,7 @@ mod tests {
|
|||||||
use prompt_store::ProjectContext;
|
use prompt_store::ProjectContext;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use util::path;
|
use util::{path, rel_path::rel_path};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_edit_nonexistent_file(cx: &mut TestAppContext) {
|
async fn test_edit_nonexistent_file(cx: &mut TestAppContext) {
|
||||||
@@ -614,13 +614,13 @@ mod tests {
|
|||||||
let mode = &EditFileMode::Create;
|
let mode = &EditFileMode::Create;
|
||||||
|
|
||||||
let result = test_resolve_path(mode, "root/new.txt", cx);
|
let result = test_resolve_path(mode, "root/new.txt", cx);
|
||||||
assert_resolved_path_eq(result.await, "new.txt");
|
assert_resolved_path_eq(result.await, rel_path("new.txt"));
|
||||||
|
|
||||||
let result = test_resolve_path(mode, "new.txt", cx);
|
let result = test_resolve_path(mode, "new.txt", cx);
|
||||||
assert_resolved_path_eq(result.await, "new.txt");
|
assert_resolved_path_eq(result.await, rel_path("new.txt"));
|
||||||
|
|
||||||
let result = test_resolve_path(mode, "dir/new.txt", cx);
|
let result = test_resolve_path(mode, "dir/new.txt", cx);
|
||||||
assert_resolved_path_eq(result.await, "dir/new.txt");
|
assert_resolved_path_eq(result.await, rel_path("dir/new.txt"));
|
||||||
|
|
||||||
let result = test_resolve_path(mode, "root/dir/subdir/existing.txt", cx);
|
let result = test_resolve_path(mode, "root/dir/subdir/existing.txt", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -642,10 +642,10 @@ mod tests {
|
|||||||
let path_with_root = "root/dir/subdir/existing.txt";
|
let path_with_root = "root/dir/subdir/existing.txt";
|
||||||
let path_without_root = "dir/subdir/existing.txt";
|
let path_without_root = "dir/subdir/existing.txt";
|
||||||
let result = test_resolve_path(mode, path_with_root, cx);
|
let result = test_resolve_path(mode, path_with_root, cx);
|
||||||
assert_resolved_path_eq(result.await, path_without_root);
|
assert_resolved_path_eq(result.await, rel_path(path_without_root));
|
||||||
|
|
||||||
let result = test_resolve_path(mode, path_without_root, cx);
|
let result = test_resolve_path(mode, path_without_root, cx);
|
||||||
assert_resolved_path_eq(result.await, path_without_root);
|
assert_resolved_path_eq(result.await, rel_path(path_without_root));
|
||||||
|
|
||||||
let result = test_resolve_path(mode, "root/nonexistent.txt", cx);
|
let result = test_resolve_path(mode, "root/nonexistent.txt", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -690,14 +690,10 @@ mod tests {
|
|||||||
cx.update(|cx| resolve_path(&input, project, cx))
|
cx.update(|cx| resolve_path(&input, project, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_resolved_path_eq(path: anyhow::Result<ProjectPath>, expected: &str) {
|
#[track_caller]
|
||||||
let actual = path
|
fn assert_resolved_path_eq(path: anyhow::Result<ProjectPath>, expected: &RelPath) {
|
||||||
.expect("Should return valid path")
|
let actual = path.expect("Should return valid path").path;
|
||||||
.path
|
assert_eq!(actual.as_ref(), expected);
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.replace("\\", "/"); // Naive Windows paths normalization
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@@ -794,7 +790,7 @@ mod tests {
|
|||||||
store.update_user_settings(cx, |settings| {
|
store.update_user_settings(cx, |settings| {
|
||||||
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
|
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
|
||||||
settings.project.all_languages.defaults.formatter =
|
settings.project.all_languages.defaults.formatter =
|
||||||
Some(language::language_settings::SelectedFormatter::Auto);
|
Some(language::language_settings::FormatterList::default());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1408,8 +1404,8 @@ mod tests {
|
|||||||
// Parent directory references - find_project_path resolves these
|
// Parent directory references - find_project_path resolves these
|
||||||
(
|
(
|
||||||
"project/../other",
|
"project/../other",
|
||||||
false,
|
true,
|
||||||
"Path with .. is resolved by find_project_path",
|
"Path with .. that goes outside of root directory",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"project/./src/file.rs",
|
"project/./src/file.rs",
|
||||||
@@ -1437,16 +1433,18 @@ mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
if should_confirm {
|
if should_confirm {
|
||||||
stream_rx.expect_authorization().await;
|
stream_rx.expect_authorization().await;
|
||||||
} else {
|
} else {
|
||||||
auth.await.unwrap();
|
|
||||||
assert!(
|
assert!(
|
||||||
stream_rx.try_next().is_err(),
|
stream_rx.try_next().is_err(),
|
||||||
"Failed for case: {} - path: {} - expected no confirmation but got one",
|
"Failed for case: {} - path: {} - expected no confirmation but got one",
|
||||||
description,
|
description,
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
|
auth.await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,10 +156,14 @@ impl AgentTool for FindPathTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Result<Vec<PathBuf>>> {
|
fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Result<Vec<PathBuf>>> {
|
||||||
let path_matcher = match PathMatcher::new([
|
let path_style = project.read(cx).path_style(cx);
|
||||||
// Sometimes models try to search for "". In this case, return all paths in the project.
|
let path_matcher = match PathMatcher::new(
|
||||||
if glob.is_empty() { "*" } else { glob },
|
[
|
||||||
]) {
|
// Sometimes models try to search for "". In this case, return all paths in the project.
|
||||||
|
if glob.is_empty() { "*" } else { glob },
|
||||||
|
],
|
||||||
|
path_style,
|
||||||
|
) {
|
||||||
Ok(matcher) => matcher,
|
Ok(matcher) => matcher,
|
||||||
Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))),
|
Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))),
|
||||||
};
|
};
|
||||||
@@ -173,9 +177,8 @@ fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Resu
|
|||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for snapshot in snapshots {
|
for snapshot in snapshots {
|
||||||
for entry in snapshot.entries(false, 0) {
|
for entry in snapshot.entries(false, 0) {
|
||||||
let root_name = PathBuf::from(snapshot.root_name());
|
if path_matcher.is_match(snapshot.root_name().join(&entry.path).as_std_path()) {
|
||||||
if path_matcher.is_match(root_name.join(&entry.path)) {
|
results.push(snapshot.absolutize(&entry.path));
|
||||||
results.push(snapshot.abs_path().join(entry.path.as_ref()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,12 +110,15 @@ impl AgentTool for GrepTool {
|
|||||||
const CONTEXT_LINES: u32 = 2;
|
const CONTEXT_LINES: u32 = 2;
|
||||||
const MAX_ANCESTOR_LINES: u32 = 10;
|
const MAX_ANCESTOR_LINES: u32 = 10;
|
||||||
|
|
||||||
|
let path_style = self.project.read(cx).path_style(cx);
|
||||||
|
|
||||||
let include_matcher = match PathMatcher::new(
|
let include_matcher = match PathMatcher::new(
|
||||||
input
|
input
|
||||||
.include_pattern
|
.include_pattern
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
path_style,
|
||||||
) {
|
) {
|
||||||
Ok(matcher) => matcher,
|
Ok(matcher) => matcher,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
@@ -132,7 +135,7 @@ impl AgentTool for GrepTool {
|
|||||||
.iter()
|
.iter()
|
||||||
.chain(global_settings.private_files.sources().iter());
|
.chain(global_settings.private_files.sources().iter());
|
||||||
|
|
||||||
match PathMatcher::new(exclude_patterns) {
|
match PathMatcher::new(exclude_patterns, path_style) {
|
||||||
Ok(matcher) => matcher,
|
Ok(matcher) => matcher,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
return Task::ready(Err(anyhow!("invalid exclude pattern: {error}")));
|
return Task::ready(Err(anyhow!("invalid exclude pattern: {error}")));
|
||||||
@@ -834,11 +837,14 @@ mod tests {
|
|||||||
"**/.secretdir".to_string(),
|
"**/.secretdir".to_string(),
|
||||||
"**/.mymetadata".to_string(),
|
"**/.mymetadata".to_string(),
|
||||||
]);
|
]);
|
||||||
settings.project.worktree.private_files = Some(vec![
|
settings.project.worktree.private_files = Some(
|
||||||
"**/.mysecrets".to_string(),
|
vec![
|
||||||
"**/*.privatekey".to_string(),
|
"**/.mysecrets".to_string(),
|
||||||
"**/*.mysensitive".to_string(),
|
"**/*.privatekey".to_string(),
|
||||||
]);
|
"**/*.mysensitive".to_string(),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1064,7 +1070,8 @@ mod tests {
|
|||||||
store.update_user_settings(cx, |settings| {
|
store.update_user_settings(cx, |settings| {
|
||||||
settings.project.worktree.file_scan_exclusions =
|
settings.project.worktree.file_scan_exclusions =
|
||||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
settings.project.worktree.private_files =
|
||||||
|
Some(vec!["**/.env".to_string()].into());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use crate::{AgentTool, ToolCallEventStream};
|
|||||||
use agent_client_protocol::ToolKind;
|
use agent_client_protocol::ToolKind;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use gpui::{App, Entity, SharedString, Task};
|
use gpui::{App, Entity, SharedString, Task};
|
||||||
use project::{Project, WorktreeSettings};
|
use project::{Project, ProjectPath, WorktreeSettings};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::{path::Path, sync::Arc};
|
use std::sync::Arc;
|
||||||
use util::markdown::MarkdownInlineCode;
|
use util::markdown::MarkdownInlineCode;
|
||||||
|
|
||||||
/// Lists files and directories in a given path. Prefer the `grep` or `find_path` tools when searching the codebase.
|
/// Lists files and directories in a given path. Prefer the `grep` or `find_path` tools when searching the codebase.
|
||||||
@@ -86,13 +86,13 @@ impl AgentTool for ListDirectoryTool {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
.filter_map(|worktree| {
|
.filter_map(|worktree| {
|
||||||
worktree.read(cx).root_entry().and_then(|entry| {
|
let worktree = worktree.read(cx);
|
||||||
if entry.is_dir() {
|
let root_entry = worktree.root_entry()?;
|
||||||
entry.path.to_str()
|
if root_entry.is_dir() {
|
||||||
} else {
|
Some(root_entry.path.display(worktree.path_style()))
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
@@ -143,7 +143,7 @@ impl AgentTool for ListDirectoryTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let worktree_snapshot = worktree.read(cx).snapshot();
|
let worktree_snapshot = worktree.read(cx).snapshot();
|
||||||
let worktree_root_name = worktree.read(cx).root_name().to_string();
|
let worktree_root_name = worktree.read(cx).root_name();
|
||||||
|
|
||||||
let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else {
|
let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else {
|
||||||
return Task::ready(Err(anyhow!("Path not found: {}", input.path)));
|
return Task::ready(Err(anyhow!("Path not found: {}", input.path)));
|
||||||
@@ -165,25 +165,17 @@ impl AgentTool for ListDirectoryTool {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self
|
let project_path: ProjectPath = (worktree_snapshot.id(), entry.path.clone()).into();
|
||||||
.project
|
if worktree_settings.is_path_excluded(&project_path.path)
|
||||||
.read(cx)
|
|| worktree_settings.is_path_private(&project_path.path)
|
||||||
.find_project_path(&entry.path, cx)
|
|
||||||
.map(|project_path| {
|
|
||||||
let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx);
|
|
||||||
|
|
||||||
worktree_settings.is_path_excluded(&project_path.path)
|
|
||||||
|| worktree_settings.is_path_private(&project_path.path)
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let full_path = Path::new(&worktree_root_name)
|
let full_path = worktree_root_name
|
||||||
.join(&entry.path)
|
.join(&entry.path)
|
||||||
.display()
|
.display(worktree_snapshot.path_style())
|
||||||
.to_string();
|
.into_owned();
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
folders.push(full_path);
|
folders.push(full_path);
|
||||||
} else {
|
} else {
|
||||||
@@ -427,11 +419,14 @@ mod tests {
|
|||||||
"**/.mymetadata".to_string(),
|
"**/.mymetadata".to_string(),
|
||||||
"**/.hidden_subdir".to_string(),
|
"**/.hidden_subdir".to_string(),
|
||||||
]);
|
]);
|
||||||
settings.project.worktree.private_files = Some(vec![
|
settings.project.worktree.private_files = Some(
|
||||||
"**/.mysecrets".to_string(),
|
vec![
|
||||||
"**/*.privatekey".to_string(),
|
"**/.mysecrets".to_string(),
|
||||||
"**/*.mysensitive".to_string(),
|
"**/*.privatekey".to_string(),
|
||||||
]);
|
"**/*.mysensitive".to_string(),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -568,7 +563,8 @@ mod tests {
|
|||||||
store.update_user_settings(cx, |settings| {
|
store.update_user_settings(cx, |settings| {
|
||||||
settings.project.worktree.file_scan_exclusions =
|
settings.project.worktree.file_scan_exclusions =
|
||||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
settings.project.worktree.private_files =
|
||||||
|
Some(vec!["**/.env".to_string()].into());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ impl AgentTool for MovePathTool {
|
|||||||
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
||||||
{
|
{
|
||||||
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
||||||
Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
|
Some(project_path) => project.rename_entry(entity.id, project_path, cx),
|
||||||
None => Task::ready(Err(anyhow!(
|
None => Task::ready(Err(anyhow!(
|
||||||
"Destination path {} was outside the project.",
|
"Destination path {} was outside the project.",
|
||||||
input.destination_path
|
input.destination_path
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ mod tests {
|
|||||||
async fn test_to_absolute_path(cx: &mut TestAppContext) {
|
async fn test_to_absolute_path(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
||||||
let temp_path = temp_dir.path().to_string_lossy().to_string();
|
let temp_path = temp_dir.path().to_string_lossy().into_owned();
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
let fs = FakeFs::new(cx.executor());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
|
|||||||
@@ -82,12 +82,12 @@ impl AgentTool for ReadFileTool {
|
|||||||
{
|
{
|
||||||
match (input.start_line, input.end_line) {
|
match (input.start_line, input.end_line) {
|
||||||
(Some(start), Some(end)) => {
|
(Some(start), Some(end)) => {
|
||||||
format!("Read file `{}` (lines {}-{})", path.display(), start, end,)
|
format!("Read file `{path}` (lines {}-{})", start, end,)
|
||||||
}
|
}
|
||||||
(Some(start), None) => {
|
(Some(start), None) => {
|
||||||
format!("Read file `{}` (from line {})", path.display(), start)
|
format!("Read file `{path}` (from line {})", start)
|
||||||
}
|
}
|
||||||
_ => format!("Read file `{}`", path.display()),
|
_ => format!("Read file `{path}`"),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
@@ -225,9 +225,12 @@ impl AgentTool for ReadFileTool {
|
|||||||
Ok(result.into())
|
Ok(result.into())
|
||||||
} else {
|
} else {
|
||||||
// No line ranges specified, so check file size to see if it's too big.
|
// No line ranges specified, so check file size to see if it's too big.
|
||||||
let buffer_content =
|
let buffer_content = outline::get_buffer_content_or_outline(
|
||||||
outline::get_buffer_content_or_outline(buffer.clone(), Some(&abs_path), cx)
|
buffer.clone(),
|
||||||
.await?;
|
Some(&abs_path.to_string_lossy()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
action_log.update(cx, |log, cx| {
|
action_log.update(cx, |log, cx| {
|
||||||
log.buffer_read(buffer.clone(), cx);
|
log.buffer_read(buffer.clone(), cx);
|
||||||
@@ -593,11 +596,14 @@ mod test {
|
|||||||
"**/.secretdir".to_string(),
|
"**/.secretdir".to_string(),
|
||||||
"**/.mymetadata".to_string(),
|
"**/.mymetadata".to_string(),
|
||||||
]);
|
]);
|
||||||
settings.project.worktree.private_files = Some(vec![
|
settings.project.worktree.private_files = Some(
|
||||||
"**/.mysecrets".to_string(),
|
vec![
|
||||||
"**/*.privatekey".to_string(),
|
"**/.mysecrets".to_string(),
|
||||||
"**/*.mysensitive".to_string(),
|
"**/*.privatekey".to_string(),
|
||||||
]);
|
"**/*.mysensitive".to_string(),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -804,7 +810,8 @@ mod test {
|
|||||||
store.update_user_settings(cx, |settings| {
|
store.update_user_settings(cx, |settings| {
|
||||||
settings.project.worktree.file_scan_exclusions =
|
settings.project.worktree.file_scan_exclusions =
|
||||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||||
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
|
settings.project.worktree.private_files =
|
||||||
|
Some(vec!["**/.env".to_string()].into());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ impl AgentTool for TerminalTool {
|
|||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"Run terminal command".into()
|
"".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ task.workspace = true
|
|||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
terminal.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
watch.workspace = true
|
watch.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ use futures::io::BufReader;
|
|||||||
use project::Project;
|
use project::Project;
|
||||||
use project::agent_server_store::AgentServerCommand;
|
use project::agent_server_store::AgentServerCommand;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use util::ResultExt as _;
|
use settings::{Settings as _, SettingsLocation};
|
||||||
|
use task::Shell;
|
||||||
|
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{any::Any, cell::RefCell};
|
use std::{any::Any, cell::RefCell};
|
||||||
@@ -19,7 +21,9 @@ use thiserror::Error;
|
|||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntity};
|
use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntity};
|
||||||
|
|
||||||
use acp_thread::{AcpThread, AuthRequired, LoadError};
|
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
|
||||||
|
use terminal::TerminalBuilder;
|
||||||
|
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("Unsupported version")]
|
#[error("Unsupported version")]
|
||||||
@@ -44,6 +48,7 @@ pub struct AcpConnection {
|
|||||||
pub struct AcpSession {
|
pub struct AcpSession {
|
||||||
thread: WeakEntity<AcpThread>,
|
thread: WeakEntity<AcpThread>,
|
||||||
suppress_abort_err: bool,
|
suppress_abort_err: bool,
|
||||||
|
models: Option<Rc<RefCell<acp::SessionModelState>>>,
|
||||||
session_modes: Option<Rc<RefCell<acp::SessionModeState>>>,
|
session_modes: Option<Rc<RefCell<acp::SessionModeState>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +83,7 @@ impl AcpConnection {
|
|||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut child = util::command::new_smol_command(command.path);
|
let mut child = util::command::new_smol_command(&command.path);
|
||||||
child
|
child
|
||||||
.args(command.args.iter().map(|arg| arg.as_str()))
|
.args(command.args.iter().map(|arg| arg.as_str()))
|
||||||
.envs(command.env.iter().flatten())
|
.envs(command.env.iter().flatten())
|
||||||
@@ -93,6 +98,11 @@ impl AcpConnection {
|
|||||||
let stdout = child.stdout.take().context("Failed to take stdout")?;
|
let stdout = child.stdout.take().context("Failed to take stdout")?;
|
||||||
let stdin = child.stdin.take().context("Failed to take stdin")?;
|
let stdin = child.stdin.take().context("Failed to take stdin")?;
|
||||||
let stderr = child.stderr.take().context("Failed to take stderr")?;
|
let stderr = child.stderr.take().context("Failed to take stderr")?;
|
||||||
|
log::info!(
|
||||||
|
"Spawning external agent server: {:?}, {:?}",
|
||||||
|
command.path,
|
||||||
|
command.args
|
||||||
|
);
|
||||||
log::trace!("Spawned (pid: {})", child.id());
|
log::trace!("Spawned (pid: {})", child.id());
|
||||||
|
|
||||||
let sessions = Rc::new(RefCell::new(HashMap::default()));
|
let sessions = Rc::new(RefCell::new(HashMap::default()));
|
||||||
@@ -159,7 +169,10 @@ impl AcpConnection {
|
|||||||
meta: None,
|
meta: None,
|
||||||
},
|
},
|
||||||
terminal: true,
|
terminal: true,
|
||||||
meta: None,
|
meta: Some(serde_json::json!({
|
||||||
|
// Experimental: Allow for rendering terminal output from the agents
|
||||||
|
"terminal_output": true,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
meta: None,
|
meta: None,
|
||||||
})
|
})
|
||||||
@@ -264,6 +277,7 @@ impl AgentConnection for AcpConnection {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let modes = response.modes.map(|modes| Rc::new(RefCell::new(modes)));
|
let modes = response.modes.map(|modes| Rc::new(RefCell::new(modes)));
|
||||||
|
let models = response.models.map(|models| Rc::new(RefCell::new(models)));
|
||||||
|
|
||||||
if let Some(default_mode) = default_mode {
|
if let Some(default_mode) = default_mode {
|
||||||
if let Some(modes) = modes.as_ref() {
|
if let Some(modes) = modes.as_ref() {
|
||||||
@@ -326,10 +340,12 @@ impl AgentConnection for AcpConnection {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
||||||
let session = AcpSession {
|
let session = AcpSession {
|
||||||
thread: thread.downgrade(),
|
thread: thread.downgrade(),
|
||||||
suppress_abort_err: false,
|
suppress_abort_err: false,
|
||||||
session_modes: modes
|
session_modes: modes,
|
||||||
|
models,
|
||||||
};
|
};
|
||||||
sessions.borrow_mut().insert(session_id, session);
|
sessions.borrow_mut().insert(session_id, session);
|
||||||
|
|
||||||
@@ -376,6 +392,10 @@ impl AgentConnection for AcpConnection {
|
|||||||
match result {
|
match result {
|
||||||
Ok(response) => Ok(response),
|
Ok(response) => Ok(response),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
|
||||||
|
return Err(anyhow!(acp::Error::auth_required()));
|
||||||
|
}
|
||||||
|
|
||||||
if err.code != ErrorCode::INTERNAL_ERROR.code {
|
if err.code != ErrorCode::INTERNAL_ERROR.code {
|
||||||
anyhow::bail!(err)
|
anyhow::bail!(err)
|
||||||
}
|
}
|
||||||
@@ -450,6 +470,27 @@ impl AgentConnection for AcpConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn model_selector(
|
||||||
|
&self,
|
||||||
|
session_id: &acp::SessionId,
|
||||||
|
) -> Option<Rc<dyn acp_thread::AgentModelSelector>> {
|
||||||
|
let sessions = self.sessions.clone();
|
||||||
|
let sessions_ref = sessions.borrow();
|
||||||
|
let Some(session) = sessions_ref.get(session_id) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(models) = session.models.as_ref() {
|
||||||
|
Some(Rc::new(AcpModelSelector::new(
|
||||||
|
session_id.clone(),
|
||||||
|
self.connection.clone(),
|
||||||
|
models.clone(),
|
||||||
|
)) as _)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -500,6 +541,82 @@ impl acp_thread::AgentSessionModes for AcpSessionModes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AcpModelSelector {
|
||||||
|
session_id: acp::SessionId,
|
||||||
|
connection: Rc<acp::ClientSideConnection>,
|
||||||
|
state: Rc<RefCell<acp::SessionModelState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcpModelSelector {
|
||||||
|
fn new(
|
||||||
|
session_id: acp::SessionId,
|
||||||
|
connection: Rc<acp::ClientSideConnection>,
|
||||||
|
state: Rc<RefCell<acp::SessionModelState>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
session_id,
|
||||||
|
connection,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl acp_thread::AgentModelSelector for AcpModelSelector {
|
||||||
|
fn list_models(&self, _cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
|
||||||
|
Task::ready(Ok(acp_thread::AgentModelList::Flat(
|
||||||
|
self.state
|
||||||
|
.borrow()
|
||||||
|
.available_models
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(acp_thread::AgentModelInfo::from)
|
||||||
|
.collect(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>> {
|
||||||
|
let connection = self.connection.clone();
|
||||||
|
let session_id = self.session_id.clone();
|
||||||
|
let old_model_id;
|
||||||
|
{
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
old_model_id = state.current_model_id.clone();
|
||||||
|
state.current_model_id = model_id.clone();
|
||||||
|
};
|
||||||
|
let state = self.state.clone();
|
||||||
|
cx.foreground_executor().spawn(async move {
|
||||||
|
let result = connection
|
||||||
|
.set_session_model(acp::SetSessionModelRequest {
|
||||||
|
session_id,
|
||||||
|
model_id,
|
||||||
|
meta: None,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
state.borrow_mut().current_model_id = old_model_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
result?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_model(&self, _cx: &mut App) -> Task<Result<acp_thread::AgentModelInfo>> {
|
||||||
|
let state = self.state.borrow();
|
||||||
|
Task::ready(
|
||||||
|
state
|
||||||
|
.available_models
|
||||||
|
.iter()
|
||||||
|
.find(|m| m.model_id == state.current_model_id)
|
||||||
|
.cloned()
|
||||||
|
.map(acp_thread::AgentModelInfo::from)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Model not found")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ClientDelegate {
|
struct ClientDelegate {
|
||||||
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
|
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
|
||||||
cx: AsyncApp,
|
cx: AsyncApp,
|
||||||
@@ -595,10 +712,100 @@ impl acp::Client for ClientDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone so we can inspect meta both before and after handing off to the thread
|
||||||
|
let update_clone = notification.update.clone();
|
||||||
|
|
||||||
|
// Pre-handle: if a ToolCall carries terminal_info, create/register a display-only terminal.
|
||||||
|
if let acp::SessionUpdate::ToolCall(tc) = &update_clone {
|
||||||
|
if let Some(meta) = &tc.meta {
|
||||||
|
if let Some(terminal_info) = meta.get("terminal_info") {
|
||||||
|
if let Some(id_str) = terminal_info.get("terminal_id").and_then(|v| v.as_str())
|
||||||
|
{
|
||||||
|
let terminal_id = acp::TerminalId(id_str.into());
|
||||||
|
let cwd = terminal_info
|
||||||
|
.get("cwd")
|
||||||
|
.and_then(|v| v.as_str().map(PathBuf::from));
|
||||||
|
|
||||||
|
// Create a minimal display-only lower-level terminal and register it.
|
||||||
|
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||||
|
let builder = TerminalBuilder::new_display_only(
|
||||||
|
CursorShape::default(),
|
||||||
|
AlternateScroll::On,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
)?;
|
||||||
|
let lower = cx.new(|cx| builder.subscribe(cx));
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Created {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
label: tc.title.clone(),
|
||||||
|
cwd,
|
||||||
|
output_byte_limit: None,
|
||||||
|
terminal: lower,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
anyhow::Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward the update to the acp_thread as usual.
|
||||||
session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||||
thread.handle_session_update(notification.update, cx)
|
thread.handle_session_update(notification.update.clone(), cx)
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
|
// Post-handle: stream terminal output/exit if present on ToolCallUpdate meta.
|
||||||
|
if let acp::SessionUpdate::ToolCallUpdate(tcu) = &update_clone {
|
||||||
|
if let Some(meta) = &tcu.meta {
|
||||||
|
if let Some(term_out) = meta.get("terminal_output") {
|
||||||
|
if let Some(id_str) = term_out.get("terminal_id").and_then(|v| v.as_str()) {
|
||||||
|
let terminal_id = acp::TerminalId(id_str.into());
|
||||||
|
if let Some(s) = term_out.get("data").and_then(|v| v.as_str()) {
|
||||||
|
let data = s.as_bytes().to_vec();
|
||||||
|
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Output {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminal_exit
|
||||||
|
if let Some(term_exit) = meta.get("terminal_exit") {
|
||||||
|
if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
|
||||||
|
let terminal_id = acp::TerminalId(id_str.into());
|
||||||
|
let status = acp::TerminalExitStatus {
|
||||||
|
exit_code: term_exit
|
||||||
|
.get("exit_code")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.map(|i| i as u32),
|
||||||
|
signal: term_exit
|
||||||
|
.get("signal")
|
||||||
|
.and_then(|v| v.as_str().map(|s| s.to_string())),
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||||
|
thread.on_terminal_provider_event(
|
||||||
|
TerminalProviderEvent::Exit {
|
||||||
|
terminal_id: terminal_id.clone(),
|
||||||
|
status,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,25 +813,83 @@ impl acp::Client for ClientDelegate {
|
|||||||
&self,
|
&self,
|
||||||
args: acp::CreateTerminalRequest,
|
args: acp::CreateTerminalRequest,
|
||||||
) -> Result<acp::CreateTerminalResponse, acp::Error> {
|
) -> Result<acp::CreateTerminalResponse, acp::Error> {
|
||||||
let terminal = self
|
let thread = self.session_thread(&args.session_id)?;
|
||||||
.session_thread(&args.session_id)?
|
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
|
||||||
.update(&mut self.cx.clone(), |thread, cx| {
|
|
||||||
thread.create_terminal(
|
let mut env = if let Some(dir) = &args.cwd {
|
||||||
args.command,
|
project
|
||||||
args.args,
|
.update(&mut self.cx.clone(), |project, cx| {
|
||||||
args.env,
|
let worktree = project.find_worktree(dir.as_path(), cx);
|
||||||
args.cwd,
|
let shell = TerminalSettings::get(
|
||||||
args.output_byte_limit,
|
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
|
||||||
|
worktree_id: worktree.read(cx).id(),
|
||||||
|
path: &path,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.shell
|
||||||
|
.clone();
|
||||||
|
project.directory_environment(&shell, dir.clone().into(), cx)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
// Disables paging for `git` and hopefully other commands
|
||||||
|
env.insert("PAGER".into(), "".into());
|
||||||
|
for var in args.env {
|
||||||
|
env.insert(var.name, var.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use remote shell or default system shell, as appropriate
|
||||||
|
let shell = project
|
||||||
|
.update(&mut self.cx.clone(), |project, cx| {
|
||||||
|
project
|
||||||
|
.remote_client()
|
||||||
|
.and_then(|r| r.read(cx).default_system_shell())
|
||||||
|
.map(Shell::Program)
|
||||||
|
})?
|
||||||
|
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
|
||||||
|
let is_windows = project
|
||||||
|
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
|
||||||
|
.unwrap_or(cfg!(windows));
|
||||||
|
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
|
||||||
|
.redirect_stdin_to_dev_null()
|
||||||
|
.build(Some(args.command.clone()), &args.args);
|
||||||
|
|
||||||
|
let terminal_entity = project
|
||||||
|
.update(&mut self.cx.clone(), |project, cx| {
|
||||||
|
project.create_terminal_task(
|
||||||
|
task::SpawnInTerminal {
|
||||||
|
command: Some(task_command),
|
||||||
|
args: task_args,
|
||||||
|
cwd: args.cwd.clone(),
|
||||||
|
env,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
Ok(
|
|
||||||
terminal.read_with(&self.cx, |terminal, _| acp::CreateTerminalResponse {
|
// Register with renderer
|
||||||
terminal_id: terminal.id().clone(),
|
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||||
meta: None,
|
thread.register_terminal_created(
|
||||||
})?,
|
acp::TerminalId(uuid::Uuid::new_v4().to_string().into()),
|
||||||
)
|
format!("{} {}", args.command, args.args.join(" ")),
|
||||||
|
args.cwd.clone(),
|
||||||
|
args.output_byte_limit,
|
||||||
|
terminal_entity,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let terminal_id =
|
||||||
|
terminal_entity.read_with(&self.cx, |terminal, _| terminal.id().clone())?;
|
||||||
|
Ok(acp::CreateTerminalResponse {
|
||||||
|
terminal_id,
|
||||||
|
meta: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn kill_terminal_command(
|
async fn kill_terminal_command(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod acp;
|
mod acp;
|
||||||
mod claude;
|
mod claude;
|
||||||
|
mod codex;
|
||||||
mod custom;
|
mod custom;
|
||||||
mod gemini;
|
mod gemini;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ pub mod e2e_tests;
|
|||||||
|
|
||||||
pub use claude::*;
|
pub use claude::*;
|
||||||
use client::ProxySettings;
|
use client::ProxySettings;
|
||||||
|
pub use codex::*;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
pub use custom::*;
|
pub use custom::*;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
@@ -99,6 +101,9 @@ pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
|
|||||||
|
|
||||||
if let Some(no_proxy) = read_no_proxy_from_env() {
|
if let Some(no_proxy) = read_no_proxy_from_env() {
|
||||||
env.insert("NO_PROXY".to_owned(), no_proxy);
|
env.insert("NO_PROXY".to_owned(), no_proxy);
|
||||||
|
} else if proxy_url.is_some() {
|
||||||
|
// We sometimes need local MCP servers that we don't want to proxy
|
||||||
|
env.insert("NO_PROXY".to_owned(), "localhost,127.0.0.1".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
env
|
env
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ impl AgentServer for ClaudeCode {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
let extra_env = load_proxy_env(cx);
|
let extra_env = load_proxy_env(cx);
|
||||||
|
|||||||
106
crates/agent_servers/src/codex.rs
Normal file
106
crates/agent_servers/src/codex.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{any::Any, path::Path};
|
||||||
|
|
||||||
|
use acp_thread::AgentConnection;
|
||||||
|
use agent_client_protocol as acp;
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui::{App, AppContext as _, SharedString, Task};
|
||||||
|
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
||||||
|
use settings::{SettingsStore, update_settings_file};
|
||||||
|
|
||||||
|
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Codex;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
crate::common_e2e_tests!(async |_, _, _| Codex, allow_option_id = "proceed_once");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AgentServer for Codex {
|
||||||
|
fn telemetry_id(&self) -> &'static str {
|
||||||
|
"codex"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> SharedString {
|
||||||
|
"Codex".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logo(&self) -> ui::IconName {
|
||||||
|
ui::IconName::AiOpenAi
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_mode(&self, cx: &mut App) -> Option<acp::SessionModeId> {
|
||||||
|
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
||||||
|
settings.get::<AllAgentServersSettings>(None).codex.clone()
|
||||||
|
});
|
||||||
|
|
||||||
|
settings
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||||
|
update_settings_file(fs, cx, |settings, _| {
|
||||||
|
settings
|
||||||
|
.agent_servers
|
||||||
|
.get_or_insert_default()
|
||||||
|
.codex
|
||||||
|
.get_or_insert_default()
|
||||||
|
.default_mode = mode_id.map(|m| m.to_string())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect(
|
||||||
|
&self,
|
||||||
|
root_dir: Option<&Path>,
|
||||||
|
delegate: AgentServerDelegate,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
|
let name = self.name();
|
||||||
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
|
let store = delegate.store.downgrade();
|
||||||
|
let extra_env = load_proxy_env(cx);
|
||||||
|
let default_mode = self.default_mode(cx);
|
||||||
|
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let (command, root_dir, login) = store
|
||||||
|
.update(cx, |store, cx| {
|
||||||
|
let agent = store
|
||||||
|
.get_external_agent(&CODEX_NAME.into())
|
||||||
|
.context("Codex is not registered")?;
|
||||||
|
anyhow::Ok(agent.get_command(
|
||||||
|
root_dir.as_deref(),
|
||||||
|
extra_env,
|
||||||
|
delegate.status_tx,
|
||||||
|
// For now, report that there are no updates.
|
||||||
|
// (A future PR will use the GitHub Releases API to fetch them.)
|
||||||
|
delegate.new_version_available,
|
||||||
|
&mut cx.to_async(),
|
||||||
|
))
|
||||||
|
})??
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let connection = crate::acp::connect(
|
||||||
|
name,
|
||||||
|
command,
|
||||||
|
root_dir.as_ref(),
|
||||||
|
default_mode,
|
||||||
|
is_remote,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok((connection, login))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ impl crate::AgentServer for CustomAgentServer {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let default_mode = self.default_mode(cx);
|
let default_mode = self.default_mode(cx);
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
|
|||||||
@@ -483,6 +483,13 @@ pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
|
|||||||
default_mode: None,
|
default_mode: None,
|
||||||
}),
|
}),
|
||||||
gemini: Some(crate::gemini::tests::local_command().into()),
|
gemini: Some(crate::gemini::tests::local_command().into()),
|
||||||
|
codex: Some(BuiltinAgentServerSettings {
|
||||||
|
path: Some("codex-acp".into()),
|
||||||
|
args: None,
|
||||||
|
env: None,
|
||||||
|
ignore_system_version: None,
|
||||||
|
default_mode: None,
|
||||||
|
}),
|
||||||
custom: collections::HashMap::default(),
|
custom: collections::HashMap::default(),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ impl AgentServer for Gemini {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
let store = delegate.store.downgrade();
|
let store = delegate.store.downgrade();
|
||||||
let mut extra_env = load_proxy_env(cx);
|
let mut extra_env = load_proxy_env(cx);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ convert_case.workspace = true
|
|||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
|
project.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ use std::sync::Arc;
|
|||||||
use collections::IndexMap;
|
use collections::IndexMap;
|
||||||
use gpui::{App, Pixels, px};
|
use gpui::{App, Pixels, px};
|
||||||
use language_model::LanguageModel;
|
use language_model::LanguageModel;
|
||||||
|
use project::DisableAiSettings;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{
|
use settings::{
|
||||||
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
|
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
|
||||||
NotifyWhenAgentWaiting, Settings, SettingsContent,
|
NotifyWhenAgentWaiting, Settings, SettingsContent,
|
||||||
};
|
};
|
||||||
use util::MergeFrom;
|
|
||||||
|
|
||||||
pub use crate::agent_profile::*;
|
pub use crate::agent_profile::*;
|
||||||
|
|
||||||
@@ -54,6 +54,10 @@ pub struct AgentSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentSettings {
|
impl AgentSettings {
|
||||||
|
pub fn enabled(&self, cx: &App) -> bool {
|
||||||
|
self.enabled && !DisableAiSettings::get_global(cx).disable_ai
|
||||||
|
}
|
||||||
|
|
||||||
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
||||||
let settings = Self::get_global(cx);
|
let settings = Self::get_global(cx);
|
||||||
for setting in settings.model_parameters.iter().rev() {
|
for setting in settings.model_parameters.iter().rev() {
|
||||||
@@ -147,7 +151,7 @@ impl Default for AgentProfileId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Settings for AgentSettings {
|
impl Settings for AgentSettings {
|
||||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||||
let agent = content.agent.clone().unwrap();
|
let agent = content.agent.clone().unwrap();
|
||||||
Self {
|
Self {
|
||||||
enabled: agent.enabled.unwrap(),
|
enabled: agent.enabled.unwrap(),
|
||||||
@@ -183,66 +187,6 @@ impl Settings for AgentSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) {
|
|
||||||
let Some(value) = &content.agent else { return };
|
|
||||||
self.enabled.merge_from(&value.enabled);
|
|
||||||
self.button.merge_from(&value.button);
|
|
||||||
self.dock.merge_from(&value.dock);
|
|
||||||
self.default_width
|
|
||||||
.merge_from(&value.default_width.map(Into::into));
|
|
||||||
self.default_height
|
|
||||||
.merge_from(&value.default_height.map(Into::into));
|
|
||||||
self.default_model = value.default_model.clone().or(self.default_model.take());
|
|
||||||
|
|
||||||
self.inline_assistant_model = value
|
|
||||||
.inline_assistant_model
|
|
||||||
.clone()
|
|
||||||
.or(self.inline_assistant_model.take());
|
|
||||||
self.commit_message_model = value
|
|
||||||
.clone()
|
|
||||||
.commit_message_model
|
|
||||||
.or(self.commit_message_model.take());
|
|
||||||
self.thread_summary_model = value
|
|
||||||
.clone()
|
|
||||||
.thread_summary_model
|
|
||||||
.or(self.thread_summary_model.take());
|
|
||||||
self.inline_alternatives
|
|
||||||
.merge_from(&value.inline_alternatives.clone());
|
|
||||||
self.default_profile
|
|
||||||
.merge_from(&value.default_profile.clone().map(AgentProfileId));
|
|
||||||
self.default_view.merge_from(&value.default_view);
|
|
||||||
self.always_allow_tool_actions
|
|
||||||
.merge_from(&value.always_allow_tool_actions);
|
|
||||||
self.notify_when_agent_waiting
|
|
||||||
.merge_from(&value.notify_when_agent_waiting);
|
|
||||||
self.play_sound_when_agent_done
|
|
||||||
.merge_from(&value.play_sound_when_agent_done);
|
|
||||||
self.stream_edits.merge_from(&value.stream_edits);
|
|
||||||
self.single_file_review
|
|
||||||
.merge_from(&value.single_file_review);
|
|
||||||
self.preferred_completion_mode
|
|
||||||
.merge_from(&value.preferred_completion_mode.map(Into::into));
|
|
||||||
self.enable_feedback.merge_from(&value.enable_feedback);
|
|
||||||
self.expand_edit_card.merge_from(&value.expand_edit_card);
|
|
||||||
self.expand_terminal_card
|
|
||||||
.merge_from(&value.expand_terminal_card);
|
|
||||||
self.use_modifier_to_send
|
|
||||||
.merge_from(&value.use_modifier_to_send);
|
|
||||||
|
|
||||||
self.model_parameters
|
|
||||||
.extend_from_slice(&value.model_parameters);
|
|
||||||
self.message_editor_min_lines
|
|
||||||
.merge_from(&value.message_editor_min_lines);
|
|
||||||
|
|
||||||
if let Some(profiles) = value.profiles.as_ref() {
|
|
||||||
self.profiles.extend(
|
|
||||||
profiles
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||||
if let Some(b) = vscode
|
if let Some(b) = vscode
|
||||||
.read_value("chat.agent.enabled")
|
.read_value("chat.agent.enabled")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
@@ -11,9 +12,9 @@ use anyhow::Result;
|
|||||||
use editor::{CompletionProvider, Editor, ExcerptId};
|
use editor::{CompletionProvider, Editor, ExcerptId};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{App, Entity, Task, WeakEntity};
|
use gpui::{App, Entity, Task, WeakEntity};
|
||||||
use language::{Buffer, CodeLabel, HighlightId};
|
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
|
||||||
use lsp::CompletionContext;
|
use lsp::CompletionContext;
|
||||||
use project::lsp_store::CompletionDocumentation;
|
use project::lsp_store::{CompletionDocumentation, SymbolLocation};
|
||||||
use project::{
|
use project::{
|
||||||
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, Project,
|
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, Project,
|
||||||
ProjectPath, Symbol, WorktreeId,
|
ProjectPath, Symbol, WorktreeId,
|
||||||
@@ -22,10 +23,11 @@ use prompt_store::PromptStore;
|
|||||||
use rope::Point;
|
use rope::Point;
|
||||||
use text::{Anchor, ToPoint as _};
|
use text::{Anchor, ToPoint as _};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
|
use util::rel_path::RelPath;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::AgentPanel;
|
use crate::AgentPanel;
|
||||||
use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
|
use crate::acp::message_editor::MessageEditor;
|
||||||
use crate::context_picker::file_context_picker::{FileMatch, search_files};
|
use crate::context_picker::file_context_picker::{FileMatch, search_files};
|
||||||
use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules};
|
use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules};
|
||||||
use crate::context_picker::symbol_context_picker::SymbolMatch;
|
use crate::context_picker::symbol_context_picker::SymbolMatch;
|
||||||
@@ -187,7 +189,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
|
|
||||||
pub(crate) fn completion_for_path(
|
pub(crate) fn completion_for_path(
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
path_prefix: &str,
|
path_prefix: &RelPath,
|
||||||
is_recent: bool,
|
is_recent: bool,
|
||||||
is_directory: bool,
|
is_directory: bool,
|
||||||
source_range: Range<Anchor>,
|
source_range: Range<Anchor>,
|
||||||
@@ -195,10 +197,12 @@ impl ContextPickerCompletionProvider {
|
|||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<Completion> {
|
) -> Option<Completion> {
|
||||||
|
let path_style = project.read(cx).path_style(cx);
|
||||||
let (file_name, directory) =
|
let (file_name, directory) =
|
||||||
crate::context_picker::file_context_picker::extract_file_name_and_directory(
|
crate::context_picker::file_context_picker::extract_file_name_and_directory(
|
||||||
&project_path.path,
|
&project_path.path,
|
||||||
path_prefix,
|
path_prefix,
|
||||||
|
path_style,
|
||||||
);
|
);
|
||||||
|
|
||||||
let label =
|
let label =
|
||||||
@@ -250,7 +254,15 @@ impl ContextPickerCompletionProvider {
|
|||||||
|
|
||||||
let label = CodeLabel::plain(symbol.name.clone(), None);
|
let label = CodeLabel::plain(symbol.name.clone(), None);
|
||||||
|
|
||||||
let abs_path = project.read(cx).absolute_path(&symbol.path, cx)?;
|
let abs_path = match &symbol.path {
|
||||||
|
SymbolLocation::InProject(project_path) => {
|
||||||
|
project.read(cx).absolute_path(&project_path, cx)?
|
||||||
|
}
|
||||||
|
SymbolLocation::OutsideProject {
|
||||||
|
abs_path,
|
||||||
|
signature: _,
|
||||||
|
} => PathBuf::from(abs_path.as_ref()),
|
||||||
|
};
|
||||||
let uri = MentionUri::Symbol {
|
let uri = MentionUri::Symbol {
|
||||||
abs_path,
|
abs_path,
|
||||||
name: symbol.name.clone(),
|
name: symbol.name.clone(),
|
||||||
@@ -661,7 +673,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
|
|
||||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||||
let mut label = CodeLabel::default();
|
let mut label = CodeLabelBuilder::default();
|
||||||
|
|
||||||
label.push_str(file_name, None);
|
label.push_str(file_name, None);
|
||||||
label.push_str(" ", None);
|
label.push_str(" ", None);
|
||||||
@@ -670,9 +682,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
|
|||||||
label.push_str(directory, comment_id);
|
label.push_str(directory, comment_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
label.filter_range = 0..label.text().len();
|
label.build()
|
||||||
|
|
||||||
label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompletionProvider for ContextPickerCompletionProvider {
|
impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
@@ -747,13 +757,13 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
move |cx| {
|
move |cx| {
|
||||||
editor
|
editor
|
||||||
.update(cx, |_editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
match intent {
|
match intent {
|
||||||
CompletionIntent::Complete
|
CompletionIntent::Complete
|
||||||
| CompletionIntent::CompleteWithInsert
|
| CompletionIntent::CompleteWithInsert
|
||||||
| CompletionIntent::CompleteWithReplace => {
|
| CompletionIntent::CompleteWithReplace => {
|
||||||
if !is_missing_argument {
|
if !is_missing_argument {
|
||||||
cx.emit(MessageEditorEvent::Send);
|
editor.send(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompletionIntent::Compose => {}
|
CompletionIntent::Compose => {}
|
||||||
@@ -763,7 +773,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
is_missing_argument
|
false
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@@ -898,6 +908,17 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
offset_to_line,
|
offset_to_line,
|
||||||
self.prompt_capabilities.borrow().embedded_context,
|
self.prompt_capabilities.borrow().embedded_context,
|
||||||
)
|
)
|
||||||
|
.filter(|completion| {
|
||||||
|
// Right now we don't support completing arguments of slash commands
|
||||||
|
let is_slash_command_with_argument = matches!(
|
||||||
|
completion,
|
||||||
|
ContextCompletion::SlashCommand(SlashCommandCompletion {
|
||||||
|
argument: Some(_),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
);
|
||||||
|
!is_slash_command_with_argument
|
||||||
|
})
|
||||||
.map(|completion| {
|
.map(|completion| {
|
||||||
completion.source_range().start <= offset_to_line + position.column as usize
|
completion.source_range().start <= offset_to_line + position.column as usize
|
||||||
&& completion.source_range().end >= offset_to_line + position.column as usize
|
&& completion.source_range().end >= offset_to_line + position.column as usize
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ impl EntryViewState {
|
|||||||
self.entries.drain(range);
|
self.entries.drain(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn agent_font_size_changed(&mut self, cx: &mut App) {
|
pub fn agent_ui_font_size_changed(&mut self, cx: &mut App) {
|
||||||
for entry in self.entries.iter() {
|
for entry in self.entries.iter() {
|
||||||
match entry {
|
match entry {
|
||||||
Entry::UserMessage { .. } | Entry::AssistantMessage { .. } => {}
|
Entry::UserMessage { .. } | Entry::AssistantMessage { .. } => {}
|
||||||
@@ -387,7 +387,7 @@ fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
|
|||||||
font_size: Some(
|
font_size: Some(
|
||||||
TextSize::Small
|
TextSize::Small
|
||||||
.rems(cx)
|
.rems(cx)
|
||||||
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
|
.to_pixels(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -414,7 +414,6 @@ mod tests {
|
|||||||
use project::Project;
|
use project::Project;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{Settings as _, SettingsStore};
|
use settings::{Settings as _, SettingsStore};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use util::path;
|
use util::path;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -544,7 +543,7 @@ mod tests {
|
|||||||
Project::init_settings(cx);
|
Project::init_settings(cx);
|
||||||
AgentSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
ThemeSettings::register(cx);
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
release_channel::init(SemanticVersion::default(), cx);
|
release_channel::init(SemanticVersion::default(), cx);
|
||||||
EditorSettings::register(cx);
|
EditorSettings::register(cx);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,13 +47,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
use text::OffsetRangeExt;
|
use text::OffsetRangeExt;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{ButtonLike, TintColor, Toggleable, prelude::*};
|
||||||
ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, ButtonStyle, Color, Element as _,
|
use util::{ResultExt, debug_panic, rel_path::RelPath};
|
||||||
FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
|
|
||||||
LabelCommon, LabelSize, ParentElement, Render, SelectableButton, Styled, TextSize, TintColor,
|
|
||||||
Toggleable, Window, div, h_flex,
|
|
||||||
};
|
|
||||||
use util::{ResultExt, debug_panic};
|
|
||||||
use workspace::{Workspace, notifications::NotifyResultExt as _};
|
use workspace::{Workspace, notifications::NotifyResultExt as _};
|
||||||
use zed_actions::agent::Chat;
|
use zed_actions::agent::Chat;
|
||||||
|
|
||||||
@@ -81,7 +76,7 @@ pub enum MessageEditorEvent {
|
|||||||
|
|
||||||
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
|
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
|
||||||
|
|
||||||
const COMMAND_HINT_INLAY_ID: usize = 0;
|
const COMMAND_HINT_INLAY_ID: u32 = 0;
|
||||||
|
|
||||||
impl MessageEditor {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -146,7 +141,9 @@ impl MessageEditor {
|
|||||||
|
|
||||||
subscriptions.push(cx.subscribe_in(&editor, window, {
|
subscriptions.push(cx.subscribe_in(&editor, window, {
|
||||||
move |this, editor, event, window, cx| {
|
move |this, editor, event, window, cx| {
|
||||||
if let EditorEvent::Edited { .. } = event {
|
if let EditorEvent::Edited { .. } = event
|
||||||
|
&& !editor.read(cx).read_only(cx)
|
||||||
|
{
|
||||||
let snapshot = editor.update(cx, |editor, cx| {
|
let snapshot = editor.update(cx, |editor, cx| {
|
||||||
let new_hints = this
|
let new_hints = this
|
||||||
.command_hint(editor.buffer(), cx)
|
.command_hint(editor.buffer(), cx)
|
||||||
@@ -295,18 +292,18 @@ impl MessageEditor {
|
|||||||
let snapshot = self
|
let snapshot = self
|
||||||
.editor
|
.editor
|
||||||
.update(cx, |editor, cx| editor.snapshot(window, cx));
|
.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||||
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else {
|
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot().as_singleton() else {
|
||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
let Some(start_anchor) = snapshot
|
let Some(start_anchor) = snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot()
|
||||||
.anchor_in_excerpt(*excerpt_id, start)
|
.anchor_in_excerpt(*excerpt_id, start)
|
||||||
else {
|
else {
|
||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
let end_anchor = snapshot
|
let end_anchor = snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot()
|
||||||
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot) + content_len + 1);
|
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1);
|
||||||
|
|
||||||
let crease = if let MentionUri::File { abs_path } = &mention_uri
|
let crease = if let MentionUri::File { abs_path } = &mention_uri
|
||||||
&& let Some(extension) = abs_path.extension()
|
&& let Some(extension) = abs_path.extension()
|
||||||
@@ -364,7 +361,7 @@ impl MessageEditor {
|
|||||||
|
|
||||||
let task = match mention_uri.clone() {
|
let task = match mention_uri.clone() {
|
||||||
MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, cx),
|
MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, cx),
|
||||||
MentionUri::Directory { abs_path } => self.confirm_mention_for_directory(abs_path, cx),
|
MentionUri::Directory { .. } => Task::ready(Ok(Mention::UriOnly)),
|
||||||
MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx),
|
MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx),
|
||||||
MentionUri::TextThread { path, .. } => self.confirm_mention_for_text_thread(path, cx),
|
MentionUri::TextThread { path, .. } => self.confirm_mention_for_text_thread(path, cx),
|
||||||
MentionUri::File { abs_path } => self.confirm_mention_for_file(abs_path, cx),
|
MentionUri::File { abs_path } => self.confirm_mention_for_file(abs_path, cx),
|
||||||
@@ -457,9 +454,12 @@ impl MessageEditor {
|
|||||||
.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
let buffer = buffer.await?;
|
let buffer = buffer.await?;
|
||||||
let buffer_content =
|
let buffer_content = outline::get_buffer_content_or_outline(
|
||||||
outline::get_buffer_content_or_outline(buffer.clone(), Some(&abs_path), &cx)
|
buffer.clone(),
|
||||||
.await?;
|
Some(&abs_path.to_string_lossy()),
|
||||||
|
&cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Mention::Text {
|
Ok(Mention::Text {
|
||||||
content: buffer_content.text,
|
content: buffer_content.text,
|
||||||
@@ -468,97 +468,6 @@ impl MessageEditor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_mention_for_directory(
|
|
||||||
&mut self,
|
|
||||||
abs_path: PathBuf,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Task<Result<Mention>> {
|
|
||||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
|
|
||||||
let mut files = Vec::new();
|
|
||||||
|
|
||||||
for entry in worktree.child_entries(path) {
|
|
||||||
if entry.is_dir() {
|
|
||||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
|
||||||
} else if entry.is_file() {
|
|
||||||
files.push((entry.path.clone(), worktree.full_path(&entry.path)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(project_path) = self
|
|
||||||
.project
|
|
||||||
.read(cx)
|
|
||||||
.project_path_for_absolute_path(&abs_path, cx)
|
|
||||||
else {
|
|
||||||
return Task::ready(Err(anyhow!("project path not found")));
|
|
||||||
};
|
|
||||||
let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else {
|
|
||||||
return Task::ready(Err(anyhow!("project entry not found")));
|
|
||||||
};
|
|
||||||
let directory_path = entry.path.clone();
|
|
||||||
let worktree_id = project_path.worktree_id;
|
|
||||||
let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) else {
|
|
||||||
return Task::ready(Err(anyhow!("worktree not found")));
|
|
||||||
};
|
|
||||||
let project = self.project.clone();
|
|
||||||
cx.spawn(async move |_, cx| {
|
|
||||||
let file_paths = worktree.read_with(cx, |worktree, _cx| {
|
|
||||||
collect_files_in_path(worktree, &directory_path)
|
|
||||||
})?;
|
|
||||||
let descendants_future = cx.update(|cx| {
|
|
||||||
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
|
|
||||||
let rel_path = worktree_path
|
|
||||||
.strip_prefix(&directory_path)
|
|
||||||
.log_err()
|
|
||||||
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
|
|
||||||
|
|
||||||
let open_task = project.update(cx, |project, cx| {
|
|
||||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
|
||||||
let project_path = ProjectPath {
|
|
||||||
worktree_id,
|
|
||||||
path: worktree_path,
|
|
||||||
};
|
|
||||||
buffer_store.open_buffer(project_path, cx)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
|
||||||
let buffer = open_task.await.log_err()?;
|
|
||||||
let buffer_content = outline::get_buffer_content_or_outline(
|
|
||||||
buffer.clone(),
|
|
||||||
Some(&full_path),
|
|
||||||
&cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
Some((rel_path, full_path, buffer_content.text, buffer))
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let contents = cx
|
|
||||||
.background_spawn(async move {
|
|
||||||
let (contents, tracked_buffers) = descendants_future
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.map(|(rel_path, full_path, rope, buffer)| {
|
|
||||||
((rel_path, full_path, rope), buffer)
|
|
||||||
})
|
|
||||||
.unzip();
|
|
||||||
Mention::Text {
|
|
||||||
content: render_directory_contents(contents),
|
|
||||||
tracked_buffers,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
anyhow::Ok(contents)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm_mention_for_fetch(
|
fn confirm_mention_for_fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
url: url::Url,
|
url: url::Url,
|
||||||
@@ -776,6 +685,7 @@ impl MessageEditor {
|
|||||||
|
|
||||||
pub fn contents(
|
pub fn contents(
|
||||||
&self,
|
&self,
|
||||||
|
full_mention_content: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>> {
|
) -> Task<Result<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>> {
|
||||||
// Check for unsupported slash commands before spawning async task
|
// Check for unsupported slash commands before spawning async task
|
||||||
@@ -787,9 +697,12 @@ impl MessageEditor {
|
|||||||
return Task::ready(Err(err));
|
return Task::ready(Err(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
let contents = self
|
let contents = self.mention_set.contents(
|
||||||
.mention_set
|
&self.prompt_capabilities.borrow(),
|
||||||
.contents(&self.prompt_capabilities.borrow(), cx);
|
full_mention_content,
|
||||||
|
self.project.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
|
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
@@ -807,7 +720,7 @@ impl MessageEditor {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot);
|
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
|
||||||
if crease_range.start > ix {
|
if crease_range.start > ix {
|
||||||
//todo(): Custom slash command ContentBlock?
|
//todo(): Custom slash command ContentBlock?
|
||||||
// let chunk = if prevent_slash_commands
|
// let chunk = if prevent_slash_commands
|
||||||
@@ -912,13 +825,20 @@ impl MessageEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&mut self, _: &Chat, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn send(&mut self, cx: &mut Context<Self>) {
|
||||||
if self.is_empty(cx) {
|
if self.is_empty(cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.clear_inlay_hints(cx);
|
||||||
|
});
|
||||||
cx.emit(MessageEditorEvent::Send)
|
cx.emit(MessageEditorEvent::Send)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chat(&mut self, _: &Chat, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.send(cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
cx.emit(MessageEditorEvent::Cancel)
|
cx.emit(MessageEditorEvent::Cancel)
|
||||||
}
|
}
|
||||||
@@ -954,11 +874,11 @@ impl MessageEditor {
|
|||||||
self.editor.update(cx, |message_editor, cx| {
|
self.editor.update(cx, |message_editor, cx| {
|
||||||
let snapshot = message_editor.snapshot(window, cx);
|
let snapshot = message_editor.snapshot(window, cx);
|
||||||
let (excerpt_id, _, buffer_snapshot) =
|
let (excerpt_id, _, buffer_snapshot) =
|
||||||
snapshot.buffer_snapshot.as_singleton().unwrap();
|
snapshot.buffer_snapshot().as_singleton().unwrap();
|
||||||
|
|
||||||
let text_anchor = buffer_snapshot.anchor_before(buffer_snapshot.len());
|
let text_anchor = buffer_snapshot.anchor_before(buffer_snapshot.len());
|
||||||
let multibuffer_anchor = snapshot
|
let multibuffer_anchor = snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot()
|
||||||
.anchor_in_excerpt(*excerpt_id, text_anchor);
|
.anchor_in_excerpt(*excerpt_id, text_anchor);
|
||||||
message_editor.edit(
|
message_editor.edit(
|
||||||
[(
|
[(
|
||||||
@@ -1039,6 +959,7 @@ impl MessageEditor {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
let path_style = self.project.read(cx).path_style(cx);
|
||||||
let buffer = self.editor.read(cx).buffer().clone();
|
let buffer = self.editor.read(cx).buffer().clone();
|
||||||
let Some(buffer) = buffer.read(cx).as_singleton() else {
|
let Some(buffer) = buffer.read(cx).as_singleton() else {
|
||||||
return;
|
return;
|
||||||
@@ -1048,18 +969,15 @@ impl MessageEditor {
|
|||||||
let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else {
|
let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else {
|
let Some(worktree) = self.project.read(cx).worktree_for_id(path.worktree_id, cx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let path_prefix = abs_path
|
let abs_path = worktree.read(cx).absolutize(&path.path);
|
||||||
.file_name()
|
|
||||||
.unwrap_or(path.path.as_os_str())
|
|
||||||
.display()
|
|
||||||
.to_string();
|
|
||||||
let (file_name, _) =
|
let (file_name, _) =
|
||||||
crate::context_picker::file_context_picker::extract_file_name_and_directory(
|
crate::context_picker::file_context_picker::extract_file_name_and_directory(
|
||||||
&path.path,
|
&path.path,
|
||||||
&path_prefix,
|
worktree.read(cx).root_name(),
|
||||||
|
path_style,
|
||||||
);
|
);
|
||||||
|
|
||||||
let uri = if entry.is_dir() {
|
let uri = if entry.is_dir() {
|
||||||
@@ -1121,6 +1039,7 @@ impl MessageEditor {
|
|||||||
) else {
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.editor.update(cx, |message_editor, cx| {
|
self.editor.update(cx, |message_editor, cx| {
|
||||||
message_editor.edit([(cursor_anchor..cursor_anchor, completion.new_text)], cx);
|
message_editor.edit([(cursor_anchor..cursor_anchor, completion.new_text)], cx);
|
||||||
});
|
});
|
||||||
@@ -1263,7 +1182,103 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_directory_contents(entries: Vec<(Arc<Path>, PathBuf, String)>) -> String {
|
fn full_mention_for_directory(
|
||||||
|
project: &Entity<Project>,
|
||||||
|
abs_path: &Path,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<Mention>> {
|
||||||
|
fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc<RelPath>, String)> {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
for entry in worktree.child_entries(path) {
|
||||||
|
if entry.is_dir() {
|
||||||
|
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||||
|
} else if entry.is_file() {
|
||||||
|
files.push((
|
||||||
|
entry.path.clone(),
|
||||||
|
worktree
|
||||||
|
.full_path(&entry.path)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(project_path) = project
|
||||||
|
.read(cx)
|
||||||
|
.project_path_for_absolute_path(&abs_path, cx)
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow!("project path not found")));
|
||||||
|
};
|
||||||
|
let Some(entry) = project.read(cx).entry_for_path(&project_path, cx) else {
|
||||||
|
return Task::ready(Err(anyhow!("project entry not found")));
|
||||||
|
};
|
||||||
|
let directory_path = entry.path.clone();
|
||||||
|
let worktree_id = project_path.worktree_id;
|
||||||
|
let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else {
|
||||||
|
return Task::ready(Err(anyhow!("worktree not found")));
|
||||||
|
};
|
||||||
|
let project = project.clone();
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let file_paths = worktree.read_with(cx, |worktree, _cx| {
|
||||||
|
collect_files_in_path(worktree, &directory_path)
|
||||||
|
})?;
|
||||||
|
let descendants_future = cx.update(|cx| {
|
||||||
|
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
|
||||||
|
let rel_path = worktree_path
|
||||||
|
.strip_prefix(&directory_path)
|
||||||
|
.log_err()
|
||||||
|
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
|
||||||
|
|
||||||
|
let open_task = project.update(cx, |project, cx| {
|
||||||
|
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||||
|
let project_path = ProjectPath {
|
||||||
|
worktree_id,
|
||||||
|
path: worktree_path,
|
||||||
|
};
|
||||||
|
buffer_store.open_buffer(project_path, cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let buffer = open_task.await.log_err()?;
|
||||||
|
let buffer_content = outline::get_buffer_content_or_outline(
|
||||||
|
buffer.clone(),
|
||||||
|
Some(&full_path),
|
||||||
|
&cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some((rel_path, full_path, buffer_content.text, buffer))
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let contents = cx
|
||||||
|
.background_spawn(async move {
|
||||||
|
let (contents, tracked_buffers) = descendants_future
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|(rel_path, full_path, rope, buffer)| {
|
||||||
|
((rel_path, full_path, rope), buffer)
|
||||||
|
})
|
||||||
|
.unzip();
|
||||||
|
Mention::Text {
|
||||||
|
content: render_directory_contents(contents),
|
||||||
|
tracked_buffers,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
anyhow::Ok(contents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_directory_contents(entries: Vec<(Arc<RelPath>, String, String)>) -> String {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
for (_relative_path, full_path, content) in entries {
|
for (_relative_path, full_path, content) in entries {
|
||||||
let fence = codeblock_fence_for_path(Some(&full_path), None);
|
let fence = codeblock_fence_for_path(Some(&full_path), None);
|
||||||
@@ -1282,24 +1297,20 @@ impl Render for MessageEditor {
|
|||||||
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 {
|
||||||
div()
|
div()
|
||||||
.key_context("MessageEditor")
|
.key_context("MessageEditor")
|
||||||
.on_action(cx.listener(Self::send))
|
.on_action(cx.listener(Self::chat))
|
||||||
.on_action(cx.listener(Self::cancel))
|
.on_action(cx.listener(Self::cancel))
|
||||||
.capture_action(cx.listener(Self::paste))
|
.capture_action(cx.listener(Self::paste))
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child({
|
.child({
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
let font_size = TextSize::Small
|
|
||||||
.rems(cx)
|
|
||||||
.to_pixels(settings.agent_font_size(cx));
|
|
||||||
let line_height = settings.buffer_line_height.value() * font_size;
|
|
||||||
|
|
||||||
let text_style = TextStyle {
|
let text_style = TextStyle {
|
||||||
color: cx.theme().colors().text,
|
color: cx.theme().colors().text,
|
||||||
font_family: settings.buffer_font.family.clone(),
|
font_family: settings.buffer_font.family.clone(),
|
||||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||||
font_features: settings.buffer_font.features.clone(),
|
font_features: settings.buffer_font.features.clone(),
|
||||||
font_size: font_size.into(),
|
font_size: settings.agent_buffer_font_size(cx).into(),
|
||||||
line_height: line_height.into(),
|
line_height: relative(settings.buffer_line_height.value()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1514,6 +1525,8 @@ impl MentionSet {
|
|||||||
fn contents(
|
fn contents(
|
||||||
&self,
|
&self,
|
||||||
prompt_capabilities: &acp::PromptCapabilities,
|
prompt_capabilities: &acp::PromptCapabilities,
|
||||||
|
full_mention_content: bool,
|
||||||
|
project: Entity<Project>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<HashMap<CreaseId, (MentionUri, Mention)>>> {
|
) -> Task<Result<HashMap<CreaseId, (MentionUri, Mention)>>> {
|
||||||
if !prompt_capabilities.embedded_context {
|
if !prompt_capabilities.embedded_context {
|
||||||
@@ -1527,13 +1540,19 @@ impl MentionSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mentions = self.mentions.clone();
|
let mentions = self.mentions.clone();
|
||||||
cx.spawn(async move |_cx| {
|
cx.spawn(async move |cx| {
|
||||||
let mut contents = HashMap::default();
|
let mut contents = HashMap::default();
|
||||||
for (crease_id, (mention_uri, task)) in mentions {
|
for (crease_id, (mention_uri, task)) in mentions {
|
||||||
contents.insert(
|
let content = if full_mention_content
|
||||||
crease_id,
|
&& let MentionUri::Directory { abs_path } = &mention_uri
|
||||||
(mention_uri, task.await.map_err(|e| anyhow!("{e}"))?),
|
{
|
||||||
);
|
cx.update(|cx| full_mention_for_directory(&project, abs_path, cx))?
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
task.await.map_err(|e| anyhow!("{e}"))?
|
||||||
|
};
|
||||||
|
|
||||||
|
contents.insert(crease_id, (mention_uri, content));
|
||||||
}
|
}
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
})
|
})
|
||||||
@@ -1541,7 +1560,7 @@ impl MentionSet {
|
|||||||
|
|
||||||
fn remove_invalid(&mut self, snapshot: EditorSnapshot) {
|
fn remove_invalid(&mut self, snapshot: EditorSnapshot) {
|
||||||
for (crease_id, crease) in snapshot.crease_snapshot.creases() {
|
for (crease_id, crease) in snapshot.crease_snapshot.creases() {
|
||||||
if !crease.range().start.is_valid(&snapshot.buffer_snapshot) {
|
if !crease.range().start.is_valid(&snapshot.buffer_snapshot()) {
|
||||||
self.mentions.remove(&crease_id);
|
self.mentions.remove(&crease_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1593,7 +1612,7 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use ui::{App, Context, IntoElement, Render, SharedString, Window};
|
use ui::{App, Context, IntoElement, Render, SharedString, Window};
|
||||||
use util::{path, uri};
|
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||||
use workspace::{AppState, Item, Workspace};
|
use workspace::{AppState, Item, Workspace};
|
||||||
|
|
||||||
use crate::acp::{
|
use crate::acp::{
|
||||||
@@ -1694,7 +1713,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let (content, _) = message_editor
|
let (content, _) = message_editor
|
||||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1757,7 +1776,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let contents_result = message_editor
|
let contents_result = message_editor
|
||||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Should fail because available_commands is empty (no commands supported)
|
// Should fail because available_commands is empty (no commands supported)
|
||||||
@@ -1780,7 +1799,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let contents_result = message_editor
|
let contents_result = message_editor
|
||||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(contents_result.is_err());
|
assert!(contents_result.is_err());
|
||||||
@@ -1795,7 +1814,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let contents_result = message_editor
|
let contents_result = message_editor
|
||||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Should succeed because /help is in available_commands
|
// Should succeed because /help is in available_commands
|
||||||
@@ -1807,7 +1826,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let (content, _) = message_editor
|
let (content, _) = message_editor
|
||||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1825,7 +1844,7 @@ mod tests {
|
|||||||
|
|
||||||
// The @ mention functionality should not be affected
|
// The @ mention functionality should not be affected
|
||||||
let (content, _) = message_editor
|
let (content, _) = message_editor
|
||||||
.update(cx, |message_editor, cx| message_editor.contents(cx))
|
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2002,21 +2021,11 @@ mod tests {
|
|||||||
editor.update_in(&mut cx, |editor, _window, cx| {
|
editor.update_in(&mut cx, |editor, _window, cx| {
|
||||||
assert_eq!(editor.text(cx), "/say-hello ");
|
assert_eq!(editor.text(cx), "/say-hello ");
|
||||||
assert_eq!(editor.display_text(cx), "/say-hello <name>");
|
assert_eq!(editor.display_text(cx), "/say-hello <name>");
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
current_completion_labels_with_documentation(editor),
|
|
||||||
&[("say-hello".into(), "Say hello to whoever you want".into())]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.simulate_input("GPT5");
|
cx.simulate_input("GPT5");
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
|
||||||
assert!(editor.has_visible_completions_menu());
|
|
||||||
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
@@ -2025,7 +2034,7 @@ mod tests {
|
|||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
|
|
||||||
// Delete argument
|
// Delete argument
|
||||||
for _ in 0..4 {
|
for _ in 0..5 {
|
||||||
editor.backspace(&editor::actions::Backspace, window, cx);
|
editor.backspace(&editor::actions::Backspace, window, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2033,13 +2042,12 @@ mod tests {
|
|||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
assert_eq!(editor.text(cx), "/say-hello ");
|
assert_eq!(editor.text(cx), "/say-hello");
|
||||||
// Hint is visible because argument was deleted
|
// Hint is visible because argument was deleted
|
||||||
assert_eq!(editor.display_text(cx), "/say-hello <name>");
|
assert_eq!(editor.display_text(cx), "/say-hello <name>");
|
||||||
|
|
||||||
// Delete last command letter
|
// Delete last command letter
|
||||||
editor.backspace(&editor::actions::Backspace, window, cx);
|
editor.backspace(&editor::actions::Backspace, window, cx);
|
||||||
editor.backspace(&editor::actions::Backspace, window, cx);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
@@ -2103,16 +2111,18 @@ mod tests {
|
|||||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||||
|
|
||||||
let paths = vec![
|
let paths = vec![
|
||||||
path!("a/one.txt"),
|
rel_path("a/one.txt"),
|
||||||
path!("a/two.txt"),
|
rel_path("a/two.txt"),
|
||||||
path!("a/three.txt"),
|
rel_path("a/three.txt"),
|
||||||
path!("a/four.txt"),
|
rel_path("a/four.txt"),
|
||||||
path!("b/five.txt"),
|
rel_path("b/five.txt"),
|
||||||
path!("b/six.txt"),
|
rel_path("b/six.txt"),
|
||||||
path!("b/seven.txt"),
|
rel_path("b/seven.txt"),
|
||||||
path!("b/eight.txt"),
|
rel_path("b/eight.txt"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let slash = PathStyle::local().separator();
|
||||||
|
|
||||||
let mut opened_editors = Vec::new();
|
let mut opened_editors = Vec::new();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let buffer = workspace
|
let buffer = workspace
|
||||||
@@ -2120,7 +2130,7 @@ mod tests {
|
|||||||
workspace.open_path(
|
workspace.open_path(
|
||||||
ProjectPath {
|
ProjectPath {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Path::new(path).into(),
|
path: path.into(),
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -2181,10 +2191,10 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
current_completion_labels(editor),
|
current_completion_labels(editor),
|
||||||
&[
|
&[
|
||||||
"eight.txt dir/b/",
|
format!("eight.txt dir{slash}b{slash}"),
|
||||||
"seven.txt dir/b/",
|
format!("seven.txt dir{slash}b{slash}"),
|
||||||
"six.txt dir/b/",
|
format!("six.txt dir{slash}b{slash}"),
|
||||||
"five.txt dir/b/",
|
format!("five.txt dir{slash}b{slash}"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
editor.set_text("", window, cx);
|
editor.set_text("", window, cx);
|
||||||
@@ -2212,14 +2222,14 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
current_completion_labels(editor),
|
current_completion_labels(editor),
|
||||||
&[
|
&[
|
||||||
"eight.txt dir/b/",
|
format!("eight.txt dir{slash}b{slash}"),
|
||||||
"seven.txt dir/b/",
|
format!("seven.txt dir{slash}b{slash}"),
|
||||||
"six.txt dir/b/",
|
format!("six.txt dir{slash}b{slash}"),
|
||||||
"five.txt dir/b/",
|
format!("five.txt dir{slash}b{slash}"),
|
||||||
"Files & Directories",
|
"Files & Directories".into(),
|
||||||
"Symbols",
|
"Symbols".into(),
|
||||||
"Threads",
|
"Threads".into(),
|
||||||
"Fetch"
|
"Fetch".into()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -2246,7 +2256,10 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), "Lorem @file one");
|
assert_eq!(editor.text(cx), "Lorem @file one");
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
|
assert_eq!(
|
||||||
|
current_completion_labels(editor),
|
||||||
|
vec![format!("one.txt dir{slash}a{slash}")]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
@@ -2254,7 +2267,11 @@ mod tests {
|
|||||||
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let url_one = uri!("file:///dir/a/one.txt");
|
let url_one = MentionUri::File {
|
||||||
|
abs_path: path!("/dir/a/one.txt").into(),
|
||||||
|
}
|
||||||
|
.to_uri()
|
||||||
|
.to_string();
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
let text = editor.text(cx);
|
let text = editor.text(cx);
|
||||||
assert_eq!(text, format!("Lorem [@one.txt]({url_one}) "));
|
assert_eq!(text, format!("Lorem [@one.txt]({url_one}) "));
|
||||||
@@ -2271,9 +2288,12 @@ mod tests {
|
|||||||
|
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update(&mut cx, |message_editor, cx| {
|
.update(&mut cx, |message_editor, cx| {
|
||||||
message_editor
|
message_editor.mention_set().contents(
|
||||||
.mention_set()
|
&all_prompt_capabilities,
|
||||||
.contents(&all_prompt_capabilities, cx)
|
false,
|
||||||
|
project.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -2290,9 +2310,12 @@ mod tests {
|
|||||||
|
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update(&mut cx, |message_editor, cx| {
|
.update(&mut cx, |message_editor, cx| {
|
||||||
message_editor
|
message_editor.mention_set().contents(
|
||||||
.mention_set()
|
&acp::PromptCapabilities::default(),
|
||||||
.contents(&acp::PromptCapabilities::default(), cx)
|
false,
|
||||||
|
project.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -2341,16 +2364,23 @@ mod tests {
|
|||||||
|
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update(&mut cx, |message_editor, cx| {
|
.update(&mut cx, |message_editor, cx| {
|
||||||
message_editor
|
message_editor.mention_set().contents(
|
||||||
.mention_set()
|
&all_prompt_capabilities,
|
||||||
.contents(&all_prompt_capabilities, cx)
|
false,
|
||||||
|
project.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_values()
|
.into_values()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let url_eight = uri!("file:///dir/b/eight.txt");
|
let url_eight = MentionUri::File {
|
||||||
|
abs_path: path!("/dir/b/eight.txt").into(),
|
||||||
|
}
|
||||||
|
.to_uri()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
{
|
{
|
||||||
let [_, (uri, Mention::Text { content, .. })] = contents.as_slice() else {
|
let [_, (uri, Mention::Text { content, .. })] = contents.as_slice() else {
|
||||||
@@ -2449,11 +2479,20 @@ mod tests {
|
|||||||
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let symbol = MentionUri::Symbol {
|
||||||
|
abs_path: path!("/dir/a/one.txt").into(),
|
||||||
|
name: "MySymbol".into(),
|
||||||
|
line_range: 0..=0,
|
||||||
|
};
|
||||||
|
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update(&mut cx, |message_editor, cx| {
|
.update(&mut cx, |message_editor, cx| {
|
||||||
message_editor
|
message_editor.mention_set().contents(
|
||||||
.mention_set()
|
&all_prompt_capabilities,
|
||||||
.contents(&all_prompt_capabilities, cx)
|
false,
|
||||||
|
project.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -2465,12 +2504,7 @@ mod tests {
|
|||||||
panic!("Unexpected mentions");
|
panic!("Unexpected mentions");
|
||||||
};
|
};
|
||||||
pretty_assertions::assert_eq!(content, "1");
|
pretty_assertions::assert_eq!(content, "1");
|
||||||
pretty_assertions::assert_eq!(
|
pretty_assertions::assert_eq!(uri, &symbol);
|
||||||
uri,
|
|
||||||
&format!("{url_one}?symbol=MySymbol#L1:1")
|
|
||||||
.parse::<MentionUri>()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
@@ -2478,7 +2512,10 @@ mod tests {
|
|||||||
editor.read_with(&cx, |editor, cx| {
|
editor.read_with(&cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
|
format!(
|
||||||
|
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
|
||||||
|
symbol.to_uri(),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2488,10 +2525,10 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
|
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
|
||||||
);
|
);
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(current_completion_labels(editor), &["x.png dir/"]);
|
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
@@ -2501,9 +2538,12 @@ mod tests {
|
|||||||
// Getting the message contents fails
|
// Getting the message contents fails
|
||||||
message_editor
|
message_editor
|
||||||
.update(&mut cx, |message_editor, cx| {
|
.update(&mut cx, |message_editor, cx| {
|
||||||
message_editor
|
message_editor.mention_set().contents(
|
||||||
.mention_set()
|
&all_prompt_capabilities,
|
||||||
.contents(&all_prompt_capabilities, cx)
|
false,
|
||||||
|
project.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.expect_err("Should fail to load x.png");
|
.expect_err("Should fail to load x.png");
|
||||||
@@ -2514,7 +2554,10 @@ mod tests {
|
|||||||
editor.read_with(&cx, |editor, cx| {
|
editor.read_with(&cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
|
format!(
|
||||||
|
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
|
||||||
|
symbol.to_uri()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2524,10 +2567,10 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
|
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
|
||||||
);
|
);
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(current_completion_labels(editor), &["x.png dir/"]);
|
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
@@ -2539,18 +2582,24 @@ mod tests {
|
|||||||
|
|
||||||
// Mention was removed
|
// Mention was removed
|
||||||
editor.read_with(&cx, |editor, cx| {
|
editor.read_with(&cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
|
format!(
|
||||||
);
|
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
|
||||||
});
|
symbol.to_uri()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Now getting the contents succeeds, because the invalid mention was removed
|
// Now getting the contents succeeds, because the invalid mention was removed
|
||||||
let contents = message_editor
|
let contents = message_editor
|
||||||
.update(&mut cx, |message_editor, cx| {
|
.update(&mut cx, |message_editor, cx| {
|
||||||
message_editor
|
message_editor.mention_set().contents(
|
||||||
.mention_set()
|
&all_prompt_capabilities,
|
||||||
.contents(&all_prompt_capabilities, cx)
|
false,
|
||||||
|
project.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -174,11 +174,16 @@ impl Render for ModeSelector {
|
|||||||
|
|
||||||
let this = cx.entity();
|
let this = cx.entity();
|
||||||
|
|
||||||
|
let icon = if self.menu_handle.is_deployed() {
|
||||||
|
IconName::ChevronUp
|
||||||
|
} else {
|
||||||
|
IconName::ChevronDown
|
||||||
|
};
|
||||||
|
|
||||||
let trigger_button = Button::new("mode-selector-trigger", current_mode_name)
|
let trigger_button = Button::new("mode-selector-trigger", current_mode_name)
|
||||||
.label_size(LabelSize::Small)
|
.label_size(LabelSize::Small)
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.icon(IconName::ChevronDown)
|
.icon(icon)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.icon_position(IconPosition::End)
|
.icon_position(IconPosition::End)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
use std::{cmp::Reverse, rc::Rc, sync::Arc};
|
use std::{cmp::Reverse, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector};
|
use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector};
|
||||||
use agent_client_protocol as acp;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::IndexMap;
|
use collections::IndexMap;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use fuzzy::{StringMatchCandidate, match_strings};
|
use fuzzy::{StringMatchCandidate, match_strings};
|
||||||
use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
|
use gpui::{AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use ui::{
|
use ui::{
|
||||||
AnyElement, App, Context, IntoElement, ListItem, ListItemSpacing, SharedString, Window,
|
DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, ListItem,
|
||||||
prelude::*, rems,
|
ListItemSpacing, prelude::*,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
|
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
|
||||||
|
|
||||||
pub fn acp_model_selector(
|
pub fn acp_model_selector(
|
||||||
session_id: acp::SessionId,
|
|
||||||
selector: Rc<dyn AgentModelSelector>,
|
selector: Rc<dyn AgentModelSelector>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<AcpModelSelector>,
|
cx: &mut Context<AcpModelSelector>,
|
||||||
) -> AcpModelSelector {
|
) -> AcpModelSelector {
|
||||||
let delegate = AcpModelPickerDelegate::new(session_id, selector, window, cx);
|
let delegate = AcpModelPickerDelegate::new(selector, window, cx);
|
||||||
Picker::list(delegate, window, cx)
|
Picker::list(delegate, window, cx)
|
||||||
.show_scrollbar(true)
|
.show_scrollbar(true)
|
||||||
.width(rems(20.))
|
.width(rems(20.))
|
||||||
@@ -36,61 +34,63 @@ enum AcpModelPickerEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcpModelPickerDelegate {
|
pub struct AcpModelPickerDelegate {
|
||||||
session_id: acp::SessionId,
|
|
||||||
selector: Rc<dyn AgentModelSelector>,
|
selector: Rc<dyn AgentModelSelector>,
|
||||||
filtered_entries: Vec<AcpModelPickerEntry>,
|
filtered_entries: Vec<AcpModelPickerEntry>,
|
||||||
models: Option<AgentModelList>,
|
models: Option<AgentModelList>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
|
selected_description: Option<(usize, SharedString)>,
|
||||||
selected_model: Option<AgentModelInfo>,
|
selected_model: Option<AgentModelInfo>,
|
||||||
_refresh_models_task: Task<()>,
|
_refresh_models_task: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcpModelPickerDelegate {
|
impl AcpModelPickerDelegate {
|
||||||
fn new(
|
fn new(
|
||||||
session_id: acp::SessionId,
|
|
||||||
selector: Rc<dyn AgentModelSelector>,
|
selector: Rc<dyn AgentModelSelector>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<AcpModelSelector>,
|
cx: &mut Context<AcpModelSelector>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut rx = selector.watch(cx);
|
let rx = selector.watch(cx);
|
||||||
let refresh_models_task = cx.spawn_in(window, {
|
let refresh_models_task = {
|
||||||
let session_id = session_id.clone();
|
cx.spawn_in(window, {
|
||||||
async move |this, cx| {
|
async move |this, cx| {
|
||||||
async fn refresh(
|
async fn refresh(
|
||||||
this: &WeakEntity<Picker<AcpModelPickerDelegate>>,
|
this: &WeakEntity<Picker<AcpModelPickerDelegate>>,
|
||||||
session_id: &acp::SessionId,
|
cx: &mut AsyncWindowContext,
|
||||||
cx: &mut AsyncWindowContext,
|
) -> Result<()> {
|
||||||
) -> Result<()> {
|
let (models_task, selected_model_task) = this.update(cx, |this, cx| {
|
||||||
let (models_task, selected_model_task) = this.update(cx, |this, cx| {
|
(
|
||||||
(
|
this.delegate.selector.list_models(cx),
|
||||||
this.delegate.selector.list_models(cx),
|
this.delegate.selector.selected_model(cx),
|
||||||
this.delegate.selector.selected_model(session_id, cx),
|
)
|
||||||
)
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let (models, selected_model) = futures::join!(models_task, selected_model_task);
|
let (models, selected_model) =
|
||||||
|
futures::join!(models_task, selected_model_task);
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.delegate.models = models.ok();
|
this.delegate.models = models.ok();
|
||||||
this.delegate.selected_model = selected_model.ok();
|
this.delegate.selected_model = selected_model.ok();
|
||||||
this.refresh(window, cx)
|
this.refresh(window, cx)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(&this, cx).await.log_err();
|
||||||
|
if let Some(mut rx) = rx {
|
||||||
|
while let Ok(()) = rx.recv().await {
|
||||||
|
refresh(&this, cx).await.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
refresh(&this, &session_id, cx).await.log_err();
|
};
|
||||||
while let Ok(()) = rx.recv().await {
|
|
||||||
refresh(&this, &session_id, cx).await.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
session_id,
|
|
||||||
selector,
|
selector,
|
||||||
filtered_entries: Vec::new(),
|
filtered_entries: Vec::new(),
|
||||||
models: None,
|
models: None,
|
||||||
selected_model: None,
|
selected_model: None,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
|
selected_description: None,
|
||||||
_refresh_models_task: refresh_models_task,
|
_refresh_models_task: refresh_models_task,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
self.filtered_entries.get(self.selected_index)
|
self.filtered_entries.get(self.selected_index)
|
||||||
{
|
{
|
||||||
self.selector
|
self.selector
|
||||||
.select_model(self.session_id.clone(), model_info.id.clone(), cx)
|
.select_model(model_info.id.clone(), cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
self.selected_model = Some(model_info.clone());
|
self.selected_model = Some(model_info.clone());
|
||||||
let current_index = self.selected_index;
|
let current_index = self.selected_index;
|
||||||
@@ -233,64 +233,64 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix)
|
div()
|
||||||
.inset(true)
|
.id(("model-picker-menu-child", ix))
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.when_some(model_info.description.clone(), |this, description| {
|
||||||
.toggle_state(selected)
|
this
|
||||||
.start_slot::<Icon>(model_info.icon.map(|icon| {
|
.on_hover(cx.listener(move |menu, hovered, _, cx| {
|
||||||
Icon::new(icon)
|
if *hovered {
|
||||||
.color(model_icon_color)
|
menu.delegate.selected_description = Some((ix, description.clone()));
|
||||||
.size(IconSize::Small)
|
} else if matches!(menu.delegate.selected_description, Some((id, _)) if id == ix) {
|
||||||
}))
|
menu.delegate.selected_description = None;
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
ListItem::new(ix)
|
||||||
.w_full()
|
.inset(true)
|
||||||
.pl_0p5()
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.gap_1p5()
|
.toggle_state(selected)
|
||||||
.w(px(240.))
|
.start_slot::<Icon>(model_info.icon.map(|icon| {
|
||||||
.child(Label::new(model_info.name.clone()).truncate()),
|
Icon::new(icon)
|
||||||
|
.color(model_icon_color)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.pl_0p5()
|
||||||
|
.gap_1p5()
|
||||||
|
.w(px(240.))
|
||||||
|
.child(Label::new(model_info.name.clone()).truncate()),
|
||||||
|
)
|
||||||
|
.end_slot(div().pr_3().when(is_selected, |this| {
|
||||||
|
this.child(
|
||||||
|
Icon::new(IconName::Check)
|
||||||
|
.color(Color::Accent)
|
||||||
|
.size(IconSize::Small),
|
||||||
|
)
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
.end_slot(div().pr_3().when(is_selected, |this| {
|
.into_any_element()
|
||||||
this.child(
|
|
||||||
Icon::new(IconName::Check)
|
|
||||||
.color(Color::Accent)
|
|
||||||
.size(IconSize::Small),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(
|
fn documentation_aside(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
_cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<gpui::AnyElement> {
|
) -> Option<ui::DocumentationAside> {
|
||||||
Some(
|
self.selected_description.as_ref().map(|(_, description)| {
|
||||||
h_flex()
|
let description = description.clone();
|
||||||
.w_full()
|
DocumentationAside::new(
|
||||||
.border_t_1()
|
DocumentationSide::Left,
|
||||||
.border_color(cx.theme().colors().border_variant)
|
DocumentationEdge::Top,
|
||||||
.p_1()
|
Rc::new(move |_| Label::new(description.clone()).into_any_element()),
|
||||||
.gap_4()
|
)
|
||||||
.justify_between()
|
})
|
||||||
.child(
|
|
||||||
Button::new("configure", "Configure")
|
|
||||||
.icon(IconName::Settings)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.icon_color(Color::Muted)
|
|
||||||
.icon_position(IconPosition::Start)
|
|
||||||
.on_click(|_, window, cx| {
|
|
||||||
window.dispatch_action(
|
|
||||||
zed_actions::agent::OpenSettings.boxed_clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_any(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +371,7 @@ async fn fuzzy_search(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use agent_client_protocol as acp;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -383,8 +384,9 @@ mod tests {
|
|||||||
models
|
models
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|model| acp_thread::AgentModelInfo {
|
.map(|model| acp_thread::AgentModelInfo {
|
||||||
id: acp_thread::AgentModelId(model.to_string().into()),
|
id: acp::ModelId(model.to_string().into()),
|
||||||
name: model.to_string().into(),
|
name: model.to_string().into(),
|
||||||
|
description: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use acp_thread::AgentModelSelector;
|
use acp_thread::AgentModelSelector;
|
||||||
use agent_client_protocol as acp;
|
|
||||||
use gpui::{Entity, FocusHandle};
|
use gpui::{Entity, FocusHandle};
|
||||||
use picker::popover_menu::PickerPopoverMenu;
|
use picker::popover_menu::PickerPopoverMenu;
|
||||||
use ui::{
|
use ui::{
|
||||||
@@ -20,7 +19,6 @@ pub struct AcpModelSelectorPopover {
|
|||||||
|
|
||||||
impl AcpModelSelectorPopover {
|
impl AcpModelSelectorPopover {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
session_id: acp::SessionId,
|
|
||||||
selector: Rc<dyn AgentModelSelector>,
|
selector: Rc<dyn AgentModelSelector>,
|
||||||
menu_handle: PopoverMenuHandle<AcpModelSelector>,
|
menu_handle: PopoverMenuHandle<AcpModelSelector>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
@@ -28,7 +26,7 @@ impl AcpModelSelectorPopover {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
selector: cx.new(move |cx| acp_model_selector(session_id, selector, window, cx)),
|
selector: cx.new(move |cx| acp_model_selector(selector, window, cx)),
|
||||||
menu_handle,
|
menu_handle,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
}
|
}
|
||||||
@@ -59,30 +57,26 @@ impl Render for AcpModelSelectorPopover {
|
|||||||
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
let color = if self.menu_handle.is_deployed() {
|
let (color, icon) = if self.menu_handle.is_deployed() {
|
||||||
Color::Accent
|
(Color::Accent, IconName::ChevronUp)
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
(Color::Muted, IconName::ChevronDown)
|
||||||
};
|
};
|
||||||
|
|
||||||
PickerPopoverMenu::new(
|
PickerPopoverMenu::new(
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
.when_some(model_icon, |this, icon| {
|
.when_some(model_icon, |this, icon| {
|
||||||
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
||||||
})
|
})
|
||||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
|
||||||
.child(
|
.child(
|
||||||
Label::new(model_name)
|
Label::new(model_name)
|
||||||
.color(color)
|
.color(color)
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.ml_0p5(),
|
.ml_0p5(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
|
||||||
Icon::new(IconName::ChevronDown)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
),
|
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
Tooltip::for_action_in(
|
Tooltip::for_action_in(
|
||||||
"Change Model",
|
"Change Model",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ mod tool_picker;
|
|||||||
|
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
use agent_settings::AgentSettings;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||||
use cloud_llm_client::{Plan, PlanV1, PlanV2};
|
use cloud_llm_client::{Plan, PlanV1, PlanV2};
|
||||||
@@ -26,13 +25,13 @@ use language_model::{
|
|||||||
};
|
};
|
||||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||||
use project::{
|
use project::{
|
||||||
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, GEMINI_NAME},
|
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME},
|
||||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||||
};
|
};
|
||||||
use settings::{Settings, SettingsStore, update_settings_file};
|
use settings::{SettingsStore, update_settings_file};
|
||||||
use ui::{
|
use ui::{
|
||||||
Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex,
|
Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex,
|
||||||
Indicator, PopoverMenu, Switch, SwitchColor, SwitchField, Tooltip, WithScrollbar, prelude::*,
|
Indicator, PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*,
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{Workspace, create_and_open_local_file};
|
use workspace::{Workspace, create_and_open_local_file};
|
||||||
@@ -402,101 +401,6 @@ impl AgentConfiguration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
|
|
||||||
let fs = self.fs.clone();
|
|
||||||
|
|
||||||
SwitchField::new(
|
|
||||||
"always-allow-tool-actions-switch",
|
|
||||||
"Allow running commands without asking for confirmation",
|
|
||||||
Some(
|
|
||||||
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
|
|
||||||
),
|
|
||||||
always_allow_tool_actions,
|
|
||||||
move |state, _window, cx| {
|
|
||||||
let allow = state == &ToggleState::Selected;
|
|
||||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
|
||||||
settings.agent.get_or_insert_default().set_always_allow_tool_actions(allow);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let single_file_review = AgentSettings::get_global(cx).single_file_review;
|
|
||||||
let fs = self.fs.clone();
|
|
||||||
|
|
||||||
SwitchField::new(
|
|
||||||
"single-file-review",
|
|
||||||
"Enable single-file agent reviews",
|
|
||||||
Some("Agent edits are also displayed in single-file editors for review.".into()),
|
|
||||||
single_file_review,
|
|
||||||
move |state, _window, cx| {
|
|
||||||
let allow = state == &ToggleState::Selected;
|
|
||||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
|
||||||
settings
|
|
||||||
.agent
|
|
||||||
.get_or_insert_default()
|
|
||||||
.set_single_file_review(allow);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
|
|
||||||
let fs = self.fs.clone();
|
|
||||||
|
|
||||||
SwitchField::new(
|
|
||||||
"sound-notification",
|
|
||||||
"Play sound when finished generating",
|
|
||||||
Some(
|
|
||||||
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
|
|
||||||
),
|
|
||||||
play_sound_when_agent_done,
|
|
||||||
move |state, _window, cx| {
|
|
||||||
let allow = state == &ToggleState::Selected;
|
|
||||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
|
||||||
settings.agent.get_or_insert_default().set_play_sound_when_agent_done(allow);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_modifier_to_send(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send;
|
|
||||||
let fs = self.fs.clone();
|
|
||||||
|
|
||||||
SwitchField::new(
|
|
||||||
"modifier-send",
|
|
||||||
"Use modifier to submit a message",
|
|
||||||
Some(
|
|
||||||
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(),
|
|
||||||
),
|
|
||||||
use_modifier_to_send,
|
|
||||||
move |state, _window, cx| {
|
|
||||||
let allow = state == &ToggleState::Selected;
|
|
||||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
|
||||||
settings.agent.get_or_insert_default().set_use_modifier_to_send(allow);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
|
||||||
.pr(DynamicSpacing::Base20.rems(cx))
|
|
||||||
.gap_2p5()
|
|
||||||
.border_b_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.child(Headline::new("General Settings"))
|
|
||||||
.child(self.render_command_permission(cx))
|
|
||||||
.child(self.render_single_file_review(cx))
|
|
||||||
.child(self.render_sound_notification(cx))
|
|
||||||
.child(self.render_modifier_to_send(cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
if let Some(plan) = plan {
|
if let Some(plan) = plan {
|
||||||
let free_chip_bg = cx
|
let free_chip_bg = cx
|
||||||
@@ -543,35 +447,23 @@ impl AgentConfiguration {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let mut registry_descriptors = self
|
let mut context_server_ids = self
|
||||||
.context_server_store
|
.context_server_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.all_registry_descriptor_ids(cx);
|
.server_ids(cx)
|
||||||
let server_count = registry_descriptors.len();
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Sort context servers: non-mcp-server ones first, then mcp-server ones
|
// Sort context servers: ones without mcp-server- prefix first, then prefixed ones
|
||||||
registry_descriptors.sort_by(|a, b| {
|
context_server_ids.sort_by(|a, b| {
|
||||||
let has_mcp_prefix_a = a.0.starts_with("mcp-server-");
|
const MCP_PREFIX: &str = "mcp-server-";
|
||||||
let has_mcp_prefix_b = b.0.starts_with("mcp-server-");
|
match (a.0.strip_prefix(MCP_PREFIX), b.0.strip_prefix(MCP_PREFIX)) {
|
||||||
|
|
||||||
match (has_mcp_prefix_a, has_mcp_prefix_b) {
|
|
||||||
// If one has mcp-server- prefix and other doesn't, non-mcp comes first
|
// If one has mcp-server- prefix and other doesn't, non-mcp comes first
|
||||||
(true, false) => std::cmp::Ordering::Greater,
|
(Some(_), None) => std::cmp::Ordering::Greater,
|
||||||
(false, true) => std::cmp::Ordering::Less,
|
(None, Some(_)) => std::cmp::Ordering::Less,
|
||||||
// If both have same prefix status, sort by appropriate key
|
// If both have same prefix status, sort by appropriate key
|
||||||
_ => {
|
(Some(a), Some(b)) => a.cmp(b),
|
||||||
let get_sort_key = |server_id: &str| -> String {
|
(None, None) => a.0.cmp(&b.0),
|
||||||
if let Some(suffix) = server_id.strip_prefix("mcp-server-") {
|
|
||||||
suffix.to_string()
|
|
||||||
} else {
|
|
||||||
server_id.to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let key_a = get_sort_key(&a.0);
|
|
||||||
let key_b = get_sort_key(&b.0);
|
|
||||||
key_a.cmp(&key_b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -636,8 +528,8 @@ impl AgentConfiguration {
|
|||||||
)
|
)
|
||||||
.child(add_server_popover),
|
.child(add_server_popover),
|
||||||
)
|
)
|
||||||
.child(v_flex().w_full().gap_1().map(|parent| {
|
.child(v_flex().w_full().gap_1().map(|mut parent| {
|
||||||
if registry_descriptors.is_empty() {
|
if context_server_ids.is_empty() {
|
||||||
parent.child(
|
parent.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.p_4()
|
.p_4()
|
||||||
@@ -653,26 +545,18 @@ impl AgentConfiguration {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
{
|
for (index, context_server_id) in context_server_ids.into_iter().enumerate() {
|
||||||
parent.children(registry_descriptors.into_iter().enumerate().flat_map(
|
if index > 0 {
|
||||||
|(index, context_server_id)| {
|
parent = parent.child(
|
||||||
let mut elements: Vec<AnyElement> = vec![
|
Divider::horizontal()
|
||||||
self.render_context_server(context_server_id, window, cx)
|
.color(DividerColor::BorderFaded)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
];
|
);
|
||||||
|
}
|
||||||
if index < server_count - 1 {
|
parent =
|
||||||
elements.push(
|
parent.child(self.render_context_server(context_server_id, window, cx));
|
||||||
Divider::horizontal()
|
|
||||||
.color(DividerColor::BorderFaded)
|
|
||||||
.into_any_element(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
elements
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
parent
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -1034,7 +918,9 @@ impl AgentConfiguration {
|
|||||||
.agent_server_store
|
.agent_server_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.external_agents()
|
.external_agents()
|
||||||
.filter(|name| name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME)
|
.filter(|name| {
|
||||||
|
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
|
||||||
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -1097,16 +983,27 @@ impl AgentConfiguration {
|
|||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(self.render_agent_server(
|
|
||||||
IconName::AiGemini,
|
|
||||||
"Gemini CLI",
|
|
||||||
))
|
|
||||||
.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
|
||||||
.child(self.render_agent_server(
|
.child(self.render_agent_server(
|
||||||
IconName::AiClaude,
|
IconName::AiClaude,
|
||||||
"Claude Code",
|
"Claude Code",
|
||||||
))
|
))
|
||||||
.children(user_defined_agents),
|
.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
||||||
|
.child(self.render_agent_server(
|
||||||
|
IconName::AiOpenAi,
|
||||||
|
"Codex",
|
||||||
|
))
|
||||||
|
.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
||||||
|
.child(self.render_agent_server(
|
||||||
|
IconName::AiGemini,
|
||||||
|
"Gemini CLI",
|
||||||
|
))
|
||||||
|
.map(|mut parent| {
|
||||||
|
for agent in user_defined_agents {
|
||||||
|
parent = parent.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
||||||
|
.child(agent);
|
||||||
|
}
|
||||||
|
parent
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,7 +1045,6 @@ impl Render for AgentConfiguration {
|
|||||||
.track_scroll(&self.scroll_handle)
|
.track_scroll(&self.scroll_handle)
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.child(self.render_general_settings_section(cx))
|
|
||||||
.child(self.render_agent_servers_section(cx))
|
.child(self.render_agent_servers_section(cx))
|
||||||
.child(self.render_context_servers_section(window, cx))
|
.child(self.render_context_servers_section(window, cx))
|
||||||
.child(self.render_provider_configuration_section(cx)),
|
.child(self.render_provider_configuration_section(cx)),
|
||||||
|
|||||||
@@ -619,10 +619,10 @@ mod tests {
|
|||||||
cx.update(|_window, cx| {
|
cx.update(|_window, cx| {
|
||||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||||
registry.register_provider(
|
registry.register_provider(
|
||||||
FakeLanguageModelProvider::new(
|
Arc::new(FakeLanguageModelProvider::new(
|
||||||
LanguageModelProviderId::new("someprovider"),
|
LanguageModelProviderId::new("someprovider"),
|
||||||
LanguageModelProviderName::new("Some Provider"),
|
LanguageModelProviderName::new("Some Provider"),
|
||||||
),
|
)),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -317,6 +317,8 @@ impl ManageProfilesModal {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement + use<> {
|
) -> impl IntoElement + use<> {
|
||||||
|
let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from(format!("profile-{}", profile.id)))
|
.id(SharedString::from(format!("profile-{}", profile.id)))
|
||||||
.track_focus(&profile.navigation.focus_handle)
|
.track_focus(&profile.navigation.focus_handle)
|
||||||
@@ -328,25 +330,27 @@ impl ManageProfilesModal {
|
|||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
|
ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
|
||||||
.toggle_state(profile.navigation.focus_handle.contains_focused(window, cx))
|
.toggle_state(is_focused)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.child(Label::new(profile.name.clone()))
|
.child(Label::new(profile.name.clone()))
|
||||||
.end_slot(
|
.when(is_focused, |this| {
|
||||||
h_flex()
|
this.end_slot(
|
||||||
.gap_1()
|
h_flex()
|
||||||
.child(
|
.gap_1()
|
||||||
Label::new("Customize")
|
.child(
|
||||||
.size(LabelSize::Small)
|
Label::new("Customize")
|
||||||
.color(Color::Muted),
|
.size(LabelSize::Small)
|
||||||
)
|
.color(Color::Muted),
|
||||||
.children(KeyBinding::for_action_in(
|
)
|
||||||
&menu::Confirm,
|
.children(KeyBinding::for_action_in(
|
||||||
&self.focus_handle,
|
&menu::Confirm,
|
||||||
window,
|
&self.focus_handle,
|
||||||
cx,
|
window,
|
||||||
)),
|
cx,
|
||||||
)
|
)),
|
||||||
|
)
|
||||||
|
})
|
||||||
.on_click({
|
.on_click({
|
||||||
let profile_id = profile.id.clone();
|
let profile_id = profile.id.clone();
|
||||||
cx.listener(move |this, _, window, cx| {
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
|||||||
@@ -562,10 +562,6 @@ impl Item for AgentDiffPane {
|
|||||||
self.editor.for_each_project_item(cx, f)
|
self.editor.for_each_project_item(cx, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_singleton(&self, _: &App) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_nav_history(
|
fn set_nav_history(
|
||||||
&mut self,
|
&mut self,
|
||||||
nav_history: ItemNavHistory,
|
nav_history: ItemNavHistory,
|
||||||
@@ -850,7 +846,7 @@ fn render_diff_hunk_controls(
|
|||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
let snapshot = editor.snapshot(window, cx);
|
let snapshot = editor.snapshot(window, cx);
|
||||||
let position =
|
let position =
|
||||||
hunk_range.end.to_point(&snapshot.buffer_snapshot);
|
hunk_range.end.to_point(&snapshot.buffer_snapshot());
|
||||||
editor.go_to_hunk_before_or_after_position(
|
editor.go_to_hunk_before_or_after_position(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
position,
|
position,
|
||||||
@@ -886,7 +882,7 @@ fn render_diff_hunk_controls(
|
|||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
let snapshot = editor.snapshot(window, cx);
|
let snapshot = editor.snapshot(window, cx);
|
||||||
let point =
|
let point =
|
||||||
hunk_range.start.to_point(&snapshot.buffer_snapshot);
|
hunk_range.start.to_point(&snapshot.buffer_snapshot());
|
||||||
editor.go_to_hunk_before_or_after_position(
|
editor.go_to_hunk_before_or_after_position(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
point,
|
point,
|
||||||
@@ -1818,7 +1814,6 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use std::{path::Path, rc::Rc};
|
use std::{path::Path, rc::Rc};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use util::path;
|
use util::path;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@@ -1831,7 +1826,7 @@ mod tests {
|
|||||||
AgentSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
ThemeSettings::register(cx);
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
EditorSettings::register(cx);
|
EditorSettings::register(cx);
|
||||||
language_model::init_settings(cx);
|
language_model::init_settings(cx);
|
||||||
});
|
});
|
||||||
@@ -1983,7 +1978,7 @@ mod tests {
|
|||||||
AgentSettings::register(cx);
|
AgentSettings::register(cx);
|
||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
workspace::init_settings(cx);
|
workspace::init_settings(cx);
|
||||||
ThemeSettings::register(cx);
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
EditorSettings::register(cx);
|
EditorSettings::register(cx);
|
||||||
language_model::init_settings(cx);
|
language_model::init_settings(cx);
|
||||||
workspace::register_project_item::<Editor>(cx);
|
workspace::register_project_item::<Editor>(cx);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use gpui::{Entity, FocusHandle, SharedString};
|
|||||||
use picker::popover_menu::PickerPopoverMenu;
|
use picker::popover_menu::PickerPopoverMenu;
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
|
||||||
use zed_actions::agent::ToggleModelSelector;
|
use zed_actions::agent::ToggleModelSelector;
|
||||||
|
|
||||||
pub struct AgentModelSelector {
|
pub struct AgentModelSelector {
|
||||||
@@ -70,6 +70,11 @@ impl Render for AgentModelSelector {
|
|||||||
.unwrap_or_else(|| SharedString::from("Select a Model"));
|
.unwrap_or_else(|| SharedString::from("Select a Model"));
|
||||||
|
|
||||||
let provider_icon = model.as_ref().map(|model| model.provider.icon());
|
let provider_icon = model.as_ref().map(|model| model.provider.icon());
|
||||||
|
let color = if self.menu_handle.is_deployed() {
|
||||||
|
Color::Accent
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
};
|
||||||
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
@@ -77,17 +82,18 @@ impl Render for AgentModelSelector {
|
|||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
.when_some(provider_icon, |this, icon| {
|
.when_some(provider_icon, |this, icon| {
|
||||||
this.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall))
|
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
||||||
})
|
})
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
.child(
|
.child(
|
||||||
Label::new(model_name)
|
Label::new(model_name)
|
||||||
.color(Color::Muted)
|
.color(color)
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
.ml_0p5(),
|
.ml_0p5(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::ChevronDown)
|
Icon::new(IconName::ChevronDown)
|
||||||
.color(Color::Muted)
|
.color(color)
|
||||||
.size(IconSize::XSmall),
|
.size(IconSize::XSmall),
|
||||||
),
|
),
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
@@ -99,10 +105,14 @@ impl Render for AgentModelSelector {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
gpui::Corner::BottomRight,
|
gpui::Corner::TopRight,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.with_handle(self.menu_handle.clone())
|
.with_handle(self.menu_handle.clone())
|
||||||
|
.offset(gpui::Point {
|
||||||
|
x: px(0.0),
|
||||||
|
y: px(2.0),
|
||||||
|
})
|
||||||
.render(window, cx)
|
.render(window, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::ops::{Not, Range};
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -7,7 +7,7 @@ use acp_thread::AcpThread;
|
|||||||
use agent2::{DbThreadMetadata, HistoryEntry};
|
use agent2::{DbThreadMetadata, HistoryEntry};
|
||||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||||
use project::agent_server_store::{
|
use project::agent_server_store::{
|
||||||
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, GEMINI_NAME,
|
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{
|
use settings::{
|
||||||
@@ -19,9 +19,10 @@ use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
|
|||||||
use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
|
use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
|
||||||
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
|
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
|
||||||
use crate::{
|
use crate::{
|
||||||
AddContextServer, DeleteRecentlyOpenThread, Follow, InlineAssistant, NewTextThread, NewThread,
|
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
|
||||||
OpenActiveThreadAsMarkdown, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
|
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory,
|
||||||
ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
|
ResetTrialEndUpsell, ResetTrialUpsell, ToggleNavigationMenu, ToggleNewThreadMenu,
|
||||||
|
ToggleOptionsMenu,
|
||||||
acp::AcpThreadView,
|
acp::AcpThreadView,
|
||||||
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
||||||
slash_command::SlashCommandCompletionProvider,
|
slash_command::SlashCommandCompletionProvider,
|
||||||
@@ -33,7 +34,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use agent::{
|
use agent::{
|
||||||
context_store::ContextStore,
|
context_store::ContextStore,
|
||||||
history_store::{HistoryEntryId, HistoryStore},
|
|
||||||
thread_store::{TextThreadStore, ThreadStore},
|
thread_store::{TextThreadStore, ThreadStore},
|
||||||
};
|
};
|
||||||
use agent_settings::AgentSettings;
|
use agent_settings::AgentSettings;
|
||||||
@@ -53,7 +53,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use language_model::{ConfigurationError, LanguageModelRegistry};
|
use language_model::{ConfigurationError, LanguageModelRegistry};
|
||||||
use project::{DisableAiSettings, Project, ProjectPath, Worktree};
|
use project::{Project, ProjectPath, Worktree};
|
||||||
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
|
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
|
||||||
use rules_library::{RulesLibrary, open_rules_library};
|
use rules_library::{RulesLibrary, open_rules_library};
|
||||||
use search::{BufferSearchBar, buffer_search};
|
use search::{BufferSearchBar, buffer_search};
|
||||||
@@ -140,6 +140,16 @@ pub fn init(cx: &mut App) {
|
|||||||
.register_action(|workspace, _: &Follow, window, cx| {
|
.register_action(|workspace, _: &Follow, window, cx| {
|
||||||
workspace.follow(CollaboratorId::Agent, window, cx);
|
workspace.follow(CollaboratorId::Agent, window, cx);
|
||||||
})
|
})
|
||||||
|
.register_action(|workspace, _: &OpenAgentDiff, window, cx| {
|
||||||
|
let thread = workspace
|
||||||
|
.panel::<AgentPanel>(cx)
|
||||||
|
.and_then(|panel| panel.read(cx).active_thread_view().cloned())
|
||||||
|
.and_then(|thread_view| thread_view.read(cx).thread().cloned());
|
||||||
|
|
||||||
|
if let Some(thread) = thread {
|
||||||
|
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
.register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
|
.register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
|
||||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||||
workspace.focus_panel::<AgentPanel>(window, cx);
|
workspace.focus_panel::<AgentPanel>(window, cx);
|
||||||
@@ -212,11 +222,11 @@ enum WhichFontSize {
|
|||||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum AgentType {
|
pub enum AgentType {
|
||||||
#[default]
|
#[default]
|
||||||
Zed,
|
NativeAgent,
|
||||||
TextThread,
|
TextThread,
|
||||||
Gemini,
|
Gemini,
|
||||||
ClaudeCode,
|
ClaudeCode,
|
||||||
NativeAgent,
|
Codex,
|
||||||
Custom {
|
Custom {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
command: AgentServerCommand,
|
command: AgentServerCommand,
|
||||||
@@ -226,19 +236,20 @@ pub enum AgentType {
|
|||||||
impl AgentType {
|
impl AgentType {
|
||||||
fn label(&self) -> SharedString {
|
fn label(&self) -> SharedString {
|
||||||
match self {
|
match self {
|
||||||
Self::Zed | Self::TextThread => "Zed Agent".into(),
|
Self::NativeAgent | Self::TextThread => "Zed Agent".into(),
|
||||||
Self::NativeAgent => "Agent 2".into(),
|
|
||||||
Self::Gemini => "Gemini CLI".into(),
|
Self::Gemini => "Gemini CLI".into(),
|
||||||
Self::ClaudeCode => "Claude Code".into(),
|
Self::ClaudeCode => "Claude Code".into(),
|
||||||
|
Self::Codex => "Codex".into(),
|
||||||
Self::Custom { name, .. } => name.into(),
|
Self::Custom { name, .. } => name.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self) -> Option<IconName> {
|
fn icon(&self) -> Option<IconName> {
|
||||||
match self {
|
match self {
|
||||||
Self::Zed | Self::NativeAgent | Self::TextThread => None,
|
Self::NativeAgent | Self::TextThread => None,
|
||||||
Self::Gemini => Some(IconName::AiGemini),
|
Self::Gemini => Some(IconName::AiGemini),
|
||||||
Self::ClaudeCode => Some(IconName::AiClaude),
|
Self::ClaudeCode => Some(IconName::AiClaude),
|
||||||
|
Self::Codex => Some(IconName::AiOpenAi),
|
||||||
Self::Custom { .. } => Some(IconName::Terminal),
|
Self::Custom { .. } => Some(IconName::Terminal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,6 +260,7 @@ impl From<ExternalAgent> for AgentType {
|
|||||||
match value {
|
match value {
|
||||||
ExternalAgent::Gemini => Self::Gemini,
|
ExternalAgent::Gemini => Self::Gemini,
|
||||||
ExternalAgent::ClaudeCode => Self::ClaudeCode,
|
ExternalAgent::ClaudeCode => Self::ClaudeCode,
|
||||||
|
ExternalAgent::Codex => Self::Codex,
|
||||||
ExternalAgent::Custom { name, command } => Self::Custom { name, command },
|
ExternalAgent::Custom { name, command } => Self::Custom { name, command },
|
||||||
ExternalAgent::NativeAgent => Self::NativeAgent,
|
ExternalAgent::NativeAgent => Self::NativeAgent,
|
||||||
}
|
}
|
||||||
@@ -294,7 +306,6 @@ impl ActiveView {
|
|||||||
|
|
||||||
pub fn prompt_editor(
|
pub fn prompt_editor(
|
||||||
context_editor: Entity<TextThreadEditor>,
|
context_editor: Entity<TextThreadEditor>,
|
||||||
history_store: Entity<HistoryStore>,
|
|
||||||
acp_history_store: Entity<agent2::HistoryStore>,
|
acp_history_store: Entity<agent2::HistoryStore>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
@@ -362,18 +373,6 @@ impl ActiveView {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
ContextEvent::PathChanged { old_path, new_path } => {
|
ContextEvent::PathChanged { old_path, new_path } => {
|
||||||
history_store.update(cx, |history_store, cx| {
|
|
||||||
if let Some(old_path) = old_path {
|
|
||||||
history_store
|
|
||||||
.replace_recently_opened_text_thread(old_path, new_path, cx);
|
|
||||||
} else {
|
|
||||||
history_store.push_recently_opened_entry(
|
|
||||||
HistoryEntryId::Context(new_path.clone()),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
acp_history_store.update(cx, |history_store, cx| {
|
acp_history_store.update(cx, |history_store, cx| {
|
||||||
if let Some(old_path) = old_path {
|
if let Some(old_path) = old_path {
|
||||||
history_store
|
history_store
|
||||||
@@ -408,13 +407,14 @@ impl ActiveView {
|
|||||||
|
|
||||||
pub struct AgentPanel {
|
pub struct AgentPanel {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
loading: bool,
|
||||||
user_store: Entity<UserStore>,
|
user_store: Entity<UserStore>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
acp_history: Entity<AcpThreadHistory>,
|
acp_history: Entity<AcpThreadHistory>,
|
||||||
acp_history_store: Entity<agent2::HistoryStore>,
|
history_store: Entity<agent2::HistoryStore>,
|
||||||
context_store: Entity<TextThreadStore>,
|
context_store: Entity<TextThreadStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
inline_assist_context_store: Entity<ContextStore>,
|
inline_assist_context_store: Entity<ContextStore>,
|
||||||
@@ -422,7 +422,6 @@ pub struct AgentPanel {
|
|||||||
configuration_subscription: Option<Subscription>,
|
configuration_subscription: Option<Subscription>,
|
||||||
active_view: ActiveView,
|
active_view: ActiveView,
|
||||||
previous_view: Option<ActiveView>,
|
previous_view: Option<ActiveView>,
|
||||||
history_store: Entity<HistoryStore>,
|
|
||||||
new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
|
new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
|
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
|
assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
@@ -513,6 +512,8 @@ impl AgentPanel {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
panel.as_mut(cx).loading = true;
|
||||||
if let Some(serialized_panel) = serialized_panel {
|
if let Some(serialized_panel) = serialized_panel {
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
panel.width = serialized_panel.width.map(|w| w.round());
|
panel.width = serialized_panel.width.map(|w| w.round());
|
||||||
@@ -527,6 +528,7 @@ impl AgentPanel {
|
|||||||
panel.new_agent_thread(AgentType::NativeAgent, window, cx);
|
panel.new_agent_thread(AgentType::NativeAgent, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
panel.as_mut(cx).loading = false;
|
||||||
panel
|
panel
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -552,10 +554,8 @@ impl AgentPanel {
|
|||||||
let inline_assist_context_store =
|
let inline_assist_context_store =
|
||||||
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
||||||
|
|
||||||
let history_store = cx.new(|cx| HistoryStore::new(context_store.clone(), [], cx));
|
let history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
|
||||||
|
let acp_history = cx.new(|cx| AcpThreadHistory::new(history_store.clone(), window, cx));
|
||||||
let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
|
|
||||||
let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx));
|
|
||||||
cx.subscribe_in(
|
cx.subscribe_in(
|
||||||
&acp_history,
|
&acp_history,
|
||||||
window,
|
window,
|
||||||
@@ -577,14 +577,12 @@ impl AgentPanel {
|
|||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
|
|
||||||
|
|
||||||
let panel_type = AgentSettings::get_global(cx).default_view;
|
let panel_type = AgentSettings::get_global(cx).default_view;
|
||||||
let active_view = match panel_type {
|
let active_view = match panel_type {
|
||||||
DefaultView::Thread => ActiveView::native_agent(
|
DefaultView::Thread => ActiveView::native_agent(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
prompt_store.clone(),
|
prompt_store.clone(),
|
||||||
acp_history_store.clone(),
|
history_store.clone(),
|
||||||
project.clone(),
|
project.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
window,
|
window,
|
||||||
@@ -610,7 +608,6 @@ impl AgentPanel {
|
|||||||
ActiveView::prompt_editor(
|
ActiveView::prompt_editor(
|
||||||
context_editor,
|
context_editor,
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
acp_history_store.clone(),
|
|
||||||
language_registry.clone(),
|
language_registry.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -674,11 +671,8 @@ impl AgentPanel {
|
|||||||
prompt_store,
|
prompt_store,
|
||||||
configuration: None,
|
configuration: None,
|
||||||
configuration_subscription: None,
|
configuration_subscription: None,
|
||||||
|
|
||||||
inline_assist_context_store,
|
inline_assist_context_store,
|
||||||
previous_view: None,
|
previous_view: None,
|
||||||
history_store: history_store.clone(),
|
|
||||||
|
|
||||||
new_thread_menu_handle: PopoverMenuHandle::default(),
|
new_thread_menu_handle: PopoverMenuHandle::default(),
|
||||||
agent_panel_menu_handle: PopoverMenuHandle::default(),
|
agent_panel_menu_handle: PopoverMenuHandle::default(),
|
||||||
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
|
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
|
||||||
@@ -689,8 +683,9 @@ impl AgentPanel {
|
|||||||
pending_serialization: None,
|
pending_serialization: None,
|
||||||
onboarding,
|
onboarding,
|
||||||
acp_history,
|
acp_history,
|
||||||
acp_history_store,
|
history_store,
|
||||||
selected_agent: AgentType::default(),
|
selected_agent: AgentType::default(),
|
||||||
|
loading: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,7 +698,6 @@ impl AgentPanel {
|
|||||||
if workspace
|
if workspace
|
||||||
.panel::<Self>(cx)
|
.panel::<Self>(cx)
|
||||||
.is_some_and(|panel| panel.read(cx).enabled(cx))
|
.is_some_and(|panel| panel.read(cx).enabled(cx))
|
||||||
&& !DisableAiSettings::get_global(cx).disable_ai
|
|
||||||
{
|
{
|
||||||
workspace.toggle_panel_focus::<Self>(window, cx);
|
workspace.toggle_panel_focus::<Self>(window, cx);
|
||||||
}
|
}
|
||||||
@@ -743,7 +737,7 @@ impl AgentPanel {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(thread) = self
|
let Some(thread) = self
|
||||||
.acp_history_store
|
.history_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.thread_from_session_id(&action.from_session_id)
|
.thread_from_session_id(&action.from_session_id)
|
||||||
else {
|
else {
|
||||||
@@ -792,7 +786,6 @@ impl AgentPanel {
|
|||||||
ActiveView::prompt_editor(
|
ActiveView::prompt_editor(
|
||||||
context_editor.clone(),
|
context_editor.clone(),
|
||||||
self.history_store.clone(),
|
self.history_store.clone(),
|
||||||
self.acp_history_store.clone(),
|
|
||||||
self.language_registry.clone(),
|
self.language_registry.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -818,12 +811,13 @@ impl AgentPanel {
|
|||||||
|
|
||||||
const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
|
const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct LastUsedExternalAgent {
|
struct LastUsedExternalAgent {
|
||||||
agent: crate::ExternalAgent,
|
agent: crate::ExternalAgent,
|
||||||
}
|
}
|
||||||
|
|
||||||
let history = self.acp_history_store.clone();
|
let loading = self.loading;
|
||||||
|
let history = self.history_store.clone();
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let ext_agent = match agent_choice {
|
let ext_agent = match agent_choice {
|
||||||
@@ -858,16 +852,18 @@ impl AgentPanel {
|
|||||||
.and_then(|value| {
|
.and_then(|value| {
|
||||||
serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
|
serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.map(|agent| agent.agent)
|
||||||
.agent
|
.unwrap_or(ExternalAgent::NativeAgent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
|
|
||||||
|
|
||||||
let server = ext_agent.server(fs, history);
|
let server = ext_agent.server(fs, history);
|
||||||
|
|
||||||
|
if !loading {
|
||||||
|
telemetry::event!("Agent Thread Started", agent = server.telemetry_id());
|
||||||
|
}
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let selected_agent = ext_agent.into();
|
let selected_agent = ext_agent.into();
|
||||||
if this.selected_agent != selected_agent {
|
if this.selected_agent != selected_agent {
|
||||||
@@ -882,7 +878,7 @@ impl AgentPanel {
|
|||||||
summarize_thread,
|
summarize_thread,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project,
|
project,
|
||||||
this.acp_history_store.clone(),
|
this.history_store.clone(),
|
||||||
this.prompt_store.clone(),
|
this.prompt_store.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -980,7 +976,6 @@ impl AgentPanel {
|
|||||||
ActiveView::prompt_editor(
|
ActiveView::prompt_editor(
|
||||||
editor,
|
editor,
|
||||||
self.history_store.clone(),
|
self.history_store.clone(),
|
||||||
self.acp_history_store.clone(),
|
|
||||||
self.language_registry.clone(),
|
self.language_registry.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -1062,15 +1057,15 @@ impl AgentPanel {
|
|||||||
WhichFontSize::AgentFont => {
|
WhichFontSize::AgentFont => {
|
||||||
if persist {
|
if persist {
|
||||||
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
|
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
|
||||||
let agent_font_size =
|
let agent_ui_font_size =
|
||||||
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
|
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
|
||||||
let _ = settings
|
let _ = settings
|
||||||
.theme
|
.theme
|
||||||
.agent_font_size
|
.agent_ui_font_size
|
||||||
.insert(Some(theme::clamp_font_size(agent_font_size).into()));
|
.insert(theme::clamp_font_size(agent_ui_font_size).into());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
theme::adjust_agent_font_size(cx, |size| size + delta);
|
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WhichFontSize::BufferFont => {
|
WhichFontSize::BufferFont => {
|
||||||
@@ -1090,10 +1085,10 @@ impl AgentPanel {
|
|||||||
) {
|
) {
|
||||||
if action.persist {
|
if action.persist {
|
||||||
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||||
settings.theme.agent_font_size = None;
|
settings.theme.agent_ui_font_size = None;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
theme::reset_agent_font_size(cx);
|
theme::reset_agent_ui_font_size(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1244,11 +1239,6 @@ impl AgentPanel {
|
|||||||
match &new_view {
|
match &new_view {
|
||||||
ActiveView::TextThread { context_editor, .. } => {
|
ActiveView::TextThread { context_editor, .. } => {
|
||||||
self.history_store.update(cx, |store, cx| {
|
self.history_store.update(cx, |store, cx| {
|
||||||
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
|
|
||||||
store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.acp_history_store.update(cx, |store, cx| {
|
|
||||||
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
|
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
|
||||||
store.push_recently_opened_entry(
|
store.push_recently_opened_entry(
|
||||||
agent2::HistoryEntryId::TextThread(path.clone()),
|
agent2::HistoryEntryId::TextThread(path.clone()),
|
||||||
@@ -1282,7 +1272,7 @@ impl AgentPanel {
|
|||||||
) -> ContextMenu {
|
) -> ContextMenu {
|
||||||
let entries = panel
|
let entries = panel
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.acp_history_store
|
.history_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.recently_opened_entries(cx);
|
.recently_opened_entries(cx);
|
||||||
|
|
||||||
@@ -1327,7 +1317,7 @@ impl AgentPanel {
|
|||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
panel
|
panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.acp_history_store.update(cx, |history_store, cx| {
|
this.history_store.update(cx, |history_store, cx| {
|
||||||
history_store.remove_recently_opened_entry(&id, cx);
|
history_store.remove_recently_opened_entry(&id, cx);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -1353,15 +1343,6 @@ impl AgentPanel {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match agent {
|
match agent {
|
||||||
AgentType::Zed => {
|
|
||||||
window.dispatch_action(
|
|
||||||
NewThread {
|
|
||||||
from_thread_id: None,
|
|
||||||
}
|
|
||||||
.boxed_clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
AgentType::TextThread => {
|
AgentType::TextThread => {
|
||||||
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
||||||
}
|
}
|
||||||
@@ -1386,6 +1367,11 @@ impl AgentPanel {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
AgentType::Codex => {
|
||||||
|
self.selected_agent = AgentType::Codex;
|
||||||
|
self.serialize(cx);
|
||||||
|
self.external_thread(Some(crate::ExternalAgent::Codex), None, None, window, cx)
|
||||||
|
}
|
||||||
AgentType::Custom { name, command } => self.external_thread(
|
AgentType::Custom { name, command } => self.external_thread(
|
||||||
Some(crate::ExternalAgent::Custom { name, command }),
|
Some(crate::ExternalAgent::Custom { name, command }),
|
||||||
None,
|
None,
|
||||||
@@ -1499,7 +1485,7 @@ impl Panel for AgentPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn enabled(&self, cx: &App) -> bool {
|
fn enabled(&self, cx: &App) -> bool {
|
||||||
DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
|
AgentSettings::get_global(cx).enabled(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
|
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
|
||||||
@@ -1898,32 +1884,6 @@ impl AgentPanel {
|
|||||||
)
|
)
|
||||||
.separator()
|
.separator()
|
||||||
.header("External Agents")
|
.header("External Agents")
|
||||||
.item(
|
|
||||||
ContextMenuEntry::new("New Gemini CLI Thread")
|
|
||||||
.icon(IconName::AiGemini)
|
|
||||||
.icon_color(Color::Muted)
|
|
||||||
.disabled(is_via_collab)
|
|
||||||
.handler({
|
|
||||||
let workspace = workspace.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade() {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
if let Some(panel) =
|
|
||||||
workspace.panel::<AgentPanel>(cx)
|
|
||||||
{
|
|
||||||
panel.update(cx, |panel, cx| {
|
|
||||||
panel.new_agent_thread(
|
|
||||||
AgentType::Gemini,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.item(
|
.item(
|
||||||
ContextMenuEntry::new("New Claude Code Thread")
|
ContextMenuEntry::new("New Claude Code Thread")
|
||||||
.icon(IconName::AiClaude)
|
.icon(IconName::AiClaude)
|
||||||
@@ -1950,12 +1910,64 @@ impl AgentPanel {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.item(
|
||||||
|
ContextMenuEntry::new("New Codex Thread")
|
||||||
|
.icon(IconName::AiOpenAi)
|
||||||
|
.disabled(is_via_collab)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.handler({
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
if let Some(workspace) = workspace.upgrade() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
if let Some(panel) =
|
||||||
|
workspace.panel::<AgentPanel>(cx)
|
||||||
|
{
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.new_agent_thread(
|
||||||
|
AgentType::Codex,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.item(
|
||||||
|
ContextMenuEntry::new("New Gemini CLI Thread")
|
||||||
|
.icon(IconName::AiGemini)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.disabled(is_via_collab)
|
||||||
|
.handler({
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
if let Some(workspace) = workspace.upgrade() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
if let Some(panel) =
|
||||||
|
workspace.panel::<AgentPanel>(cx)
|
||||||
|
{
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.new_agent_thread(
|
||||||
|
AgentType::Gemini,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
.map(|mut menu| {
|
.map(|mut menu| {
|
||||||
let agent_names = agent_server_store
|
let agent_names = agent_server_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.external_agents()
|
.external_agents()
|
||||||
.filter(|name| {
|
.filter(|name| {
|
||||||
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME
|
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -2127,10 +2139,7 @@ impl AgentPanel {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let history_is_empty = self.acp_history_store.read(cx).is_empty(cx)
|
let history_is_empty = self.history_store.read(cx).is_empty(cx);
|
||||||
&& self
|
|
||||||
.history_store
|
|
||||||
.update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
|
|
||||||
|
|
||||||
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
|
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
|
||||||
.providers()
|
.providers()
|
||||||
@@ -2491,7 +2500,7 @@ impl Render for AgentPanel {
|
|||||||
|
|
||||||
match self.active_view.which_font_size_used() {
|
match self.active_view.which_font_size_used() {
|
||||||
WhichFontSize::AgentFont => {
|
WhichFontSize::AgentFont => {
|
||||||
WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
|
WithRemSize::new(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(content)
|
.child(content)
|
||||||
.into_any()
|
.into_any()
|
||||||
|
|||||||
@@ -161,12 +161,12 @@ pub struct NewNativeAgentThreadFromSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO unify this with AgentType
|
// TODO unify this with AgentType
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
enum ExternalAgent {
|
pub enum ExternalAgent {
|
||||||
#[default]
|
|
||||||
Gemini,
|
Gemini,
|
||||||
ClaudeCode,
|
ClaudeCode,
|
||||||
|
Codex,
|
||||||
NativeAgent,
|
NativeAgent,
|
||||||
Custom {
|
Custom {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
@@ -183,12 +183,13 @@ fn placeholder_command() -> AgentServerCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExternalAgent {
|
impl ExternalAgent {
|
||||||
fn name(&self) -> &'static str {
|
pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option<Self> {
|
||||||
match self {
|
match server.telemetry_id() {
|
||||||
Self::NativeAgent => "zed",
|
"gemini-cli" => Some(Self::Gemini),
|
||||||
Self::Gemini => "gemini-cli",
|
"claude-code" => Some(Self::ClaudeCode),
|
||||||
Self::ClaudeCode => "claude-code",
|
"codex" => Some(Self::Codex),
|
||||||
Self::Custom { .. } => "custom",
|
"zed" => Some(Self::NativeAgent),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +201,7 @@ impl ExternalAgent {
|
|||||||
match self {
|
match self {
|
||||||
Self::Gemini => Rc::new(agent_servers::Gemini),
|
Self::Gemini => Rc::new(agent_servers::Gemini),
|
||||||
Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
|
Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
|
||||||
|
Self::Codex => Rc::new(agent_servers::Codex),
|
||||||
Self::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs, history)),
|
Self::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs, history)),
|
||||||
Self::Custom { name, command: _ } => {
|
Self::Custom { name, command: _ } => {
|
||||||
Rc::new(agent_servers::CustomAgentServer::new(name.clone()))
|
Rc::new(agent_servers::CustomAgentServer::new(name.clone()))
|
||||||
@@ -264,7 +266,7 @@ pub fn init(
|
|||||||
init_language_model_settings(cx);
|
init_language_model_settings(cx);
|
||||||
}
|
}
|
||||||
assistant_slash_command::init(cx);
|
assistant_slash_command::init(cx);
|
||||||
agent::init(cx);
|
agent::init(fs.clone(), cx);
|
||||||
agent_panel::init(cx);
|
agent_panel::init(cx);
|
||||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||||
TextThreadEditor::init(cx);
|
TextThreadEditor::init(cx);
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ use thread_context_picker::{
|
|||||||
use ui::{
|
use ui::{
|
||||||
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
||||||
};
|
};
|
||||||
|
use util::paths::PathStyle;
|
||||||
|
use util::rel_path::RelPath;
|
||||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||||
|
|
||||||
use agent::{
|
use agent::{
|
||||||
@@ -228,12 +230,19 @@ impl ContextPicker {
|
|||||||
let context_picker = cx.entity();
|
let context_picker = cx.entity();
|
||||||
|
|
||||||
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
|
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return menu;
|
||||||
|
};
|
||||||
|
let path_style = workspace.read(cx).path_style(cx);
|
||||||
let recent = self.recent_entries(cx);
|
let recent = self.recent_entries(cx);
|
||||||
let has_recent = !recent.is_empty();
|
let has_recent = !recent.is_empty();
|
||||||
let recent_entries = recent
|
let recent_entries = recent
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.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, path_style)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let entries = self
|
let entries = self
|
||||||
.workspace
|
.workspace
|
||||||
@@ -395,6 +404,7 @@ impl ContextPicker {
|
|||||||
context_picker: Entity<ContextPicker>,
|
context_picker: Entity<ContextPicker>,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
entry: RecentEntry,
|
entry: RecentEntry,
|
||||||
|
path_style: PathStyle,
|
||||||
) -> ContextMenuItem {
|
) -> ContextMenuItem {
|
||||||
match entry {
|
match entry {
|
||||||
RecentEntry::File {
|
RecentEntry::File {
|
||||||
@@ -413,6 +423,7 @@ impl ContextPicker {
|
|||||||
&path,
|
&path,
|
||||||
&path_prefix,
|
&path_prefix,
|
||||||
false,
|
false,
|
||||||
|
path_style,
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -586,7 +597,7 @@ impl Render for ContextPicker {
|
|||||||
pub(crate) enum RecentEntry {
|
pub(crate) enum RecentEntry {
|
||||||
File {
|
File {
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
path_prefix: Arc<str>,
|
path_prefix: Arc<RelPath>,
|
||||||
},
|
},
|
||||||
Thread(ThreadContextEntry),
|
Thread(ThreadContextEntry),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
|||||||
use gpui::{App, Entity, Task, WeakEntity};
|
use gpui::{App, Entity, Task, WeakEntity};
|
||||||
use http_client::HttpClientWithUrl;
|
use http_client::HttpClientWithUrl;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{Buffer, CodeLabel, HighlightId};
|
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
|
||||||
use lsp::CompletionContext;
|
use lsp::CompletionContext;
|
||||||
|
use project::lsp_store::SymbolLocation;
|
||||||
use project::{
|
use project::{
|
||||||
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, ProjectPath,
|
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, ProjectPath,
|
||||||
Symbol, WorktreeId,
|
Symbol, WorktreeId,
|
||||||
@@ -22,6 +23,8 @@ use rope::Point;
|
|||||||
use text::{Anchor, OffsetRangeExt, ToPoint};
|
use text::{Anchor, OffsetRangeExt, ToPoint};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
use util::paths::PathStyle;
|
||||||
|
use util::rel_path::RelPath;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use agent::{
|
use agent::{
|
||||||
@@ -574,11 +577,12 @@ impl ContextPickerCompletionProvider {
|
|||||||
|
|
||||||
fn completion_for_path(
|
fn completion_for_path(
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
path_prefix: &str,
|
path_prefix: &RelPath,
|
||||||
is_recent: bool,
|
is_recent: bool,
|
||||||
is_directory: bool,
|
is_directory: bool,
|
||||||
excerpt_id: ExcerptId,
|
excerpt_id: ExcerptId,
|
||||||
source_range: Range<Anchor>,
|
source_range: Range<Anchor>,
|
||||||
|
path_style: PathStyle,
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
@@ -586,6 +590,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
||||||
&project_path.path,
|
&project_path.path,
|
||||||
path_prefix,
|
path_prefix,
|
||||||
|
path_style,
|
||||||
);
|
);
|
||||||
|
|
||||||
let label =
|
let label =
|
||||||
@@ -657,17 +662,22 @@ impl ContextPickerCompletionProvider {
|
|||||||
workspace: Entity<Workspace>,
|
workspace: Entity<Workspace>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<Completion> {
|
) -> Option<Completion> {
|
||||||
|
let path_style = workspace.read(cx).path_style(cx);
|
||||||
|
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
let path_prefix = workspace
|
let path_prefix = workspace
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.project()
|
.project()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.worktree_for_id(symbol.path.worktree_id, cx)?
|
.worktree_for_id(symbol_path.worktree_id, cx)?
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.root_name();
|
.root_name();
|
||||||
|
|
||||||
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
||||||
&symbol.path.path,
|
&symbol_path.path,
|
||||||
path_prefix,
|
path_prefix,
|
||||||
|
path_style,
|
||||||
);
|
);
|
||||||
let full_path = if let Some(directory) = directory {
|
let full_path = if let Some(directory) = directory {
|
||||||
format!("{}{}", directory, file_name)
|
format!("{}{}", directory, file_name)
|
||||||
@@ -676,7 +686,8 @@ impl ContextPickerCompletionProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||||
let mut label = CodeLabel::plain(symbol.name.clone(), None);
|
let mut label = CodeLabelBuilder::default();
|
||||||
|
label.push_str(&symbol.name, None);
|
||||||
label.push_str(" ", None);
|
label.push_str(" ", None);
|
||||||
label.push_str(&file_name, comment_id);
|
label.push_str(&file_name, comment_id);
|
||||||
label.push_str(&format!(" L{}", symbol.range.start.0.row + 1), comment_id);
|
label.push_str(&format!(" L{}", symbol.range.start.0.row + 1), comment_id);
|
||||||
@@ -686,7 +697,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
Some(Completion {
|
Some(Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label,
|
label: label.build(),
|
||||||
documentation: None,
|
documentation: None,
|
||||||
source: project::CompletionSource::Custom,
|
source: project::CompletionSource::Custom,
|
||||||
icon_path: Some(IconName::Code.path().into()),
|
icon_path: Some(IconName::Code.path().into()),
|
||||||
@@ -719,7 +730,7 @@ impl ContextPickerCompletionProvider {
|
|||||||
|
|
||||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||||
let mut label = CodeLabel::default();
|
let mut label = CodeLabelBuilder::default();
|
||||||
|
|
||||||
label.push_str(file_name, None);
|
label.push_str(file_name, None);
|
||||||
label.push_str(" ", None);
|
label.push_str(" ", None);
|
||||||
@@ -728,9 +739,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
|
|||||||
label.push_str(directory, comment_id);
|
label.push_str(directory, comment_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
label.filter_range = 0..label.text().len();
|
label.build()
|
||||||
|
|
||||||
label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompletionProvider for ContextPickerCompletionProvider {
|
impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
@@ -768,6 +777,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
let text_thread_store = self.text_thread_store.clone();
|
let text_thread_store = self.text_thread_store.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let http_client = workspace.read(cx).client().http_client();
|
let http_client = workspace.read(cx).client().http_client();
|
||||||
|
let path_style = workspace.read(cx).path_style(cx);
|
||||||
|
|
||||||
let MentionCompletion { mode, argument, .. } = state;
|
let MentionCompletion { mode, argument, .. } = state;
|
||||||
let query = argument.unwrap_or_else(|| "".to_string());
|
let query = argument.unwrap_or_else(|| "".to_string());
|
||||||
@@ -834,6 +844,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
|||||||
mat.is_dir,
|
mat.is_dir,
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.clone(),
|
source_range.clone(),
|
||||||
|
path_style,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
@@ -1064,7 +1075,7 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{ops::Deref, rc::Rc};
|
use std::{ops::Deref, rc::Rc};
|
||||||
use util::path;
|
use util::{path, rel_path::rel_path};
|
||||||
use workspace::{AppState, Item};
|
use workspace::{AppState, Item};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1215,16 +1226,18 @@ mod tests {
|
|||||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||||
|
|
||||||
let paths = vec![
|
let paths = vec![
|
||||||
path!("a/one.txt"),
|
rel_path("a/one.txt"),
|
||||||
path!("a/two.txt"),
|
rel_path("a/two.txt"),
|
||||||
path!("a/three.txt"),
|
rel_path("a/three.txt"),
|
||||||
path!("a/four.txt"),
|
rel_path("a/four.txt"),
|
||||||
path!("b/five.txt"),
|
rel_path("b/five.txt"),
|
||||||
path!("b/six.txt"),
|
rel_path("b/six.txt"),
|
||||||
path!("b/seven.txt"),
|
rel_path("b/seven.txt"),
|
||||||
path!("b/eight.txt"),
|
rel_path("b/eight.txt"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let slash = PathStyle::local().separator();
|
||||||
|
|
||||||
let mut opened_editors = Vec::new();
|
let mut opened_editors = Vec::new();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let buffer = workspace
|
let buffer = workspace
|
||||||
@@ -1232,7 +1245,7 @@ mod tests {
|
|||||||
workspace.open_path(
|
workspace.open_path(
|
||||||
ProjectPath {
|
ProjectPath {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Path::new(path).into(),
|
path: path.into(),
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1308,13 +1321,13 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
current_completion_labels(editor),
|
current_completion_labels(editor),
|
||||||
&[
|
&[
|
||||||
"seven.txt dir/b/",
|
format!("seven.txt dir{slash}b{slash}"),
|
||||||
"six.txt dir/b/",
|
format!("six.txt dir{slash}b{slash}"),
|
||||||
"five.txt dir/b/",
|
format!("five.txt dir{slash}b{slash}"),
|
||||||
"four.txt dir/a/",
|
format!("four.txt dir{slash}a{slash}"),
|
||||||
"Files & Directories",
|
"Files & Directories".into(),
|
||||||
"Symbols",
|
"Symbols".into(),
|
||||||
"Fetch"
|
"Fetch".into()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1341,7 +1354,10 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), "Lorem @file one");
|
assert_eq!(editor.text(cx), "Lorem @file one");
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
|
assert_eq!(
|
||||||
|
current_completion_labels(editor),
|
||||||
|
vec![format!("one.txt dir{slash}a{slash}")]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
@@ -1350,7 +1366,10 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
|
assert_eq!(
|
||||||
|
editor.text(cx),
|
||||||
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
|
||||||
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
@@ -1361,7 +1380,10 @@ mod tests {
|
|||||||
cx.simulate_input(" ");
|
cx.simulate_input(" ");
|
||||||
|
|
||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
|
assert_eq!(
|
||||||
|
editor.text(cx),
|
||||||
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
|
||||||
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fold_ranges(editor, cx),
|
fold_ranges(editor, cx),
|
||||||
@@ -1374,7 +1396,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum "),
|
||||||
);
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1388,7 +1410,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum @file "),
|
||||||
);
|
);
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1406,7 +1428,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) "
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) ")
|
||||||
);
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1423,7 +1445,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@"
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n@")
|
||||||
);
|
);
|
||||||
assert!(editor.has_visible_completions_menu());
|
assert!(editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1444,7 +1466,7 @@ mod tests {
|
|||||||
editor.update(&mut cx, |editor, cx| {
|
editor.update(&mut cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) "
|
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n[@six.txt](@file:dir{slash}b{slash}six.txt) ")
|
||||||
);
|
);
|
||||||
assert!(!editor.has_visible_completions_menu());
|
assert!(!editor.has_visible_completions_menu());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ use gpui::{
|
|||||||
use picker::{Picker, PickerDelegate};
|
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 _, paths::PathStyle, rel_path::RelPath};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context_picker::ContextPicker;
|
use crate::context_picker::ContextPicker;
|
||||||
@@ -161,6 +160,8 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let FileMatch { mat, .. } = &self.matches.get(ix)?;
|
let FileMatch { mat, .. } = &self.matches.get(ix)?;
|
||||||
|
let workspace = self.workspace.upgrade()?;
|
||||||
|
let path_style = workspace.read(cx).path_style(cx);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
@@ -172,6 +173,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
&mat.path,
|
&mat.path,
|
||||||
&mat.path_prefix,
|
&mat.path_prefix,
|
||||||
mat.is_dir,
|
mat.is_dir,
|
||||||
|
path_style,
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
@@ -214,14 +216,13 @@ pub(crate) fn search_files(
|
|||||||
|
|
||||||
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();
|
|
||||||
worktree.entries(false, 0).map(move |entry| FileMatch {
|
worktree.entries(false, 0).map(move |entry| FileMatch {
|
||||||
mat: PathMatch {
|
mat: PathMatch {
|
||||||
score: 0.,
|
score: 0.,
|
||||||
positions: Vec::new(),
|
positions: Vec::new(),
|
||||||
worktree_id: worktree.id().to_usize(),
|
worktree_id: worktree.id().to_usize(),
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
path_prefix: path_prefix.clone(),
|
path_prefix: worktree.root_name().into(),
|
||||||
distance_to_relative_ancestor: 0,
|
distance_to_relative_ancestor: 0,
|
||||||
is_dir: entry.is_dir(),
|
is_dir: entry.is_dir(),
|
||||||
},
|
},
|
||||||
@@ -251,7 +252,7 @@ pub(crate) fn search_files(
|
|||||||
fuzzy::match_path_sets(
|
fuzzy::match_path_sets(
|
||||||
candidate_sets.as_slice(),
|
candidate_sets.as_slice(),
|
||||||
query.as_str(),
|
query.as_str(),
|
||||||
None,
|
&None,
|
||||||
false,
|
false,
|
||||||
100,
|
100,
|
||||||
&cancellation_flag,
|
&cancellation_flag,
|
||||||
@@ -269,51 +270,31 @@ pub(crate) fn search_files(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_file_name_and_directory(
|
pub fn extract_file_name_and_directory(
|
||||||
path: &Path,
|
path: &RelPath,
|
||||||
path_prefix: &str,
|
path_prefix: &RelPath,
|
||||||
|
path_style: PathStyle,
|
||||||
) -> (SharedString, Option<SharedString>) {
|
) -> (SharedString, Option<SharedString>) {
|
||||||
if path == Path::new("") {
|
let full_path = path_prefix.join(path);
|
||||||
(
|
let file_name = full_path.file_name().unwrap_or_default();
|
||||||
SharedString::from(
|
let display_path = full_path.display(path_style);
|
||||||
path_prefix
|
let (directory, file_name) = display_path.split_at(display_path.len() - file_name.len());
|
||||||
.trim_end_matches(std::path::MAIN_SEPARATOR)
|
(
|
||||||
.to_string(),
|
file_name.to_string().into(),
|
||||||
),
|
Some(SharedString::new(directory)).filter(|dir| !dir.is_empty()),
|
||||||
None,
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let file_name = path
|
|
||||||
.file_name()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let mut directory = path_prefix
|
|
||||||
.trim_end_matches(std::path::MAIN_SEPARATOR)
|
|
||||||
.to_string();
|
|
||||||
if !directory.ends_with('/') {
|
|
||||||
directory.push('/');
|
|
||||||
}
|
|
||||||
if let Some(parent) = path.parent().filter(|parent| parent != &Path::new("")) {
|
|
||||||
directory.push_str(&parent.to_string_lossy());
|
|
||||||
directory.push('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
(file_name, Some(directory.into()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_file_context_entry(
|
pub fn render_file_context_entry(
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
worktree_id: WorktreeId,
|
worktree_id: WorktreeId,
|
||||||
path: &Arc<Path>,
|
path: &Arc<RelPath>,
|
||||||
path_prefix: &Arc<str>,
|
path_prefix: &Arc<RelPath>,
|
||||||
is_directory: bool,
|
is_directory: bool,
|
||||||
|
path_style: PathStyle,
|
||||||
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, path_style);
|
||||||
|
|
||||||
let added = context_store.upgrade().and_then(|context_store| {
|
let added = context_store.upgrade().and_then(|context_store| {
|
||||||
let project_path = ProjectPath {
|
let project_path = ProjectPath {
|
||||||
@@ -330,9 +311,9 @@ pub fn render_file_context_entry(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let file_icon = if is_directory {
|
let file_icon = if is_directory {
|
||||||
FileIcons::get_folder_icon(false, path, cx)
|
FileIcons::get_folder_icon(false, path.as_std_path(), cx)
|
||||||
} else {
|
} else {
|
||||||
FileIcons::get_icon(path, cx)
|
FileIcons::get_icon(path.as_std_path(), cx)
|
||||||
}
|
}
|
||||||
.map(Icon::from_path)
|
.map(Icon::from_path)
|
||||||
.unwrap_or_else(|| Icon::new(IconName::File));
|
.unwrap_or_else(|| Icon::new(IconName::File));
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ use std::cmp::Reverse;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Result, anyhow};
|
||||||
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,
|
||||||
};
|
};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
|
use project::lsp_store::SymbolLocation;
|
||||||
use project::{DocumentSymbol, Symbol};
|
use project::{DocumentSymbol, Symbol};
|
||||||
use ui::{ListItem, prelude::*};
|
use ui::{ListItem, prelude::*};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
@@ -191,7 +192,10 @@ pub(crate) fn add_symbol(
|
|||||||
) -> Task<Result<(Option<AgentContextHandle>, bool)>> {
|
) -> Task<Result<(Option<AgentContextHandle>, bool)>> {
|
||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
let open_buffer_task = project.update(cx, |project, cx| {
|
let open_buffer_task = project.update(cx, |project, cx| {
|
||||||
project.open_buffer(symbol.path.clone(), cx)
|
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
|
||||||
|
return Task::ready(Err(anyhow!("can't add symbol from outside of project")));
|
||||||
|
};
|
||||||
|
project.open_buffer(symbol_path.clone(), cx)
|
||||||
});
|
});
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let buffer = open_buffer_task.await?;
|
let buffer = open_buffer_task.await?;
|
||||||
@@ -291,10 +295,11 @@ pub(crate) fn search_symbols(
|
|||||||
.map(|(id, symbol)| {
|
.map(|(id, symbol)| {
|
||||||
StringMatchCandidate::new(id, symbol.label.filter_text())
|
StringMatchCandidate::new(id, symbol.label.filter_text())
|
||||||
})
|
})
|
||||||
.partition(|candidate| {
|
.partition(|candidate| match &symbols[candidate.id].path {
|
||||||
project
|
SymbolLocation::InProject(project_path) => project
|
||||||
.entry_for_path(&symbols[candidate.id].path, cx)
|
.entry_for_path(project_path, cx)
|
||||||
.is_some_and(|e| !e.is_ignored)
|
.is_some_and(|e| !e.is_ignored),
|
||||||
|
SymbolLocation::OutsideProject { .. } => false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.log_err()
|
.log_err()
|
||||||
@@ -360,13 +365,18 @@ fn compute_symbol_entries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful<Div> {
|
pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful<Div> {
|
||||||
let path = entry
|
let path = match &entry.symbol.path {
|
||||||
.symbol
|
SymbolLocation::InProject(project_path) => {
|
||||||
.path
|
project_path.path.file_name().unwrap_or_default().into()
|
||||||
.path
|
}
|
||||||
.file_name()
|
SymbolLocation::OutsideProject {
|
||||||
.map(|s| s.to_string_lossy())
|
abs_path,
|
||||||
.unwrap_or_default();
|
signature: _,
|
||||||
|
} => abs_path
|
||||||
|
.file_name()
|
||||||
|
.map(|f| f.to_string_lossy())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
let symbol_location = format!("{} L{}", path, entry.symbol.range.start.0.row + 1);
|
let symbol_location = format!("{} L{}", path, entry.symbol.range.start.0.row + 1);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user