Compare commits
764 Commits
streaming-
...
fix-assumi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b06459ee00 | ||
|
|
db7425e264 | ||
|
|
704bcf96ba | ||
|
|
ab31eb5d51 | ||
|
|
7d67bd480b | ||
|
|
a24fc5a1a5 | ||
|
|
0079771e34 | ||
|
|
d1db6d6782 | ||
|
|
5509e0141a | ||
|
|
8be5ed22f6 | ||
|
|
5343f1cdaf | ||
|
|
8075c2458f | ||
|
|
d0276e6666 | ||
|
|
c729842804 | ||
|
|
715e23a491 | ||
|
|
63f0fda350 | ||
|
|
7984f0f11c | ||
|
|
37ebb47238 | ||
|
|
8546dc101d | ||
|
|
33912011b7 | ||
|
|
dce824f095 | ||
|
|
a1bef28da3 | ||
|
|
8a212be0b1 | ||
|
|
9bbb1e5476 | ||
|
|
50ad71a630 | ||
|
|
76c46c5bab | ||
|
|
8f0bacddd8 | ||
|
|
051483200d | ||
|
|
27cafe5567 | ||
|
|
ddc102c7e0 | ||
|
|
d517a212dc | ||
|
|
dc64ec9cc8 | ||
|
|
d50905e000 | ||
|
|
0729d24d77 | ||
|
|
d51aa2ffb0 | ||
|
|
d40b49ceb9 | ||
|
|
edf712d45b | ||
|
|
627ae7af6f | ||
|
|
17aecfde6f | ||
|
|
01a2c8eb01 | ||
|
|
fc269dfaf9 | ||
|
|
e1e8c1786e | ||
|
|
c8a9a74e6a | ||
|
|
f6d58f76e4 | ||
|
|
e6c64ebf7e | ||
|
|
12c58d01bb | ||
|
|
75689c1c88 | ||
|
|
ca6be249dc | ||
|
|
9b44bacc28 | ||
|
|
9b40770e9f | ||
|
|
5b2adfbb50 | ||
|
|
078b241223 | ||
|
|
e42406f9d5 | ||
|
|
4ee20dda23 | ||
|
|
74dd32d52c | ||
|
|
342acdd080 | ||
|
|
9f8776d1af | ||
|
|
548a8d75e6 | ||
|
|
73f77a7fbb | ||
|
|
4970fe2d56 | ||
|
|
7fe6188f8e | ||
|
|
8add90d7cb | ||
|
|
141a6c3915 | ||
|
|
b4254a33e0 | ||
|
|
f86977e2a7 | ||
|
|
8ecf553279 | ||
|
|
e171d16ae3 | ||
|
|
35da9c0cdc | ||
|
|
8b3eb98d86 | ||
|
|
d912b0dd36 | ||
|
|
044508ef77 | ||
|
|
d63658cee2 | ||
|
|
790b56f52c | ||
|
|
43142000a6 | ||
|
|
3e400a4969 | ||
|
|
e11e7df724 | ||
|
|
bbd1e628f0 | ||
|
|
01b400ea29 | ||
|
|
9445005bff | ||
|
|
5c0adde7bb | ||
|
|
4a5c492188 | ||
|
|
4da987dad4 | ||
|
|
be657aefa3 | ||
|
|
d0ae604eda | ||
|
|
08bb17a7eb | ||
|
|
55c1f9d26c | ||
|
|
28f0ba3381 | ||
|
|
b5dc09c0ca | ||
|
|
e90411efa2 | ||
|
|
fcadcbb510 | ||
|
|
94ed0b7767 | ||
|
|
8a307e7b89 | ||
|
|
c8fb95cd1b | ||
|
|
55d934a3be | ||
|
|
a916bbf00c | ||
|
|
da47013e56 | ||
|
|
82ce187bfc | ||
|
|
e843790490 | ||
|
|
83f3fbfef5 | ||
|
|
60a7455f12 | ||
|
|
4315b2fc8a | ||
|
|
68d453da52 | ||
|
|
20eab9038f | ||
|
|
a201263448 | ||
|
|
1a4ba59d4e | ||
|
|
24ad97008b | ||
|
|
f6c81a0595 | ||
|
|
7ac51e4c82 | ||
|
|
c8105863c8 | ||
|
|
9f72e05c40 | ||
|
|
bb15f4c493 | ||
|
|
dc6004066d | ||
|
|
2863bd1836 | ||
|
|
f1ce83b533 | ||
|
|
edc7d73643 | ||
|
|
4839195003 | ||
|
|
56eb650f09 | ||
|
|
61be869352 | ||
|
|
7537f0557f | ||
|
|
85740ddaa4 | ||
|
|
6550a96e15 | ||
|
|
93c0056065 | ||
|
|
84dd2366bc | ||
|
|
8e12eb0ab1 | ||
|
|
3b158461be | ||
|
|
12a8b850ef | ||
|
|
d35f5a4197 | ||
|
|
1f5d57bece | ||
|
|
ca9fb2399e | ||
|
|
a360365410 | ||
|
|
df6ee1fc4a | ||
|
|
fc99557952 | ||
|
|
52c1e0021c | ||
|
|
6ead57d5ed | ||
|
|
4a10a0ca77 | ||
|
|
cc6d4e3c62 | ||
|
|
3f7c8c97c2 | ||
|
|
ab5ba66b94 | ||
|
|
a20a534ecf | ||
|
|
5bb979820b | ||
|
|
2dee03ebca | ||
|
|
1c7cf1a5c1 | ||
|
|
f15a241d3e | ||
|
|
76d3a9a0f0 | ||
|
|
e6c473a488 | ||
|
|
06960670bd | ||
|
|
71ddb3dad4 | ||
|
|
2bf9c472bd | ||
|
|
82a06f0ca9 | ||
|
|
cd6b1d32d0 | ||
|
|
5033a2aba0 | ||
|
|
0392ef10cf | ||
|
|
7354ef91e1 | ||
|
|
926d10cc45 | ||
|
|
a7697be857 | ||
|
|
97392a23e3 | ||
|
|
3f40e0f433 | ||
|
|
3e6d5c0814 | ||
|
|
2bc91e8c59 | ||
|
|
bbc80c78fd | ||
|
|
24ab5afa10 | ||
|
|
af8acba353 | ||
|
|
231e9c2000 | ||
|
|
47b94e5ef0 | ||
|
|
29e2e13e6d | ||
|
|
e635798fe0 | ||
|
|
6924720b35 | ||
|
|
1e8b50f471 | ||
|
|
5f8c53ffe8 | ||
|
|
6e82bbf367 | ||
|
|
0ac717c3a8 | ||
|
|
44aff7cd46 | ||
|
|
2b5095ac91 | ||
|
|
9e02fee98d | ||
|
|
999ad77a59 | ||
|
|
780d0eb427 | ||
|
|
7b40ab30d7 | ||
|
|
0a3c8a6790 | ||
|
|
1463b4d201 | ||
|
|
77856bf017 | ||
|
|
848a99c605 | ||
|
|
435a36b9f9 | ||
|
|
8b3ddcd545 | ||
|
|
13bf179aae | ||
|
|
cdaad2655a | ||
|
|
7e4320f587 | ||
|
|
130abc8998 | ||
|
|
9db4c8b710 | ||
|
|
e67ad1a1b6 | ||
|
|
82536f5243 | ||
|
|
9eacac62a9 | ||
|
|
82b0881dcb | ||
|
|
0a49ccbebf | ||
|
|
d232150d67 | ||
|
|
9a2dfa687d | ||
|
|
1e22faebc9 | ||
|
|
1d9c581ae0 | ||
|
|
39af3b434a | ||
|
|
64b3eea3cd | ||
|
|
72318df4b5 | ||
|
|
d52291bac1 | ||
|
|
7462e74fbf | ||
|
|
de67d93a92 | ||
|
|
a02f7e5cda | ||
|
|
d70ac64fe4 | ||
|
|
df583d73b9 | ||
|
|
e091c5faaf | ||
|
|
931a6d6f40 | ||
|
|
a605b66ce1 | ||
|
|
7376c6f377 | ||
|
|
ee08776f34 | ||
|
|
30f7e896cf | ||
|
|
8c8f50d916 | ||
|
|
76192ea93c | ||
|
|
fb2586a553 | ||
|
|
28e0bb5a12 | ||
|
|
a65ea2708c | ||
|
|
1574a3a2fd | ||
|
|
152432f1d9 | ||
|
|
5953c6167b | ||
|
|
aab02a4166 | ||
|
|
9fc570c4be | ||
|
|
581d67398a | ||
|
|
4a30b960d4 | ||
|
|
503bf607c5 | ||
|
|
46e86f003f | ||
|
|
0339d654d2 | ||
|
|
24d76a64c3 | ||
|
|
3ba624391f | ||
|
|
31e3c13ea9 | ||
|
|
0fec04a1b7 | ||
|
|
bf255486c0 | ||
|
|
cd1e56d6c7 | ||
|
|
2fe2028e20 | ||
|
|
f9212a001e | ||
|
|
954a56ce0c | ||
|
|
275cecb262 | ||
|
|
6b7167a32d | ||
|
|
430814c0a9 | ||
|
|
5aba5e1b3d | ||
|
|
408e157d0f | ||
|
|
2414855121 | ||
|
|
e7f7ed349b | ||
|
|
e273de5490 | ||
|
|
7046b9641d | ||
|
|
9b883e02a6 | ||
|
|
ffee218070 | ||
|
|
d9dcc59334 | ||
|
|
8f1023360d | ||
|
|
9468b9699e | ||
|
|
f1a9fdddab | ||
|
|
d40f8889b9 | ||
|
|
6715c8582f | ||
|
|
baf03e355b | ||
|
|
35ec4753b4 | ||
|
|
b85492bd00 | ||
|
|
fca7ce9a14 | ||
|
|
42f01cc903 | ||
|
|
ffa736e566 | ||
|
|
e9e6529df4 | ||
|
|
3205ae3884 | ||
|
|
03102b4d7e | ||
|
|
c32dece1b8 | ||
|
|
bcfc9e4437 | ||
|
|
a52095f558 | ||
|
|
be83c5e1c5 | ||
|
|
46d67a33c7 | ||
|
|
10c04afc81 | ||
|
|
920eda07a5 | ||
|
|
a469c0e261 | ||
|
|
5d05c4aa70 | ||
|
|
5465198d0d | ||
|
|
bbc7fcc54f | ||
|
|
11552cc0bd | ||
|
|
699369995b | ||
|
|
4a5f89aded | ||
|
|
e661a0afd6 | ||
|
|
57ffd6d78d | ||
|
|
350c1e41d2 | ||
|
|
a23e293ca2 | ||
|
|
f2be201495 | ||
|
|
43712285bf | ||
|
|
f38ee81e83 | ||
|
|
ddf8d07f02 | ||
|
|
7db9077835 | ||
|
|
4e33aaa55c | ||
|
|
d253d46fdf | ||
|
|
07727f939e | ||
|
|
08e8109e79 | ||
|
|
f19e1e3b5f | ||
|
|
8294b9f674 | ||
|
|
352c71f8a6 | ||
|
|
9f0b09007b | ||
|
|
f4d1e7901c | ||
|
|
044eb7b990 | ||
|
|
d96a50b029 | ||
|
|
b5e5959339 | ||
|
|
9918b6cade | ||
|
|
fa677bdc38 | ||
|
|
d82b547596 | ||
|
|
4c86cda909 | ||
|
|
90649fbc89 | ||
|
|
c783fd072f | ||
|
|
85a761cb2b | ||
|
|
739f45eb23 | ||
|
|
16ad7424d6 | ||
|
|
b32c792b68 | ||
|
|
1db621dc50 | ||
|
|
6143da95fc | ||
|
|
7ced1b7a90 | ||
|
|
0e9e2d70cd | ||
|
|
a52e2f9553 | ||
|
|
a551a6139c | ||
|
|
c394a3a890 | ||
|
|
1cca2e37b0 | ||
|
|
0de5c2ed53 | ||
|
|
6397872c49 | ||
|
|
93bd32b425 | ||
|
|
f119550838 | ||
|
|
4e93e38b0a | ||
|
|
05aa8880a4 | ||
|
|
6bced3a834 | ||
|
|
e14ebcf267 | ||
|
|
9d965bc98a | ||
|
|
579868110b | ||
|
|
a709d4c7c6 | ||
|
|
2ffce4f516 | ||
|
|
33fc1f4af2 | ||
|
|
7ade7d8e45 | ||
|
|
8f86cd758a | ||
|
|
962709f42c | ||
|
|
cf7d639fbc | ||
|
|
9134630841 | ||
|
|
bc1c0a2297 | ||
|
|
700af63c45 | ||
|
|
4b5df2189b | ||
|
|
48b1a43f5e | ||
|
|
9609e04bb2 | ||
|
|
a74f2bb18b | ||
|
|
ac452799b0 | ||
|
|
7b80cd865d | ||
|
|
7931b1d345 | ||
|
|
27ebedf517 | ||
|
|
f9f5126d2c | ||
|
|
6408ae81d1 | ||
|
|
c60a7034c8 | ||
|
|
7feb50fafe | ||
|
|
f365b80814 | ||
|
|
d0641a38a4 | ||
|
|
2e8c0ff244 | ||
|
|
4421bdd12e | ||
|
|
aa2fe9cce1 | ||
|
|
e3578fc44a | ||
|
|
aae81fd54c | ||
|
|
de99febd9b | ||
|
|
5bef32f3ed | ||
|
|
23e8519057 | ||
|
|
1180b6fbc7 | ||
|
|
14920ab910 | ||
|
|
000b981cb4 | ||
|
|
c9bff6e762 | ||
|
|
9fd2d064ee | ||
|
|
11425cf5f1 | ||
|
|
b54c92079f | ||
|
|
3bbdc546ec | ||
|
|
e4e3ce6a38 | ||
|
|
8cd96cbf59 | ||
|
|
274124256d | ||
|
|
1cf252f8eb | ||
|
|
e46c72f4a8 | ||
|
|
63f656faae | ||
|
|
31b8c36479 | ||
|
|
dfdca540ec | ||
|
|
14c036931d | ||
|
|
5387ae9ed8 | ||
|
|
c30fb5f1ec | ||
|
|
f7e2b7b679 | ||
|
|
b3bf3e2d53 | ||
|
|
1cc59b317c | ||
|
|
efd3f8a8f1 | ||
|
|
930dba4a7f | ||
|
|
7cfd919523 | ||
|
|
edd1b48e7c | ||
|
|
3ec69a5bc0 | ||
|
|
33faa66e35 | ||
|
|
68262fe7e4 | ||
|
|
2491426be7 | ||
|
|
4487dc1064 | ||
|
|
e03edc2a76 | ||
|
|
d722067000 | ||
|
|
d51cd15e4d | ||
|
|
ef14bc8e76 | ||
|
|
9fe243efa5 | ||
|
|
74a39c7263 | ||
|
|
5f398071b2 | ||
|
|
410a942d57 | ||
|
|
06ffdc6791 | ||
|
|
394215599a | ||
|
|
e8a40085de | ||
|
|
6303751325 | ||
|
|
3edf930007 | ||
|
|
584a70ca5e | ||
|
|
2230f3b09d | ||
|
|
84a8d48178 | ||
|
|
ac5dafc6b2 | ||
|
|
23686aa394 | ||
|
|
3874d315ec | ||
|
|
1d33bfde37 | ||
|
|
9377ef9817 | ||
|
|
c3b5046347 | ||
|
|
44fff08ed6 | ||
|
|
d4daa0a3a2 | ||
|
|
81582cd7f3 | ||
|
|
0f5a3afe94 | ||
|
|
382f9f6151 | ||
|
|
15d2420031 | ||
|
|
026c7274d9 | ||
|
|
1aefa5178b | ||
|
|
7f2e3fb5bd | ||
|
|
68a572873b | ||
|
|
c042a02cf4 | ||
|
|
73ac3d9a99 | ||
|
|
2269f996f7 | ||
|
|
e9033a75ac | ||
|
|
a2ae6a1c77 | ||
|
|
985ac4e5f2 | ||
|
|
89ae4ca9a3 | ||
|
|
1d4afe6daa | ||
|
|
777c88bcea | ||
|
|
959a024861 | ||
|
|
ed510b5e93 | ||
|
|
674c572a28 | ||
|
|
4a39fc2644 | ||
|
|
48fe134408 | ||
|
|
22b8662275 | ||
|
|
cc36cd9768 | ||
|
|
628a61d929 | ||
|
|
7f23875c5e | ||
|
|
e7bba1c252 | ||
|
|
41a60ffecf | ||
|
|
ed4e654fdf | ||
|
|
baaafddbeb | ||
|
|
b70f21c08d | ||
|
|
5615be51cc | ||
|
|
06e9f0e309 | ||
|
|
41a2be7e54 | ||
|
|
e38ae423f1 | ||
|
|
68bb3bd5eb | ||
|
|
122e73f152 | ||
|
|
4b775505f5 | ||
|
|
a9f7c0549c | ||
|
|
ac617e278e | ||
|
|
26f4b2a491 | ||
|
|
fdcacb3849 | ||
|
|
f61d3d28e0 | ||
|
|
a5621662b2 | ||
|
|
b6198ad516 | ||
|
|
5210d9e8b4 | ||
|
|
1139904ef5 | ||
|
|
b4ef3791bb | ||
|
|
88907eeb38 | ||
|
|
cd5d7e82d0 | ||
|
|
0851842d2c | ||
|
|
1397e01735 | ||
|
|
2b2b9c1624 | ||
|
|
a05066cd83 | ||
|
|
cb439e672d | ||
|
|
6b0a282c9c | ||
|
|
25772b8777 | ||
|
|
94b63808e0 | ||
|
|
798af67dc1 | ||
|
|
db1d2defa5 | ||
|
|
430bd83e4d | ||
|
|
dbe5399fc4 | ||
|
|
aba242d576 | ||
|
|
ddc210abfc | ||
|
|
65994c0576 | ||
|
|
011f823f33 | ||
|
|
3d1ae68f83 | ||
|
|
1f62274a89 | ||
|
|
c2f62d261b | ||
|
|
7d433a30ec | ||
|
|
52567f4b72 | ||
|
|
a0ee84d3ac | ||
|
|
6cac0b33dc | ||
|
|
45606abfdb | ||
|
|
8ba6ce43ac | ||
|
|
040d42fc24 | ||
|
|
22d905dc03 | ||
|
|
bf735da3f2 | ||
|
|
210d8d5530 | ||
|
|
a0f995d2ae | ||
|
|
8f560daec2 | ||
|
|
d5bb12631a | ||
|
|
8a31dcaeb0 | ||
|
|
ef91e7afae | ||
|
|
c220fb387d | ||
|
|
adbde210fd | ||
|
|
b81a1ad91d | ||
|
|
5f390f1bf8 | ||
|
|
c282acbe65 | ||
|
|
021d6584cc | ||
|
|
b547cd1c70 | ||
|
|
8f841d1ab7 | ||
|
|
4b153e7f7f | ||
|
|
b61171f152 | ||
|
|
0b492c11de | ||
|
|
265caed15e | ||
|
|
148131786f | ||
|
|
7c1405db37 | ||
|
|
96b747e31d | ||
|
|
7a888de9f5 | ||
|
|
e9b4fa1465 | ||
|
|
ead60d1857 | ||
|
|
768dfc8b6b | ||
|
|
f2f9c786da | ||
|
|
e5d2678d94 | ||
|
|
3ad9074e63 | ||
|
|
f40b22c02a | ||
|
|
8490d0d4ef | ||
|
|
afd0da97b9 | ||
|
|
1bf1c7223f | ||
|
|
ba8b9ec2c7 | ||
|
|
685536c27e | ||
|
|
ae017c3f96 | ||
|
|
f587e95a7e | ||
|
|
83dfdb0cfe | ||
|
|
566c5f91a7 | ||
|
|
21057e3af7 | ||
|
|
f68a475eca | ||
|
|
c62210b178 | ||
|
|
ad14dcc57b | ||
|
|
b9432dbe42 | ||
|
|
41c373eff1 | ||
|
|
6a95ec6a64 | ||
|
|
8d7b021f92 | ||
|
|
798a34bfc2 | ||
|
|
a4a9f6bd07 | ||
|
|
bfe4c40f73 | ||
|
|
daa16bcf42 | ||
|
|
22ad7b17c5 | ||
|
|
728a5eb388 | ||
|
|
8d8e5d3635 | ||
|
|
a05a480ed9 | ||
|
|
d141fa027e | ||
|
|
8e0e291bd5 | ||
|
|
e3c0f56a96 | ||
|
|
3935e8343a | ||
|
|
0c84170071 | ||
|
|
a38687d278 | ||
|
|
b75b308459 | ||
|
|
dffa725c7d | ||
|
|
22f1429f97 | ||
|
|
6bdd2cf7db | ||
|
|
a7f3b22051 | ||
|
|
f3703fa8be | ||
|
|
a0be6c8cb2 | ||
|
|
b5a7fb13c3 | ||
|
|
2183fc674d | ||
|
|
0ad5979f19 | ||
|
|
ed1938dd9a | ||
|
|
f7927d3fa4 | ||
|
|
8361c32a34 | ||
|
|
2edadd9352 | ||
|
|
85384fb9c6 | ||
|
|
00359271d1 | ||
|
|
18fcdf1d2c | ||
|
|
55c927b039 | ||
|
|
1be3f81920 | ||
|
|
2eb4d6b7eb | ||
|
|
25f407baab | ||
|
|
79874872cb | ||
|
|
95208a6576 | ||
|
|
1034d1a6b5 | ||
|
|
d4eab557b2 | ||
|
|
b75964a636 | ||
|
|
87cdb68cca | ||
|
|
b0b65420f6 | ||
|
|
8ec0309645 | ||
|
|
6767e98e00 | ||
|
|
8cf5af1a84 | ||
|
|
247ee880d2 | ||
|
|
2e217759c0 | ||
|
|
0a0c163692 | ||
|
|
e80df25386 | ||
|
|
d9590f3f0e | ||
|
|
4ecd1b5174 | ||
|
|
70c973f6c3 | ||
|
|
e842b4eade | ||
|
|
606aa7a78c | ||
|
|
0081b816fe | ||
|
|
21949bcf1a | ||
|
|
ee7ed6d5b8 | ||
|
|
07b67c1bd3 | ||
|
|
f116b44ae8 | ||
|
|
43ab7fe0e2 | ||
|
|
6044773043 | ||
|
|
81af2c0bed | ||
|
|
ab199fda47 | ||
|
|
e60e8f3a0a | ||
|
|
edeed7b619 | ||
|
|
9be7934f12 | ||
|
|
009b90291e | ||
|
|
8b17dc66f6 | ||
|
|
de07b712fd | ||
|
|
be8f3b3791 | ||
|
|
3131b0459f | ||
|
|
3ec323ce0d | ||
|
|
c8b782d870 | ||
|
|
7bca15704b | ||
|
|
5268e74315 | ||
|
|
91c209900b | ||
|
|
74c29f1818 | ||
|
|
5858e61327 | ||
|
|
21cf2e38c5 | ||
|
|
a3ca5554fd | ||
|
|
acf9b22466 | ||
|
|
ffcd023f83 | ||
|
|
6259ad559b | ||
|
|
8d259a9dbe | ||
|
|
010c5a2c4e | ||
|
|
45b126a977 | ||
|
|
5f74297576 | ||
|
|
349f57381f | ||
|
|
41eb586ec8 | ||
|
|
6bf6fcaa51 | ||
|
|
6e89537830 | ||
|
|
669c6a3d5e | ||
|
|
910531bc33 | ||
|
|
690f26cf8b | ||
|
|
6b56fee6b0 | ||
|
|
d94001f445 | ||
|
|
6bcfc4014b | ||
|
|
47a89ad243 | ||
|
|
f3f97895a9 | ||
|
|
30afba50a9 | ||
|
|
036c123488 | ||
|
|
050f5f6723 | ||
|
|
2cd970f137 | ||
|
|
d6255fb3d2 | ||
|
|
f9a66ecaed | ||
|
|
cfb9a4beb0 | ||
|
|
9902cd54ce | ||
|
|
96510b72b8 | ||
|
|
a364a13458 | ||
|
|
09a4cfd307 | ||
|
|
5d66c3db85 | ||
|
|
28f33d0103 | ||
|
|
55a90f576a | ||
|
|
8d6abf6537 | ||
|
|
04961a0186 | ||
|
|
fd7ab20ea4 | ||
|
|
7019aca59d | ||
|
|
d43bcc04db | ||
|
|
2b94a35aaa | ||
|
|
e8208643bb | ||
|
|
a90f80725f | ||
|
|
4e6c37d23b | ||
|
|
0cf6259fec | ||
|
|
5cb5e92185 | ||
|
|
da61a28839 | ||
|
|
efdb769f9b | ||
|
|
9cce5a650e | ||
|
|
2021ca5bff | ||
|
|
1771250b04 | ||
|
|
18259c0fd4 | ||
|
|
41ddd1cc97 | ||
|
|
e175878008 | ||
|
|
1cfbfc199c | ||
|
|
f59f2caf7e | ||
|
|
401342c6ec | ||
|
|
0df1e4a489 | ||
|
|
9bd3e156f5 | ||
|
|
42c655751b | ||
|
|
ff1d78df3b | ||
|
|
c2e4fdf63d | ||
|
|
bf11b888c3 | ||
|
|
d562f58e76 | ||
|
|
94e4aa626d | ||
|
|
8ceba89d81 | ||
|
|
c37d6d5fed | ||
|
|
1a3597d726 | ||
|
|
c747cccde3 | ||
|
|
d81e7683ea | ||
|
|
8b29ee6033 | ||
|
|
96a75e08af | ||
|
|
06cbff6714 | ||
|
|
ce05813e7c | ||
|
|
4d1d8d6d78 | ||
|
|
1f8b14f4f1 | ||
|
|
082cc6184c | ||
|
|
6cfc4dc857 | ||
|
|
b9c48685e8 | ||
|
|
570c396e84 | ||
|
|
5fd034e604 | ||
|
|
63dab5f891 | ||
|
|
a2d6df3ed6 | ||
|
|
30e86ac939 | ||
|
|
976fc3ee97 | ||
|
|
63091459d8 | ||
|
|
659fae70f8 | ||
|
|
02e970192f | ||
|
|
5ecc67f2ef | ||
|
|
73dfb10c16 | ||
|
|
e513e81046 | ||
|
|
2fc4dec58f | ||
|
|
3891381d3e | ||
|
|
b91e929086 | ||
|
|
013a646799 | ||
|
|
ed52e759d7 | ||
|
|
6da099a9d7 | ||
|
|
5f159bc95e | ||
|
|
a4462577bf | ||
|
|
c147b58558 | ||
|
|
84fe1bfe9b | ||
|
|
657d7a911d | ||
|
|
ee05cc3ad9 | ||
|
|
5ed144f9d2 | ||
|
|
2a862b3c54 | ||
|
|
4a7c84f490 | ||
|
|
230e2e4107 | ||
|
|
d732b8ba0f | ||
|
|
7c3eecc9c7 | ||
|
|
fff37ab823 | ||
|
|
8a7a78fafb | ||
|
|
6de3ac3e17 | ||
|
|
5aae3bdc69 | ||
|
|
e298301b40 | ||
|
|
ed6bf7f161 | ||
|
|
f14d6670ba | ||
|
|
22d9b5d8ca | ||
|
|
6ed6e8bc26 | ||
|
|
4846e6fb3a | ||
|
|
cb543f9546 | ||
|
|
450d727a04 | ||
|
|
60b3eb3f76 | ||
|
|
bbe7c9a738 | ||
|
|
f6345a6995 | ||
|
|
e70d0edfac | ||
|
|
921c24e274 | ||
|
|
18f3f8097f | ||
|
|
4f6682c7fe | ||
|
|
f57dece2d5 | ||
|
|
103ad635d9 | ||
|
|
ec5e7a2653 | ||
|
|
05d3ee8555 | ||
|
|
1b34437839 | ||
|
|
3ff2c8fc38 | ||
|
|
b0b0b00fae | ||
|
|
80fb88520f | ||
|
|
aef84d453a | ||
|
|
e06d010aab | ||
|
|
14148f53d4 | ||
|
|
efde5aa2bb | ||
|
|
fcc5e27455 | ||
|
|
ed417da536 | ||
|
|
d1c67897c5 | ||
|
|
a887f3b340 | ||
|
|
f8deebc6db | ||
|
|
205f9a9f03 | ||
|
|
b0d1024f66 | ||
|
|
622ed8a032 | ||
|
|
09c51f9641 | ||
|
|
8422a81d88 |
@@ -26,3 +26,6 @@ rustflags = [
|
|||||||
"-C",
|
"-C",
|
||||||
"target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows
|
"target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[env]
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = "10.15.7"
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
# https://github.com/zed-industries/zed/pull/2394
|
# https://github.com/zed-industries/zed/pull/2394
|
||||||
eca93c124a488b4e538946cd2d313bd571aa2b86
|
eca93c124a488b4e538946cd2d313bd571aa2b86
|
||||||
|
|
||||||
|
# 2024-02-15 Format YAML files
|
||||||
|
# https://github.com/zed-industries/zed/pull/7887
|
||||||
|
a161a7d0c95ca7505bf9218bfae640ee5444c88b
|
||||||
|
|
||||||
# 2024-02-25 Format JSON files in assets/
|
# 2024-02-25 Format JSON files in assets/
|
||||||
# https://github.com/zed-industries/zed/pull/8405
|
# https://github.com/zed-industries/zed/pull/8405
|
||||||
ffdda588b41f7d9d270ffe76cab116f828ad545e
|
ffdda588b41f7d9d270ffe76cab116f828ad545e
|
||||||
|
|||||||
51
.github/ISSUE_TEMPLATE/0_git_beta_bug_report.yml
vendored
@@ -1,51 +0,0 @@
|
|||||||
name: Git Beta
|
|
||||||
description: There is a bug related to new Git features in Zed
|
|
||||||
type: "Bug"
|
|
||||||
labels: [git]
|
|
||||||
title: "Git Beta: <a short description of the Git bug>"
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Summary
|
|
||||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
|
||||||
value: |
|
|
||||||
<!-- Please insert a one line summary of the issue below -->
|
|
||||||
|
|
||||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
|
||||||
Steps to trigger the problem:
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
Actual Behavior:
|
|
||||||
|
|
||||||
Expected Behavior:
|
|
||||||
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: environment
|
|
||||||
attributes:
|
|
||||||
label: Zed Version and System Specs
|
|
||||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
|
||||||
placeholder: |
|
|
||||||
Output of "zed: Copy System Specs Into Clipboard"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
|
||||||
description: |
|
|
||||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
|
||||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
|
||||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
|
||||||
value: |
|
|
||||||
<details><summary>Zed.log</summary>
|
|
||||||
|
|
||||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
|||||||
cargo install cargo-nextest --locked
|
cargo install cargo-nextest --locked
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
2
.github/actions/run_tests_windows/action.yml
vendored
@@ -16,7 +16,7 @@ runs:
|
|||||||
run: cargo install cargo-nextest --locked
|
run: cargo install cargo-nextest --locked
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
215
.github/workflows/ci.yml
vendored
@@ -23,9 +23,53 @@ env:
|
|||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
job_spec:
|
||||||
|
name: Decide which jobs to run
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
outputs:
|
||||||
|
run_tests: ${{ steps.filter.outputs.run_tests }}
|
||||||
|
run_license: ${{ steps.filter.outputs.run_license }}
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
with:
|
||||||
|
# 350 is arbitrary; ~10days of history on main (5secs); full history is ~25secs
|
||||||
|
fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
|
||||||
|
- name: Fetch git history and generate output filters
|
||||||
|
id: filter
|
||||||
|
run: |
|
||||||
|
if [ -z "$GITHUB_BASE_REF" ]; then
|
||||||
|
echo "Not in a PR context (i.e., push to main/stable/preview)"
|
||||||
|
COMPARE_REV=$(git rev-parse HEAD~1)
|
||||||
|
else
|
||||||
|
echo "In a PR context comparing to pull_request.base.ref"
|
||||||
|
git fetch origin "$GITHUB_BASE_REF" --depth=350
|
||||||
|
COMPARE_REV=$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)
|
||||||
|
fi
|
||||||
|
# Specify anything which should skip full CI in this regex:
|
||||||
|
# - docs/
|
||||||
|
# - .github/ISSUE_TEMPLATE/
|
||||||
|
# - .github/workflows/ (except .github/workflows/ci.yml)
|
||||||
|
SKIP_REGEX='^(docs/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))'
|
||||||
|
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -vP "$SKIP_REGEX") ]]; then
|
||||||
|
echo "run_tests=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "run_tests=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep '^Cargo.lock') ]]; then
|
||||||
|
echo "run_license=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "run_license=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
migration_checks:
|
migration_checks:
|
||||||
name: Check Postgres and Protobuf migrations, mergability
|
name: Check Postgres and Protobuf migrations, mergability
|
||||||
if: github.repository_owner == 'zed-industries'
|
needs: [job_spec]
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
@@ -69,6 +113,7 @@ jobs:
|
|||||||
style:
|
style:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Check formatting and spelling
|
name: Check formatting and spelling
|
||||||
|
needs: [job_spec]
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-8vcpu-ubuntu-2204
|
- buildjet-8vcpu-ubuntu-2204
|
||||||
@@ -76,6 +121,21 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Prettier Check on /docs
|
||||||
|
working-directory: ./docs
|
||||||
|
run: |
|
||||||
|
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
|
||||||
|
echo "To fix, run from the root of the zed repo:"
|
||||||
|
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
|
||||||
|
false
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
PRETTIER_VERSION: 3.5.0
|
||||||
|
|
||||||
# To support writing comments that they will certainly be revisited.
|
# To support writing comments that they will certainly be revisited.
|
||||||
- name: Check for todo! and FIXME comments
|
- name: Check for todo! and FIXME comments
|
||||||
run: script/check-todos
|
run: script/check-todos
|
||||||
@@ -91,7 +151,10 @@ jobs:
|
|||||||
macos_tests:
|
macos_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (macOS) Run Clippy and tests
|
name: (macOS) Run Clippy and tests
|
||||||
if: github.repository_owner == 'zed-industries'
|
needs: [job_spec]
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
@@ -123,7 +186,9 @@ jobs:
|
|||||||
- name: Check licenses
|
- name: Check licenses
|
||||||
run: |
|
run: |
|
||||||
script/check-licenses
|
script/check-licenses
|
||||||
script/generate-licenses /tmp/zed_licenses_output
|
if [[ "${{ needs.job_spec.outputs.run_license }}" == "true" ]]; then
|
||||||
|
script/generate-licenses /tmp/zed_licenses_output
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Check for new vulnerable dependencies
|
- name: Check for new vulnerable dependencies
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
@@ -144,7 +209,6 @@ jobs:
|
|||||||
cargo check -p workspace
|
cargo check -p workspace
|
||||||
cargo build -p remote_server
|
cargo build -p remote_server
|
||||||
cargo check -p gpui --examples
|
cargo check -p gpui --examples
|
||||||
script/check-rust-livekit-macos
|
|
||||||
|
|
||||||
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
|
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||||
- name: Clean CI config file
|
- name: Clean CI config file
|
||||||
@@ -154,7 +218,10 @@ jobs:
|
|||||||
linux_tests:
|
linux_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Linux) Run Clippy and tests
|
name: (Linux) Run Clippy and tests
|
||||||
if: github.repository_owner == 'zed-industries'
|
needs: [job_spec]
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
@@ -167,7 +234,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
cache-provider: "buildjet"
|
||||||
@@ -203,9 +270,12 @@ jobs:
|
|||||||
build_remote_server:
|
build_remote_server:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Linux) Build Remote Server
|
name: (Linux) Build Remote Server
|
||||||
if: github.repository_owner == 'zed-industries'
|
needs: [job_spec]
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-8vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
@@ -216,7 +286,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
cache-provider: "buildjet"
|
||||||
@@ -239,21 +309,12 @@ jobs:
|
|||||||
windows_clippy:
|
windows_clippy:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Windows) Run Clippy
|
name: (Windows) Run Clippy
|
||||||
if: github.repository_owner == 'zed-industries'
|
needs: [job_spec]
|
||||||
runs-on: hosted-windows-2
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
|
runs-on: windows-2025-16
|
||||||
steps:
|
steps:
|
||||||
# Temporarily Collect some metadata about the hardware behind our runners.
|
|
||||||
- name: GHA Runner Info
|
|
||||||
run: |
|
|
||||||
Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute?api-version=2023-07-01" |
|
|
||||||
ConvertTo-Json -Depth 10 |
|
|
||||||
jq "{ vm_size: .vmSize, location: .location, os_disk_gb: (.storageProfile.osDisk.diskSizeGB | tonumber), rs_disk_gb: (.storageProfile.resourceDisk.size | tonumber / 1024) }"
|
|
||||||
@{
|
|
||||||
Cores = (Get-CimInstance Win32_Processor).NumberOfCores
|
|
||||||
vCPUs = (Get-CimInstance Win32_Processor).NumberOfLogicalProcessors
|
|
||||||
RamGb = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
|
|
||||||
cpuid = (Get-CimInstance Win32_Processor).Name.Trim()
|
|
||||||
} | ConvertTo-Json
|
|
||||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||||
- name: Enable longer pathnames for git
|
- name: Enable longer pathnames for git
|
||||||
run: git config --system core.longpaths true
|
run: git config --system core.longpaths true
|
||||||
@@ -272,7 +333,7 @@ jobs:
|
|||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
workspaces: ${{ env.ZED_WORKSPACE }}
|
workspaces: ${{ env.ZED_WORKSPACE }}
|
||||||
@@ -306,21 +367,13 @@ jobs:
|
|||||||
windows_tests:
|
windows_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Windows) Run Tests
|
name: (Windows) Run Tests
|
||||||
if: ${{ github.repository_owner == 'zed-industries' && (github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'windows')) }}
|
needs: [job_spec]
|
||||||
runs-on: hosted-windows-2
|
if: |
|
||||||
|
github.repository_owner == 'zed-industries' &&
|
||||||
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
|
# Use bigger runners for PRs (speed); smaller for async (cost)
|
||||||
|
runs-on: ${{ github.event_name == 'pull_request' && 'windows-2025-32' || 'windows-2025-16' }}
|
||||||
steps:
|
steps:
|
||||||
# Temporarily Collect some metadata about the hardware behind our runners.
|
|
||||||
- name: GHA Runner Info
|
|
||||||
run: |
|
|
||||||
Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute?api-version=2023-07-01" |
|
|
||||||
ConvertTo-Json -Depth 10 |
|
|
||||||
jq "{ vm_size: .vmSize, location: .location, os_disk_gb: (.storageProfile.osDisk.diskSizeGB | tonumber), rs_disk_gb: (.storageProfile.resourceDisk.size | tonumber / 1024) }"
|
|
||||||
@{
|
|
||||||
Cores = (Get-CimInstance Win32_Processor).NumberOfCores
|
|
||||||
vCPUs = (Get-CimInstance Win32_Processor).NumberOfLogicalProcessors
|
|
||||||
RamGb = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
|
|
||||||
cpuid = (Get-CimInstance Win32_Processor).Name.Trim()
|
|
||||||
} | ConvertTo-Json
|
|
||||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||||
- name: Enable longer pathnames for git
|
- name: Enable longer pathnames for git
|
||||||
run: git config --system core.longpaths true
|
run: git config --system core.longpaths true
|
||||||
@@ -339,7 +392,7 @@ jobs:
|
|||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
workspaces: ${{ env.ZED_WORKSPACE }}
|
workspaces: ${{ env.ZED_WORKSPACE }}
|
||||||
@@ -372,13 +425,49 @@ jobs:
|
|||||||
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
|
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tests_pass:
|
||||||
|
name: Tests Pass
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- job_spec
|
||||||
|
- style
|
||||||
|
- migration_checks
|
||||||
|
- linux_tests
|
||||||
|
- build_remote_server
|
||||||
|
- macos_tests
|
||||||
|
- windows_clippy
|
||||||
|
- windows_tests
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Check all tests passed
|
||||||
|
run: |
|
||||||
|
# Check dependent jobs...
|
||||||
|
RET_CODE=0
|
||||||
|
# Always check style
|
||||||
|
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
|
||||||
|
|
||||||
|
# Only check test jobs if they were supposed to run
|
||||||
|
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
||||||
|
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
|
||||||
|
[[ "${{ needs.linux_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
|
||||||
|
[[ "${{ needs.windows_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
|
||||||
|
[[ "${{ needs.windows_clippy.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows clippy failed"; }
|
||||||
|
[[ "${{ needs.build_remote_server.result }}" != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
|
||||||
|
fi
|
||||||
|
if [[ "$RET_CODE" -eq 0 ]]; then
|
||||||
|
echo "All tests passed successfully!"
|
||||||
|
fi
|
||||||
|
exit $RET_CODE
|
||||||
|
|
||||||
bundle-mac:
|
bundle-mac:
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
name: Create a macOS bundle
|
name: Create a macOS bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- bundle
|
- bundle
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
needs: [macos_tests]
|
needs: [macos_tests]
|
||||||
env:
|
env:
|
||||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
@@ -392,7 +481,7 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
@@ -436,14 +525,14 @@ jobs:
|
|||||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||||
|
|
||||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||||
with:
|
with:
|
||||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||||
|
|
||||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||||
with:
|
with:
|
||||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||||
@@ -468,7 +557,9 @@ jobs:
|
|||||||
name: Linux x86_x64 release bundle
|
name: Linux x86_x64 release bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2004
|
- buildjet-16vcpu-ubuntu-2004
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
env:
|
env:
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
@@ -485,7 +576,7 @@ jobs:
|
|||||||
run: ./script/linux && ./script/install-mold 2.34.0
|
run: ./script/linux && ./script/install-mold 2.34.0
|
||||||
|
|
||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
run: |
|
run: |
|
||||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||||
script/determine-release-channel
|
script/determine-release-channel
|
||||||
@@ -494,15 +585,19 @@ jobs:
|
|||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
github.ref == 'refs/heads/main'
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
with:
|
with:
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||||
path: target/release/zed-*.tar.gz
|
path: target/release/zed-*.tar.gz
|
||||||
|
|
||||||
- name: Upload Linux remote server to workflow run if main branch or specific label
|
- name: Upload Linux remote server to workflow run if main branch or specific label
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
github.ref == 'refs/heads/main'
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
with:
|
with:
|
||||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
|
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
|
||||||
path: target/zed-remote-server-linux-x86_64.gz
|
path: target/zed-remote-server-linux-x86_64.gz
|
||||||
@@ -523,7 +618,9 @@ jobs:
|
|||||||
name: Linux arm64 release bundle
|
name: Linux arm64 release bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204-arm
|
- buildjet-16vcpu-ubuntu-2204-arm
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
env:
|
env:
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
@@ -540,7 +637,7 @@ jobs:
|
|||||||
run: ./script/linux
|
run: ./script/linux
|
||||||
|
|
||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
run: |
|
run: |
|
||||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||||
script/determine-release-channel
|
script/determine-release-channel
|
||||||
@@ -549,15 +646,19 @@ jobs:
|
|||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
github.ref == 'refs/heads/main'
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
with:
|
with:
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||||
path: target/release/zed-*.tar.gz
|
path: target/release/zed-*.tar.gz
|
||||||
|
|
||||||
- name: Upload Linux remote server to workflow run if main branch or specific label
|
- name: Upload Linux remote server to workflow run if main branch or specific label
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: |
|
||||||
|
github.ref == 'refs/heads/main'
|
||||||
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
with:
|
with:
|
||||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
|
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
|
||||||
path: target/zed-remote-server-linux-aarch64.gz
|
path: target/zed-remote-server-linux-aarch64.gz
|
||||||
@@ -575,7 +676,9 @@ jobs:
|
|||||||
|
|
||||||
auto-release-preview:
|
auto-release-preview:
|
||||||
name: Auto release preview
|
name: Auto release preview
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
|
if: |
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
||||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64]
|
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64]
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: "Close Stale Issues"
|
name: "Close Stale Issues"
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 11 * * 2"
|
- cron: "0 7,9,11 * * 3"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
41
.github/workflows/community_release_actions.yml
vendored
@@ -13,11 +13,12 @@ jobs:
|
|||||||
id: get-release-url
|
id: get-release-url
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||||
URL="https://zed.dev/releases/preview/latest"
|
URL="https://zed.dev/releases/preview/latest"
|
||||||
else
|
else
|
||||||
URL="https://zed.dev/releases/stable/latest"
|
URL="https://zed.dev/releases/stable/latest"
|
||||||
fi
|
fi
|
||||||
echo "::set-output name=URL::$URL"
|
|
||||||
|
echo "URL=$URL" >> $GITHUB_OUTPUT
|
||||||
- name: Get content
|
- name: Get content
|
||||||
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
|
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
|
||||||
id: get-content
|
id: get-content
|
||||||
@@ -33,3 +34,37 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||||
content: ${{ steps.get-content.outputs.string }}
|
content: ${{ steps.get-content.outputs.string }}
|
||||||
|
|
||||||
|
send_release_notes_email:
|
||||||
|
if: github.repository_owner == 'zed-industries' && !github.event.release.prerelease
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check if release was promoted from preview
|
||||||
|
id: check-promotion-from-preview
|
||||||
|
run: |
|
||||||
|
VERSION="${{ github.event.release.tag_name }}"
|
||||||
|
PREVIEW_TAG="${VERSION}-pre"
|
||||||
|
|
||||||
|
if git rev-parse "$PREVIEW_TAG" > /dev/null 2>&1; then
|
||||||
|
echo "was_promoted_from_preview=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "was_promoted_from_preview=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Send release notes email
|
||||||
|
if: steps.check-promotion-from-preview.outputs.was_promoted_from_preview == 'true'
|
||||||
|
run: |
|
||||||
|
TAG="${{ github.event.release.tag_name }}"
|
||||||
|
cat << 'EOF' > release_body.txt
|
||||||
|
${{ github.event.release.body }}
|
||||||
|
EOF
|
||||||
|
jq -n --arg tag "$TAG" --rawfile body release_body.txt '{version: $tag, markdown_body: $body}' \
|
||||||
|
> release_data.json
|
||||||
|
curl -X POST "https://zed.dev/api/send_release_notes_email" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.RELEASE_NOTES_API_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @release_data.json
|
||||||
|
|||||||
2
.github/workflows/danger.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
10
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,35 +37,35 @@ jobs:
|
|||||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||||
|
|
||||||
- name: Deploy Docs
|
- name: Deploy Docs
|
||||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: pages deploy target/deploy --project-name=docs
|
command: pages deploy target/deploy --project-name=docs
|
||||||
|
|
||||||
- name: Deploy Install
|
- name: Deploy Install
|
||||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||||
|
|
||||||
- name: Deploy Docs Workers
|
- name: Deploy Docs Workers
|
||||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||||
|
|
||||||
- name: Deploy Install Workers
|
- name: Deploy Install Workers
|
||||||
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
|
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||||
|
|
||||||
- name: Preserve Wrangler logs
|
- name: Preserve Wrangler logs
|
||||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: wrangler_logs
|
name: wrangler_logs
|
||||||
|
|||||||
39
.github/workflows/docs.yml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "docs/**"
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_formatting:
|
|
||||||
name: "Check formatting"
|
|
||||||
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: Prettier Check on /docs
|
|
||||||
working-directory: ./docs
|
|
||||||
run: |
|
|
||||||
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
|
|
||||||
echo "To fix, run from the root of the zed repo:"
|
|
||||||
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
|
|
||||||
false
|
|
||||||
}
|
|
||||||
env:
|
|
||||||
PRETTIER_VERSION: 3.5.0
|
|
||||||
|
|
||||||
- name: Check for Typos with Typos-CLI
|
|
||||||
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
|
||||||
with:
|
|
||||||
config: ./typos.toml
|
|
||||||
files: ./docs/
|
|
||||||
2
.github/workflows/issue_response.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
2
.github/workflows/publish_extension_cli.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "github"
|
cache-provider: "github"
|
||||||
|
|||||||
2
.github/workflows/randomized_tests.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
53
.github/workflows/release_nightly.yml
vendored
@@ -71,7 +71,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
@@ -170,6 +170,57 @@ jobs:
|
|||||||
- name: Upload Zed Nightly
|
- name: Upload Zed Nightly
|
||||||
run: script/upload-nightly linux-targz
|
run: script/upload-nightly linux-targz
|
||||||
|
|
||||||
|
bundle-nix:
|
||||||
|
timeout-minutes: 60
|
||||||
|
name: (${{ matrix.system.os }}) Nix Build
|
||||||
|
continue-on-error: true
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
system:
|
||||||
|
- os: x86 Linux
|
||||||
|
runner: buildjet-16vcpu-ubuntu-2204
|
||||||
|
install_nix: true
|
||||||
|
- os: arm Mac
|
||||||
|
runner: [macOS, ARM64, test]
|
||||||
|
install_nix: false
|
||||||
|
- os: arm Linux
|
||||||
|
runner: buildjet-16vcpu-ubuntu-2204-arm
|
||||||
|
install_nix: true
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
runs-on: ${{ matrix.system.runner }}
|
||||||
|
needs: tests
|
||||||
|
env:
|
||||||
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
|
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
with:
|
||||||
|
clean: false
|
||||||
|
|
||||||
|
# on our macs we manually install nix. for some reason the cachix action is running
|
||||||
|
# under a non-login /bin/bash shell which doesn't source the proper script to add the
|
||||||
|
# nix profile to PATH, so we manually add them here
|
||||||
|
- name: Set path
|
||||||
|
if: ${{ ! matrix.system.install_nix }}
|
||||||
|
run: |
|
||||||
|
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
||||||
|
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
||||||
|
if: ${{ matrix.system.install_nix }}
|
||||||
|
with:
|
||||||
|
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
|
with:
|
||||||
|
name: zed-industries
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
- run: nix build
|
||||||
|
- run: nix-collect-garbage -d
|
||||||
|
|
||||||
update-nightly-tag:
|
update-nightly-tag:
|
||||||
name: Update nightly tag
|
name: Update nightly tag
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
|||||||
19
.zed/debug.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Debug Zed with LLDB",
|
||||||
|
"adapter": "lldb",
|
||||||
|
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Debug Zed with GDB",
|
||||||
|
"adapter": "gdb",
|
||||||
|
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
|
"initialize_args": {
|
||||||
|
"stopAtBeginningOfMainSubprogram": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
1904
Cargo.lock
generated
67
Cargo.toml
@@ -8,6 +8,7 @@ members = [
|
|||||||
"crates/assistant",
|
"crates/assistant",
|
||||||
"crates/assistant2",
|
"crates/assistant2",
|
||||||
"crates/assistant_context_editor",
|
"crates/assistant_context_editor",
|
||||||
|
"crates/assistant_eval",
|
||||||
"crates/assistant_settings",
|
"crates/assistant_settings",
|
||||||
"crates/assistant_slash_command",
|
"crates/assistant_slash_command",
|
||||||
"crates/assistant_slash_commands",
|
"crates/assistant_slash_commands",
|
||||||
@@ -36,6 +37,10 @@ members = [
|
|||||||
"crates/context_server_settings",
|
"crates/context_server_settings",
|
||||||
"crates/copilot",
|
"crates/copilot",
|
||||||
"crates/credentials_provider",
|
"crates/credentials_provider",
|
||||||
|
"crates/dap",
|
||||||
|
"crates/dap_adapters",
|
||||||
|
"crates/debugger_tools",
|
||||||
|
"crates/debugger_ui",
|
||||||
"crates/db",
|
"crates/db",
|
||||||
"crates/deepseek",
|
"crates/deepseek",
|
||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
@@ -64,6 +69,8 @@ members = [
|
|||||||
"crates/gpui_tokio",
|
"crates/gpui_tokio",
|
||||||
"crates/html_to_markdown",
|
"crates/html_to_markdown",
|
||||||
"crates/http_client",
|
"crates/http_client",
|
||||||
|
"crates/http_client_tls",
|
||||||
|
"crates/icons",
|
||||||
"crates/image_viewer",
|
"crates/image_viewer",
|
||||||
"crates/indexed_docs",
|
"crates/indexed_docs",
|
||||||
"crates/inline_completion",
|
"crates/inline_completion",
|
||||||
@@ -80,7 +87,6 @@ members = [
|
|||||||
"crates/languages",
|
"crates/languages",
|
||||||
"crates/livekit_api",
|
"crates/livekit_api",
|
||||||
"crates/livekit_client",
|
"crates/livekit_client",
|
||||||
"crates/livekit_client_macos",
|
|
||||||
"crates/lmstudio",
|
"crates/lmstudio",
|
||||||
"crates/lsp",
|
"crates/lsp",
|
||||||
"crates/markdown",
|
"crates/markdown",
|
||||||
@@ -118,7 +124,6 @@ members = [
|
|||||||
"crates/rope",
|
"crates/rope",
|
||||||
"crates/rpc",
|
"crates/rpc",
|
||||||
"crates/schema_generator",
|
"crates/schema_generator",
|
||||||
"crates/scripting_tool",
|
|
||||||
"crates/search",
|
"crates/search",
|
||||||
"crates/semantic_index",
|
"crates/semantic_index",
|
||||||
"crates/semantic_version",
|
"crates/semantic_version",
|
||||||
@@ -154,6 +159,7 @@ members = [
|
|||||||
"crates/ui",
|
"crates/ui",
|
||||||
"crates/ui_input",
|
"crates/ui_input",
|
||||||
"crates/ui_macros",
|
"crates/ui_macros",
|
||||||
|
"crates/ui_prompt",
|
||||||
"crates/util",
|
"crates/util",
|
||||||
"crates/util_macros",
|
"crates/util_macros",
|
||||||
"crates/vim",
|
"crates/vim",
|
||||||
@@ -164,6 +170,8 @@ members = [
|
|||||||
"crates/zed",
|
"crates/zed",
|
||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
"crates/zeta",
|
"crates/zeta",
|
||||||
|
"crates/zlog",
|
||||||
|
"crates/zlog_settings",
|
||||||
|
|
||||||
#
|
#
|
||||||
# Extensions
|
# Extensions
|
||||||
@@ -171,19 +179,14 @@ members = [
|
|||||||
|
|
||||||
"extensions/emmet",
|
"extensions/emmet",
|
||||||
"extensions/glsl",
|
"extensions/glsl",
|
||||||
"extensions/haskell",
|
|
||||||
"extensions/html",
|
"extensions/html",
|
||||||
"extensions/perplexity",
|
"extensions/perplexity",
|
||||||
"extensions/proto",
|
"extensions/proto",
|
||||||
"extensions/purescript",
|
|
||||||
"extensions/ruff",
|
"extensions/ruff",
|
||||||
"extensions/slash-commands-example",
|
"extensions/slash-commands-example",
|
||||||
"extensions/snippets",
|
"extensions/snippets",
|
||||||
"extensions/terraform",
|
|
||||||
"extensions/test-extension",
|
"extensions/test-extension",
|
||||||
"extensions/toml",
|
"extensions/toml",
|
||||||
"extensions/uiua",
|
|
||||||
"extensions/zig",
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tooling
|
# Tooling
|
||||||
@@ -195,7 +198,7 @@ default-members = ["crates/zed"]
|
|||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
publish = false
|
publish = false
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
||||||
@@ -211,6 +214,7 @@ assets = { path = "crates/assets" }
|
|||||||
assistant = { path = "crates/assistant" }
|
assistant = { path = "crates/assistant" }
|
||||||
assistant2 = { path = "crates/assistant2" }
|
assistant2 = { path = "crates/assistant2" }
|
||||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||||
|
assistant_eval = { path = "crates/assistant_eval" }
|
||||||
assistant_settings = { path = "crates/assistant_settings" }
|
assistant_settings = { path = "crates/assistant_settings" }
|
||||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||||
@@ -238,7 +242,11 @@ context_server = { path = "crates/context_server" }
|
|||||||
context_server_settings = { path = "crates/context_server_settings" }
|
context_server_settings = { path = "crates/context_server_settings" }
|
||||||
copilot = { path = "crates/copilot" }
|
copilot = { path = "crates/copilot" }
|
||||||
credentials_provider = { path = "crates/credentials_provider" }
|
credentials_provider = { path = "crates/credentials_provider" }
|
||||||
|
dap = { path = "crates/dap" }
|
||||||
|
dap_adapters = { path = "crates/dap_adapters" }
|
||||||
db = { path = "crates/db" }
|
db = { path = "crates/db" }
|
||||||
|
debugger_ui = { path = "crates/debugger_ui" }
|
||||||
|
debugger_tools = { path = "crates/debugger_tools" }
|
||||||
deepseek = { path = "crates/deepseek" }
|
deepseek = { path = "crates/deepseek" }
|
||||||
diagnostics = { path = "crates/diagnostics" }
|
diagnostics = { path = "crates/diagnostics" }
|
||||||
buffer_diff = { path = "crates/buffer_diff" }
|
buffer_diff = { path = "crates/buffer_diff" }
|
||||||
@@ -265,6 +273,8 @@ gpui_macros = { path = "crates/gpui_macros" }
|
|||||||
gpui_tokio = { path = "crates/gpui_tokio" }
|
gpui_tokio = { path = "crates/gpui_tokio" }
|
||||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||||
http_client = { path = "crates/http_client" }
|
http_client = { path = "crates/http_client" }
|
||||||
|
http_client_tls = { path = "crates/http_client_tls" }
|
||||||
|
icons = { path = "crates/icons" }
|
||||||
image_viewer = { path = "crates/image_viewer" }
|
image_viewer = { path = "crates/image_viewer" }
|
||||||
indexed_docs = { path = "crates/indexed_docs" }
|
indexed_docs = { path = "crates/indexed_docs" }
|
||||||
inline_completion = { path = "crates/inline_completion" }
|
inline_completion = { path = "crates/inline_completion" }
|
||||||
@@ -281,7 +291,6 @@ language_tools = { path = "crates/language_tools" }
|
|||||||
languages = { path = "crates/languages" }
|
languages = { path = "crates/languages" }
|
||||||
livekit_api = { path = "crates/livekit_api" }
|
livekit_api = { path = "crates/livekit_api" }
|
||||||
livekit_client = { path = "crates/livekit_client" }
|
livekit_client = { path = "crates/livekit_client" }
|
||||||
livekit_client_macos = { path = "crates/livekit_client_macos" }
|
|
||||||
lmstudio = { path = "crates/lmstudio" }
|
lmstudio = { path = "crates/lmstudio" }
|
||||||
lsp = { path = "crates/lsp" }
|
lsp = { path = "crates/lsp" }
|
||||||
markdown = { path = "crates/markdown" }
|
markdown = { path = "crates/markdown" }
|
||||||
@@ -319,7 +328,6 @@ reqwest_client = { path = "crates/reqwest_client" }
|
|||||||
rich_text = { path = "crates/rich_text" }
|
rich_text = { path = "crates/rich_text" }
|
||||||
rope = { path = "crates/rope" }
|
rope = { path = "crates/rope" }
|
||||||
rpc = { path = "crates/rpc" }
|
rpc = { path = "crates/rpc" }
|
||||||
scripting_tool = { path = "crates/scripting_tool" }
|
|
||||||
search = { path = "crates/search" }
|
search = { path = "crates/search" }
|
||||||
semantic_index = { path = "crates/semantic_index" }
|
semantic_index = { path = "crates/semantic_index" }
|
||||||
semantic_version = { path = "crates/semantic_version" }
|
semantic_version = { path = "crates/semantic_version" }
|
||||||
@@ -355,6 +363,7 @@ toolchain_selector = { path = "crates/toolchain_selector" }
|
|||||||
ui = { path = "crates/ui" }
|
ui = { path = "crates/ui" }
|
||||||
ui_input = { path = "crates/ui_input" }
|
ui_input = { path = "crates/ui_input" }
|
||||||
ui_macros = { path = "crates/ui_macros" }
|
ui_macros = { path = "crates/ui_macros" }
|
||||||
|
ui_prompt = { path = "crates/ui_prompt" }
|
||||||
util = { path = "crates/util" }
|
util = { path = "crates/util" }
|
||||||
util_macros = { path = "crates/util_macros" }
|
util_macros = { path = "crates/util_macros" }
|
||||||
vim = { path = "crates/vim" }
|
vim = { path = "crates/vim" }
|
||||||
@@ -365,13 +374,15 @@ worktree = { path = "crates/worktree" }
|
|||||||
zed = { path = "crates/zed" }
|
zed = { path = "crates/zed" }
|
||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
zeta = { path = "crates/zeta" }
|
zeta = { path = "crates/zeta" }
|
||||||
|
zlog = { path = "crates/zlog" }
|
||||||
|
zlog_settings = { path = "crates/zlog_settings" }
|
||||||
|
|
||||||
#
|
#
|
||||||
# External crates
|
# External crates
|
||||||
#
|
#
|
||||||
|
|
||||||
aho-corasick = "1.1"
|
aho-corasick = "1.1"
|
||||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" }
|
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||||
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"] }
|
||||||
@@ -403,14 +414,17 @@ bytes = "1.0"
|
|||||||
cargo_metadata = "0.19"
|
cargo_metadata = "0.19"
|
||||||
cargo_toml = "0.21"
|
cargo_toml = "0.21"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
circular-buffer = "1.0"
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
cocoa = "0.26"
|
cocoa = "0.26"
|
||||||
cocoa-foundation = "0.2.0"
|
cocoa-foundation = "0.2.0"
|
||||||
|
core-video = { version = "0.4.3", features = ["metal"] }
|
||||||
convert_case = "0.8.0"
|
convert_case = "0.8.0"
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.10.0"
|
||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
ctor = "0.4.0"
|
ctor = "0.4.0"
|
||||||
dashmap = "6.0"
|
dashmap = "6.0"
|
||||||
|
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "bfd4af0" }
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
dirs = "4.0"
|
dirs = "4.0"
|
||||||
ec4rs = "1.1"
|
ec4rs = "1.1"
|
||||||
@@ -422,8 +436,7 @@ fork = "0.2.0"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-batch = "0.6.1"
|
futures-batch = "0.6.1"
|
||||||
futures-lite = "1.13"
|
futures-lite = "1.13"
|
||||||
# TODO: get back to regular versions when https://github.com/rust-lang/git2-rs/pull/1120 is released
|
git2 = { version = "0.20.1", default-features = false }
|
||||||
git2 = { git = "https://github.com/rust-lang/git2-rs", rev = "a3b90cb3756c1bb63e2317bf9cfa57838178de5c", default-features = false }
|
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
handlebars = "4.3"
|
handlebars = "4.3"
|
||||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||||
@@ -445,17 +458,13 @@ libc = "0.2"
|
|||||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
linkme = "0.3.31"
|
linkme = "0.3.31"
|
||||||
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
|
|
||||||
"dispatcher",
|
|
||||||
"services-dispatcher",
|
|
||||||
"rustls-tls-native-roots",
|
|
||||||
], default-features = false }
|
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
mlua = { version = "0.10", features = ["lua54", "vendored"] }
|
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
nbformat = { version = "0.10.0" }
|
nbformat = { version = "0.10.0" }
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
|
open = "5.0.0"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||||
@@ -525,7 +534,7 @@ sys-locale = "0.3.1"
|
|||||||
sysinfo = "0.31.0"
|
sysinfo = "0.31.0"
|
||||||
take-until = "0.2.0"
|
take-until = "0.2.0"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.29"
|
thiserror = "2.0.12"
|
||||||
tiktoken-rs = "0.6.0"
|
tiktoken-rs = "0.6.0"
|
||||||
time = { version = "0.3", features = [
|
time = { version = "0.3", features = [
|
||||||
"macros",
|
"macros",
|
||||||
@@ -537,6 +546,7 @@ time = { version = "0.3", features = [
|
|||||||
tiny_http = "0.8"
|
tiny_http = "0.8"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
|
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"]}
|
||||||
tower-http = "0.4.4"
|
tower-http = "0.4.4"
|
||||||
tree-sitter = { version = "0.25.3", features = ["wasm"] }
|
tree-sitter = { version = "0.25.3", features = ["wasm"] }
|
||||||
tree-sitter-bash = "0.23"
|
tree-sitter-bash = "0.23"
|
||||||
@@ -566,6 +576,7 @@ unindent = "0.2.0"
|
|||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
unicode-script = "0.5.7"
|
unicode-script = "0.5.7"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
||||||
wasmparser = "0.221"
|
wasmparser = "0.221"
|
||||||
wasm-encoder = "0.221"
|
wasm-encoder = "0.221"
|
||||||
@@ -581,7 +592,7 @@ which = "6.0.0"
|
|||||||
wit-component = "0.221"
|
wit-component = "0.221"
|
||||||
zed_llm_client = "0.4"
|
zed_llm_client = "0.4"
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
metal = "0.31"
|
metal = "0.29"
|
||||||
|
|
||||||
[workspace.dependencies.async-stripe]
|
[workspace.dependencies.async-stripe]
|
||||||
git = "https://github.com/zed-industries/async-stripe"
|
git = "https://github.com/zed-industries/async-stripe"
|
||||||
@@ -598,12 +609,12 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
version = "0.58"
|
version = "0.61"
|
||||||
features = [
|
features = [
|
||||||
"implement",
|
|
||||||
"Foundation_Collections",
|
"Foundation_Collections",
|
||||||
"Foundation_Numerics",
|
"Foundation_Numerics",
|
||||||
"Storage",
|
"Storage_Search",
|
||||||
|
"Storage_Streams",
|
||||||
"System_Threading",
|
"System_Threading",
|
||||||
"UI_StartScreen",
|
"UI_StartScreen",
|
||||||
"UI_ViewManagement",
|
"UI_ViewManagement",
|
||||||
@@ -624,9 +635,11 @@ features = [
|
|||||||
"Win32_System_Com_StructuredStorage",
|
"Win32_System_Com_StructuredStorage",
|
||||||
"Win32_System_Console",
|
"Win32_System_Console",
|
||||||
"Win32_System_DataExchange",
|
"Win32_System_DataExchange",
|
||||||
|
"Win32_System_IO",
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_System_Memory",
|
"Win32_System_Memory",
|
||||||
"Win32_System_Ole",
|
"Win32_System_Ole",
|
||||||
|
"Win32_System_Pipes",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
@@ -752,5 +765,9 @@ new_ret_no_self = { level = "allow" }
|
|||||||
should_implement_trait = { level = "allow" }
|
should_implement_trait = { level = "allow" }
|
||||||
let_underscore_future = "allow"
|
let_underscore_future = "allow"
|
||||||
|
|
||||||
|
# in Rust it can be very tedious to reduce argument count without
|
||||||
|
# running afoul of the borrow checker.
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
|
||||||
[workspace.metadata.cargo-machete]
|
[workspace.metadata.cargo-machete]
|
||||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"]
|
ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"]
|
||||||
|
|||||||
1
assets/icons/arrow_right_left.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right-left"><path d="m16 3 4 4-4 4"/><path d="M20 7H4"/><path d="m8 21-4-4 4-4"/><path d="M4 17h16"/></svg>
|
||||||
|
After Width: | Height: | Size: 316 B |
3
assets/icons/arrow_up_right_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.4 2.6H5.75C5.75 2.50717 5.71312 2.41815 5.64749 2.35251C5.58185 2.28688 5.49283 2.25 5.4 2.25V2.6ZM2.6 2.25C2.4067 2.25 2.25 2.4067 2.25 2.6C2.25 2.7933 2.4067 2.95 2.6 2.95V2.25ZM5.05 5.4C5.05 5.5933 5.2067 5.75 5.4 5.75C5.5933 5.75 5.75 5.5933 5.75 5.4H5.05ZM2.35252 5.15251C2.21583 5.2892 2.21583 5.5108 2.35252 5.64748C2.4892 5.78417 2.7108 5.78417 2.84749 5.64748L2.35252 5.15251ZM5.4 2.25H2.6V2.95H5.4V2.25ZM5.05 2.6V5.4H5.75V2.6H5.05ZM5.15252 2.35251L2.35252 5.15251L2.84749 5.64748L5.64749 2.84748L5.15252 2.35251Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 650 B |
1
assets/icons/brain.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-brain"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"/><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/><path d="M3.477 10.896a4 4 0 0 1 .585-.396"/><path d="M19.938 10.5a4 4 0 0 1 .585.396"/><path d="M6 18a4 4 0 0 1-1.967-.516"/><path d="M19.967 17.484A4 4 0 0 1 18 18"/></svg>
|
||||||
|
After Width: | Height: | Size: 718 B |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle-more"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/><path d="M8 12h.01"/><path d="M12 12h.01"/><path d="M16 12h.01"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>
|
||||||
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 358 B |
1
assets/icons/cog.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cog"><path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"/><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M12 2v2"/><path d="M12 22v-2"/><path d="m17 20.66-1-1.73"/><path d="M11 10.27 7 3.34"/><path d="m20.66 17-1.73-1"/><path d="m3.34 7 1.73 1"/><path d="M14 12h8"/><path d="M2 12h2"/><path d="m20.66 7-1.73 1"/><path d="m3.34 17 1.73-1"/><path d="m17 3.34-1 1.73"/><path d="m11 13.73-4 6.93"/></svg>
|
||||||
|
After Width: | Height: | Size: 608 B |
1
assets/icons/debug.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>
|
||||||
|
After Width: | Height: | Size: 615 B |
1
assets/icons/debug_breakpoint.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle"><circle cx="12" cy="12" r="10"/></svg>
|
||||||
|
After Width: | Height: | Size: 257 B |
1
assets/icons/debug_continue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-step-forward"><line x1="6" x2="6" y1="4" y2="20"/><polygon points="10,4 20,12 10,20"/></svg>
|
||||||
|
After Width: | Height: | Size: 295 B |
1
assets/icons/debug_disabled_breakpoint.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle"><circle cx="12" cy="12" r="10"/></svg>
|
||||||
|
After Width: | Height: | Size: 249 B |
1
assets/icons/debug_disabled_log_breakpoint.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 267 B |
1
assets/icons/debug_disconnect.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-unplug"><path d="m19 5 3-3"/><path d="m2 22 3-3"/><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"/><path d="M7.5 13.5 10 11"/><path d="M10.5 16.5 13 14"/><path d="m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 474 B |
1
assets/icons/debug_ignore_breakpoints.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-off"><path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/></svg>
|
||||||
|
After Width: | Height: | Size: 334 B |
1
assets/icons/debug_log_breakpoint.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 275 B |
1
assets/icons/debug_pause.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pause"><rect x="14" y="4" width="4" height="16" rx="1"/><rect x="6" y="4" width="4" height="16" rx="1"/></svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
1
assets/icons/debug_restart.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||||
|
After Width: | Height: | Size: 302 B |
1
assets/icons/debug_step_back.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo-dot"><path d="M21 17a9 9 0 0 0-15-6.7L3 13"/><path d="M3 7v6h6"/><circle cx="12" cy="17" r="1"/></svg>
|
||||||
|
After Width: | Height: | Size: 310 B |
5
assets/icons/debug_step_into.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-from-dot">
|
||||||
|
<path d="m5 15 7 7 7-7"/>
|
||||||
|
<path d="M12 8v14"/>
|
||||||
|
<circle cx="12" cy="3" r="1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
5
assets/icons/debug_step_out.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-from-dot">
|
||||||
|
<path d="m3 10 9-8 9 8"/>
|
||||||
|
<path d="M12 17V2"/>
|
||||||
|
<circle cx="12" cy="21" r="1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 314 B |
5
assets/icons/debug_step_over.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-redo-dot">
|
||||||
|
<circle cx="12" cy="17" r="1"/>
|
||||||
|
<path d="M21 7v6h-6"/>
|
||||||
|
<path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 335 B |
1
assets/icons/debug_stop.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square"><rect width="18" height="18" x="3" y="3" rx="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 266 B |
4
assets/icons/expand_down.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5 8.5L7.5 11.5M7.5 11.5L4.5 8.5M7.5 11.5L7.5 5.5" stroke="black" stroke-linecap="square"/>
|
||||||
|
<path d="M5 3.5L10 3.5" stroke="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 248 B |
4
assets/icons/expand_up.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.5 6.5L7.5 3.5M7.5 3.5L10.5 6.5M7.5 3.5V9.5" stroke="black" stroke-linecap="square"/>
|
||||||
|
<path d="M5 11.5H10" stroke="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 238 B |
5
assets/icons/file_create.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.0001 1.33334H4.00008C3.64646 1.33334 3.30732 1.47382 3.05727 1.72387C2.80722 1.97392 2.66675 2.31305 2.66675 2.66668V13.3333C2.66675 13.687 2.80722 14.0261 3.05727 14.2762C3.30732 14.5262 3.64646 14.6667 4.00008 14.6667H12.0001C12.3537 14.6667 12.6928 14.5262 12.9429 14.2762C13.1929 14.0261 13.3334 13.687 13.3334 13.3333V4.66668L10.0001 1.33334Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 8H10" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 10V6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 762 B |
5
assets/icons/file_delete.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.0001 1.33334H4.00008C3.64646 1.33334 3.30732 1.47382 3.05727 1.72387C2.80722 1.97392 2.66675 2.31305 2.66675 2.66668V13.3333C2.66675 13.687 2.80722 14.0261 3.05727 14.2762C3.30732 14.5262 3.64646 14.6667 4.00008 14.6667H12.0001C12.3537 14.6667 12.6928 14.5262 12.9429 14.2762C13.1929 14.0261 13.3334 13.687 13.3334 13.3333V4.66668L10.0001 1.33334Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9.66659 6.5L6.33325 9.83333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.33325 6.5L9.66659 9.83333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 804 B |
3
assets/icons/file_icons/luau.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36197 1.67985C5.3748 1.41534 4.36011 2.00117 4.0956 2.98834L2.17985 10.138C1.91534 11.1252 2.50117 12.1399 3.48833 12.4044L10.638 14.3202C11.6252 14.5847 12.6399 13.9988 12.9044 13.0117L14.8202 5.86197C15.0847 4.8748 14.4988 3.86012 13.5117 3.59561L6.36197 1.67985ZM10.0457 4.58266C9.77896 4.51119 9.50479 4.66948 9.43332 4.93621L8.76235 7.44028C8.69088 7.70701 8.84917 7.98118 9.11591 8.05265L11.62 8.72362C11.8867 8.79509 12.1609 8.6368 12.2324 8.37006L12.9033 5.86599C12.9748 5.59926 12.8165 5.32509 12.5498 5.25362L10.0457 4.58266Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 707 B |
7
assets/icons/file_icons/wgsl.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.5 13L1.5 5H11.5L6.5 13Z" fill="black"/>
|
||||||
|
<path d="M14 9H9L11.5 5L14 9Z" fill="black" fill-opacity="0.75"/>
|
||||||
|
<path d="M9 9L14 9L11.5 13L9 9Z" fill="black" fill-opacity="0.65"/>
|
||||||
|
<path d="M14 5L15.25 7L12.75 7L14 5Z" fill="black" fill-opacity="0.5"/>
|
||||||
|
<path d="M14 9L12.75 7H15.25L14 9Z" fill="black" fill-opacity="0.55"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 432 B |
40
assets/icons/git_onboarding_bg.svg
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<svg width="400" height="120" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<pattern id="tilePattern" width="124" height="24" patternUnits="userSpaceOnUse">
|
||||||
|
<svg width="124" height="24" viewBox="0 0 124 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g opacity="0.2">
|
||||||
|
<path d="M16.666 12.0013L11.9993 16.668L7.33268 12.0013" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 7.33464L12 16.668" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M29 8.33464C29.3682 8.33464 29.6667 8.03616 29.6667 7.66797C29.6667 7.29978 29.3682 7.0013 29 7.0013C28.6318 7.0013 28.3333 7.29978 28.3333 7.66797C28.3333 8.03616 28.6318 8.33464 29 8.33464ZM29 9.66797C30.1046 9.66797 31 8.77254 31 7.66797C31 6.5634 30.1046 5.66797 29 5.66797C27.8954 5.66797 27 6.5634 27 7.66797C27 8.77254 27.8954 9.66797 29 9.66797Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M35 8.33464C35.3682 8.33464 35.6667 8.03616 35.6667 7.66797C35.6667 7.29978 35.3682 7.0013 35 7.0013C34.6318 7.0013 34.3333 7.29978 34.3333 7.66797C34.3333 8.03616 34.6318 8.33464 35 8.33464ZM35 9.66797C36.1046 9.66797 37 8.77254 37 7.66797C37 6.5634 36.1046 5.66797 35 5.66797C33.8954 5.66797 33 6.5634 33 7.66797C33 8.77254 33.8954 9.66797 35 9.66797Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M29 16.9987C29.3682 16.9987 29.6667 16.7002 29.6667 16.332C29.6667 15.9638 29.3682 15.6654 29 15.6654C28.6318 15.6654 28.3333 15.9638 28.3333 16.332C28.3333 16.7002 28.6318 16.9987 29 16.9987ZM29 18.332C30.1046 18.332 31 17.4366 31 16.332C31 15.2275 30.1046 14.332 29 14.332C27.8954 14.332 27 15.2275 27 16.332C27 17.4366 27.8954 18.332 29 18.332Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.334 9H29.6673V11.4615C30.2383 11.1443 31.0005 11 32.0007 11H33.6675C34.0356 11 34.334 10.7017 34.334 10.3333V9H35.6673V10.3333C35.6673 11.4378 34.7723 12.3333 33.6675 12.3333H32.0007C30.8614 12.3333 30.3692 12.5484 30.1298 12.7549C29.9016 12.9516 29.7857 13.2347 29.6673 13.742V15H28.334V9Z" fill="white"/>
|
||||||
|
<path d="M48.668 8.66406H55.3346V15.3307" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M48.668 15.3307L55.3346 8.66406" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M76.5871 9.40624C76.8514 9.14195 77 8.78346 77 8.40965C77 8.03583 76.8516 7.67731 76.5873 7.41295C76.323 7.14859 75.9645 7.00005 75.5907 7C75.2169 6.99995 74.8584 7.14841 74.594 7.4127L67.921 14.0874C67.8049 14.2031 67.719 14.3456 67.671 14.5024L67.0105 16.6784C66.9975 16.7217 66.9966 16.7676 67.0076 16.8113C67.0187 16.8551 67.0414 16.895 67.0734 16.9269C67.1053 16.9588 67.1453 16.9815 67.1891 16.9925C67.2328 17.0035 67.2788 17.0024 67.322 16.9894L69.4985 16.3294C69.6551 16.2818 69.7976 16.1964 69.9135 16.0809L76.5871 9.40624Z" stroke="white" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M74 8L76 10" stroke="white" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M70.3877 7.53516V6.53516" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M73.5693 16.6992V17.6992" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M66.3877 10.5352H67.3877" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M77.5693 13.6992H76.5693" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M68.3877 8.53516L67.3877 7.53516" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M75.5693 15.6992L76.5693 16.6992" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M87.334 11.9987L92.0007 7.33203L96.6673 11.9987" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M92 16.6654V7.33203" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M117 12C117 10.6739 116.473 9.40215 115.536 8.46447C114.598 7.52678 113.326 7 112 7C110.602 7.00526 109.261 7.55068 108.256 8.52222L107 9.77778" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M107 7V9.77778H109.778" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M107 12C107 13.3261 107.527 14.5979 108.464 15.5355C109.402 16.4732 110.674 17 112 17C113.398 16.9947 114.739 16.4493 115.744 15.4778L117 14.2222" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M114.223 14.2188H117V16.9965" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</pattern>
|
||||||
|
<linearGradient id="fade" y2="1" x2="0">
|
||||||
|
<stop offset="0" stop-color="white" stop-opacity=".52"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<mask id="fadeMask" maskContentUnits="objectBoundingBox">
|
||||||
|
<rect width="1" height="1" fill="url(#fade)"/>
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
<rect width="100%" height="100%" fill="url(#tilePattern)" mask="url(#fadeMask)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3 4H8" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3 4H8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M6 10L11 10" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M6 10L11 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<circle cx="4" cy="10" r="1.875" stroke="black" stroke-width="1.75"/>
|
<circle cx="4" cy="10" r="1.875" stroke="black" stroke-width="1.5"/>
|
||||||
<circle cx="10" cy="4" r="1.875" stroke="black" stroke-width="1.75"/>
|
<circle cx="10" cy="4" r="1.875" stroke="black" stroke-width="1.5"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 446 B |
1
assets/icons/user_round_pen.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-round-pen-icon lucide-user-round-pen"><path d="M2 21a8 8 0 0 1 10.821-7.487"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/><circle cx="10" cy="8" r="5"/></svg>
|
||||||
|
After Width: | Height: | Size: 461 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M5.27772 1.38585L4.39187 4.07909C4.34653 4.21692 4.26946 4.34219 4.16685 4.44479C4.06425 4.5474 3.93898 4.62447 3.80115 4.66981L1.10791 5.55566L3.80115 6.44151C3.93898 6.48685 4.06425 6.56392 4.16685 6.66653C4.26946 6.76913 4.34653 6.8944 4.39187 7.03223L5.27772 9.72547L6.16357 7.03223C6.20891 6.8944 6.28598 6.76913 6.38859 6.66653C6.49119 6.56392 6.61646 6.48685 6.7543 6.44151L9.44753 5.55566L6.7543 4.66981C6.61646 4.62447 6.49119 4.5474 6.38859 4.44479C6.28598 4.34219 6.20891 4.21692 6.16357 4.07909L5.27772 1.38585Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M8.35938 12.3555C8.35938 12.0664 8.52734 11.8086 9.00781 11.3594L10.2031 10.2344C10.6094 9.85156 10.7891 9.60156 10.7891 9.34375C10.7891 9.05469 10.5781 8.85938 10.2734 8.85938C10.0391 8.85938 9.87109 8.95312 9.66406 9.21094C9.42578 9.50781 9.25391 9.60938 8.99219 9.60938C8.61719 9.60938 8.35156 9.35938 8.35156 9.01172C8.35156 8.25 9.26953 7.57812 10.3594 7.57812C11.4961 7.57812 12.3438 8.26172 12.3438 9.17969C12.3438 9.75391 12.0391 10.3008 11.418 10.8516L10.4961 11.6719V11.7344H11.8047C12.2578 11.7344 12.5391 11.9766 12.5391 12.3711C12.5391 12.7656 12.2656 13 11.8047 13H9.08203C8.65234 13 8.35938 12.7383 8.35938 12.3555Z" fill="black"/>
|
|
||||||
<path d="M11.0834 1.38585V3.71918M9.91675 2.55248H12.2501" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -30,6 +30,13 @@
|
|||||||
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||||
"ctrl-,": "zed::OpenSettings",
|
"ctrl-,": "zed::OpenSettings",
|
||||||
"ctrl-q": "zed::Quit",
|
"ctrl-q": "zed::Quit",
|
||||||
|
"f4": "debugger::Start",
|
||||||
|
"f5": "debugger::Continue",
|
||||||
|
"shift-f5": "debugger::Stop",
|
||||||
|
"f6": "debugger::Pause",
|
||||||
|
"f7": "debugger::StepOver",
|
||||||
|
"cmd-f11": "debugger::StepInto",
|
||||||
|
"shift-f11": "debugger::StepOut",
|
||||||
"f11": "zed::ToggleFullScreen",
|
"f11": "zed::ToggleFullScreen",
|
||||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
||||||
"ctrl-shift-i": "edit_prediction::ToggleMenu"
|
"ctrl-shift-i": "edit_prediction::ToggleMenu"
|
||||||
@@ -46,7 +53,9 @@
|
|||||||
"context": "Prompt",
|
"context": "Prompt",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"left": "menu::SelectPrevious",
|
"left": "menu::SelectPrevious",
|
||||||
"right": "menu::SelectNext"
|
"right": "menu::SelectNext",
|
||||||
|
"h": "menu::SelectPrevious",
|
||||||
|
"l": "menu::SelectNext"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -107,6 +116,7 @@
|
|||||||
"ctrl-a": "editor::SelectAll",
|
"ctrl-a": "editor::SelectAll",
|
||||||
"ctrl-l": "editor::SelectLine",
|
"ctrl-l": "editor::SelectLine",
|
||||||
"ctrl-shift-i": "editor::Format",
|
"ctrl-shift-i": "editor::Format",
|
||||||
|
"alt-shift-o": "editor::OrganizeImports",
|
||||||
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }],
|
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||||
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||||
@@ -116,14 +126,31 @@
|
|||||||
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
|
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||||
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
||||||
"ctrl-;": "editor::ToggleLineNumbers",
|
"ctrl-;": "editor::ToggleLineNumbers",
|
||||||
"ctrl-k ctrl-r": "git::Restore",
|
|
||||||
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
||||||
"ctrl-\"": "editor::ExpandAllDiffHunks",
|
"ctrl-\"": "editor::ExpandAllDiffHunks",
|
||||||
"ctrl-i": "editor::ShowSignatureHelp",
|
"ctrl-i": "editor::ShowSignatureHelp",
|
||||||
"alt-g b": "editor::ToggleGitBlame",
|
"alt-g b": "editor::ToggleGitBlame",
|
||||||
"menu": "editor::OpenContextMenu",
|
"menu": "editor::OpenContextMenu",
|
||||||
"shift-f10": "editor::OpenContextMenu",
|
"shift-f10": "editor::OpenContextMenu",
|
||||||
"ctrl-shift-e": "editor::ToggleEditPrediction"
|
"ctrl-shift-e": "editor::ToggleEditPrediction",
|
||||||
|
"f9": "editor::ToggleBreakpoint",
|
||||||
|
"shift-f9": "editor::EditLogBreakpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && !assistant_diff",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-k ctrl-r": "git::Restore",
|
||||||
|
"ctrl-alt-y": "git::ToggleStaged",
|
||||||
|
"alt-y": "git::StageAndNext",
|
||||||
|
"alt-shift-y": "git::UnstageAndNext"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AssistantDiff",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-y": "assistant2::ToggleKeep",
|
||||||
|
"ctrl-k ctrl-r": "assistant2::Reject"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -183,7 +210,7 @@
|
|||||||
"ctrl-shift-g": "search::SelectPreviousMatch",
|
"ctrl-shift-g": "search::SelectPreviousMatch",
|
||||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||||
"ctrl-k h": "assistant::DeployHistory",
|
"ctrl-k h": "assistant::DeployHistory",
|
||||||
"ctrl-k l": "assistant::DeployPromptLibrary",
|
"ctrl-k l": "assistant::OpenPromptLibrary",
|
||||||
"new": "assistant::NewChat",
|
"new": "assistant::NewChat",
|
||||||
"ctrl-t": "assistant::NewChat",
|
"ctrl-t": "assistant::NewChat",
|
||||||
"ctrl-n": "assistant::NewChat"
|
"ctrl-n": "assistant::NewChat"
|
||||||
@@ -362,6 +389,7 @@
|
|||||||
"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-.": "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",
|
||||||
@@ -369,9 +397,6 @@
|
|||||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||||
"ctrl-shift-v": "markdown::OpenPreview",
|
"ctrl-shift-v": "markdown::OpenPreview",
|
||||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||||
"ctrl-alt-y": "git::ToggleStaged",
|
|
||||||
"alt-y": "git::StageAndNext",
|
|
||||||
"alt-shift-y": "git::UnstageAndNext",
|
|
||||||
"alt-.": "editor::GoToHunk",
|
"alt-.": "editor::GoToHunk",
|
||||||
"alt-,": "editor::GoToPreviousHunk"
|
"alt-,": "editor::GoToPreviousHunk"
|
||||||
}
|
}
|
||||||
@@ -393,6 +418,7 @@
|
|||||||
"alt-shift-open": "projects::OpenRemote",
|
"alt-shift-open": "projects::OpenRemote",
|
||||||
"alt-ctrl-shift-o": "projects::OpenRemote",
|
"alt-ctrl-shift-o": "projects::OpenRemote",
|
||||||
"alt-ctrl-shift-b": "branches::OpenRecent",
|
"alt-ctrl-shift-b": "branches::OpenRecent",
|
||||||
|
"alt-shift-enter": "toast::RunAction",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"save": "workspace::Save",
|
"save": "workspace::Save",
|
||||||
"ctrl-s": "workspace::Save",
|
"ctrl-s": "workspace::Save",
|
||||||
@@ -475,9 +501,7 @@
|
|||||||
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
|
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
|
||||||
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
|
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
|
||||||
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
|
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
|
||||||
// "ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
|
|
||||||
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
|
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
|
||||||
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
|
|
||||||
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
||||||
"ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart",
|
"ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart",
|
||||||
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
|
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||||
@@ -605,7 +629,10 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-n": "assistant2::NewThread",
|
"ctrl-n": "assistant2::NewThread",
|
||||||
"new": "assistant2::NewThread",
|
"new": "assistant2::NewThread",
|
||||||
|
"ctrl-alt-n": "assistant2::NewPromptEditor",
|
||||||
"ctrl-shift-h": "assistant2::OpenHistory",
|
"ctrl-shift-h": "assistant2::OpenHistory",
|
||||||
|
"ctrl-alt-c": "assistant2::OpenConfiguration",
|
||||||
|
"ctrl-i": "assistant2::ToggleProfileSelector",
|
||||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||||
"ctrl-shift-a": "assistant2::ToggleContextPicker",
|
"ctrl-shift-a": "assistant2::ToggleContextPicker",
|
||||||
"ctrl-e": "assistant2::ChatMode",
|
"ctrl-e": "assistant2::ChatMode",
|
||||||
@@ -623,7 +650,9 @@
|
|||||||
{
|
{
|
||||||
"context": "MessageEditor > Editor",
|
"context": "MessageEditor > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "assistant2::Chat"
|
"enter": "assistant2::Chat",
|
||||||
|
"ctrl-i": "assistant2::ToggleProfileSelector",
|
||||||
|
"shift-ctrl-r": "assistant2::OpenAssistantDiff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -733,28 +762,52 @@
|
|||||||
"up": "menu::SelectPrevious",
|
"up": "menu::SelectPrevious",
|
||||||
"down": "menu::SelectNext",
|
"down": "menu::SelectNext",
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
|
"alt-y": "git::StageFile",
|
||||||
|
"alt-shift-y": "git::UnstageFile",
|
||||||
|
"ctrl-alt-y": "git::ToggleStaged",
|
||||||
"space": "git::ToggleStaged",
|
"space": "git::ToggleStaged",
|
||||||
"ctrl-space": "git::StageAll",
|
|
||||||
"ctrl-shift-space": "git::UnstageAll",
|
|
||||||
"tab": "git_panel::FocusEditor",
|
"tab": "git_panel::FocusEditor",
|
||||||
"shift-tab": "git_panel::FocusEditor",
|
"shift-tab": "git_panel::FocusEditor",
|
||||||
"escape": "git_panel::ToggleFocus",
|
"escape": "git_panel::ToggleFocus",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
"alt-enter": "menu::SecondaryConfirm"
|
"alt-enter": "menu::SecondaryConfirm",
|
||||||
|
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
|
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
|
"shift-delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
|
"ctrl-backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
|
"ctrl-delete": ["git::RestoreFile", { "skip_prompt": false }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitCommit > Editor",
|
"context": "GitCommit > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"escape": "menu::Cancel",
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitPanel",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-g ctrl-g": "git::Fetch",
|
||||||
|
"ctrl-g up": "git::Push",
|
||||||
|
"ctrl-g down": "git::Pull",
|
||||||
|
"ctrl-g shift-up": "git::ForcePush",
|
||||||
|
"ctrl-g d": "git::Diff",
|
||||||
|
"ctrl-g backspace": "git::RestoreTrackedFiles",
|
||||||
|
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
|
||||||
|
"ctrl-space": "git::StageAll",
|
||||||
|
"ctrl-shift-space": "git::UnstageAll"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitDiff > Editor",
|
"context": "GitDiff > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "git::Commit"
|
"ctrl-enter": "git::Commit",
|
||||||
|
"ctrl-space": "git::StageAll",
|
||||||
|
"ctrl-shift-space": "git::UnstageAll"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -769,6 +822,7 @@
|
|||||||
"escape": "git_panel::FocusChanges",
|
"escape": "git_panel::FocusChanges",
|
||||||
"tab": "git_panel::FocusChanges",
|
"tab": "git_panel::FocusChanges",
|
||||||
"shift-tab": "git_panel::FocusChanges",
|
"shift-tab": "git_panel::FocusChanges",
|
||||||
|
"enter": "editor::Newline",
|
||||||
"ctrl-enter": "git::Commit",
|
"ctrl-enter": "git::Commit",
|
||||||
"alt-up": "git_panel::FocusChanges",
|
"alt-up": "git_panel::FocusChanges",
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
@@ -842,21 +896,22 @@
|
|||||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||||
// Overrides for conflicting keybindings
|
// Overrides for conflicting keybindings
|
||||||
|
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
||||||
|
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||||
|
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
||||||
|
"ctrl-o": ["terminal::SendKeystroke", "ctrl-o"],
|
||||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||||
"ctrl-shift-a": "editor::SelectAll",
|
"ctrl-shift-a": "editor::SelectAll",
|
||||||
"find": "buffer_search::Deploy",
|
"find": "buffer_search::Deploy",
|
||||||
"ctrl-shift-f": "buffer_search::Deploy",
|
"ctrl-shift-f": "buffer_search::Deploy",
|
||||||
"ctrl-shift-l": "terminal::Clear",
|
"ctrl-shift-l": "terminal::Clear",
|
||||||
"ctrl-shift-w": "pane::CloseActiveItem",
|
"ctrl-shift-w": "pane::CloseActiveItem",
|
||||||
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
|
||||||
"up": ["terminal::SendKeystroke", "up"],
|
"up": ["terminal::SendKeystroke", "up"],
|
||||||
"pageup": ["terminal::SendKeystroke", "pageup"],
|
"pageup": ["terminal::SendKeystroke", "pageup"],
|
||||||
"down": ["terminal::SendKeystroke", "down"],
|
"down": ["terminal::SendKeystroke", "down"],
|
||||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||||
"escape": ["terminal::SendKeystroke", "escape"],
|
"escape": ["terminal::SendKeystroke", "escape"],
|
||||||
"enter": ["terminal::SendKeystroke", "enter"],
|
"enter": ["terminal::SendKeystroke", "enter"],
|
||||||
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
|
||||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
|
||||||
"shift-pageup": "terminal::ScrollPageUp",
|
"shift-pageup": "terminal::ScrollPageUp",
|
||||||
"shift-pagedown": "terminal::ScrollPageDown",
|
"shift-pagedown": "terminal::ScrollPageDown",
|
||||||
"shift-up": "terminal::ScrollLineUp",
|
"shift-up": "terminal::ScrollLineUp",
|
||||||
|
|||||||
@@ -14,6 +14,13 @@
|
|||||||
{
|
{
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"f4": "debugger::Start",
|
||||||
|
"f5": "debugger::Continue",
|
||||||
|
"shift-f5": "debugger::Stop",
|
||||||
|
"f6": "debugger::Pause",
|
||||||
|
"f7": "debugger::StepOver",
|
||||||
|
"f11": "debugger::StepInto",
|
||||||
|
"shift-f11": "debugger::StepOut",
|
||||||
"home": "menu::SelectFirst",
|
"home": "menu::SelectFirst",
|
||||||
"shift-pageup": "menu::SelectFirst",
|
"shift-pageup": "menu::SelectFirst",
|
||||||
"pageup": "menu::SelectFirst",
|
"pageup": "menu::SelectFirst",
|
||||||
@@ -31,13 +38,13 @@
|
|||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
"ctrl-enter": "menu::SecondaryConfirm",
|
"ctrl-enter": "menu::SecondaryConfirm",
|
||||||
"cmd-enter": "menu::SecondaryConfirm",
|
"cmd-enter": "menu::SecondaryConfirm",
|
||||||
|
"cmd-escape": "menu::Cancel",
|
||||||
"ctrl-escape": "menu::Cancel",
|
"ctrl-escape": "menu::Cancel",
|
||||||
"ctrl-c": "menu::Cancel",
|
"ctrl-c": "menu::Cancel",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"alt-shift-enter": "menu::Restart",
|
"alt-shift-enter": "menu::Restart",
|
||||||
"cmd-shift-w": "workspace::CloseWindow",
|
"cmd-shift-w": "workspace::CloseWindow",
|
||||||
"shift-escape": "workspace::ToggleZoom",
|
"shift-escape": "workspace::ToggleZoom",
|
||||||
"cmd-escape": "menu::Cancel",
|
|
||||||
"cmd-o": "workspace::Open",
|
"cmd-o": "workspace::Open",
|
||||||
"cmd-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
"cmd-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||||
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||||
@@ -108,8 +115,8 @@
|
|||||||
"cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
"cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||||
"cmd-up": "editor::MoveToStartOfExcerpt",
|
"cmd-up": "editor::MoveToBeginning",
|
||||||
"cmd-down": "editor::MoveToEndOfExcerpt",
|
"cmd-down": "editor::MoveToEnd",
|
||||||
"cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left`
|
"cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left`
|
||||||
"cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right`
|
"cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right`
|
||||||
"shift-up": "editor::SelectUp",
|
"shift-up": "editor::SelectUp",
|
||||||
@@ -124,8 +131,8 @@
|
|||||||
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
||||||
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||||
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||||
"cmd-shift-up": "editor::SelectToStartOfExcerpt",
|
"cmd-shift-up": "editor::SelectToBeginning",
|
||||||
"cmd-shift-down": "editor::SelectToEndOfExcerpt",
|
"cmd-shift-down": "editor::SelectToEnd",
|
||||||
"cmd-a": "editor::SelectAll",
|
"cmd-a": "editor::SelectAll",
|
||||||
"cmd-l": "editor::SelectLine",
|
"cmd-l": "editor::SelectLine",
|
||||||
"cmd-shift-i": "editor::Format",
|
"cmd-shift-i": "editor::Format",
|
||||||
@@ -140,14 +147,12 @@
|
|||||||
"ctrl-shift-v": ["editor::MovePageUp", { "center_cursor": true }],
|
"ctrl-shift-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||||
"ctrl-cmd-space": "editor::ShowCharacterPalette",
|
"ctrl-cmd-space": "editor::ShowCharacterPalette",
|
||||||
"cmd-;": "editor::ToggleLineNumbers",
|
"cmd-;": "editor::ToggleLineNumbers",
|
||||||
"cmd-alt-z": "git::Restore",
|
|
||||||
"cmd-alt-y": "git::ToggleStaged",
|
|
||||||
"cmd-y": "git::StageAndNext",
|
|
||||||
"cmd-shift-y": "git::UnstageAndNext",
|
|
||||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||||
"cmd-i": "editor::ShowSignatureHelp",
|
"cmd-i": "editor::ShowSignatureHelp",
|
||||||
|
"f9": "editor::ToggleBreakpoint",
|
||||||
|
"shift-f9": "editor::EditLogBreakpoint",
|
||||||
"ctrl-f12": "editor::GoToDeclaration",
|
"ctrl-f12": "editor::GoToDeclaration",
|
||||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
|
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
|
||||||
"ctrl-cmd-e": "editor::ToggleEditPrediction"
|
"ctrl-cmd-e": "editor::ToggleEditPrediction"
|
||||||
@@ -172,6 +177,16 @@
|
|||||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && multibuffer",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-up": "editor::MoveToStartOfExcerpt",
|
||||||
|
"cmd-down": "editor::MoveToStartOfNextExcerpt",
|
||||||
|
"cmd-shift-up": "editor::SelectToStartOfExcerpt",
|
||||||
|
"cmd-shift-down": "editor::SelectToStartOfNextExcerpt"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full && edit_prediction",
|
"context": "Editor && mode == full && edit_prediction",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -212,6 +227,24 @@
|
|||||||
"ctrl-alt-enter": "repl::RunInPlace"
|
"ctrl-alt-enter": "repl::RunInPlace"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && !assistant_diff",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-alt-z": "git::Restore",
|
||||||
|
"cmd-alt-y": "git::ToggleStaged",
|
||||||
|
"cmd-y": "git::StageAndNext",
|
||||||
|
"cmd-shift-y": "git::UnstageAndNext"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "AssistantDiff",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"cmd-y": "assistant2::ToggleKeep",
|
||||||
|
"cmd-alt-z": "assistant2::Reject"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "AssistantPanel",
|
"context": "AssistantPanel",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -222,7 +255,7 @@
|
|||||||
"cmd-shift-g": "search::SelectPreviousMatch",
|
"cmd-shift-g": "search::SelectPreviousMatch",
|
||||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||||
"cmd-k h": "assistant::DeployHistory",
|
"cmd-k h": "assistant::DeployHistory",
|
||||||
"cmd-k l": "assistant::DeployPromptLibrary",
|
"cmd-k l": "assistant::OpenPromptLibrary",
|
||||||
"cmd-t": "assistant::NewChat",
|
"cmd-t": "assistant::NewChat",
|
||||||
"cmd-n": "assistant::NewChat"
|
"cmd-n": "assistant::NewChat"
|
||||||
}
|
}
|
||||||
@@ -248,8 +281,10 @@
|
|||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-n": "assistant2::NewThread",
|
"cmd-n": "assistant2::NewThread",
|
||||||
"cmd-alt-p": "assistant2::NewPromptEditor",
|
"cmd-alt-n": "assistant2::NewPromptEditor",
|
||||||
"cmd-shift-h": "assistant2::OpenHistory",
|
"cmd-shift-h": "assistant2::OpenHistory",
|
||||||
|
"cmd-alt-c": "assistant2::OpenConfiguration",
|
||||||
|
"cmd-i": "assistant2::ToggleProfileSelector",
|
||||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||||
"cmd-e": "assistant2::ChatMode",
|
"cmd-e": "assistant2::ChatMode",
|
||||||
@@ -268,7 +303,9 @@
|
|||||||
"context": "MessageEditor > Editor",
|
"context": "MessageEditor > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "assistant2::Chat"
|
"enter": "assistant2::Chat",
|
||||||
|
"cmd-i": "assistant2::ToggleProfileSelector",
|
||||||
|
"shift-ctrl-r": "assistant2::OpenAssistantDiff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -456,6 +493,7 @@
|
|||||||
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
|
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
|
||||||
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
|
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||||
"cmd-.": "editor::ToggleCodeActions",
|
"cmd-.": "editor::ToggleCodeActions",
|
||||||
"cmd-k r": "editor::RevealInFileManager",
|
"cmd-k r": "editor::RevealInFileManager",
|
||||||
"cmd-k p": "editor::CopyPath",
|
"cmd-k p": "editor::CopyPath",
|
||||||
@@ -504,6 +542,7 @@
|
|||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
"cmd-k s": "workspace::SaveWithoutFormat",
|
"cmd-k s": "workspace::SaveWithoutFormat",
|
||||||
|
"alt-shift-enter": "toast::RunAction",
|
||||||
"cmd-shift-s": "workspace::SaveAs",
|
"cmd-shift-s": "workspace::SaveAs",
|
||||||
"cmd-shift-n": "workspace::NewWindow",
|
"cmd-shift-n": "workspace::NewWindow",
|
||||||
"ctrl-`": "terminal_panel::ToggleFocus",
|
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||||
@@ -684,6 +723,16 @@
|
|||||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Prompt",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"left": "menu::SelectPrevious",
|
||||||
|
"right": "menu::SelectNext",
|
||||||
|
"h": "menu::SelectPrevious",
|
||||||
|
"l": "menu::SelectNext"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar && !in_replace",
|
"context": "ProjectSearchBar && !in_replace",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -744,6 +793,14 @@
|
|||||||
"space": "project_panel::Open"
|
"space": "project_panel::Open"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "VariableList",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"left": "variable_list::CollapseSelectedEntry",
|
||||||
|
"right": "variable_list::ExpandSelectedEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitPanel && ChangesList",
|
"context": "GitPanel && ChangesList",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
@@ -753,28 +810,28 @@
|
|||||||
"cmd-up": "menu::SelectFirst",
|
"cmd-up": "menu::SelectFirst",
|
||||||
"cmd-down": "menu::SelectLast",
|
"cmd-down": "menu::SelectLast",
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
|
"cmd-alt-y": "git::ToggleStaged",
|
||||||
"space": "git::ToggleStaged",
|
"space": "git::ToggleStaged",
|
||||||
"cmd-shift-space": "git::StageAll",
|
"cmd-y": "git::StageFile",
|
||||||
"ctrl-shift-space": "git::UnstageAll",
|
"cmd-shift-y": "git::UnstageFile",
|
||||||
"alt-down": "git_panel::FocusEditor",
|
"alt-down": "git_panel::FocusEditor",
|
||||||
"tab": "git_panel::FocusEditor",
|
"tab": "git_panel::FocusEditor",
|
||||||
"shift-tab": "git_panel::FocusEditor",
|
"shift-tab": "git_panel::FocusEditor",
|
||||||
"escape": "git_panel::ToggleFocus",
|
"escape": "git_panel::ToggleFocus",
|
||||||
"cmd-enter": "git::Commit"
|
"cmd-enter": "git::Commit",
|
||||||
|
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
|
"delete": ["git::RestoreFile", { "skip_prompt": false }],
|
||||||
|
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
|
||||||
|
"cmd-delete": ["git::RestoreFile", { "skip_prompt": true }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitDiff > Editor",
|
"context": "GitDiff > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "git::Commit"
|
"cmd-enter": "git::Commit",
|
||||||
}
|
"cmd-ctrl-y": "git::StageAll",
|
||||||
},
|
"cmd-ctrl-shift-y": "git::UnstageAll"
|
||||||
{
|
|
||||||
"context": "AskPass > Editor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"enter": "menu::Confirm"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -790,11 +847,27 @@
|
|||||||
"alt-tab": "git::GenerateCommitMessage"
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "GitPanel",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-g ctrl-g": "git::Fetch",
|
||||||
|
"ctrl-g up": "git::Push",
|
||||||
|
"ctrl-g down": "git::Pull",
|
||||||
|
"ctrl-g shift-up": "git::ForcePush",
|
||||||
|
"ctrl-g d": "git::Diff",
|
||||||
|
"ctrl-g backspace": "git::RestoreTrackedFiles",
|
||||||
|
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
|
||||||
|
"cmd-ctrl-y": "git::StageAll",
|
||||||
|
"cmd-ctrl-shift-y": "git::UnstageAll"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "GitCommit > Editor",
|
"context": "GitCommit > Editor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
|
"escape": "menu::Cancel",
|
||||||
"cmd-enter": "git::Commit",
|
"cmd-enter": "git::Commit",
|
||||||
"alt-tab": "git::GenerateCommitMessage"
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,14 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-s": "zed::OpenSettings",
|
"ctrl-alt-s": "zed::OpenSettings",
|
||||||
"ctrl-{": "pane::ActivatePreviousItem",
|
"ctrl-{": "pane::ActivatePreviousItem",
|
||||||
"ctrl-}": "pane::ActivateNextItem"
|
"ctrl-}": "pane::ActivateNextItem",
|
||||||
|
"ctrl-f2": "debugger::Stop",
|
||||||
|
"f6": "debugger::Pause",
|
||||||
|
"f7": "debugger::StepInto",
|
||||||
|
"f8": "debugger::StepOver",
|
||||||
|
"shift-f8": "debugger::StepOut",
|
||||||
|
"f9": "debugger::Continue",
|
||||||
|
"alt-shift-f9": "debugger::Start"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,6 +38,7 @@
|
|||||||
"shift-alt-up": "editor::MoveLineUp",
|
"shift-alt-up": "editor::MoveLineUp",
|
||||||
"shift-alt-down": "editor::MoveLineDown",
|
"shift-alt-down": "editor::MoveLineDown",
|
||||||
"ctrl-alt-l": "editor::Format",
|
"ctrl-alt-l": "editor::Format",
|
||||||
|
"ctrl-alt-o": "editor::OrganizeImports",
|
||||||
"shift-f6": "editor::Rename",
|
"shift-f6": "editor::Rename",
|
||||||
"ctrl-alt-left": "pane::GoBack",
|
"ctrl-alt-left": "pane::GoBack",
|
||||||
"ctrl-alt-right": "pane::GoForward",
|
"ctrl-alt-right": "pane::GoForward",
|
||||||
@@ -48,7 +56,9 @@
|
|||||||
"ctrl-home": "editor::MoveToBeginning",
|
"ctrl-home": "editor::MoveToBeginning",
|
||||||
"ctrl-end": "editor::MoveToEnd",
|
"ctrl-end": "editor::MoveToEnd",
|
||||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||||
"ctrl-shift-end": "editor::SelectToEnd"
|
"ctrl-shift-end": "editor::SelectToEnd",
|
||||||
|
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||||
|
"ctrl-shift-f8": "editor::EditLogBreakpoint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,14 @@
|
|||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-{": "pane::ActivatePreviousItem",
|
"cmd-{": "pane::ActivatePreviousItem",
|
||||||
"cmd-}": "pane::ActivateNextItem"
|
"cmd-}": "pane::ActivateNextItem",
|
||||||
|
"ctrl-f2": "debugger::Stop",
|
||||||
|
"f6": "debugger::Pause",
|
||||||
|
"f7": "debugger::StepInto",
|
||||||
|
"f8": "debugger::StepOver",
|
||||||
|
"shift-f8": "debugger::StepOut",
|
||||||
|
"f9": "debugger::Continue",
|
||||||
|
"alt-shift-f9": "debugger::Start"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -29,6 +36,7 @@
|
|||||||
"shift-alt-up": "editor::MoveLineUp",
|
"shift-alt-up": "editor::MoveLineUp",
|
||||||
"shift-alt-down": "editor::MoveLineDown",
|
"shift-alt-down": "editor::MoveLineDown",
|
||||||
"cmd-alt-l": "editor::Format",
|
"cmd-alt-l": "editor::Format",
|
||||||
|
"ctrl-alt-o": "editor::OrganizeImports",
|
||||||
"shift-f6": "editor::Rename",
|
"shift-f6": "editor::Rename",
|
||||||
"cmd-[": "pane::GoBack",
|
"cmd-[": "pane::GoBack",
|
||||||
"cmd-]": "pane::GoForward",
|
"cmd-]": "pane::GoForward",
|
||||||
@@ -45,7 +53,9 @@
|
|||||||
"cmd-home": "editor::MoveToBeginning",
|
"cmd-home": "editor::MoveToBeginning",
|
||||||
"cmd-end": "editor::MoveToEnd",
|
"cmd-end": "editor::MoveToEnd",
|
||||||
"cmd-shift-home": "editor::SelectToBeginning",
|
"cmd-shift-home": "editor::SelectToBeginning",
|
||||||
"cmd-shift-end": "editor::SelectToEnd"
|
"cmd-shift-end": "editor::SelectToEnd",
|
||||||
|
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||||
|
"ctrl-shift-f8": "editor::EditLogBreakpoint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -258,9 +258,10 @@
|
|||||||
"u": "vim::ConvertToLowerCase",
|
"u": "vim::ConvertToLowerCase",
|
||||||
"shift-u": "vim::ConvertToUpperCase",
|
"shift-u": "vim::ConvertToUpperCase",
|
||||||
"shift-o": "vim::OtherEnd",
|
"shift-o": "vim::OtherEnd",
|
||||||
"o": "vim::OtherEnd",
|
"o": "vim::OtherEndRowAware",
|
||||||
"d": "vim::VisualDelete",
|
"d": "vim::VisualDelete",
|
||||||
"x": "vim::VisualDelete",
|
"x": "vim::VisualDelete",
|
||||||
|
"delete": "vim::VisualDelete",
|
||||||
"shift-d": "vim::VisualDeleteLine",
|
"shift-d": "vim::VisualDeleteLine",
|
||||||
"shift-x": "vim::VisualDeleteLine",
|
"shift-x": "vim::VisualDeleteLine",
|
||||||
"y": "vim::VisualYank",
|
"y": "vim::VisualYank",
|
||||||
@@ -338,6 +339,10 @@
|
|||||||
"w": "vim::NextWordStart",
|
"w": "vim::NextWordStart",
|
||||||
"e": "vim::NextWordEnd",
|
"e": "vim::NextWordEnd",
|
||||||
"b": "vim::PreviousWordStart",
|
"b": "vim::PreviousWordStart",
|
||||||
|
"x": "vim::CurrentLine",
|
||||||
|
"X": "vim::CurrentLine",
|
||||||
|
"y": "vim::HelixYank",
|
||||||
|
"p": "vim::HelixPaste",
|
||||||
|
|
||||||
"h": "vim::Left",
|
"h": "vim::Left",
|
||||||
"j": "vim::Down",
|
"j": "vim::Down",
|
||||||
|
|||||||
34
assets/prompts/assistant_system_prompt.hbs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
You are an AI assistant integrated into a code editor. You have the programming ability of an expert programmer who takes pride in writing high-quality code and is driven to the point of obsession about solving problems effectively. Your goal is to do one of the following two things:
|
||||||
|
|
||||||
|
1. Help users answer questions and perform tasks related to their codebase.
|
||||||
|
2. Answer general-purpose questions unrelated to their particular codebase.
|
||||||
|
|
||||||
|
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
|
||||||
|
|
||||||
|
You should only perform actions that modify the user's system if explicitly requested by the user:
|
||||||
|
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user’s system without explicit instruction.
|
||||||
|
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
|
||||||
|
- The editing actions you perform might produce errors or warnings. At the end of your changes, check whether you introduced any problems, and fix them before providing a summary of the changes you made. You may only attempt to fix these up to 3 times. If you have tried 3 times to fix them, and there are still problems remaining, you must not continue trying to fix them, and must instead tell the user that there are problems remaining - and ask if the user would like you to attempt to solve them further.
|
||||||
|
- Do not fix errors unrelated to your changes unless the user explicitly asks you to do so.
|
||||||
|
|
||||||
|
Be concise and direct in your responses. Never apologize or thank the user. Don't comment that you have just realized or understood something. When you are going to make a tool call, tersely explain your reasoning for choosing to use that tool, with no flourishes or commentary beyond that information. For example, rather than saying "You're absolutely right! Thank you for providing that context. Now I understand that we're missing a dependency, and I need to add it:" say "I'll add that missing dependency:" instead. Also, don't restate what a tool call is about to do (or just did). For example, don't say "Now I'm going to check diagnostics to see if there are any warnings or errors," followed by running a tool which checks diagnostics and reports warnings or errors; instead, just request the tool call without saying anything.
|
||||||
|
|
||||||
|
The user has opened a project that contains the following root directories/files. Whenever you specify a path in the project, it must be a relative path which begins with one of these root directories/files:
|
||||||
|
|
||||||
|
{{#each worktrees}}
|
||||||
|
- `{{root_name}}` (absolute path: `{{abs_path}}`)
|
||||||
|
{{/each}}
|
||||||
|
{{#if has_rules}}
|
||||||
|
|
||||||
|
There are rules that apply to these root directories:
|
||||||
|
{{#each worktrees}}
|
||||||
|
{{#if rules_file}}
|
||||||
|
|
||||||
|
`{{root_name}}/{{rules_file.rel_path}}`:
|
||||||
|
|
||||||
|
``````
|
||||||
|
{{{rules_file.text}}}
|
||||||
|
``````
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
// Features that can be globally enabled or disabled
|
// Features that can be globally enabled or disabled
|
||||||
"features": {
|
"features": {
|
||||||
// Which edit prediction provider to use.
|
// Which edit prediction provider to use.
|
||||||
"edit_prediction_provider": "copilot"
|
"edit_prediction_provider": "zed"
|
||||||
},
|
},
|
||||||
// The name of a font to use for rendering text in the editor
|
// The name of a font to use for rendering text in the editor
|
||||||
"buffer_font_family": "Zed Plex Mono",
|
"buffer_font_family": "Zed Plex Mono",
|
||||||
@@ -115,6 +115,15 @@
|
|||||||
"confirm_quit": false,
|
"confirm_quit": false,
|
||||||
// Whether to restore last closed project when fresh Zed instance is opened.
|
// Whether to restore last closed project when fresh Zed instance is opened.
|
||||||
"restore_on_startup": "last_session",
|
"restore_on_startup": "last_session",
|
||||||
|
// Whether to attempt to restore previous file's state when opening it again.
|
||||||
|
// The state is stored per pane.
|
||||||
|
// When disabled, defaults are applied instead of the state restoration.
|
||||||
|
//
|
||||||
|
// E.g. for editors, selections, folds and scroll positions are restored, if the same file is closed and, later, opened again in the same pane.
|
||||||
|
// When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default.
|
||||||
|
//
|
||||||
|
// Default: true
|
||||||
|
"restore_on_file_reopen": true,
|
||||||
// Size of the drop target in the editor.
|
// Size of the drop target in the editor.
|
||||||
"drop_target_size": 0.2,
|
"drop_target_size": 0.2,
|
||||||
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
||||||
@@ -136,6 +145,11 @@
|
|||||||
// Whether to use the system provided dialogs for Open and Save As.
|
// Whether to use the system provided dialogs for Open and Save As.
|
||||||
// When set to false, Zed will use the built-in keyboard-first pickers.
|
// When set to false, Zed will use the built-in keyboard-first pickers.
|
||||||
"use_system_path_prompts": true,
|
"use_system_path_prompts": true,
|
||||||
|
// Whether to use the system provided dialogs for prompts, such as confirmation
|
||||||
|
// prompts.
|
||||||
|
// When set to false, Zed will use its built-in prompts. Note that on Linux,
|
||||||
|
// this option is ignored and Zed will always use the built-in prompts.
|
||||||
|
"use_system_prompts": true,
|
||||||
// Whether the cursor blinks in the editor.
|
// Whether the cursor blinks in the editor.
|
||||||
"cursor_blink": true,
|
"cursor_blink": true,
|
||||||
// Cursor shape for the default editor.
|
// Cursor shape for the default editor.
|
||||||
@@ -150,6 +164,8 @@
|
|||||||
//
|
//
|
||||||
// Default: not set, defaults to "bar"
|
// Default: not set, defaults to "bar"
|
||||||
"cursor_shape": null,
|
"cursor_shape": null,
|
||||||
|
// Determines when the mouse cursor should be hidden in an editor or input box.
|
||||||
|
"hide_mouse": "on_typing_and_movement",
|
||||||
// How to highlight the current line in the editor.
|
// How to highlight the current line in the editor.
|
||||||
//
|
//
|
||||||
// 1. Don't highlight the current line:
|
// 1. Don't highlight the current line:
|
||||||
@@ -179,6 +195,11 @@
|
|||||||
// Whether to show the signature help after completion or a bracket pair inserted.
|
// Whether to show the signature help after completion or a bracket pair inserted.
|
||||||
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||||
"show_signature_help_after_edits": false,
|
"show_signature_help_after_edits": false,
|
||||||
|
// What to do when go to definition yields no results.
|
||||||
|
//
|
||||||
|
// 1. Do nothing: `none`
|
||||||
|
// 2. Find references for the same symbol: `find_all_references` (default)
|
||||||
|
"go_to_definition_fallback": "find_all_references",
|
||||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||||
@@ -324,6 +345,8 @@
|
|||||||
"code_actions": true,
|
"code_actions": true,
|
||||||
// Whether to show runnables buttons in the gutter.
|
// Whether to show runnables buttons in the gutter.
|
||||||
"runnables": true,
|
"runnables": true,
|
||||||
|
// Whether to show breakpoints in the gutter.
|
||||||
|
"breakpoints": true,
|
||||||
// Whether to show fold buttons in the gutter.
|
// Whether to show fold buttons in the gutter.
|
||||||
"folds": true
|
"folds": true
|
||||||
},
|
},
|
||||||
@@ -336,14 +359,14 @@
|
|||||||
"active_line_width": 1,
|
"active_line_width": 1,
|
||||||
// Determines how indent guides are colored.
|
// Determines how indent guides are colored.
|
||||||
// This setting can take the following three values:
|
// This setting can take the following three values:
|
||||||
///
|
//
|
||||||
// 1. "disabled"
|
// 1. "disabled"
|
||||||
// 2. "fixed"
|
// 2. "fixed"
|
||||||
// 3. "indent_aware"
|
// 3. "indent_aware"
|
||||||
"coloring": "fixed",
|
"coloring": "fixed",
|
||||||
// Determines how indent guide backgrounds are colored.
|
// Determines how indent guide backgrounds are colored.
|
||||||
// This setting can take the following two values:
|
// This setting can take the following two values:
|
||||||
///
|
//
|
||||||
// 1. "disabled"
|
// 1. "disabled"
|
||||||
// 2. "indent_aware"
|
// 2. "indent_aware"
|
||||||
"background_coloring": "disabled"
|
"background_coloring": "disabled"
|
||||||
@@ -402,8 +425,8 @@
|
|||||||
// Time to wait after scrolling the buffer, before requesting the hints,
|
// Time to wait after scrolling the buffer, before requesting the hints,
|
||||||
// set to 0 to disable debouncing.
|
// set to 0 to disable debouncing.
|
||||||
"scroll_debounce_ms": 50,
|
"scroll_debounce_ms": 50,
|
||||||
/// A set of modifiers which, when pressed, will toggle the visibility of inlay hints.
|
// A set of modifiers which, when pressed, will toggle the visibility of inlay hints.
|
||||||
/// If the set if empty or not all the modifiers specified are pressed, inlay hints will not be toggled.
|
// If the set if empty or not all the modifiers specified are pressed, inlay hints will not be toggled.
|
||||||
"toggle_on_modifiers_press": {
|
"toggle_on_modifiers_press": {
|
||||||
"control": false,
|
"control": false,
|
||||||
"shift": false,
|
"shift": false,
|
||||||
@@ -415,6 +438,8 @@
|
|||||||
"project_panel": {
|
"project_panel": {
|
||||||
// Whether to show the project panel button in the status bar
|
// Whether to show the project panel button in the status bar
|
||||||
"button": true,
|
"button": true,
|
||||||
|
// Whether to hide the gitignore entries in the project panel.
|
||||||
|
"hide_gitignore": false,
|
||||||
// Default width of the project panel.
|
// Default width of the project panel.
|
||||||
"default_width": 240,
|
"default_width": 240,
|
||||||
// Where to dock the project panel. Can be 'left' or 'right'.
|
// Where to dock the project panel. Can be 'left' or 'right'.
|
||||||
@@ -440,7 +465,7 @@
|
|||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the project panel.
|
// When to show the scrollbar in the project panel.
|
||||||
// This setting can take five values:
|
// This setting can take five values:
|
||||||
///
|
//
|
||||||
// 1. null (default): Inherit editor settings
|
// 1. null (default): Inherit editor settings
|
||||||
// 2. Show the scrollbar if there's important information or
|
// 2. Show the scrollbar if there's important information or
|
||||||
// follow the system's configured behavior (default):
|
// follow the system's configured behavior (default):
|
||||||
@@ -455,7 +480,7 @@
|
|||||||
},
|
},
|
||||||
// Which files containing diagnostic errors/warnings to mark in the project panel.
|
// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||||
// This setting can take the following three values:
|
// This setting can take the following three values:
|
||||||
///
|
//
|
||||||
// 1. Do not mark any files:
|
// 1. Do not mark any files:
|
||||||
// "off"
|
// "off"
|
||||||
// 2. Only mark files with errors:
|
// 2. Only mark files with errors:
|
||||||
@@ -512,7 +537,7 @@
|
|||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the project panel.
|
// When to show the scrollbar in the project panel.
|
||||||
// This setting can take five values:
|
// This setting can take five values:
|
||||||
///
|
//
|
||||||
// 1. null (default): Inherit editor settings
|
// 1. null (default): Inherit editor settings
|
||||||
// 2. Show the scrollbar if there's important information or
|
// 2. Show the scrollbar if there's important information or
|
||||||
// follow the system's configured behavior (default):
|
// follow the system's configured behavior (default):
|
||||||
@@ -547,7 +572,7 @@
|
|||||||
"git_panel": {
|
"git_panel": {
|
||||||
// Whether to show the git panel button in the status bar.
|
// Whether to show the git panel button in the status bar.
|
||||||
"button": true,
|
"button": true,
|
||||||
// Where to the git panel. Can be 'left' or 'right'.
|
// Where to show the git panel. Can be 'left' or 'right'.
|
||||||
"dock": "left",
|
"dock": "left",
|
||||||
// Default width of the git panel.
|
// Default width of the git panel.
|
||||||
"default_width": 360,
|
"default_width": 360,
|
||||||
@@ -555,6 +580,12 @@
|
|||||||
//
|
//
|
||||||
// Default: icon
|
// Default: icon
|
||||||
"status_style": "icon",
|
"status_style": "icon",
|
||||||
|
// What branch name to use if init.defaultBranch
|
||||||
|
// is not set
|
||||||
|
//
|
||||||
|
// Default: main
|
||||||
|
"fallback_branch_name": "main",
|
||||||
|
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the git panel.
|
// When to show the scrollbar in the git panel.
|
||||||
//
|
//
|
||||||
@@ -594,7 +625,60 @@
|
|||||||
"provider": "zed.dev",
|
"provider": "zed.dev",
|
||||||
// The model to use.
|
// The model to use.
|
||||||
"model": "claude-3-5-sonnet-latest"
|
"model": "claude-3-5-sonnet-latest"
|
||||||
}
|
},
|
||||||
|
// The model to use when applying edits from the assistant.
|
||||||
|
"editor_model": {
|
||||||
|
// The provider to use.
|
||||||
|
"provider": "zed.dev",
|
||||||
|
// The model to use.
|
||||||
|
"model": "claude-3-5-sonnet-latest"
|
||||||
|
},
|
||||||
|
"default_profile": "write",
|
||||||
|
"profiles": {
|
||||||
|
"ask": {
|
||||||
|
"name": "Ask",
|
||||||
|
"tools": {
|
||||||
|
"diagnostics": true,
|
||||||
|
"fetch": true,
|
||||||
|
"list-directory": true,
|
||||||
|
"now": true,
|
||||||
|
"path-search": true,
|
||||||
|
"read-file": true,
|
||||||
|
"regex-search": true,
|
||||||
|
"thinking": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"name": "Write",
|
||||||
|
"tools": {
|
||||||
|
"bash": true,
|
||||||
|
"batch-tool": true,
|
||||||
|
"code-symbols": true,
|
||||||
|
"copy-path": true,
|
||||||
|
"create-file": true,
|
||||||
|
"delete-path": true,
|
||||||
|
"diagnostics": true,
|
||||||
|
"find-replace-file": true,
|
||||||
|
"edit-files": false,
|
||||||
|
"fetch": true,
|
||||||
|
"list-directory": true,
|
||||||
|
"move-path": true,
|
||||||
|
"now": true,
|
||||||
|
"path-search": true,
|
||||||
|
"read-file": true,
|
||||||
|
"regex-search": true,
|
||||||
|
"symbol-info": true,
|
||||||
|
"thinking": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Where to show notifications when an agent has either completed
|
||||||
|
// its response, or else needs confirmation before it can run a
|
||||||
|
// tool action.
|
||||||
|
// "primary_screen" - Show the notification only on your primary screen (default)
|
||||||
|
// "all_screens" - Show these notifications on all screens
|
||||||
|
// "never" - Never show these notifications
|
||||||
|
"notify_when_agent_waiting": "primary_screen"
|
||||||
},
|
},
|
||||||
// The settings for slash commands.
|
// The settings for slash commands.
|
||||||
"slash_commands": {
|
"slash_commands": {
|
||||||
@@ -673,7 +757,7 @@
|
|||||||
// Which files containing diagnostic errors/warnings to mark in the tabs.
|
// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||||
// Diagnostics are only shown when file icons are also active.
|
// Diagnostics are only shown when file icons are also active.
|
||||||
// This setting only works when can take the following three values:
|
// This setting only works when can take the following three values:
|
||||||
///
|
//
|
||||||
// 1. Do not mark any files:
|
// 1. Do not mark any files:
|
||||||
// "off"
|
// "off"
|
||||||
// 2. Only mark files with errors:
|
// 2. Only mark files with errors:
|
||||||
@@ -841,12 +925,20 @@
|
|||||||
// How git hunks are displayed visually in the editor.
|
// How git hunks are displayed visually in the editor.
|
||||||
// This setting can take two values:
|
// This setting can take two values:
|
||||||
//
|
//
|
||||||
// 1. Show unstaged hunks with a transparent background (default):
|
// 1. Show unstaged hunks filled and staged hunks hollow:
|
||||||
// "hunk_style": "transparent"
|
// "hunk_style": "staged_hollow"
|
||||||
// 2. Show unstaged hunks with a pattern background:
|
// 2. Show unstaged hunks hollow and staged hunks filled:
|
||||||
// "hunk_style": "pattern"
|
// "hunk_style": "unstaged_hollow"
|
||||||
"hunk_style": "transparent"
|
"hunk_style": "staged_hollow"
|
||||||
},
|
},
|
||||||
|
// The list of custom Git hosting providers.
|
||||||
|
"git_hosting_providers": [
|
||||||
|
// {
|
||||||
|
// "provider": "github",
|
||||||
|
// "name": "BigCorp GitHub",
|
||||||
|
// "base_url": "https://code.big-corp.com"
|
||||||
|
// }
|
||||||
|
],
|
||||||
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
||||||
// 1. Load direnv configuration using `direnv export json` directly.
|
// 1. Load direnv configuration using `direnv export json` directly.
|
||||||
// "load_direnv": "direct"
|
// "load_direnv": "direct"
|
||||||
@@ -953,7 +1045,7 @@
|
|||||||
// "alternate_scroll": "on",
|
// "alternate_scroll": "on",
|
||||||
// 2. Default alternate scroll mode to off
|
// 2. Default alternate scroll mode to off
|
||||||
// "alternate_scroll": "off",
|
// "alternate_scroll": "off",
|
||||||
"alternate_scroll": "off",
|
"alternate_scroll": "on",
|
||||||
// Set whether the option key behaves as the meta key.
|
// Set whether the option key behaves as the meta key.
|
||||||
// May take 2 values:
|
// May take 2 values:
|
||||||
// 1. Rely on default platform handling of option key, on macOS
|
// 1. Rely on default platform handling of option key, on macOS
|
||||||
@@ -1009,7 +1101,7 @@
|
|||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the terminal.
|
// When to show the scrollbar in the terminal.
|
||||||
// This setting can take five values:
|
// This setting can take five values:
|
||||||
///
|
//
|
||||||
// 1. null (default): Inherit editor settings
|
// 1. null (default): Inherit editor settings
|
||||||
// 2. Show the scrollbar if there's important information or
|
// 2. Show the scrollbar if there's important information or
|
||||||
// follow the system's configured behavior (default):
|
// follow the system's configured behavior (default):
|
||||||
@@ -1055,7 +1147,6 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
"file_types": {
|
"file_types": {
|
||||||
"Plain Text": ["txt"],
|
|
||||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
||||||
"Shell Script": [".env.*"]
|
"Shell Script": [".env.*"]
|
||||||
},
|
},
|
||||||
@@ -1081,6 +1172,32 @@
|
|||||||
"auto_install_extensions": {
|
"auto_install_extensions": {
|
||||||
"html": true
|
"html": true
|
||||||
},
|
},
|
||||||
|
// Controls how completions are processed for this language.
|
||||||
|
"completions": {
|
||||||
|
// Controls how words are completed.
|
||||||
|
// For large documents, not all words may be fetched for completion.
|
||||||
|
//
|
||||||
|
// May take 3 values:
|
||||||
|
// 1. "enabled"
|
||||||
|
// Always fetch document's words for completions along with LSP completions.
|
||||||
|
// 2. "fallback"
|
||||||
|
// Only if LSP response errors or times out, use document's words to show completions.
|
||||||
|
// 3. "disabled"
|
||||||
|
// Never fetch or complete document's words for completions.
|
||||||
|
// (Word-based completions can still be queried via a separate action)
|
||||||
|
//
|
||||||
|
// Default: fallback
|
||||||
|
"words": "fallback",
|
||||||
|
// Whether to fetch LSP completions or not.
|
||||||
|
//
|
||||||
|
// Default: true
|
||||||
|
"lsp": true,
|
||||||
|
// When fetching LSP completions, determines how long to wait for a response of a particular server.
|
||||||
|
// When set to 0, waits indefinitely.
|
||||||
|
//
|
||||||
|
// Default: 0
|
||||||
|
"lsp_fetch_timeout_ms": 0
|
||||||
|
},
|
||||||
// Different settings for specific languages.
|
// Different settings for specific languages.
|
||||||
"languages": {
|
"languages": {
|
||||||
"Astro": {
|
"Astro": {
|
||||||
@@ -1171,11 +1288,19 @@
|
|||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"LaTeX": {
|
||||||
|
"format_on_save": "on",
|
||||||
|
"formatter": "language_server",
|
||||||
|
"language_servers": ["texlab", "..."],
|
||||||
|
"prettier": {
|
||||||
|
"allowed": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"Markdown": {
|
"Markdown": {
|
||||||
"format_on_save": "off",
|
"format_on_save": "off",
|
||||||
"use_on_type_format": false,
|
"use_on_type_format": false,
|
||||||
"allow_rewrap": "anywhere",
|
"allow_rewrap": "anywhere",
|
||||||
"soft_wrap": "bounded",
|
"soft_wrap": "editor_width",
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
@@ -1301,8 +1426,7 @@
|
|||||||
},
|
},
|
||||||
// Settings for auto-closing of JSX tags.
|
// Settings for auto-closing of JSX tags.
|
||||||
"jsx_tag_auto_close": {
|
"jsx_tag_auto_close": {
|
||||||
// // Whether to auto-close JSX tags.
|
"enabled": true
|
||||||
// "enabled": true
|
|
||||||
},
|
},
|
||||||
// LSP Specific settings.
|
// LSP Specific settings.
|
||||||
"lsp": {
|
"lsp": {
|
||||||
@@ -1358,11 +1482,6 @@
|
|||||||
"dev": {
|
"dev": {
|
||||||
// "theme": "Andromeda"
|
// "theme": "Andromeda"
|
||||||
},
|
},
|
||||||
// Task-related settings.
|
|
||||||
"task": {
|
|
||||||
// Whether to show task status indicator in the status bar. Default: true
|
|
||||||
"show_status_indicator": true
|
|
||||||
},
|
|
||||||
// Whether to show full labels in line indicator or short ones
|
// Whether to show full labels in line indicator or short ones
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
@@ -1408,6 +1527,12 @@
|
|||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
"ssh_connections": [],
|
"ssh_connections": [],
|
||||||
|
|
||||||
// Configures context servers for use in the Assistant.
|
// Configures context servers for use in the Assistant.
|
||||||
"context_servers": {}
|
"context_servers": {},
|
||||||
|
"debugger": {
|
||||||
|
"stepping_granularity": "line",
|
||||||
|
"save_breakpoints": true,
|
||||||
|
"button": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
assets/settings/initial_debug_tasks.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Debug active PHP file",
|
||||||
|
"adapter": "php",
|
||||||
|
"program": "$ZED_FILE",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Debug active Python file",
|
||||||
|
"adapter": "python",
|
||||||
|
"program": "$ZED_FILE",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Debug active JavaScript file",
|
||||||
|
"adapter": "javascript",
|
||||||
|
"program": "$ZED_FILE",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "JavaScript debug terminal",
|
||||||
|
"adapter": "javascript",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
|
"initialize_args": {
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -6,15 +6,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Gruvbox Dark",
|
"name": "Gruvbox Dark",
|
||||||
"appearance": "dark",
|
"appearance": "dark",
|
||||||
"accents": [
|
"accents": ["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"],
|
||||||
"#cc241dff",
|
|
||||||
"#98971aff",
|
|
||||||
"#d79921ff",
|
|
||||||
"#458588ff",
|
|
||||||
"#b16286ff",
|
|
||||||
"#689d6aff",
|
|
||||||
"#d65d0eff"
|
|
||||||
],
|
|
||||||
"style": {
|
"style": {
|
||||||
"border": "#5b534dff",
|
"border": "#5b534dff",
|
||||||
"border.variant": "#494340ff",
|
"border.variant": "#494340ff",
|
||||||
@@ -105,9 +97,9 @@
|
|||||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||||
"terminal.ansi.dim_white": "#b0a189ff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#83a598ff",
|
"link_text.hover": "#83a598ff",
|
||||||
"version_control_added": "#b7bb26ff",
|
"version_control.added": "#b7bb26ff",
|
||||||
"version_control_modified": "#f9bd2fff",
|
"version_control.modified": "#f9bd2fff",
|
||||||
"version_control_deleted": "#fb4a35ff",
|
"version_control.deleted": "#fb4a35ff",
|
||||||
"conflict": "#f9bd2fff",
|
"conflict": "#f9bd2fff",
|
||||||
"conflict.background": "#572e10ff",
|
"conflict.background": "#572e10ff",
|
||||||
"conflict.border": "#754916ff",
|
"conflict.border": "#754916ff",
|
||||||
@@ -383,6 +375,11 @@
|
|||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
"variable.special": {
|
||||||
|
"color": "#83a598ff",
|
||||||
|
"font_style": null,
|
||||||
|
"font_weight": null
|
||||||
|
},
|
||||||
"variant": {
|
"variant": {
|
||||||
"color": "#83a598ff",
|
"color": "#83a598ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
@@ -394,15 +391,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Gruvbox Dark Hard",
|
"name": "Gruvbox Dark Hard",
|
||||||
"appearance": "dark",
|
"appearance": "dark",
|
||||||
"accents": [
|
"accents": ["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"],
|
||||||
"#cc241dff",
|
|
||||||
"#98971aff",
|
|
||||||
"#d79921ff",
|
|
||||||
"#458588ff",
|
|
||||||
"#b16286ff",
|
|
||||||
"#689d6aff",
|
|
||||||
"#d65d0eff"
|
|
||||||
],
|
|
||||||
"style": {
|
"style": {
|
||||||
"border": "#5b534dff",
|
"border": "#5b534dff",
|
||||||
"border.variant": "#494340ff",
|
"border.variant": "#494340ff",
|
||||||
@@ -493,9 +482,9 @@
|
|||||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||||
"terminal.ansi.dim_white": "#b0a189ff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#83a598ff",
|
"link_text.hover": "#83a598ff",
|
||||||
"version_control_added": "#b7bb26ff",
|
"version_control.added": "#b7bb26ff",
|
||||||
"version_control_modified": "#f9bd2fff",
|
"version_control.modified": "#f9bd2fff",
|
||||||
"version_control_deleted": "#fb4a35ff",
|
"version_control.deleted": "#fb4a35ff",
|
||||||
"conflict": "#f9bd2fff",
|
"conflict": "#f9bd2fff",
|
||||||
"conflict.background": "#572e10ff",
|
"conflict.background": "#572e10ff",
|
||||||
"conflict.border": "#754916ff",
|
"conflict.border": "#754916ff",
|
||||||
@@ -771,6 +760,11 @@
|
|||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
"variable.special": {
|
||||||
|
"color": "#83a598ff",
|
||||||
|
"font_style": null,
|
||||||
|
"font_weight": null
|
||||||
|
},
|
||||||
"variant": {
|
"variant": {
|
||||||
"color": "#83a598ff",
|
"color": "#83a598ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
@@ -782,15 +776,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Gruvbox Dark Soft",
|
"name": "Gruvbox Dark Soft",
|
||||||
"appearance": "dark",
|
"appearance": "dark",
|
||||||
"accents": [
|
"accents": ["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"],
|
||||||
"#cc241dff",
|
|
||||||
"#98971aff",
|
|
||||||
"#d79921ff",
|
|
||||||
"#458588ff",
|
|
||||||
"#b16286ff",
|
|
||||||
"#689d6aff",
|
|
||||||
"#d65d0eff"
|
|
||||||
],
|
|
||||||
"style": {
|
"style": {
|
||||||
"border": "#5b534dff",
|
"border": "#5b534dff",
|
||||||
"border.variant": "#494340ff",
|
"border.variant": "#494340ff",
|
||||||
@@ -881,9 +867,9 @@
|
|||||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||||
"terminal.ansi.dim_white": "#b0a189ff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#83a598ff",
|
"link_text.hover": "#83a598ff",
|
||||||
"version_control_added": "#b7bb26ff",
|
"version_control.added": "#b7bb26ff",
|
||||||
"version_control_modified": "#f9bd2fff",
|
"version_control.modified": "#f9bd2fff",
|
||||||
"version_control_deleted": "#fb4a35ff",
|
"version_control.deleted": "#fb4a35ff",
|
||||||
"conflict": "#f9bd2fff",
|
"conflict": "#f9bd2fff",
|
||||||
"conflict.background": "#572e10ff",
|
"conflict.background": "#572e10ff",
|
||||||
"conflict.border": "#754916ff",
|
"conflict.border": "#754916ff",
|
||||||
@@ -1159,6 +1145,11 @@
|
|||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
"variable.special": {
|
||||||
|
"color": "#83a598ff",
|
||||||
|
"font_style": null,
|
||||||
|
"font_weight": null
|
||||||
|
},
|
||||||
"variant": {
|
"variant": {
|
||||||
"color": "#83a598ff",
|
"color": "#83a598ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
@@ -1170,15 +1161,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Gruvbox Light",
|
"name": "Gruvbox Light",
|
||||||
"appearance": "light",
|
"appearance": "light",
|
||||||
"accents": [
|
"accents": ["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"],
|
||||||
"#cc241dff",
|
|
||||||
"#98971aff",
|
|
||||||
"#d79921ff",
|
|
||||||
"#458588ff",
|
|
||||||
"#b16286ff",
|
|
||||||
"#689d6aff",
|
|
||||||
"#d65d0eff"
|
|
||||||
],
|
|
||||||
"style": {
|
"style": {
|
||||||
"border": "#c8b899ff",
|
"border": "#c8b899ff",
|
||||||
"border.variant": "#ddcca7ff",
|
"border.variant": "#ddcca7ff",
|
||||||
@@ -1269,9 +1252,9 @@
|
|||||||
"terminal.ansi.bright_white": "#282828ff",
|
"terminal.ansi.bright_white": "#282828ff",
|
||||||
"terminal.ansi.dim_white": "#73675eff",
|
"terminal.ansi.dim_white": "#73675eff",
|
||||||
"link_text.hover": "#0b6678ff",
|
"link_text.hover": "#0b6678ff",
|
||||||
"version_control_added": "#797410ff",
|
"version_control.added": "#797410ff",
|
||||||
"version_control_modified": "#b57615ff",
|
"version_control.modified": "#b57615ff",
|
||||||
"version_control_deleted": "#9d0308ff",
|
"version_control.deleted": "#9d0308ff",
|
||||||
"conflict": "#b57615ff",
|
"conflict": "#b57615ff",
|
||||||
"conflict.background": "#f5e2d0ff",
|
"conflict.background": "#f5e2d0ff",
|
||||||
"conflict.border": "#ebccabff",
|
"conflict.border": "#ebccabff",
|
||||||
@@ -1547,6 +1530,11 @@
|
|||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
"variable.special": {
|
||||||
|
"color": "#066578ff",
|
||||||
|
"font_style": null,
|
||||||
|
"font_weight": null
|
||||||
|
},
|
||||||
"variant": {
|
"variant": {
|
||||||
"color": "#0b6678ff",
|
"color": "#0b6678ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
@@ -1558,15 +1546,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Gruvbox Light Hard",
|
"name": "Gruvbox Light Hard",
|
||||||
"appearance": "light",
|
"appearance": "light",
|
||||||
"accents": [
|
"accents": ["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"],
|
||||||
"#cc241dff",
|
|
||||||
"#98971aff",
|
|
||||||
"#d79921ff",
|
|
||||||
"#458588ff",
|
|
||||||
"#b16286ff",
|
|
||||||
"#689d6aff",
|
|
||||||
"#d65d0eff"
|
|
||||||
],
|
|
||||||
"style": {
|
"style": {
|
||||||
"border": "#c8b899ff",
|
"border": "#c8b899ff",
|
||||||
"border.variant": "#ddcca7ff",
|
"border.variant": "#ddcca7ff",
|
||||||
@@ -1657,9 +1637,9 @@
|
|||||||
"terminal.ansi.bright_white": "#282828ff",
|
"terminal.ansi.bright_white": "#282828ff",
|
||||||
"terminal.ansi.dim_white": "#73675eff",
|
"terminal.ansi.dim_white": "#73675eff",
|
||||||
"link_text.hover": "#0b6678ff",
|
"link_text.hover": "#0b6678ff",
|
||||||
"version_control_added": "#797410ff",
|
"version_control.added": "#797410ff",
|
||||||
"version_control_modified": "#b57615ff",
|
"version_control.modified": "#b57615ff",
|
||||||
"version_control_deleted": "#9d0308ff",
|
"version_control.deleted": "#9d0308ff",
|
||||||
"conflict": "#b57615ff",
|
"conflict": "#b57615ff",
|
||||||
"conflict.background": "#f5e2d0ff",
|
"conflict.background": "#f5e2d0ff",
|
||||||
"conflict.border": "#ebccabff",
|
"conflict.border": "#ebccabff",
|
||||||
@@ -1935,6 +1915,11 @@
|
|||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
"variable.special": {
|
||||||
|
"color": "#066578ff",
|
||||||
|
"font_style": null,
|
||||||
|
"font_weight": null
|
||||||
|
},
|
||||||
"variant": {
|
"variant": {
|
||||||
"color": "#0b6678ff",
|
"color": "#0b6678ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
@@ -1946,15 +1931,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Gruvbox Light Soft",
|
"name": "Gruvbox Light Soft",
|
||||||
"appearance": "light",
|
"appearance": "light",
|
||||||
"accents": [
|
"accents": ["#cc241dff", "#98971aff", "#d79921ff", "#458588ff", "#b16286ff", "#689d6aff", "#d65d0eff"],
|
||||||
"#cc241dff",
|
|
||||||
"#98971aff",
|
|
||||||
"#d79921ff",
|
|
||||||
"#458588ff",
|
|
||||||
"#b16286ff",
|
|
||||||
"#689d6aff",
|
|
||||||
"#d65d0eff"
|
|
||||||
],
|
|
||||||
"style": {
|
"style": {
|
||||||
"border": "#c8b899ff",
|
"border": "#c8b899ff",
|
||||||
"border.variant": "#ddcca7ff",
|
"border.variant": "#ddcca7ff",
|
||||||
@@ -2045,9 +2022,9 @@
|
|||||||
"terminal.ansi.bright_white": "#282828ff",
|
"terminal.ansi.bright_white": "#282828ff",
|
||||||
"terminal.ansi.dim_white": "#73675eff",
|
"terminal.ansi.dim_white": "#73675eff",
|
||||||
"link_text.hover": "#0b6678ff",
|
"link_text.hover": "#0b6678ff",
|
||||||
"version_control_added": "#797410ff",
|
"version_control.added": "#797410ff",
|
||||||
"version_control_modified": "#b57615ff",
|
"version_control.modified": "#b57615ff",
|
||||||
"version_control_deleted": "#9d0308ff",
|
"version_control.deleted": "#9d0308ff",
|
||||||
"conflict": "#b57615ff",
|
"conflict": "#b57615ff",
|
||||||
"conflict.background": "#f5e2d0ff",
|
"conflict.background": "#f5e2d0ff",
|
||||||
"conflict.border": "#ebccabff",
|
"conflict.border": "#ebccabff",
|
||||||
@@ -2323,6 +2300,11 @@
|
|||||||
"font_style": null,
|
"font_style": null,
|
||||||
"font_weight": null
|
"font_weight": null
|
||||||
},
|
},
|
||||||
|
"variable.special": {
|
||||||
|
"color": "#066578ff",
|
||||||
|
"font_style": null,
|
||||||
|
"font_weight": null
|
||||||
|
},
|
||||||
"variant": {
|
"variant": {
|
||||||
"color": "#0b6678ff",
|
"color": "#0b6678ff",
|
||||||
"font_style": null,
|
"font_style": null,
|
||||||
|
|||||||
@@ -96,9 +96,9 @@
|
|||||||
"terminal.ansi.bright_white": "#dce0e5ff",
|
"terminal.ansi.bright_white": "#dce0e5ff",
|
||||||
"terminal.ansi.dim_white": "#575d65ff",
|
"terminal.ansi.dim_white": "#575d65ff",
|
||||||
"link_text.hover": "#74ade8ff",
|
"link_text.hover": "#74ade8ff",
|
||||||
"version_control_added": "#a7c088ff",
|
"version_control.added": "#27a657ff",
|
||||||
"version_control_modified": "#dec184ff",
|
"version_control.modified": "#d3b020ff",
|
||||||
"version_control_deleted": "#d07277ff",
|
"version_control.deleted": "#e06c76ff",
|
||||||
"conflict": "#dec184ff",
|
"conflict": "#dec184ff",
|
||||||
"conflict.background": "#dec1841a",
|
"conflict.background": "#dec1841a",
|
||||||
"conflict.border": "#5d4c2fff",
|
"conflict.border": "#5d4c2fff",
|
||||||
@@ -475,9 +475,9 @@
|
|||||||
"terminal.ansi.bright_white": "#242529ff",
|
"terminal.ansi.bright_white": "#242529ff",
|
||||||
"terminal.ansi.dim_white": "#97979aff",
|
"terminal.ansi.dim_white": "#97979aff",
|
||||||
"link_text.hover": "#5c78e2ff",
|
"link_text.hover": "#5c78e2ff",
|
||||||
"version_control_added": "#669f59ff",
|
"version_control.added": "#27a657ff",
|
||||||
"version_control_modified": "#a48819ff",
|
"version_control.modified": "#d3b020ff",
|
||||||
"version_control_deleted": "#d36151ff",
|
"version_control.deleted": "#e06c76ff",
|
||||||
"conflict": "#a48819ff",
|
"conflict": "#a48819ff",
|
||||||
"conflict.background": "#faf2e6ff",
|
"conflict.background": "#faf2e6ff",
|
||||||
"conflict.border": "#f4e7d1ff",
|
"conflict.border": "#f4e7d1ff",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ extension_host.workspace = true
|
|||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
lsp.workspace = true
|
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
|||||||
@@ -3,38 +3,40 @@ use editor::Editor;
|
|||||||
use extension_host::ExtensionStore;
|
use extension_host::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, percentage, Animation, AnimationExt as _, App, Context, CursorStyle, Entity,
|
Animation, AnimationExt as _, App, Context, CursorStyle, Entity, EventEmitter,
|
||||||
EventEmitter, InteractiveElement as _, ParentElement as _, Render, SharedString,
|
InteractiveElement as _, ParentElement as _, Render, SharedString, StatefulInteractiveElement,
|
||||||
StatefulInteractiveElement, Styled, Transformation, Window,
|
Styled, Transformation, Window, actions, percentage,
|
||||||
|
};
|
||||||
|
use language::{BinaryStatus, LanguageRegistry, LanguageServerId};
|
||||||
|
use project::{
|
||||||
|
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
||||||
|
ProjectEnvironmentEvent, WorktreeId,
|
||||||
};
|
};
|
||||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
|
|
||||||
use lsp::LanguageServerName;
|
|
||||||
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||||
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
|
use ui::{ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use util::truncate_and_trailoff;
|
use util::truncate_and_trailoff;
|
||||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
use workspace::{StatusItemView, Workspace, item::ItemHandle};
|
||||||
|
|
||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ShowError {
|
ShowError {
|
||||||
lsp_name: LanguageServerName,
|
server_name: SharedString,
|
||||||
error: String,
|
error: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActivityIndicator {
|
pub struct ActivityIndicator {
|
||||||
statuses: Vec<LspStatus>,
|
statuses: Vec<ServerStatus>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
auto_updater: Option<Entity<AutoUpdater>>,
|
auto_updater: Option<Entity<AutoUpdater>>,
|
||||||
context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LspStatus {
|
struct ServerStatus {
|
||||||
name: LanguageServerName,
|
name: SharedString,
|
||||||
status: LanguageServerBinaryStatus,
|
status: BinaryStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingWork<'a> {
|
struct PendingWork<'a> {
|
||||||
@@ -61,11 +63,11 @@ impl ActivityIndicator {
|
|||||||
let auto_updater = AutoUpdater::get(cx);
|
let auto_updater = AutoUpdater::get(cx);
|
||||||
let this = cx.new(|cx| {
|
let this = cx.new(|cx| {
|
||||||
let mut status_events = languages.language_server_binary_statuses();
|
let mut status_events = languages.language_server_binary_statuses();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(async move |this, cx| {
|
||||||
while let Some((name, status)) = status_events.next().await {
|
while let Some((name, status)) = status_events.next().await {
|
||||||
this.update(&mut cx, |this: &mut ActivityIndicator, cx| {
|
this.update(cx, |this: &mut ActivityIndicator, cx| {
|
||||||
this.statuses.retain(|s| s.name != name);
|
this.statuses.retain(|s| s.name != name);
|
||||||
this.statuses.push(LspStatus { name, status });
|
this.statuses.push(ServerStatus { name, status });
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -73,7 +75,35 @@ impl ActivityIndicator {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
let mut status_events = languages.dap_server_binary_statuses();
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
while let Some((name, status)) = status_events.next().await {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.statuses.retain(|s| s.name != name);
|
||||||
|
this.statuses.push(ServerStatus { name, status });
|
||||||
|
cx.notify();
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
cx.subscribe(
|
||||||
|
&project.read(cx).lsp_store(),
|
||||||
|
|_, _, event, cx| match event {
|
||||||
|
LspStoreEvent::LanguageServerUpdate { .. } => cx.notify(),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
cx.subscribe(
|
||||||
|
&project.read(cx).environment().clone(),
|
||||||
|
|_, _, event, cx| match event {
|
||||||
|
ProjectEnvironmentEvent::ErrorsUpdated => cx.notify(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
if let Some(auto_updater) = auto_updater.as_ref() {
|
||||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
||||||
@@ -88,25 +118,25 @@ impl ActivityIndicator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
|
cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
|
||||||
Event::ShowError { lsp_name, error } => {
|
Event::ShowError { server_name, error } => {
|
||||||
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
let error = error.clone();
|
let error = error.clone();
|
||||||
let lsp_name = lsp_name.clone();
|
let server_name = server_name.clone();
|
||||||
cx.spawn_in(window, |workspace, mut cx| async move {
|
cx.spawn_in(window, async move |workspace, cx| {
|
||||||
let buffer = create_buffer.await?;
|
let buffer = create_buffer.await?;
|
||||||
buffer.update(&mut cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.edit(
|
buffer.edit(
|
||||||
[(
|
[(
|
||||||
0..0,
|
0..0,
|
||||||
format!("Language server error: {}\n\n{}", lsp_name, error),
|
format!("Language server error: {}\n\n{}", server_name, error),
|
||||||
)],
|
)],
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
buffer.set_capability(language::Capability::ReadOnly, cx);
|
buffer.set_capability(language::Capability::ReadOnly, cx);
|
||||||
})?;
|
})?;
|
||||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.add_item_to_active_pane(
|
workspace.add_item_to_active_pane(
|
||||||
Box::new(cx.new(|cx| {
|
Box::new(cx.new(|cx| {
|
||||||
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
|
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
|
||||||
@@ -129,9 +159,9 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
fn show_error_message(&mut self, _: &ShowErrorMessage, _: &mut Window, cx: &mut Context<Self>) {
|
fn show_error_message(&mut self, _: &ShowErrorMessage, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.statuses.retain(|status| {
|
self.statuses.retain(|status| {
|
||||||
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
if let BinaryStatus::Failed { error } = &status.status {
|
||||||
cx.emit(Event::ShowError {
|
cx.emit(Event::ShowError {
|
||||||
lsp_name: status.name.clone(),
|
server_name: status.name.clone(),
|
||||||
error: error.clone(),
|
error: error.clone(),
|
||||||
});
|
});
|
||||||
false
|
false
|
||||||
@@ -204,7 +234,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(cx, worktree_id);
|
project.remove_environment_error(worktree_id, cx);
|
||||||
});
|
});
|
||||||
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
window.dispatch_action(Box::new(workspace::OpenLog), cx);
|
||||||
})),
|
})),
|
||||||
@@ -260,12 +290,10 @@ impl ActivityIndicator {
|
|||||||
let mut failed = SmallVec::<[_; 3]>::new();
|
let mut failed = SmallVec::<[_; 3]>::new();
|
||||||
for status in &self.statuses {
|
for status in &self.statuses {
|
||||||
match status.status {
|
match status.status {
|
||||||
LanguageServerBinaryStatus::CheckingForUpdate => {
|
BinaryStatus::CheckingForUpdate => checking_for_update.push(status.name.clone()),
|
||||||
checking_for_update.push(status.name.clone())
|
BinaryStatus::Downloading => downloading.push(status.name.clone()),
|
||||||
}
|
BinaryStatus::Failed { .. } => failed.push(status.name.clone()),
|
||||||
LanguageServerBinaryStatus::Downloading => downloading.push(status.name.clone()),
|
BinaryStatus::None => {}
|
||||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.clone()),
|
|
||||||
LanguageServerBinaryStatus::None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +306,7 @@ impl ActivityIndicator {
|
|||||||
),
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Downloading {}...",
|
"Downloading {}...",
|
||||||
downloading.iter().map(|name| name.0.as_ref()).fold(
|
downloading.iter().map(|name| name.as_ref()).fold(
|
||||||
String::new(),
|
String::new(),
|
||||||
|mut acc, s| {
|
|mut acc, s| {
|
||||||
if !acc.is_empty() {
|
if !acc.is_empty() {
|
||||||
@@ -306,7 +334,7 @@ impl ActivityIndicator {
|
|||||||
),
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Checking for updates to {}...",
|
"Checking for updates to {}...",
|
||||||
checking_for_update.iter().map(|name| name.0.as_ref()).fold(
|
checking_for_update.iter().map(|name| name.as_ref()).fold(
|
||||||
String::new(),
|
String::new(),
|
||||||
|mut acc, s| {
|
|mut acc, s| {
|
||||||
if !acc.is_empty() {
|
if !acc.is_empty() {
|
||||||
@@ -336,7 +364,7 @@ impl ActivityIndicator {
|
|||||||
"Failed to run {}. Click to show error.",
|
"Failed to run {}. Click to show error.",
|
||||||
failed
|
failed
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name| name.0.as_ref())
|
.map(|name| name.as_ref())
|
||||||
.fold(String::new(), |mut acc, s| {
|
.fold(String::new(), |mut acc, s| {
|
||||||
if !acc.is_empty() {
|
if !acc.is_empty() {
|
||||||
acc.push_str(", ");
|
acc.push_str(", ");
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ mod supported_countries;
|
|||||||
|
|
||||||
use std::{pin::Pin, str::FromStr};
|
use std::{pin::Pin, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
use futures::{AsyncBufReadExt, AsyncReadExt, Stream, StreamExt, io::BufReader, stream::BoxStream};
|
||||||
use http_client::http::{HeaderMap, HeaderValue};
|
use http_client::http::{HeaderMap, HeaderValue};
|
||||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -24,6 +24,16 @@ pub struct AnthropicModelCacheConfiguration {
|
|||||||
pub max_cache_anchors: usize,
|
pub max_cache_anchors: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub enum AnthropicModelMode {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Thinking {
|
||||||
|
budget_tokens: Option<u32>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||||
pub enum Model {
|
pub enum Model {
|
||||||
@@ -32,6 +42,11 @@ pub enum Model {
|
|||||||
Claude3_5Sonnet,
|
Claude3_5Sonnet,
|
||||||
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
|
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
|
||||||
Claude3_7Sonnet,
|
Claude3_7Sonnet,
|
||||||
|
#[serde(
|
||||||
|
rename = "claude-3-7-sonnet-thinking",
|
||||||
|
alias = "claude-3-7-sonnet-thinking-latest"
|
||||||
|
)]
|
||||||
|
Claude3_7SonnetThinking,
|
||||||
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||||
Claude3_5Haiku,
|
Claude3_5Haiku,
|
||||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||||
@@ -54,6 +69,8 @@ pub enum Model {
|
|||||||
default_temperature: Option<f32>,
|
default_temperature: Option<f32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
extra_beta_headers: Vec<String>,
|
extra_beta_headers: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
mode: AnthropicModelMode,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +78,8 @@ impl Model {
|
|||||||
pub fn from_id(id: &str) -> Result<Self> {
|
pub fn from_id(id: &str) -> Result<Self> {
|
||||||
if id.starts_with("claude-3-5-sonnet") {
|
if id.starts_with("claude-3-5-sonnet") {
|
||||||
Ok(Self::Claude3_5Sonnet)
|
Ok(Self::Claude3_5Sonnet)
|
||||||
|
} else if id.starts_with("claude-3-7-sonnet-thinking") {
|
||||||
|
Ok(Self::Claude3_7SonnetThinking)
|
||||||
} else if id.starts_with("claude-3-7-sonnet") {
|
} else if id.starts_with("claude-3-7-sonnet") {
|
||||||
Ok(Self::Claude3_7Sonnet)
|
Ok(Self::Claude3_7Sonnet)
|
||||||
} else if id.starts_with("claude-3-5-haiku") {
|
} else if id.starts_with("claude-3-5-haiku") {
|
||||||
@@ -80,6 +99,20 @@ impl Model {
|
|||||||
match self {
|
match self {
|
||||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||||
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
|
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
|
||||||
|
Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
|
||||||
|
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||||
|
Model::Claude3Opus => "claude-3-opus-latest",
|
||||||
|
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||||
|
Model::Claude3Haiku => "claude-3-haiku-20240307",
|
||||||
|
Self::Custom { name, .. } => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The id of the model that should be used for making API requests
|
||||||
|
pub fn request_id(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||||
|
Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
|
||||||
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||||
Model::Claude3Opus => "claude-3-opus-latest",
|
Model::Claude3Opus => "claude-3-opus-latest",
|
||||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||||
@@ -92,6 +125,7 @@ impl Model {
|
|||||||
match self {
|
match self {
|
||||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||||
|
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
|
||||||
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
||||||
Self::Claude3Opus => "Claude 3 Opus",
|
Self::Claude3Opus => "Claude 3 Opus",
|
||||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||||
@@ -107,6 +141,7 @@ impl Model {
|
|||||||
Self::Claude3_5Sonnet
|
Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
|
| Self::Claude3_7SonnetThinking
|
||||||
| Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
|
| Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
|
||||||
min_total_token: 2_048,
|
min_total_token: 2_048,
|
||||||
should_speculate: true,
|
should_speculate: true,
|
||||||
@@ -125,6 +160,7 @@ impl Model {
|
|||||||
Self::Claude3_5Sonnet
|
Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
|
| Self::Claude3_7SonnetThinking
|
||||||
| Self::Claude3Opus
|
| Self::Claude3Opus
|
||||||
| Self::Claude3Sonnet
|
| Self::Claude3Sonnet
|
||||||
| Self::Claude3Haiku => 200_000,
|
| Self::Claude3Haiku => 200_000,
|
||||||
@@ -135,7 +171,10 @@ impl Model {
|
|||||||
pub fn max_output_tokens(&self) -> u32 {
|
pub fn max_output_tokens(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
|
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
|
||||||
Self::Claude3_5Sonnet | Self::Claude3_7Sonnet | Self::Claude3_5Haiku => 8_192,
|
Self::Claude3_5Sonnet
|
||||||
|
| Self::Claude3_7Sonnet
|
||||||
|
| Self::Claude3_7SonnetThinking
|
||||||
|
| Self::Claude3_5Haiku => 8_192,
|
||||||
Self::Custom {
|
Self::Custom {
|
||||||
max_output_tokens, ..
|
max_output_tokens, ..
|
||||||
} => max_output_tokens.unwrap_or(4_096),
|
} => max_output_tokens.unwrap_or(4_096),
|
||||||
@@ -146,6 +185,7 @@ impl Model {
|
|||||||
match self {
|
match self {
|
||||||
Self::Claude3_5Sonnet
|
Self::Claude3_5Sonnet
|
||||||
| Self::Claude3_7Sonnet
|
| Self::Claude3_7Sonnet
|
||||||
|
| Self::Claude3_7SonnetThinking
|
||||||
| Self::Claude3_5Haiku
|
| Self::Claude3_5Haiku
|
||||||
| Self::Claude3Opus
|
| Self::Claude3Opus
|
||||||
| Self::Claude3Sonnet
|
| Self::Claude3Sonnet
|
||||||
@@ -157,6 +197,21 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mode(&self) -> AnthropicModelMode {
|
||||||
|
match self {
|
||||||
|
Self::Claude3_5Sonnet
|
||||||
|
| Self::Claude3_7Sonnet
|
||||||
|
| Self::Claude3_5Haiku
|
||||||
|
| Self::Claude3Opus
|
||||||
|
| Self::Claude3Sonnet
|
||||||
|
| Self::Claude3Haiku => AnthropicModelMode::Default,
|
||||||
|
Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
|
||||||
|
budget_tokens: Some(4_096),
|
||||||
|
},
|
||||||
|
Self::Custom { mode, .. } => mode.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const DEFAULT_BETA_HEADERS: &[&str] = &["prompt-caching-2024-07-31"];
|
pub const DEFAULT_BETA_HEADERS: &[&str] = &["prompt-caching-2024-07-31"];
|
||||||
|
|
||||||
pub fn beta_headers(&self) -> String {
|
pub fn beta_headers(&self) -> String {
|
||||||
@@ -188,7 +243,7 @@ impl Model {
|
|||||||
{
|
{
|
||||||
tool_override
|
tool_override
|
||||||
} else {
|
} else {
|
||||||
self.id()
|
self.request_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,6 +464,8 @@ pub async fn extract_tool_args_from_events(
|
|||||||
Err(error) => Some(Err(error)),
|
Err(error) => Some(Err(error)),
|
||||||
Ok(Event::ContentBlockDelta { index, delta }) => match delta {
|
Ok(Event::ContentBlockDelta { index, delta }) => match delta {
|
||||||
ContentDelta::TextDelta { .. } => None,
|
ContentDelta::TextDelta { .. } => None,
|
||||||
|
ContentDelta::ThinkingDelta { .. } => None,
|
||||||
|
ContentDelta::SignatureDelta { .. } => None,
|
||||||
ContentDelta::InputJsonDelta { partial_json } => {
|
ContentDelta::InputJsonDelta { partial_json } => {
|
||||||
if index == tool_use_index {
|
if index == tool_use_index {
|
||||||
Some(Ok(partial_json))
|
Some(Ok(partial_json))
|
||||||
@@ -487,6 +544,10 @@ pub enum RequestContent {
|
|||||||
pub enum ResponseContent {
|
pub enum ResponseContent {
|
||||||
#[serde(rename = "text")]
|
#[serde(rename = "text")]
|
||||||
Text { text: String },
|
Text { text: String },
|
||||||
|
#[serde(rename = "thinking")]
|
||||||
|
Thinking { thinking: String },
|
||||||
|
#[serde(rename = "redacted_thinking")]
|
||||||
|
RedactedThinking { data: String },
|
||||||
#[serde(rename = "tool_use")]
|
#[serde(rename = "tool_use")]
|
||||||
ToolUse {
|
ToolUse {
|
||||||
id: String,
|
id: String,
|
||||||
@@ -518,6 +579,19 @@ pub enum ToolChoice {
|
|||||||
Tool { name: String },
|
Tool { name: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
|
pub enum Thinking {
|
||||||
|
Enabled { budget_tokens: Option<u32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum StringOrContents {
|
||||||
|
String(String),
|
||||||
|
Content(Vec<RequestContent>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub model: String,
|
pub model: String,
|
||||||
@@ -526,9 +600,11 @@ pub struct Request {
|
|||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub tools: Vec<Tool>,
|
pub tools: Vec<Tool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub thinking: Option<Thinking>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub tool_choice: Option<ToolChoice>,
|
pub tool_choice: Option<ToolChoice>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub system: Option<String>,
|
pub system: Option<StringOrContents>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub metadata: Option<Metadata>,
|
pub metadata: Option<Metadata>,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
@@ -553,7 +629,7 @@ pub struct Metadata {
|
|||||||
pub user_id: Option<String>,
|
pub user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
pub struct Usage {
|
pub struct Usage {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub input_tokens: Option<u32>,
|
pub input_tokens: Option<u32>,
|
||||||
@@ -609,6 +685,10 @@ pub enum Event {
|
|||||||
pub enum ContentDelta {
|
pub enum ContentDelta {
|
||||||
#[serde(rename = "text_delta")]
|
#[serde(rename = "text_delta")]
|
||||||
TextDelta { text: String },
|
TextDelta { text: String },
|
||||||
|
#[serde(rename = "thinking_delta")]
|
||||||
|
ThinkingDelta { thinking: String },
|
||||||
|
#[serde(rename = "signature_delta")]
|
||||||
|
SignatureDelta { signature: String },
|
||||||
#[serde(rename = "input_json_delta")]
|
#[serde(rename = "input_json_delta")]
|
||||||
InputJsonDelta { partial_json: String },
|
InputJsonDelta { partial_json: String },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use std::time::Duration;
|
|||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use futures::channel::{mpsc, oneshot};
|
use futures::channel::{mpsc, oneshot};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use futures::{io::BufReader, AsyncBufReadExt as _};
|
use futures::{AsyncBufReadExt as _, io::BufReader};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use futures::{select_biased, AsyncWriteExt as _, FutureExt as _};
|
use futures::{AsyncWriteExt as _, FutureExt as _, select_biased};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use gpui::{AsyncApp, BackgroundExecutor, Task};
|
use gpui::{AsyncApp, BackgroundExecutor, Task};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -34,9 +34,9 @@ impl AskPassDelegate {
|
|||||||
password_prompt: impl Fn(String, oneshot::Sender<String>, &mut AsyncApp) + Send + Sync + 'static,
|
password_prompt: impl Fn(String, oneshot::Sender<String>, &mut AsyncApp) + Send + Sync + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender<String>)>();
|
let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender<String>)>();
|
||||||
let task = cx.spawn(|mut cx| async move {
|
let task = cx.spawn(async move |cx: &mut AsyncApp| {
|
||||||
while let Some((prompt, channel)) = rx.next().await {
|
while let Some((prompt, channel)) = rx.next().await {
|
||||||
password_prompt(prompt, channel, &mut cx);
|
password_prompt(prompt, channel, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Self { tx, _task: task }
|
Self { tx, _task: task }
|
||||||
@@ -188,7 +188,7 @@ impl AskPassSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> AskPassResult {
|
pub async fn run(&mut self) -> AskPassResult {
|
||||||
futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(10))).await;
|
futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(20))).await;
|
||||||
AskPassResult::Timedout
|
AskPassResult::Timedout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ impl Assets {
|
|||||||
|
|
||||||
pub fn load_test_fonts(&self, cx: &App) {
|
pub fn load_test_fonts(&self, cx: &App) {
|
||||||
cx.text_system()
|
cx.text_system()
|
||||||
.add_fonts(vec![self
|
.add_fonts(vec![
|
||||||
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
|
self.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()])
|
.unwrap(),
|
||||||
|
])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ lsp.workspace = true
|
|||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
prompt_library.workspace = true
|
prompt_library.workspace = true
|
||||||
prompt_store.workspace = true
|
prompt_store.workspace = true
|
||||||
@@ -56,7 +55,6 @@ proto.workspace = true
|
|||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
search.workspace = true
|
search.workspace = true
|
||||||
semantic_index.workspace = true
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
|||||||
@@ -10,17 +10,15 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag};
|
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{actions, App, Global, UpdateGlobal};
|
use gpui::{App, Global, ReadGlobal, UpdateGlobal, actions};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
||||||
};
|
};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
|
||||||
@@ -86,6 +84,10 @@ impl Assistant {
|
|||||||
filter.show_namespace(Self::NAMESPACE);
|
filter.show_namespace(Self::NAMESPACE);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enabled(cx: &App) -> bool {
|
||||||
|
Self::global(cx).enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
@@ -98,33 +100,6 @@ pub fn init(
|
|||||||
AssistantSettings::register(cx);
|
AssistantSettings::register(cx);
|
||||||
SlashCommandSettings::register(cx);
|
SlashCommandSettings::register(cx);
|
||||||
|
|
||||||
cx.spawn(|mut cx| {
|
|
||||||
let client = client.clone();
|
|
||||||
async move {
|
|
||||||
let is_search_slash_command_enabled = cx
|
|
||||||
.update(|cx| cx.wait_for_flag::<SearchSlashCommandFeatureFlag>())?
|
|
||||||
.await;
|
|
||||||
let is_project_slash_command_enabled = cx
|
|
||||||
.update(|cx| cx.wait_for_flag::<ProjectSlashCommandFeatureFlag>())?
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if !is_search_slash_command_enabled && !is_project_slash_command_enabled {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
|
||||||
let semantic_index = SemanticDb::new(
|
|
||||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
|
||||||
Arc::new(embedding_provider),
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
cx.update(|cx| cx.set_global(semantic_index))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
assistant_context_editor::init(client.clone(), cx);
|
assistant_context_editor::init(client.clone(), cx);
|
||||||
prompt_library::init(cx);
|
prompt_library::init(cx);
|
||||||
init_language_model_settings(cx);
|
init_language_model_settings(cx);
|
||||||
@@ -133,7 +108,7 @@ pub fn init(
|
|||||||
assistant_panel::init(cx);
|
assistant_panel::init(cx);
|
||||||
context_server::init(cx);
|
context_server::init(cx);
|
||||||
|
|
||||||
register_slash_commands(Some(prompt_builder.clone()), cx);
|
register_slash_commands(cx);
|
||||||
inline_assistant::init(
|
inline_assistant::init(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
@@ -186,8 +161,12 @@ fn init_language_model_settings(cx: &mut App) {
|
|||||||
|
|
||||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AssistantSettings::get_global(cx);
|
||||||
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
|
let active_model_provider_name =
|
||||||
let model_id = LanguageModelId::from(settings.default_model.model.clone());
|
LanguageModelProviderId::from(settings.default_model.provider.clone());
|
||||||
|
let active_model_id = LanguageModelId::from(settings.default_model.model.clone());
|
||||||
|
let editor_provider_name =
|
||||||
|
LanguageModelProviderId::from(settings.editor_model.provider.clone());
|
||||||
|
let editor_model_id = LanguageModelId::from(settings.editor_model.model.clone());
|
||||||
let inline_alternatives = settings
|
let inline_alternatives = settings
|
||||||
.inline_alternatives
|
.inline_alternatives
|
||||||
.iter()
|
.iter()
|
||||||
@@ -199,12 +178,13 @@ fn update_active_language_model_from_settings(cx: &mut App) {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||||
registry.select_active_model(&provider_name, &model_id, cx);
|
registry.select_active_model(&active_model_provider_name, &active_model_id, cx);
|
||||||
|
registry.select_editor_model(&editor_provider_name, &editor_model_id, cx);
|
||||||
registry.select_inline_alternative_models(inline_alternatives, cx);
|
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut App) {
|
fn register_slash_commands(cx: &mut App) {
|
||||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||||
|
|
||||||
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
||||||
@@ -222,33 +202,6 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||||
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
||||||
|
|
||||||
if let Some(prompt_builder) = prompt_builder {
|
|
||||||
cx.observe_flag::<assistant_slash_commands::ProjectSlashCommandFeatureFlag, _>({
|
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
|
||||||
move |is_enabled, _cx| {
|
|
||||||
if is_enabled {
|
|
||||||
slash_command_registry.register_command(
|
|
||||||
assistant_slash_commands::ProjectSlashCommand::new(prompt_builder.clone()),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.observe_flag::<assistant_slash_commands::AutoSlashCommandFeatureFlag, _>({
|
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
|
||||||
move |is_enabled, _cx| {
|
|
||||||
if is_enabled {
|
|
||||||
// [#auto-staff-ship] TODO remove this when /auto is no longer staff-shipped
|
|
||||||
slash_command_registry
|
|
||||||
.register_command(assistant_slash_commands::AutoCommand, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
let slash_command_registry = slash_command_registry.clone();
|
||||||
move |is_enabled, _cx| {
|
move |is_enabled, _cx| {
|
||||||
@@ -265,17 +218,6 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
update_slash_commands_from_settings(cx);
|
update_slash_commands_from_settings(cx);
|
||||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.observe_flag::<assistant_slash_commands::SearchSlashCommandFeatureFlag, _>({
|
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
|
||||||
move |is_enabled, _cx| {
|
|
||||||
if is_enabled {
|
|
||||||
slash_command_registry
|
|
||||||
.register_command(assistant_slash_commands::SearchSlashCommand, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_slash_commands_from_settings(cx: &mut App) {
|
fn update_slash_commands_from_settings(cx: &mut App) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{canvas, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
|
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription, canvas};
|
||||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||||
use ui::{prelude::*, ElevationIndex};
|
use ui::{ElevationIndex, prelude::*};
|
||||||
use workspace::Item;
|
use workspace::Item;
|
||||||
|
|
||||||
pub struct ConfigurationView {
|
pub struct ConfigurationView {
|
||||||
|
|||||||
@@ -1,44 +1,46 @@
|
|||||||
|
use crate::Assistant;
|
||||||
use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
|
use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
|
||||||
use crate::{
|
use crate::{
|
||||||
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewChat,
|
DeployHistory, InlineAssistant, NewChat, terminal_inline_assistant::TerminalInlineAssistant,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use assistant_context_editor::{
|
use assistant_context_editor::{
|
||||||
make_lsp_adapter_delegate, AssistantContext, AssistantPanelDelegate, ContextEditor,
|
AssistantContext, AssistantPanelDelegate, ContextEditor, ContextEditorToolbarItem,
|
||||||
ContextEditorToolbarItem, ContextEditorToolbarItemEvent, ContextHistory, ContextId,
|
ContextEditorToolbarItemEvent, ContextHistory, ContextId, ContextStore, ContextStoreEvent,
|
||||||
ContextStore, ContextStoreEvent, InsertDraggedFiles, SlashCommandCompletionProvider,
|
DEFAULT_TAB_TITLE, InsertDraggedFiles, SlashCommandCompletionProvider,
|
||||||
DEFAULT_TAB_TITLE,
|
make_lsp_adapter_delegate,
|
||||||
};
|
};
|
||||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||||
use assistant_slash_command::SlashCommandWorkingSet;
|
use assistant_slash_command::SlashCommandWorkingSet;
|
||||||
use client::{proto, Client, Status};
|
use client::{Client, Status, proto};
|
||||||
use editor::{Editor, EditorEvent};
|
use editor::{Editor, EditorEvent};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::*, Action, App, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, FocusHandle,
|
Action, App, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable,
|
||||||
Focusable, InteractiveElement, IntoElement, ParentElement, Pixels, Render, Styled,
|
InteractiveElement, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, Task,
|
||||||
Subscription, Task, UpdateGlobal, WeakEntity,
|
UpdateGlobal, WeakEntity, prelude::*,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
|
AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
|
||||||
};
|
};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_library::{open_prompt_library, PromptLibrary};
|
use prompt_library::{PromptLibrary, open_prompt_library};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{BufferSearchBar, buffer_search::DivRegistrar};
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{Settings, update_settings_file};
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
|
use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||||
use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
|
use ui::{ContextMenu, PopoverMenu, Tooltip, prelude::*};
|
||||||
use util::{maybe, ResultExt};
|
use util::{ResultExt, maybe};
|
||||||
use workspace::DraggedTab;
|
use workspace::DraggedTab;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,
|
pane,
|
||||||
};
|
};
|
||||||
use zed_actions::assistant::{DeployPromptLibrary, InlineAssist, ToggleFocus};
|
use zed_actions::assistant::{InlineAssist, OpenPromptLibrary, ToggleFocus};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
||||||
@@ -58,8 +60,7 @@ pub fn init(cx: &mut App) {
|
|||||||
|
|
||||||
cx.observe_new(
|
cx.observe_new(
|
||||||
|terminal_panel: &mut TerminalPanel, _, cx: &mut Context<TerminalPanel>| {
|
|terminal_panel: &mut TerminalPanel, _, cx: &mut Context<TerminalPanel>| {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
terminal_panel.set_assistant_enabled(Assistant::enabled(cx), cx);
|
||||||
terminal_panel.set_assistant_enabled(settings.enabled, cx);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
@@ -98,16 +99,16 @@ impl AssistantPanel {
|
|||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: AsyncWindowContext,
|
cx: AsyncWindowContext,
|
||||||
) -> Task<Result<Entity<Self>>> {
|
) -> Task<Result<Entity<Self>>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(async move |cx| {
|
||||||
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
||||||
let context_store = workspace
|
let context_store = workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
|
ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
// TODO: deserialize state.
|
// TODO: deserialize state.
|
||||||
cx.new(|cx| Self::new(workspace, context_store, window, cx))
|
cx.new(|cx| Self::new(workspace, context_store, window, cx))
|
||||||
})
|
})
|
||||||
@@ -259,7 +260,7 @@ impl AssistantPanel {
|
|||||||
menu.context(focus_handle.clone())
|
menu.context(focus_handle.clone())
|
||||||
.action("New Chat", Box::new(NewChat))
|
.action("New Chat", Box::new(NewChat))
|
||||||
.action("History", Box::new(DeployHistory))
|
.action("History", Box::new(DeployHistory))
|
||||||
.action("Prompt Library", Box::new(DeployPromptLibrary))
|
.action("Prompt Library", Box::new(OpenPromptLibrary))
|
||||||
.action("Configure", Box::new(ShowConfiguration))
|
.action("Configure", Box::new(ShowConfiguration))
|
||||||
.action(zoom_label, Box::new(ToggleZoom))
|
.action(zoom_label, Box::new(ToggleZoom))
|
||||||
}))
|
}))
|
||||||
@@ -297,7 +298,8 @@ impl AssistantPanel {
|
|||||||
&LanguageModelRegistry::global(cx),
|
&LanguageModelRegistry::global(cx),
|
||||||
window,
|
window,
|
||||||
|this, _, event: &language_model::Event, window, cx| match event {
|
|this, _, event: &language_model::Event, window, cx| match event {
|
||||||
language_model::Event::ActiveModelChanged => {
|
language_model::Event::ActiveModelChanged
|
||||||
|
| language_model::Event::EditorModelChanged => {
|
||||||
this.completion_provider_changed(window, cx);
|
this.completion_provider_changed(window, cx);
|
||||||
}
|
}
|
||||||
language_model::Event::ProviderStateChanged => {
|
language_model::Event::ProviderStateChanged => {
|
||||||
@@ -341,12 +343,12 @@ impl AssistantPanel {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
if workspace
|
||||||
if !settings.enabled {
|
.panel::<Self>(cx)
|
||||||
return;
|
.is_some_and(|panel| panel.read(cx).enabled(cx))
|
||||||
|
{
|
||||||
|
workspace.toggle_panel_focus::<Self>(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace.toggle_panel_focus::<Self>(window, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch_client_status(
|
fn watch_client_status(
|
||||||
@@ -356,9 +358,9 @@ impl AssistantPanel {
|
|||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let mut status_rx = client.status();
|
let mut status_rx = client.status();
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
while let Some(status) = status_rx.next().await {
|
while let Some(status) = status_rx.next().await {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if this.client_status.is_none()
|
if this.client_status.is_none()
|
||||||
|| this
|
|| this
|
||||||
.client_status
|
.client_status
|
||||||
@@ -370,7 +372,7 @@ impl AssistantPanel {
|
|||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
this.update(&mut cx, |this, _cx| this.watch_client_status = None)
|
this.update(cx, |this, _cx| this.watch_client_status = None)
|
||||||
.log_err();
|
.log_err();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -575,11 +577,11 @@ impl AssistantPanel {
|
|||||||
if self.authenticate_provider_task.is_none() {
|
if self.authenticate_provider_task.is_none() {
|
||||||
self.authenticate_provider_task = Some((
|
self.authenticate_provider_task = Some((
|
||||||
provider.id(),
|
provider.id(),
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Some(future) = load_credentials {
|
if let Some(future) = load_credentials {
|
||||||
let _ = future.await;
|
let _ = future.await;
|
||||||
}
|
}
|
||||||
this.update(&mut cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
this.authenticate_provider_task = None;
|
this.authenticate_provider_task = None;
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
@@ -594,12 +596,10 @@ impl AssistantPanel {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
let Some(assistant_panel) = workspace
|
||||||
if !settings.enabled {
|
.panel::<AssistantPanel>(cx)
|
||||||
return;
|
.filter(|panel| panel.read(cx).enabled(cx))
|
||||||
}
|
else {
|
||||||
|
|
||||||
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -640,9 +640,9 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let assistant_panel = assistant_panel.downgrade();
|
let assistant_panel = assistant_panel.downgrade();
|
||||||
cx.spawn_in(window, |workspace, mut cx| async move {
|
cx.spawn_in(window, async move |workspace, cx| {
|
||||||
let Some(task) =
|
let Some(task) =
|
||||||
assistant_panel.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
|
assistant_panel.update(cx, |assistant, cx| assistant.authenticate(cx))?
|
||||||
else {
|
else {
|
||||||
let answer = cx
|
let answer = cx
|
||||||
.prompt(
|
.prompt(
|
||||||
@@ -664,7 +664,7 @@ impl AssistantPanel {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
task.await?;
|
task.await?;
|
||||||
if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
|
if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx))? {
|
||||||
cx.update(|window, cx| match inline_assist_target {
|
cx.update(|window, cx| match inline_assist_target {
|
||||||
InlineAssistTarget::Editor(active_editor, include_context) => {
|
InlineAssistTarget::Editor(active_editor, include_context) => {
|
||||||
let assistant_panel = if include_context {
|
let assistant_panel = if include_context {
|
||||||
@@ -697,7 +697,7 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx)
|
workspace.focus_panel::<AssistantPanel>(window, cx)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -790,10 +790,10 @@ impl AssistantPanel {
|
|||||||
.context_store
|
.context_store
|
||||||
.update(cx, |store, cx| store.create_remote_context(cx));
|
.update(cx, |store, cx| store.create_remote_context(cx));
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let context = task.await?;
|
let context = task.await?;
|
||||||
|
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let workspace = this.workspace.clone();
|
let workspace = this.workspace.clone();
|
||||||
let project = this.project.clone();
|
let project = this.project.clone();
|
||||||
let lsp_adapter_delegate =
|
let lsp_adapter_delegate =
|
||||||
@@ -846,9 +846,9 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
self.show_context(editor.clone(), window, cx);
|
self.show_context(editor.clone(), window, cx);
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
cx.spawn_in(window, move |_, mut cx| async move {
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
workspace
|
workspace
|
||||||
.update_in(&mut cx, |workspace, window, cx| {
|
.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
@@ -1027,7 +1027,7 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
fn deploy_prompt_library(
|
fn deploy_prompt_library(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &DeployPromptLibrary,
|
_: &OpenPromptLibrary,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
@@ -1068,8 +1068,8 @@ impl AssistantPanel {
|
|||||||
.filter(|editor| editor.read(cx).context().read(cx).path() == Some(&path))
|
.filter(|editor| editor.read(cx).context().read(cx).path() == Some(&path))
|
||||||
});
|
});
|
||||||
if let Some(existing_context) = existing_context {
|
if let Some(existing_context) = existing_context {
|
||||||
return cx.spawn_in(window, |this, mut cx| async move {
|
return cx.spawn_in(window, async move |this, cx| {
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.show_context(existing_context, window, cx)
|
this.show_context(existing_context, window, cx)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -1084,9 +1084,9 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let context = context.await?;
|
let context = context.await?;
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let editor = cx.new(|cx| {
|
let editor = cx.new(|cx| {
|
||||||
ContextEditor::for_context(
|
ContextEditor::for_context(
|
||||||
context,
|
context,
|
||||||
@@ -1116,8 +1116,8 @@ impl AssistantPanel {
|
|||||||
.filter(|editor| *editor.read(cx).context().read(cx).id() == id)
|
.filter(|editor| *editor.read(cx).context().read(cx).id() == id)
|
||||||
});
|
});
|
||||||
if let Some(existing_context) = existing_context {
|
if let Some(existing_context) = existing_context {
|
||||||
return cx.spawn_in(window, |this, mut cx| async move {
|
return cx.spawn_in(window, async move |this, cx| {
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.show_context(existing_context.clone(), window, cx)
|
this.show_context(existing_context.clone(), window, cx)
|
||||||
})?;
|
})?;
|
||||||
Ok(existing_context)
|
Ok(existing_context)
|
||||||
@@ -1133,9 +1133,9 @@ impl AssistantPanel {
|
|||||||
.log_err()
|
.log_err()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let context = context.await?;
|
let context = context.await?;
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let editor = cx.new(|cx| {
|
let editor = cx.new(|cx| {
|
||||||
ContextEditor::for_context(
|
ContextEditor::for_context(
|
||||||
context,
|
context,
|
||||||
@@ -1297,12 +1297,8 @@ impl Panel for AssistantPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
|
fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
(self.enabled(cx) && AssistantSettings::get_global(cx).button)
|
||||||
if !settings.enabled || !settings.button {
|
.then_some(IconName::ZedAssistant)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(IconName::ZedAssistant)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
|
fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
|
||||||
@@ -1316,6 +1312,10 @@ impl Panel for AssistantPanel {
|
|||||||
fn activation_priority(&self) -> u32 {
|
fn activation_priority(&self) -> u32 {
|
||||||
4
|
4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enabled(&self, cx: &App) -> bool {
|
||||||
|
Assistant::enabled(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for AssistantPanel {}
|
impl EventEmitter<PanelEvent> for AssistantPanel {}
|
||||||
|
|||||||
@@ -1,47 +1,49 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, CyclePreviousInlineAssist,
|
Assistant, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist,
|
||||||
|
CyclePreviousInlineAssist,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use assistant_context_editor::{humanize_token_count, RequestType};
|
use assistant_context_editor::{RequestType, humanize_token_count};
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use client::{telemetry::Telemetry, ErrorExt};
|
use client::{ErrorExt, telemetry::Telemetry};
|
||||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||||
use editor::{
|
use editor::{
|
||||||
|
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
|
||||||
|
EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot,
|
||||||
|
ToOffset as _, ToPoint,
|
||||||
actions::{MoveDown, MoveUp, SelectAll},
|
actions::{MoveDown, MoveUp, SelectAll},
|
||||||
display_map::{
|
display_map::{
|
||||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||||
ToDisplayPoint,
|
ToDisplayPoint,
|
||||||
},
|
},
|
||||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
|
|
||||||
EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot,
|
|
||||||
ToOffset as _, ToPoint,
|
|
||||||
};
|
};
|
||||||
use feature_flags::{
|
use feature_flags::{
|
||||||
Assistant2FeatureFlag, FeatureFlagAppExt as _, FeatureFlagViewExt as _, ZedPro,
|
Assistant2FeatureFlag, FeatureFlagAppExt as _, FeatureFlagViewExt as _, ZedPro,
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{
|
use futures::{
|
||||||
|
SinkExt, Stream, StreamExt,
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
future::{BoxFuture, LocalBoxFuture},
|
future::{BoxFuture, LocalBoxFuture},
|
||||||
join, SinkExt, Stream, StreamExt,
|
join,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, point, AnyElement, App, ClickEvent, Context, CursorStyle, Entity,
|
AnyElement, App, ClickEvent, Context, CursorStyle, Entity, EventEmitter, FocusHandle,
|
||||||
EventEmitter, FocusHandle, Focusable, FontWeight, Global, HighlightStyle, Subscription, Task,
|
Focusable, FontWeight, Global, HighlightStyle, Subscription, Task, TextStyle, UpdateGlobal,
|
||||||
TextStyle, UpdateGlobal, WeakEntity, Window,
|
WeakEntity, Window, anchored, deferred, point,
|
||||||
};
|
};
|
||||||
use language::{line_diff, Buffer, IndentKind, Point, Selection, TransactionId};
|
use language::{Buffer, IndentKind, Point, Selection, TransactionId, line_diff};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
LanguageModelTextStream, Role, report_assistant_event,
|
||||||
};
|
};
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{ActionVariant, CodeAction, ProjectTransaction};
|
use project::{CodeAction, LspAction, ProjectTransaction};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
use settings::{update_settings_file, Settings, SettingsStore};
|
use settings::{Settings, SettingsStore, update_settings_file};
|
||||||
use smol::future::FutureExt;
|
use smol::future::FutureExt;
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
@@ -60,10 +62,10 @@ use terminal_view::terminal_panel::TerminalPanel;
|
|||||||
use text::{OffsetRangeExt, ToPoint as _};
|
use text::{OffsetRangeExt, ToPoint as _};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
|
CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip, prelude::*, text_for_action,
|
||||||
};
|
};
|
||||||
use util::{RangeExt, ResultExt};
|
use util::{RangeExt, ResultExt};
|
||||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
use workspace::{ItemHandle, Toast, Workspace, notifications::NotificationId};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
@@ -232,7 +234,7 @@ impl InlineAssistant {
|
|||||||
) {
|
) {
|
||||||
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
||||||
(
|
(
|
||||||
editor.buffer().read(cx).snapshot(cx),
|
editor.snapshot(window, cx),
|
||||||
editor.selections.all::<Point>(cx),
|
editor.selections.all::<Point>(cx),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -246,7 +248,37 @@ impl InlineAssistant {
|
|||||||
if selection.end.column == 0 {
|
if selection.end.column == 0 {
|
||||||
selection.end.row -= 1;
|
selection.end.row -= 1;
|
||||||
}
|
}
|
||||||
selection.end.column = snapshot.line_len(MultiBufferRow(selection.end.row));
|
selection.end.column = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.line_len(MultiBufferRow(selection.end.row));
|
||||||
|
} else if let Some(fold) =
|
||||||
|
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||||
|
{
|
||||||
|
selection.start = fold.range().start;
|
||||||
|
selection.end = fold.range().end;
|
||||||
|
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
|
||||||
|
let chars = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.chars_at(Point::new(selection.end.row + 1, 0));
|
||||||
|
|
||||||
|
for c in chars {
|
||||||
|
if c == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if c.is_whitespace() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if snapshot
|
||||||
|
.language_at(selection.end)
|
||||||
|
.is_some_and(|language| language.config().brackets.is_closing_brace(c))
|
||||||
|
{
|
||||||
|
selection.end.row += 1;
|
||||||
|
selection.end.column = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.line_len(MultiBufferRow(selection.end.row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(prev_selection) = selections.last_mut() {
|
if let Some(prev_selection) = selections.last_mut() {
|
||||||
@@ -262,6 +294,7 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
selections.push(selection);
|
selections.push(selection);
|
||||||
}
|
}
|
||||||
|
let snapshot = &snapshot.buffer_snapshot;
|
||||||
let newest_selection = newest_selection.unwrap();
|
let newest_selection = newest_selection.unwrap();
|
||||||
|
|
||||||
let mut codegen_ranges = Vec::new();
|
let mut codegen_ranges = Vec::new();
|
||||||
@@ -386,7 +419,6 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn suggest_assist(
|
pub fn suggest_assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &Entity<Editor>,
|
editor: &Entity<Editor>,
|
||||||
@@ -1247,7 +1279,7 @@ impl InlineAssistant {
|
|||||||
});
|
});
|
||||||
|
|
||||||
enum DeletedLines {}
|
enum DeletedLines {}
|
||||||
let mut editor = Editor::for_multibuffer(multi_buffer, None, true, window, cx);
|
let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
|
||||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_gutter(false, cx);
|
editor.set_show_gutter(false, cx);
|
||||||
@@ -1312,9 +1344,9 @@ impl EditorInlineAssists {
|
|||||||
assist_ids: Vec::new(),
|
assist_ids: Vec::new(),
|
||||||
scroll_lock: None,
|
scroll_lock: None,
|
||||||
highlight_updates: highlight_updates_tx,
|
highlight_updates: highlight_updates_tx,
|
||||||
_update_highlights: cx.spawn(|cx| {
|
_update_highlights: cx.spawn({
|
||||||
let editor = editor.downgrade();
|
let editor = editor.downgrade();
|
||||||
async move {
|
async move |cx| {
|
||||||
while let Ok(()) = highlight_updates_rx.changed().await {
|
while let Ok(()) = highlight_updates_rx.changed().await {
|
||||||
let editor = editor.upgrade().context("editor was dropped")?;
|
let editor = editor.upgrade().context("editor was dropped")?;
|
||||||
cx.update_global(|assistant: &mut InlineAssistant, cx| {
|
cx.update_global(|assistant: &mut InlineAssistant, cx| {
|
||||||
@@ -1674,7 +1706,6 @@ impl Focusable for PromptEditor {
|
|||||||
impl PromptEditor {
|
impl PromptEditor {
|
||||||
const MAX_LINES: u8 = 8;
|
const MAX_LINES: u8 = 8;
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn new(
|
fn new(
|
||||||
id: InlineAssistId,
|
id: InlineAssistId,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
@@ -1695,7 +1726,6 @@ impl PromptEditor {
|
|||||||
},
|
},
|
||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -1853,7 +1883,7 @@ impl PromptEditor {
|
|||||||
|
|
||||||
fn count_tokens(&mut self, cx: &mut Context<Self>) {
|
fn count_tokens(&mut self, cx: &mut Context<Self>) {
|
||||||
let assist_id = self.id;
|
let assist_id = self.id;
|
||||||
self.pending_token_count = cx.spawn(|this, mut cx| async move {
|
self.pending_token_count = cx.spawn(async move |this, cx| {
|
||||||
cx.background_executor().timer(Duration::from_secs(1)).await;
|
cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||||
let token_count = cx
|
let token_count = cx
|
||||||
.update_global(|inline_assistant: &mut InlineAssistant, cx| {
|
.update_global(|inline_assistant: &mut InlineAssistant, cx| {
|
||||||
@@ -1865,7 +1895,7 @@ impl PromptEditor {
|
|||||||
})??
|
})??
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.token_counts = Some(token_count);
|
this.token_counts = Some(token_count);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -2333,7 +2363,6 @@ struct InlineAssist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InlineAssist {
|
impl InlineAssist {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn new(
|
fn new(
|
||||||
assist_id: InlineAssistId,
|
assist_id: InlineAssistId,
|
||||||
group_id: InlineAssistGroupId,
|
group_id: InlineAssistGroupId,
|
||||||
@@ -2886,7 +2915,7 @@ impl CodegenAlternative {
|
|||||||
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
|
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
|
||||||
self.request = Some(request.clone());
|
self.request = Some(request.clone());
|
||||||
|
|
||||||
cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
|
cx.spawn(async move |_, cx| model.stream_completion_text(request, &cx).await)
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
};
|
};
|
||||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||||
@@ -3003,213 +3032,207 @@ impl CodegenAlternative {
|
|||||||
let completion = Arc::new(Mutex::new(String::new()));
|
let completion = Arc::new(Mutex::new(String::new()));
|
||||||
let completion_clone = completion.clone();
|
let completion_clone = completion.clone();
|
||||||
|
|
||||||
self.generation = cx.spawn(|codegen, mut cx| {
|
self.generation = cx.spawn(async move |codegen, cx| {
|
||||||
async move {
|
let stream = stream.await;
|
||||||
let stream = stream.await;
|
let message_id = stream
|
||||||
let message_id = stream
|
.as_ref()
|
||||||
.as_ref()
|
.ok()
|
||||||
.ok()
|
.and_then(|stream| stream.message_id.clone());
|
||||||
.and_then(|stream| stream.message_id.clone());
|
let generate = async {
|
||||||
let generate = async {
|
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
||||||
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
let executor = cx.background_executor().clone();
|
||||||
let executor = cx.background_executor().clone();
|
let message_id = message_id.clone();
|
||||||
let message_id = message_id.clone();
|
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
||||||
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
cx.background_spawn(async move {
|
||||||
cx.background_spawn(async move {
|
let mut response_latency = None;
|
||||||
let mut response_latency = None;
|
let request_start = Instant::now();
|
||||||
let request_start = Instant::now();
|
let diff = async {
|
||||||
let diff = async {
|
let chunks = StripInvalidSpans::new(stream?.stream);
|
||||||
let chunks = StripInvalidSpans::new(stream?.stream);
|
futures::pin_mut!(chunks);
|
||||||
futures::pin_mut!(chunks);
|
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
let mut line_diff = LineDiff::default();
|
||||||
let mut line_diff = LineDiff::default();
|
|
||||||
|
|
||||||
let mut new_text = String::new();
|
let mut new_text = String::new();
|
||||||
let mut base_indent = None;
|
let mut base_indent = None;
|
||||||
let mut line_indent = None;
|
let mut line_indent = None;
|
||||||
let mut first_line = true;
|
let mut first_line = true;
|
||||||
|
|
||||||
while let Some(chunk) = chunks.next().await {
|
while let Some(chunk) = chunks.next().await {
|
||||||
if response_latency.is_none() {
|
if response_latency.is_none() {
|
||||||
response_latency = Some(request_start.elapsed());
|
response_latency = Some(request_start.elapsed());
|
||||||
}
|
}
|
||||||
let chunk = chunk?;
|
let chunk = chunk?;
|
||||||
completion_clone.lock().push_str(&chunk);
|
completion_clone.lock().push_str(&chunk);
|
||||||
|
|
||||||
let mut lines = chunk.split('\n').peekable();
|
let mut lines = chunk.split('\n').peekable();
|
||||||
while let Some(line) = lines.next() {
|
while let Some(line) = lines.next() {
|
||||||
new_text.push_str(line);
|
new_text.push_str(line);
|
||||||
if line_indent.is_none() {
|
if line_indent.is_none() {
|
||||||
if let Some(non_whitespace_ch_ix) =
|
if let Some(non_whitespace_ch_ix) =
|
||||||
new_text.find(|ch: char| !ch.is_whitespace())
|
new_text.find(|ch: char| !ch.is_whitespace())
|
||||||
{
|
{
|
||||||
line_indent = Some(non_whitespace_ch_ix);
|
line_indent = Some(non_whitespace_ch_ix);
|
||||||
base_indent = base_indent.or(line_indent);
|
base_indent = base_indent.or(line_indent);
|
||||||
|
|
||||||
let line_indent = line_indent.unwrap();
|
let line_indent = line_indent.unwrap();
|
||||||
let base_indent = base_indent.unwrap();
|
let base_indent = base_indent.unwrap();
|
||||||
let indent_delta =
|
let indent_delta =
|
||||||
line_indent as i32 - base_indent as i32;
|
line_indent as i32 - base_indent as i32;
|
||||||
let mut corrected_indent_len = cmp::max(
|
let mut corrected_indent_len = cmp::max(
|
||||||
0,
|
0,
|
||||||
suggested_line_indent.len as i32 + indent_delta,
|
suggested_line_indent.len as i32 + indent_delta,
|
||||||
)
|
)
|
||||||
as usize;
|
as usize;
|
||||||
if first_line {
|
if first_line {
|
||||||
corrected_indent_len = corrected_indent_len
|
corrected_indent_len = corrected_indent_len
|
||||||
.saturating_sub(
|
.saturating_sub(
|
||||||
selection_start.column as usize,
|
selection_start.column as usize,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let indent_char = suggested_line_indent.char();
|
|
||||||
let mut indent_buffer = [0; 4];
|
|
||||||
let indent_str =
|
|
||||||
indent_char.encode_utf8(&mut indent_buffer);
|
|
||||||
new_text.replace_range(
|
|
||||||
..line_indent,
|
|
||||||
&indent_str.repeat(corrected_indent_len),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if line_indent.is_some() {
|
let indent_char = suggested_line_indent.char();
|
||||||
let char_ops = diff.push_new(&new_text);
|
let mut indent_buffer = [0; 4];
|
||||||
line_diff
|
let indent_str =
|
||||||
.push_char_operations(&char_ops, &selected_text);
|
indent_char.encode_utf8(&mut indent_buffer);
|
||||||
diff_tx
|
new_text.replace_range(
|
||||||
.send((char_ops, line_diff.line_operations()))
|
..line_indent,
|
||||||
.await?;
|
&indent_str.repeat(corrected_indent_len),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if line_indent.is_some() {
|
||||||
|
let char_ops = diff.push_new(&new_text);
|
||||||
|
line_diff.push_char_operations(&char_ops, &selected_text);
|
||||||
|
diff_tx
|
||||||
|
.send((char_ops, line_diff.line_operations()))
|
||||||
|
.await?;
|
||||||
|
new_text.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
let char_ops = diff.push_new("\n");
|
||||||
|
line_diff.push_char_operations(&char_ops, &selected_text);
|
||||||
|
diff_tx
|
||||||
|
.send((char_ops, line_diff.line_operations()))
|
||||||
|
.await?;
|
||||||
|
if line_indent.is_none() {
|
||||||
|
// Don't write out the leading indentation in empty lines on the next line
|
||||||
|
// This is the case where the above if statement didn't clear the buffer
|
||||||
new_text.clear();
|
new_text.clear();
|
||||||
}
|
}
|
||||||
|
line_indent = None;
|
||||||
if lines.peek().is_some() {
|
first_line = false;
|
||||||
let char_ops = diff.push_new("\n");
|
|
||||||
line_diff
|
|
||||||
.push_char_operations(&char_ops, &selected_text);
|
|
||||||
diff_tx
|
|
||||||
.send((char_ops, line_diff.line_operations()))
|
|
||||||
.await?;
|
|
||||||
if line_indent.is_none() {
|
|
||||||
// Don't write out the leading indentation in empty lines on the next line
|
|
||||||
// This is the case where the above if statement didn't clear the buffer
|
|
||||||
new_text.clear();
|
|
||||||
}
|
|
||||||
line_indent = None;
|
|
||||||
first_line = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut char_ops = diff.push_new(&new_text);
|
|
||||||
char_ops.extend(diff.finish());
|
|
||||||
line_diff.push_char_operations(&char_ops, &selected_text);
|
|
||||||
line_diff.finish(&selected_text);
|
|
||||||
diff_tx
|
|
||||||
.send((char_ops, line_diff.line_operations()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = diff.await;
|
|
||||||
|
|
||||||
let error_message =
|
|
||||||
result.as_ref().err().map(|error| error.to_string());
|
|
||||||
report_assistant_event(
|
|
||||||
AssistantEvent {
|
|
||||||
conversation_id: None,
|
|
||||||
message_id,
|
|
||||||
kind: AssistantKind::Inline,
|
|
||||||
phase: AssistantPhase::Response,
|
|
||||||
model: model_telemetry_id,
|
|
||||||
model_provider: model_provider_id.to_string(),
|
|
||||||
response_latency,
|
|
||||||
error_message,
|
|
||||||
language_name: language_name.map(|name| name.to_proto()),
|
|
||||||
},
|
|
||||||
telemetry,
|
|
||||||
http_client,
|
|
||||||
model_api_key,
|
|
||||||
&executor,
|
|
||||||
);
|
|
||||||
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
|
||||||
codegen.update(&mut cx, |codegen, cx| {
|
|
||||||
codegen.last_equal_ranges.clear();
|
|
||||||
|
|
||||||
let edits = char_ops
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|operation| match operation {
|
|
||||||
CharOperation::Insert { text } => {
|
|
||||||
let edit_start = snapshot.anchor_after(edit_start);
|
|
||||||
Some((edit_start..edit_start, text))
|
|
||||||
}
|
|
||||||
CharOperation::Delete { bytes } => {
|
|
||||||
let edit_end = edit_start + bytes;
|
|
||||||
let edit_range = snapshot.anchor_after(edit_start)
|
|
||||||
..snapshot.anchor_before(edit_end);
|
|
||||||
edit_start = edit_end;
|
|
||||||
Some((edit_range, String::new()))
|
|
||||||
}
|
|
||||||
CharOperation::Keep { bytes } => {
|
|
||||||
let edit_end = edit_start + bytes;
|
|
||||||
let edit_range = snapshot.anchor_after(edit_start)
|
|
||||||
..snapshot.anchor_before(edit_end);
|
|
||||||
edit_start = edit_end;
|
|
||||||
codegen.last_equal_ranges.push(edit_range);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if codegen.active {
|
|
||||||
codegen.apply_edits(edits.iter().cloned(), cx);
|
|
||||||
codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
|
|
||||||
}
|
}
|
||||||
codegen.edits.extend(edits);
|
|
||||||
codegen.line_operations = line_ops;
|
|
||||||
codegen.edit_position = Some(snapshot.anchor_after(edit_start));
|
|
||||||
|
|
||||||
cx.notify();
|
let mut char_ops = diff.push_new(&new_text);
|
||||||
})?;
|
char_ops.extend(diff.finish());
|
||||||
}
|
line_diff.push_char_operations(&char_ops, &selected_text);
|
||||||
|
line_diff.finish(&selected_text);
|
||||||
|
diff_tx
|
||||||
|
.send((char_ops, line_diff.line_operations()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
|
anyhow::Ok(())
|
||||||
// That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
|
};
|
||||||
// It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
|
|
||||||
let batch_diff_task =
|
|
||||||
codegen.update(&mut cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
|
|
||||||
let (line_based_stream_diff, ()) =
|
|
||||||
join!(line_based_stream_diff, batch_diff_task);
|
|
||||||
line_based_stream_diff?;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
let result = diff.await;
|
||||||
};
|
|
||||||
|
|
||||||
let result = generate.await;
|
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||||
let elapsed_time = start_time.elapsed().as_secs_f64();
|
report_assistant_event(
|
||||||
|
AssistantEvent {
|
||||||
|
conversation_id: None,
|
||||||
|
message_id,
|
||||||
|
kind: AssistantKind::Inline,
|
||||||
|
phase: AssistantPhase::Response,
|
||||||
|
model: model_telemetry_id,
|
||||||
|
model_provider: model_provider_id.to_string(),
|
||||||
|
response_latency,
|
||||||
|
error_message,
|
||||||
|
language_name: language_name.map(|name| name.to_proto()),
|
||||||
|
},
|
||||||
|
telemetry,
|
||||||
|
http_client,
|
||||||
|
model_api_key,
|
||||||
|
&executor,
|
||||||
|
);
|
||||||
|
|
||||||
codegen
|
result?;
|
||||||
.update(&mut cx, |this, cx| {
|
Ok(())
|
||||||
this.message_id = message_id;
|
});
|
||||||
this.last_equal_ranges.clear();
|
|
||||||
if let Err(error) = result {
|
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
||||||
this.status = CodegenStatus::Error(error);
|
codegen.update(cx, |codegen, cx| {
|
||||||
} else {
|
codegen.last_equal_ranges.clear();
|
||||||
this.status = CodegenStatus::Done;
|
|
||||||
|
let edits = char_ops
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|operation| match operation {
|
||||||
|
CharOperation::Insert { text } => {
|
||||||
|
let edit_start = snapshot.anchor_after(edit_start);
|
||||||
|
Some((edit_start..edit_start, text))
|
||||||
|
}
|
||||||
|
CharOperation::Delete { bytes } => {
|
||||||
|
let edit_end = edit_start + bytes;
|
||||||
|
let edit_range = snapshot.anchor_after(edit_start)
|
||||||
|
..snapshot.anchor_before(edit_end);
|
||||||
|
edit_start = edit_end;
|
||||||
|
Some((edit_range, String::new()))
|
||||||
|
}
|
||||||
|
CharOperation::Keep { bytes } => {
|
||||||
|
let edit_end = edit_start + bytes;
|
||||||
|
let edit_range = snapshot.anchor_after(edit_start)
|
||||||
|
..snapshot.anchor_before(edit_end);
|
||||||
|
edit_start = edit_end;
|
||||||
|
codegen.last_equal_ranges.push(edit_range);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if codegen.active {
|
||||||
|
codegen.apply_edits(edits.iter().cloned(), cx);
|
||||||
|
codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
|
||||||
}
|
}
|
||||||
this.elapsed_time = Some(elapsed_time);
|
codegen.edits.extend(edits);
|
||||||
this.completion = Some(completion.lock().clone());
|
codegen.line_operations = line_ops;
|
||||||
cx.emit(CodegenEvent::Finished);
|
codegen.edit_position = Some(snapshot.anchor_after(edit_start));
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})?;
|
||||||
.ok();
|
}
|
||||||
}
|
|
||||||
|
// Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
|
||||||
|
// That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
|
||||||
|
// It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
|
||||||
|
let batch_diff_task =
|
||||||
|
codegen.update(cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
|
||||||
|
let (line_based_stream_diff, ()) = join!(line_based_stream_diff, batch_diff_task);
|
||||||
|
line_based_stream_diff?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = generate.await;
|
||||||
|
let elapsed_time = start_time.elapsed().as_secs_f64();
|
||||||
|
|
||||||
|
codegen
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.message_id = message_id;
|
||||||
|
this.last_equal_ranges.clear();
|
||||||
|
if let Err(error) = result {
|
||||||
|
this.status = CodegenStatus::Error(error);
|
||||||
|
} else {
|
||||||
|
this.status = CodegenStatus::Done;
|
||||||
|
}
|
||||||
|
this.elapsed_time = Some(elapsed_time);
|
||||||
|
this.completion = Some(completion.lock().clone());
|
||||||
|
cx.emit(CodegenEvent::Finished);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -3327,7 +3350,7 @@ impl CodegenAlternative {
|
|||||||
let new_snapshot = self.buffer.read(cx).snapshot(cx);
|
let new_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
let new_range = self.range.to_point(&new_snapshot);
|
let new_range = self.range.to_point(&new_snapshot);
|
||||||
|
|
||||||
cx.spawn(|codegen, mut cx| async move {
|
cx.spawn(async move |codegen, cx| {
|
||||||
let (deleted_row_ranges, inserted_row_ranges) = cx
|
let (deleted_row_ranges, inserted_row_ranges) = cx
|
||||||
.background_spawn(async move {
|
.background_spawn(async move {
|
||||||
let old_text = old_snapshot
|
let old_text = old_snapshot
|
||||||
@@ -3377,7 +3400,7 @@ impl CodegenAlternative {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
codegen
|
codegen
|
||||||
.update(&mut cx, |codegen, cx| {
|
.update(cx, |codegen, cx| {
|
||||||
codegen.diff.deleted_row_ranges = deleted_row_ranges;
|
codegen.diff.deleted_row_ranges = deleted_row_ranges;
|
||||||
codegen.diff.inserted_row_ranges = inserted_row_ranges;
|
codegen.diff.inserted_row_ranges = inserted_row_ranges;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -3534,7 +3557,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<Vec<CodeAction>>> {
|
) -> Task<Result<Vec<CodeAction>>> {
|
||||||
if !AssistantSettings::get_global(cx).enabled {
|
if !Assistant::enabled(cx) {
|
||||||
return Task::ready(Ok(Vec::new()));
|
return Task::ready(Ok(Vec::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3569,10 +3592,11 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
Task::ready(Ok(vec![CodeAction {
|
Task::ready(Ok(vec![CodeAction {
|
||||||
server_id: language::LanguageServerId(0),
|
server_id: language::LanguageServerId(0),
|
||||||
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
||||||
lsp_action: ActionVariant::Action(Box::new(lsp::CodeAction {
|
lsp_action: LspAction::Action(Box::new(lsp::CodeAction {
|
||||||
title: "Fix with Assistant".into(),
|
title: "Fix with Assistant".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
|
resolved: true,
|
||||||
}]))
|
}]))
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(Vec::new()))
|
Task::ready(Ok(Vec::new()))
|
||||||
@@ -3590,10 +3614,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
) -> Task<Result<ProjectTransaction>> {
|
) -> Task<Result<ProjectTransaction>> {
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
window.spawn(cx, |mut cx| async move {
|
window.spawn(cx, async move |cx| {
|
||||||
let editor = editor.upgrade().context("editor was released")?;
|
let editor = editor.upgrade().context("editor was released")?;
|
||||||
let range = editor
|
let range = editor
|
||||||
.update(&mut cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
editor.buffer().update(cx, |multibuffer, cx| {
|
editor.buffer().update(cx, |multibuffer, cx| {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let multibuffer_snapshot = multibuffer.read(cx);
|
let multibuffer_snapshot = multibuffer.read(cx);
|
||||||
@@ -3628,7 +3652,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.context("invalid range")?;
|
.context("invalid range")?;
|
||||||
let assistant_panel = workspace.update(&mut cx, |workspace, cx| {
|
let assistant_panel = workspace.update(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.panel::<AssistantPanel>(cx)
|
.panel::<AssistantPanel>(cx)
|
||||||
.context("assistant panel was released")
|
.context("assistant panel was released")
|
||||||
@@ -3687,10 +3711,10 @@ mod tests {
|
|||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
|
Buffer, Language, LanguageConfig, LanguageMatcher, Point, language_settings,
|
||||||
Point,
|
tree_sitter_rust,
|
||||||
};
|
};
|
||||||
use language_model::LanguageModelRegistry;
|
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@@ -4069,6 +4093,7 @@ mod tests {
|
|||||||
future::ready(Ok(LanguageModelTextStream {
|
future::ready(Ok(LanguageModelTextStream {
|
||||||
message_id: None,
|
message_id: None,
|
||||||
stream: chunks_rx.map(Ok).boxed(),
|
stream: chunks_rx.map(Ok).boxed(),
|
||||||
|
last_token_usage: Arc::new(Mutex::new(TokenUsage::default())),
|
||||||
})),
|
})),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
use crate::{AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
|
use crate::{AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use assistant_context_editor::{humanize_token_count, RequestType};
|
use assistant_context_editor::{RequestType, humanize_token_count};
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{MoveDown, MoveUp, SelectAll},
|
|
||||||
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||||
|
actions::{MoveDown, MoveUp, SelectAll},
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription, Task,
|
App, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription, Task,
|
||||||
TextStyle, UpdateGlobal, WeakEntity,
|
TextStyle, UpdateGlobal, WeakEntity,
|
||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||||
LanguageModelRequestMessage, Role,
|
report_assistant_event,
|
||||||
};
|
};
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{Settings, update_settings_file};
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -31,9 +31,9 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
|||||||
use terminal::Terminal;
|
use terminal::Terminal;
|
||||||
use terminal_view::TerminalView;
|
use terminal_view::TerminalView;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
|
use ui::{IconButtonShape, Tooltip, prelude::*, text_for_action};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{notifications::NotificationId, Toast, Workspace};
|
use workspace::{Toast, Workspace, notifications::NotificationId};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
@@ -702,7 +702,6 @@ impl Focusable for PromptEditor {
|
|||||||
impl PromptEditor {
|
impl PromptEditor {
|
||||||
const MAX_LINES: u8 = 8;
|
const MAX_LINES: u8 = 8;
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn new(
|
fn new(
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
@@ -721,7 +720,6 @@ impl PromptEditor {
|
|||||||
},
|
},
|
||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -827,7 +825,7 @@ impl PromptEditor {
|
|||||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.pending_token_count = cx.spawn(|this, mut cx| async move {
|
self.pending_token_count = cx.spawn(async move |this, cx| {
|
||||||
cx.background_executor().timer(Duration::from_secs(1)).await;
|
cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||||
let request =
|
let request =
|
||||||
cx.update_global(|inline_assistant: &mut TerminalInlineAssistant, cx| {
|
cx.update_global(|inline_assistant: &mut TerminalInlineAssistant, cx| {
|
||||||
@@ -835,7 +833,7 @@ impl PromptEditor {
|
|||||||
})??;
|
})??;
|
||||||
|
|
||||||
let token_count = cx.update(|cx| model.count_tokens(request, cx))?.await?;
|
let token_count = cx.update(|cx| model.count_tokens(request, cx))?.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.token_count = Some(token_count);
|
this.token_count = Some(token_count);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -1142,7 +1140,7 @@ impl Codegen {
|
|||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
self.status = CodegenStatus::Pending;
|
self.status = CodegenStatus::Pending;
|
||||||
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
||||||
self.generation = cx.spawn(|this, mut cx| async move {
|
self.generation = cx.spawn(async move |this, cx| {
|
||||||
let model_telemetry_id = model.telemetry_id();
|
let model_telemetry_id = model.telemetry_id();
|
||||||
let model_provider_id = model.provider_id();
|
let model_provider_id = model.provider_id();
|
||||||
let response = model.stream_completion_text(prompt, &cx).await;
|
let response = model.stream_completion_text(prompt, &cx).await;
|
||||||
@@ -1199,12 +1197,12 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(cx, |this, _| {
|
||||||
this.message_id = message_id;
|
this.message_id = message_id;
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
while let Some(hunk) = hunks_rx.next().await {
|
while let Some(hunk) = hunks_rx.next().await {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if let Some(transaction) = &mut this.transaction {
|
if let Some(transaction) = &mut this.transaction {
|
||||||
transaction.push(hunk, cx);
|
transaction.push(hunk, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -1218,7 +1216,7 @@ impl Codegen {
|
|||||||
|
|
||||||
let result = generate.await;
|
let result = generate.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
this.status = CodegenStatus::Error(error);
|
this.status = CodegenStatus::Error(error);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ assistant_settings.workspace = true
|
|||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
async-watch.workspace = true
|
async-watch.workspace = true
|
||||||
|
buffer_diff.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
context_server.workspace = true
|
context_server.workspace = true
|
||||||
|
convert_case.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
@@ -38,10 +40,12 @@ file_icons.workspace = true
|
|||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
|
git.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
heed.workspace = true
|
heed.workspace = true
|
||||||
html_to_markdown.workspace = true
|
html_to_markdown.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
|
indexmap.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
@@ -51,6 +55,7 @@ lsp.workspace = true
|
|||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
|
ordered-float.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
@@ -58,12 +63,16 @@ project.workspace = true
|
|||||||
prompt_library.workspace = true
|
prompt_library.workspace = true
|
||||||
prompt_store.workspace = true
|
prompt_store.workspace = true
|
||||||
proto.workspace = true
|
proto.workspace = true
|
||||||
|
release_channel.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
streaming_diff.workspace = true
|
streaming_diff.workspace = true
|
||||||
|
telemetry.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
terminal.workspace = true
|
terminal.workspace = true
|
||||||
terminal_view.workspace = true
|
terminal_view.workspace = true
|
||||||
@@ -79,10 +88,11 @@ workspace.workspace = true
|
|||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
gpui = { workspace = true, "features" = ["test-support"] }
|
gpui = { workspace = true, "features" = ["test-support"] }
|
||||||
|
indoc.workspace = true
|
||||||
language = { workspace = true, "features" = ["test-support"] }
|
language = { workspace = true, "features" = ["test-support"] }
|
||||||
language_model = { workspace = true, "features" = ["test-support"] }
|
language_model = { workspace = true, "features" = ["test-support"] }
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
indoc.workspace = true
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod active_thread;
|
mod active_thread;
|
||||||
mod assistant_configuration;
|
mod assistant_configuration;
|
||||||
|
mod assistant_diff;
|
||||||
mod assistant_model_selector;
|
mod assistant_model_selector;
|
||||||
mod assistant_panel;
|
mod assistant_panel;
|
||||||
mod buffer_codegen;
|
mod buffer_codegen;
|
||||||
@@ -11,6 +12,7 @@ mod history_store;
|
|||||||
mod inline_assistant;
|
mod inline_assistant;
|
||||||
mod inline_prompt_editor;
|
mod inline_prompt_editor;
|
||||||
mod message_editor;
|
mod message_editor;
|
||||||
|
mod profile_selector;
|
||||||
mod terminal_codegen;
|
mod terminal_codegen;
|
||||||
mod terminal_inline_assistant;
|
mod terminal_inline_assistant;
|
||||||
mod thread;
|
mod thread;
|
||||||
@@ -26,12 +28,19 @@ use client::Client;
|
|||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{actions, App};
|
use gpui::{App, actions, impl_actions};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
|
|
||||||
|
pub use crate::active_thread::ActiveThread;
|
||||||
|
use crate::assistant_configuration::{AddContextServerModal, ManageProfilesModal};
|
||||||
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
|
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
|
||||||
pub use crate::inline_assistant::InlineAssistant;
|
pub use crate::inline_assistant::InlineAssistant;
|
||||||
|
pub use crate::thread::{Message, RequestKind, Thread, ThreadEvent};
|
||||||
|
pub use crate::thread_store::ThreadStore;
|
||||||
|
pub use assistant_diff::{AssistantDiff, AssistantDiffToolbar};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
assistant2,
|
assistant2,
|
||||||
@@ -39,9 +48,11 @@ actions!(
|
|||||||
NewThread,
|
NewThread,
|
||||||
NewPromptEditor,
|
NewPromptEditor,
|
||||||
ToggleContextPicker,
|
ToggleContextPicker,
|
||||||
|
ToggleProfileSelector,
|
||||||
RemoveAllContext,
|
RemoveAllContext,
|
||||||
OpenHistory,
|
OpenHistory,
|
||||||
OpenConfiguration,
|
OpenConfiguration,
|
||||||
|
AddContextServer,
|
||||||
RemoveSelectedThread,
|
RemoveSelectedThread,
|
||||||
Chat,
|
Chat,
|
||||||
ChatMode,
|
ChatMode,
|
||||||
@@ -52,10 +63,32 @@ actions!(
|
|||||||
FocusLeft,
|
FocusLeft,
|
||||||
FocusRight,
|
FocusRight,
|
||||||
RemoveFocusedContext,
|
RemoveFocusedContext,
|
||||||
AcceptSuggestedContext
|
AcceptSuggestedContext,
|
||||||
|
OpenActiveThreadAsMarkdown,
|
||||||
|
OpenAssistantDiff,
|
||||||
|
ToggleKeep,
|
||||||
|
Reject,
|
||||||
|
RejectAll,
|
||||||
|
KeepAll
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||||
|
pub struct ManageProfiles {
|
||||||
|
#[serde(default)]
|
||||||
|
pub customize_tools: Option<Arc<str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManageProfiles {
|
||||||
|
pub fn customize_tools(profile_id: Arc<str>) -> Self {
|
||||||
|
Self {
|
||||||
|
customize_tools: Some(profile_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_actions!(assistant, [ManageProfiles]);
|
||||||
|
|
||||||
const NAMESPACE: &str = "assistant2";
|
const NAMESPACE: &str = "assistant2";
|
||||||
|
|
||||||
/// Initializes the `assistant2` crate.
|
/// Initializes the `assistant2` crate.
|
||||||
@@ -81,6 +114,8 @@ pub fn init(
|
|||||||
client.telemetry().clone(),
|
client.telemetry().clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
cx.observe_new(AddContextServerModal::register).detach();
|
||||||
|
cx.observe_new(ManageProfilesModal::register).detach();
|
||||||
|
|
||||||
feature_gate_assistant2_actions(cx);
|
feature_gate_assistant2_actions(cx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,39 @@
|
|||||||
|
mod add_context_server_modal;
|
||||||
|
mod manage_profiles_modal;
|
||||||
|
mod tool_picker;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{Action, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
|
use context_server::manager::ContextServerManager;
|
||||||
|
use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription};
|
||||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||||
use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
|
use ui::{Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Switch, prelude::*};
|
||||||
use zed_actions::assistant::DeployPromptLibrary;
|
use util::ResultExt as _;
|
||||||
|
use zed_actions::ExtensionCategoryFilter;
|
||||||
|
|
||||||
|
pub(crate) use add_context_server_modal::AddContextServerModal;
|
||||||
|
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||||
|
|
||||||
|
use crate::AddContextServer;
|
||||||
|
|
||||||
pub struct AssistantConfiguration {
|
pub struct AssistantConfiguration {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
||||||
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
|
expanded_context_server_tools: HashMap<Arc<str>, bool>,
|
||||||
|
tools: Arc<ToolWorkingSet>,
|
||||||
_registry_subscription: Subscription,
|
_registry_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantConfiguration {
|
impl AssistantConfiguration {
|
||||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(
|
||||||
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
let registry_subscription = cx.subscribe_in(
|
let registry_subscription = cx.subscribe_in(
|
||||||
@@ -36,6 +56,9 @@ impl AssistantConfiguration {
|
|||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
focus_handle,
|
focus_handle,
|
||||||
configuration_views_by_provider: HashMap::default(),
|
configuration_views_by_provider: HashMap::default(),
|
||||||
|
context_server_manager,
|
||||||
|
expanded_context_server_tools: HashMap::default(),
|
||||||
|
tools,
|
||||||
_registry_subscription: registry_subscription,
|
_registry_subscription: registry_subscription,
|
||||||
};
|
};
|
||||||
this.build_provider_configuration_views(window, cx);
|
this.build_provider_configuration_views(window, cx);
|
||||||
@@ -82,7 +105,7 @@ impl AssistantConfiguration {
|
|||||||
&mut self,
|
&mut self,
|
||||||
provider: &Arc<dyn LanguageModelProvider>,
|
provider: &Arc<dyn LanguageModelProvider>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement + use<> {
|
||||||
let provider_id = provider.id().0.clone();
|
let provider_id = provider.id().0.clone();
|
||||||
let provider_name = provider.name().0.clone();
|
let provider_name = provider.name().0.clone();
|
||||||
let configuration_view = self
|
let configuration_view = self
|
||||||
@@ -143,6 +166,186 @@ impl AssistantConfiguration {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let context_servers = self.context_server_manager.read(cx).all_servers().clone();
|
||||||
|
let tools_by_source = self.tools.tools_by_source(cx);
|
||||||
|
let empty = Vec::new();
|
||||||
|
|
||||||
|
const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly.";
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.p(DynamicSpacing::Base16.rems(cx))
|
||||||
|
.gap_2()
|
||||||
|
.flex_1()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_0p5()
|
||||||
|
.child(Headline::new("Context Servers (MCP)").size(HeadlineSize::Small))
|
||||||
|
.child(Label::new(SUBHEADING).color(Color::Muted)),
|
||||||
|
)
|
||||||
|
.children(context_servers.into_iter().map(|context_server| {
|
||||||
|
let is_running = context_server.client().is_some();
|
||||||
|
let are_tools_expanded = self
|
||||||
|
.expanded_context_server_tools
|
||||||
|
.get(&context_server.id())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let tools = tools_by_source
|
||||||
|
.get(&ToolSource::ContextServer {
|
||||||
|
id: context_server.id().into(),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| &empty);
|
||||||
|
let tool_count = tools.len();
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.id(SharedString::from(context_server.id()))
|
||||||
|
.border_1()
|
||||||
|
.rounded_sm()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_between()
|
||||||
|
.px_2()
|
||||||
|
.py_1()
|
||||||
|
.when(are_tools_expanded, |element| {
|
||||||
|
element
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Disclosure::new("tool-list-disclosure", are_tools_expanded)
|
||||||
|
.on_click(cx.listener({
|
||||||
|
let context_server_id = context_server.id();
|
||||||
|
move |this, _event, _window, _cx| {
|
||||||
|
let is_open = this
|
||||||
|
.expanded_context_server_tools
|
||||||
|
.entry(context_server_id.clone())
|
||||||
|
.or_insert(false);
|
||||||
|
|
||||||
|
*is_open = !*is_open;
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(Indicator::dot().color(if is_running {
|
||||||
|
Color::Success
|
||||||
|
} else {
|
||||||
|
Color::Error
|
||||||
|
}))
|
||||||
|
.child(Label::new(context_server.id()))
|
||||||
|
.child(
|
||||||
|
Label::new(format!("{tool_count} tools"))
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(h_flex().child(
|
||||||
|
Switch::new("context-server-switch", is_running.into()).on_click({
|
||||||
|
let context_server_manager =
|
||||||
|
self.context_server_manager.clone();
|
||||||
|
let context_server = context_server.clone();
|
||||||
|
move |state, _window, cx| match state {
|
||||||
|
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||||
|
context_server_manager.update(cx, |this, cx| {
|
||||||
|
this.stop_server(context_server.clone(), cx)
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ToggleState::Selected => {
|
||||||
|
cx.spawn({
|
||||||
|
let context_server_manager =
|
||||||
|
context_server_manager.clone();
|
||||||
|
let context_server = context_server.clone();
|
||||||
|
async move |cx| {
|
||||||
|
if let Some(start_server_task) =
|
||||||
|
context_server_manager
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.start_server(
|
||||||
|
context_server,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.log_err()
|
||||||
|
{
|
||||||
|
start_server_task.await.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.map(|parent| {
|
||||||
|
if !are_tools_expanded {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.child(v_flex().children(tools.into_iter().enumerate().map(
|
||||||
|
|(ix, tool)| {
|
||||||
|
h_flex()
|
||||||
|
.px_2()
|
||||||
|
.py_1()
|
||||||
|
.when(ix < tool_count - 1, |element| {
|
||||||
|
element
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
})
|
||||||
|
.child(Label::new(tool.name()))
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.justify_between()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
h_flex().w_full().child(
|
||||||
|
Button::new("add-context-server", "Add Context Server")
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ModalSurface)
|
||||||
|
.full_width()
|
||||||
|
.icon(IconName::Plus)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(AddContextServer.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex().w_full().child(
|
||||||
|
Button::new(
|
||||||
|
"install-context-server-extensions",
|
||||||
|
"Install Context Server Extensions",
|
||||||
|
)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ModalSurface)
|
||||||
|
.full_width()
|
||||||
|
.icon(IconName::DatabaseZap)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
zed_actions::Extensions {
|
||||||
|
category_filter: Some(
|
||||||
|
ExtensionCategoryFilter::ContextServers,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.boxed_clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AssistantConfiguration {
|
impl Render for AssistantConfiguration {
|
||||||
@@ -155,32 +358,7 @@ impl Render for AssistantConfiguration {
|
|||||||
.bg(cx.theme().colors().panel_background)
|
.bg(cx.theme().colors().panel_background)
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.child(
|
.child(self.render_context_servers_section(cx))
|
||||||
v_flex()
|
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
|
||||||
.child(
|
|
||||||
Label::new("Create reusable prompts and tag which ones you want sent in every LLM interaction.")
|
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("open-prompt-library", "Open Prompt Library")
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ModalSurface)
|
|
||||||
.full_width()
|
|
||||||
.icon(IconName::Book)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.icon_position(IconPosition::Start)
|
|
||||||
.on_click(|_event, window, cx| {
|
|
||||||
window.dispatch_action(DeployPromptLibrary.boxed_clone(), cx)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(Divider::horizontal().color(DividerColor::Border))
|
.child(Divider::horizontal().color(DividerColor::Border))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
use context_server::{ContextServerSettings, ServerCommand, ServerConfig};
|
||||||
|
use editor::Editor;
|
||||||
|
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
||||||
|
use serde_json::json;
|
||||||
|
use settings::update_settings_file;
|
||||||
|
use ui::{Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||||
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
use crate::AddContextServer;
|
||||||
|
|
||||||
|
pub struct AddContextServerModal {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
name_editor: Entity<Editor>,
|
||||||
|
command_editor: Entity<Editor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddContextServerModal {
|
||||||
|
pub fn register(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_window: Option<&mut Window>,
|
||||||
|
_cx: &mut Context<Workspace>,
|
||||||
|
) {
|
||||||
|
workspace.register_action(|workspace, _: &AddContextServer, window, cx| {
|
||||||
|
let workspace_handle = cx.entity().downgrade();
|
||||||
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
|
Self::new(workspace_handle, window, cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let name_editor = cx.new(|cx| Editor::single_line(window, cx));
|
||||||
|
let command_editor = cx.new(|cx| Editor::single_line(window, cx));
|
||||||
|
|
||||||
|
name_editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_placeholder_text("Context server name", cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
command_editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_placeholder_text("Command to run the context server", cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name_editor,
|
||||||
|
command_editor,
|
||||||
|
workspace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let name = self.name_editor.read(cx).text(cx).trim().to_string();
|
||||||
|
let command = self.command_editor.read(cx).text(cx).trim().to_string();
|
||||||
|
|
||||||
|
if name.is_empty() || command.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command_parts = command.split(' ').map(|part| part.trim().to_string());
|
||||||
|
let Some(path) = command_parts.next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let args = command_parts.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(workspace) = self.workspace.upgrade() {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
let fs = workspace.app_state().fs.clone();
|
||||||
|
update_settings_file::<ContextServerSettings>(fs.clone(), cx, |settings, _| {
|
||||||
|
settings.context_servers.insert(
|
||||||
|
name.into(),
|
||||||
|
ServerConfig {
|
||||||
|
command: Some(ServerCommand {
|
||||||
|
path,
|
||||||
|
args,
|
||||||
|
env: None,
|
||||||
|
}),
|
||||||
|
settings: Some(json!({})),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&mut self, cx: &mut Context<Self>) {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModalView for AddContextServerModal {}
|
||||||
|
|
||||||
|
impl Focusable for AddContextServerModal {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
self.name_editor.focus_handle(cx).clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for AddContextServerModal {}
|
||||||
|
|
||||||
|
impl Render for AddContextServerModal {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let is_name_empty = self.name_editor.read(cx).text(cx).trim().is_empty();
|
||||||
|
let is_command_empty = self.command_editor.read(cx).text(cx).trim().is_empty();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.elevation_3(cx)
|
||||||
|
.w(rems(34.))
|
||||||
|
.key_context("AddContextServerModal")
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(cx)))
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
|
||||||
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
|
this.focus_handle(cx).focus(window);
|
||||||
|
}))
|
||||||
|
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
||||||
|
.child(
|
||||||
|
Modal::new("add-context-server", None)
|
||||||
|
.header(ModalHeader::new().headline("Add Context Server"))
|
||||||
|
.section(
|
||||||
|
Section::new()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Label::new("Name"))
|
||||||
|
.child(self.name_editor.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Label::new("Command"))
|
||||||
|
.child(self.command_editor.clone()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.footer(
|
||||||
|
ModalFooter::new()
|
||||||
|
.start_slot(
|
||||||
|
Button::new("cancel", "Cancel").on_click(
|
||||||
|
cx.listener(|this, _event, _window, cx| this.cancel(cx)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.end_slot(
|
||||||
|
Button::new("add-server", "Add Server")
|
||||||
|
.disabled(is_name_empty || is_command_empty)
|
||||||
|
.map(|button| {
|
||||||
|
if is_name_empty {
|
||||||
|
button.tooltip(Tooltip::text("Name is required"))
|
||||||
|
} else if is_command_empty {
|
||||||
|
button.tooltip(Tooltip::text("Command is required"))
|
||||||
|
} else {
|
||||||
|
button
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click(
|
||||||
|
cx.listener(|this, _event, _window, cx| this.confirm(cx)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,561 @@
|
|||||||
|
mod profile_modal_header;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use assistant_settings::{AgentProfile, AssistantSettings};
|
||||||
|
use assistant_tool::ToolWorkingSet;
|
||||||
|
use convert_case::{Case, Casing as _};
|
||||||
|
use editor::Editor;
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui::{
|
||||||
|
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, WeakEntity,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use settings::{Settings as _, update_settings_file};
|
||||||
|
use ui::{
|
||||||
|
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
|
||||||
|
};
|
||||||
|
use util::ResultExt as _;
|
||||||
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
use crate::assistant_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
|
||||||
|
use crate::assistant_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
|
||||||
|
use crate::{AssistantPanel, ManageProfiles, ThreadStore};
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
ChooseProfile(ChooseProfileMode),
|
||||||
|
NewProfile(NewProfileMode),
|
||||||
|
ViewProfile(ViewProfileMode),
|
||||||
|
ConfigureTools {
|
||||||
|
profile_id: Arc<str>,
|
||||||
|
tool_picker: Entity<ToolPicker>,
|
||||||
|
_subscription: Subscription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
let mut profiles = settings.profiles.clone();
|
||||||
|
profiles.sort_unstable_by(|_, a, _, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
|
let profiles = profiles
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, profile)| ProfileEntry {
|
||||||
|
id,
|
||||||
|
name: profile.name,
|
||||||
|
navigation: NavigableEntry::focusable(cx),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Self::ChooseProfile(ChooseProfileMode {
|
||||||
|
profiles,
|
||||||
|
add_new_profile: NavigableEntry::focusable(cx),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ProfileEntry {
|
||||||
|
pub id: Arc<str>,
|
||||||
|
pub name: SharedString,
|
||||||
|
pub navigation: NavigableEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ChooseProfileMode {
|
||||||
|
profiles: Vec<ProfileEntry>,
|
||||||
|
add_new_profile: NavigableEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ViewProfileMode {
|
||||||
|
profile_id: Arc<str>,
|
||||||
|
fork_profile: NavigableEntry,
|
||||||
|
configure_tools: NavigableEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NewProfileMode {
|
||||||
|
name_editor: Entity<Editor>,
|
||||||
|
base_profile_id: Option<Arc<str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ManageProfilesModal {
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
mode: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManageProfilesModal {
|
||||||
|
pub fn register(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_window: Option<&mut Window>,
|
||||||
|
_cx: &mut Context<Workspace>,
|
||||||
|
) {
|
||||||
|
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
let fs = workspace.app_state().fs.clone();
|
||||||
|
let thread_store = panel.read(cx).thread_store();
|
||||||
|
let tools = thread_store.read(cx).tools();
|
||||||
|
let thread_store = thread_store.downgrade();
|
||||||
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
|
let mut this = Self::new(fs, tools, thread_store, window, cx);
|
||||||
|
|
||||||
|
if let Some(profile_id) = action.customize_tools.clone() {
|
||||||
|
this.configure_tools(profile_id, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fs,
|
||||||
|
tools,
|
||||||
|
thread_store,
|
||||||
|
focus_handle,
|
||||||
|
mode: Mode::choose_profile(window, cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_profile(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.mode = Mode::choose_profile(window, cx);
|
||||||
|
self.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_profile(
|
||||||
|
&mut self,
|
||||||
|
base_profile_id: Option<Arc<str>>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let name_editor = cx.new(|cx| Editor::single_line(window, cx));
|
||||||
|
name_editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_placeholder_text("Profile name", cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.mode = Mode::NewProfile(NewProfileMode {
|
||||||
|
name_editor,
|
||||||
|
base_profile_id,
|
||||||
|
});
|
||||||
|
self.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_profile(
|
||||||
|
&mut self,
|
||||||
|
profile_id: Arc<str>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.mode = Mode::ViewProfile(ViewProfileMode {
|
||||||
|
profile_id,
|
||||||
|
fork_profile: NavigableEntry::focusable(cx),
|
||||||
|
configure_tools: NavigableEntry::focusable(cx),
|
||||||
|
});
|
||||||
|
self.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_tools(
|
||||||
|
&mut self,
|
||||||
|
profile_id: Arc<str>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let tool_picker = cx.new(|cx| {
|
||||||
|
let delegate = ToolPickerDelegate::new(
|
||||||
|
self.fs.clone(),
|
||||||
|
self.tools.clone(),
|
||||||
|
self.thread_store.clone(),
|
||||||
|
profile_id.clone(),
|
||||||
|
profile,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
ToolPicker::new(delegate, window, cx)
|
||||||
|
});
|
||||||
|
let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
|
||||||
|
let profile_id = profile_id.clone();
|
||||||
|
move |this, _tool_picker, _: &DismissEvent, window, cx| {
|
||||||
|
this.view_profile(profile_id.clone(), window, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.mode = Mode::ConfigureTools {
|
||||||
|
profile_id,
|
||||||
|
tool_picker,
|
||||||
|
_subscription: dismiss_subscription,
|
||||||
|
};
|
||||||
|
self.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
match &self.mode {
|
||||||
|
Mode::ChooseProfile { .. } => {}
|
||||||
|
Mode::NewProfile(mode) => {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
let base_profile = mode
|
||||||
|
.base_profile_id
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|profile_id| settings.profiles.get(profile_id).cloned());
|
||||||
|
|
||||||
|
let name = mode.name_editor.read(cx).text(cx);
|
||||||
|
let profile_id: Arc<str> = name.to_case(Case::Kebab).into();
|
||||||
|
|
||||||
|
let profile = AgentProfile {
|
||||||
|
name: name.into(),
|
||||||
|
tools: base_profile
|
||||||
|
.as_ref()
|
||||||
|
.map(|profile| profile.tools.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
context_servers: base_profile
|
||||||
|
.map(|profile| profile.context_servers)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.create_profile(profile_id.clone(), profile, cx);
|
||||||
|
self.view_profile(profile_id, window, cx);
|
||||||
|
}
|
||||||
|
Mode::ViewProfile(_) => {}
|
||||||
|
Mode::ConfigureTools { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
match &self.mode {
|
||||||
|
Mode::ChooseProfile { .. } => {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
Mode::NewProfile(mode) => {
|
||||||
|
if let Some(profile_id) = mode.base_profile_id.clone() {
|
||||||
|
self.view_profile(profile_id, window, cx);
|
||||||
|
} else {
|
||||||
|
self.choose_profile(window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::ViewProfile(_) => self.choose_profile(window, cx),
|
||||||
|
Mode::ConfigureTools { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_profile(&self, profile_id: Arc<str>, profile: AgentProfile, cx: &mut Context<Self>) {
|
||||||
|
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
|
||||||
|
move |settings, _cx| {
|
||||||
|
settings.create_profile(profile_id, profile).log_err();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModalView for ManageProfilesModal {}
|
||||||
|
|
||||||
|
impl Focusable for ManageProfilesModal {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
match &self.mode {
|
||||||
|
Mode::ChooseProfile(_) => self.focus_handle.clone(),
|
||||||
|
Mode::NewProfile(mode) => mode.name_editor.focus_handle(cx),
|
||||||
|
Mode::ViewProfile(_) => self.focus_handle.clone(),
|
||||||
|
Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for ManageProfilesModal {}
|
||||||
|
|
||||||
|
impl ManageProfilesModal {
|
||||||
|
fn render_choose_profile(
|
||||||
|
&mut self,
|
||||||
|
mode: ChooseProfileMode,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
Navigable::new(
|
||||||
|
div()
|
||||||
|
.track_focus(&self.focus_handle(cx))
|
||||||
|
.size_full()
|
||||||
|
.child(ProfileModalHeader::new(
|
||||||
|
"Agent Profiles",
|
||||||
|
IconName::ZedAssistant,
|
||||||
|
))
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.pb_1()
|
||||||
|
.child(ListSeparator)
|
||||||
|
.children(mode.profiles.iter().map(|profile| {
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!("profile-{}", profile.id)))
|
||||||
|
.track_focus(&profile.navigation.focus_handle)
|
||||||
|
.on_action({
|
||||||
|
let profile_id = profile.id.clone();
|
||||||
|
cx.listener(move |this, _: &menu::Confirm, window, cx| {
|
||||||
|
this.view_profile(profile_id.clone(), window, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
ListItem::new(SharedString::from(format!(
|
||||||
|
"profile-{}",
|
||||||
|
profile.id
|
||||||
|
)))
|
||||||
|
.toggle_state(
|
||||||
|
profile
|
||||||
|
.navigation
|
||||||
|
.focus_handle
|
||||||
|
.contains_focused(window, cx),
|
||||||
|
)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.child(Label::new(profile.name.clone()))
|
||||||
|
.end_slot(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Label::new("Customize").size(LabelSize::Small))
|
||||||
|
.children(KeyBinding::for_action_in(
|
||||||
|
&menu::Confirm,
|
||||||
|
&self.focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let profile_id = profile.id.clone();
|
||||||
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
this.view_profile(profile_id.clone(), window, cx);
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.child(ListSeparator)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id("new-profile")
|
||||||
|
.track_focus(&mode.add_new_profile.focus_handle)
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
|
||||||
|
this.new_profile(None, window, cx);
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
ListItem::new("new-profile")
|
||||||
|
.toggle_state(
|
||||||
|
mode.add_new_profile
|
||||||
|
.focus_handle
|
||||||
|
.contains_focused(window, cx),
|
||||||
|
)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.start_slot(Icon::new(IconName::Plus))
|
||||||
|
.child(Label::new("Add New Profile"))
|
||||||
|
.on_click({
|
||||||
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
this.new_profile(None, window, cx);
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.map(|mut navigable| {
|
||||||
|
for profile in mode.profiles {
|
||||||
|
navigable = navigable.entry(profile.navigation);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigable
|
||||||
|
})
|
||||||
|
.entry(mode.add_new_profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_new_profile(
|
||||||
|
&mut self,
|
||||||
|
mode: NewProfileMode,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
|
||||||
|
settings
|
||||||
|
.profiles
|
||||||
|
.get(base_profile_id)
|
||||||
|
.map(|profile| profile.name.clone())
|
||||||
|
.unwrap_or_else(|| "Unknown".into())
|
||||||
|
});
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.id("new-profile")
|
||||||
|
.track_focus(&self.focus_handle(cx))
|
||||||
|
.child(ProfileModalHeader::new(
|
||||||
|
match base_profile_name {
|
||||||
|
Some(base_profile) => format!("Fork {base_profile}"),
|
||||||
|
None => "New Profile".into(),
|
||||||
|
},
|
||||||
|
IconName::Plus,
|
||||||
|
))
|
||||||
|
.child(ListSeparator)
|
||||||
|
.child(h_flex().p_2().child(mode.name_editor.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_view_profile(
|
||||||
|
&mut self,
|
||||||
|
mode: ViewProfileMode,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
let profile_name = settings
|
||||||
|
.profiles
|
||||||
|
.get(&mode.profile_id)
|
||||||
|
.map(|profile| profile.name.clone())
|
||||||
|
.unwrap_or_else(|| "Unknown".into());
|
||||||
|
|
||||||
|
Navigable::new(
|
||||||
|
div()
|
||||||
|
.track_focus(&self.focus_handle(cx))
|
||||||
|
.size_full()
|
||||||
|
.child(ProfileModalHeader::new(
|
||||||
|
profile_name,
|
||||||
|
IconName::ZedAssistant,
|
||||||
|
))
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.pb_1()
|
||||||
|
.child(ListSeparator)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id("fork-profile")
|
||||||
|
.track_focus(&mode.fork_profile.focus_handle)
|
||||||
|
.on_action({
|
||||||
|
let profile_id = mode.profile_id.clone();
|
||||||
|
cx.listener(move |this, _: &menu::Confirm, window, cx| {
|
||||||
|
this.new_profile(Some(profile_id.clone()), window, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
ListItem::new("fork-profile")
|
||||||
|
.toggle_state(
|
||||||
|
mode.fork_profile
|
||||||
|
.focus_handle
|
||||||
|
.contains_focused(window, cx),
|
||||||
|
)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.start_slot(Icon::new(IconName::GitBranch))
|
||||||
|
.child(Label::new("Fork Profile"))
|
||||||
|
.on_click({
|
||||||
|
let profile_id = mode.profile_id.clone();
|
||||||
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
this.new_profile(
|
||||||
|
Some(profile_id.clone()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id("configure-tools")
|
||||||
|
.track_focus(&mode.configure_tools.focus_handle)
|
||||||
|
.on_action({
|
||||||
|
let profile_id = mode.profile_id.clone();
|
||||||
|
cx.listener(move |this, _: &menu::Confirm, window, cx| {
|
||||||
|
this.configure_tools(profile_id.clone(), window, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
ListItem::new("configure-tools")
|
||||||
|
.toggle_state(
|
||||||
|
mode.configure_tools
|
||||||
|
.focus_handle
|
||||||
|
.contains_focused(window, cx),
|
||||||
|
)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.start_slot(Icon::new(IconName::Cog))
|
||||||
|
.child(Label::new("Configure Tools"))
|
||||||
|
.on_click({
|
||||||
|
let profile_id = mode.profile_id.clone();
|
||||||
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
this.configure_tools(
|
||||||
|
profile_id.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.entry(mode.fork_profile)
|
||||||
|
.entry(mode.configure_tools)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ManageProfilesModal {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.elevation_3(cx)
|
||||||
|
.w(rems(34.))
|
||||||
|
.key_context("ManageProfilesModal")
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
|
||||||
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
|
this.focus_handle(cx).focus(window);
|
||||||
|
}))
|
||||||
|
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
||||||
|
.child(match &self.mode {
|
||||||
|
Mode::ChooseProfile(mode) => self
|
||||||
|
.render_choose_profile(mode.clone(), window, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
Mode::NewProfile(mode) => self
|
||||||
|
.render_new_profile(mode.clone(), window, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
Mode::ViewProfile(mode) => self
|
||||||
|
.render_view_profile(mode.clone(), window, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
Mode::ConfigureTools {
|
||||||
|
profile_id,
|
||||||
|
tool_picker,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let profile_name = settings
|
||||||
|
.profiles
|
||||||
|
.get(profile_id)
|
||||||
|
.map(|profile| profile.name.clone())
|
||||||
|
.unwrap_or_else(|| "Unknown".into());
|
||||||
|
|
||||||
|
div()
|
||||||
|
.child(ProfileModalHeader::new(
|
||||||
|
format!("{profile_name}: Configure Tools"),
|
||||||
|
IconName::Cog,
|
||||||
|
))
|
||||||
|
.child(ListSeparator)
|
||||||
|
.child(tool_picker.clone())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct ProfileModalHeader {
|
||||||
|
label: SharedString,
|
||||||
|
icon: IconName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfileModalHeader {
|
||||||
|
pub fn new(label: impl Into<SharedString>, icon: IconName) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
icon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for ProfileModalHeader {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.px(DynamicSpacing::Base12.rems(cx))
|
||||||
|
.pt(DynamicSpacing::Base08.rems(cx))
|
||||||
|
.pb(DynamicSpacing::Base04.rems(cx))
|
||||||
|
.rounded_t_sm()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(Icon::new(self.icon).size(IconSize::XSmall))
|
||||||
|
.child(
|
||||||
|
h_flex().gap_1().overflow_x_hidden().child(
|
||||||
|
div()
|
||||||
|
.max_w_96()
|
||||||
|
.overflow_x_hidden()
|
||||||
|
.text_ellipsis()
|
||||||
|
.child(Headline::new(self.label).size(HeadlineSize::XSmall)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
299
crates/assistant2/src/assistant_configuration/tool_picker.rs
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use assistant_settings::{
|
||||||
|
AgentProfile, AgentProfileContent, AssistantSettings, AssistantSettingsContent,
|
||||||
|
ContextServerPresetContent, VersionedAssistantSettingsContent,
|
||||||
|
};
|
||||||
|
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||||
|
use fs::Fs;
|
||||||
|
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
||||||
|
use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window};
|
||||||
|
use picker::{Picker, PickerDelegate};
|
||||||
|
use settings::{Settings as _, update_settings_file};
|
||||||
|
use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
|
||||||
|
use util::ResultExt as _;
|
||||||
|
|
||||||
|
use crate::ThreadStore;
|
||||||
|
|
||||||
|
pub struct ToolPicker {
|
||||||
|
picker: Entity<Picker<ToolPickerDelegate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolPicker {
|
||||||
|
pub fn new(delegate: ToolPickerDelegate, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
|
||||||
|
Self { picker }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for ToolPicker {}
|
||||||
|
|
||||||
|
impl Focusable for ToolPicker {
|
||||||
|
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||||
|
self.picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ToolPicker {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
v_flex().w(rems(34.)).child(self.picker.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ToolEntry {
|
||||||
|
pub name: Arc<str>,
|
||||||
|
pub source: ToolSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ToolPickerDelegate {
|
||||||
|
tool_picker: WeakEntity<ToolPicker>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
tools: Vec<ToolEntry>,
|
||||||
|
profile_id: Arc<str>,
|
||||||
|
profile: AgentProfile,
|
||||||
|
matches: Vec<StringMatch>,
|
||||||
|
selected_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolPickerDelegate {
|
||||||
|
pub fn new(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
tool_set: Arc<ToolWorkingSet>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
profile_id: Arc<str>,
|
||||||
|
profile: AgentProfile,
|
||||||
|
cx: &mut Context<ToolPicker>,
|
||||||
|
) -> Self {
|
||||||
|
let mut tool_entries = Vec::new();
|
||||||
|
|
||||||
|
for (source, tools) in tool_set.tools_by_source(cx) {
|
||||||
|
tool_entries.extend(tools.into_iter().map(|tool| ToolEntry {
|
||||||
|
name: tool.name().into(),
|
||||||
|
source: source.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
tool_picker: cx.entity().downgrade(),
|
||||||
|
thread_store,
|
||||||
|
fs,
|
||||||
|
tools: tool_entries,
|
||||||
|
profile_id,
|
||||||
|
profile,
|
||||||
|
matches: Vec::new(),
|
||||||
|
selected_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickerDelegate for ToolPickerDelegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
|
fn match_count(&self) -> usize {
|
||||||
|
self.matches.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_index(&self) -> usize {
|
||||||
|
self.selected_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Picker<Self>>,
|
||||||
|
) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||||
|
"Search tools…".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(
|
||||||
|
&mut self,
|
||||||
|
query: String,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Task<()> {
|
||||||
|
let background = cx.background_executor().clone();
|
||||||
|
let candidates = self
|
||||||
|
.tools
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, profile)| StringMatchCandidate::new(id, profile.name.as_ref()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let matches = if query.is_empty() {
|
||||||
|
candidates
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, candidate)| StringMatch {
|
||||||
|
candidate_id: index,
|
||||||
|
string: candidate.string,
|
||||||
|
positions: Vec::new(),
|
||||||
|
score: 0.,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
100,
|
||||||
|
&Default::default(),
|
||||||
|
background,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update(cx, |this, _cx| {
|
||||||
|
this.delegate.matches = matches;
|
||||||
|
this.delegate.selected_index = this
|
||||||
|
.delegate
|
||||||
|
.selected_index
|
||||||
|
.min(this.delegate.matches.len().saturating_sub(1));
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
if self.matches.is_empty() {
|
||||||
|
self.dismissed(window, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let candidate_id = self.matches[self.selected_index].candidate_id;
|
||||||
|
let tool = &self.tools[candidate_id];
|
||||||
|
|
||||||
|
let is_enabled = match &tool.source {
|
||||||
|
ToolSource::Native => {
|
||||||
|
let is_enabled = self.profile.tools.entry(tool.name.clone()).or_default();
|
||||||
|
*is_enabled = !*is_enabled;
|
||||||
|
*is_enabled
|
||||||
|
}
|
||||||
|
ToolSource::ContextServer { id } => {
|
||||||
|
let preset = self
|
||||||
|
.profile
|
||||||
|
.context_servers
|
||||||
|
.entry(id.clone().into())
|
||||||
|
.or_default();
|
||||||
|
let is_enabled = preset.tools.entry(tool.name.clone()).or_default();
|
||||||
|
*is_enabled = !*is_enabled;
|
||||||
|
*is_enabled
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let active_profile_id = &AssistantSettings::get_global(cx).default_profile;
|
||||||
|
if active_profile_id == &self.profile_id {
|
||||||
|
self.thread_store
|
||||||
|
.update(cx, |this, _cx| {
|
||||||
|
this.load_profile(&self.profile);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
|
||||||
|
let profile_id = self.profile_id.clone();
|
||||||
|
let default_profile = self.profile.clone();
|
||||||
|
let tool = tool.clone();
|
||||||
|
move |settings, _cx| match settings {
|
||||||
|
AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(
|
||||||
|
settings,
|
||||||
|
)) => {
|
||||||
|
let profiles = settings.profiles.get_or_insert_default();
|
||||||
|
let profile =
|
||||||
|
profiles
|
||||||
|
.entry(profile_id)
|
||||||
|
.or_insert_with(|| AgentProfileContent {
|
||||||
|
name: default_profile.name.into(),
|
||||||
|
tools: default_profile.tools,
|
||||||
|
context_servers: default_profile
|
||||||
|
.context_servers
|
||||||
|
.into_iter()
|
||||||
|
.map(|(server_id, preset)| {
|
||||||
|
(
|
||||||
|
server_id,
|
||||||
|
ContextServerPresetContent {
|
||||||
|
tools: preset.tools,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
|
||||||
|
match tool.source {
|
||||||
|
ToolSource::Native => {
|
||||||
|
*profile.tools.entry(tool.name).or_default() = is_enabled;
|
||||||
|
}
|
||||||
|
ToolSource::ContextServer { id } => {
|
||||||
|
let preset = profile
|
||||||
|
.context_servers
|
||||||
|
.entry(id.clone().into())
|
||||||
|
.or_default();
|
||||||
|
*preset.tools.entry(tool.name.clone()).or_default() = is_enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
self.tool_picker
|
||||||
|
.update(cx, |_this, cx| cx.emit(DismissEvent))
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
selected: bool,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Option<Self::ListItem> {
|
||||||
|
let tool_match = &self.matches[ix];
|
||||||
|
let tool = &self.tools[tool_match.candidate_id];
|
||||||
|
|
||||||
|
let is_enabled = match &tool.source {
|
||||||
|
ToolSource::Native => self.profile.tools.get(&tool.name).copied().unwrap_or(false),
|
||||||
|
ToolSource::ContextServer { id } => self
|
||||||
|
.profile
|
||||||
|
.context_servers
|
||||||
|
.get(id.as_ref())
|
||||||
|
.and_then(|preset| preset.tools.get(&tool.name))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(
|
||||||
|
ListItem::new(ix)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.toggle_state(selected)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(HighlightedLabel::new(
|
||||||
|
tool_match.string.clone(),
|
||||||
|
tool_match.positions.clone(),
|
||||||
|
))
|
||||||
|
.map(|parent| match &tool.source {
|
||||||
|
ToolSource::Native => parent,
|
||||||
|
ToolSource::ContextServer { id } => parent
|
||||||
|
.child(Label::new(id).size(LabelSize::XSmall).color(Color::Muted)),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.end_slot::<Icon>(is_enabled.then(|| {
|
||||||
|
Icon::new(IconName::Check)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Success)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
772
crates/assistant2/src/assistant_diff.rs
Normal file
@@ -0,0 +1,772 @@
|
|||||||
|
use crate::{Thread, ThreadEvent, ToggleKeep};
|
||||||
|
use anyhow::Result;
|
||||||
|
use buffer_diff::DiffHunkStatus;
|
||||||
|
use collections::HashSet;
|
||||||
|
use editor::{
|
||||||
|
Direction, Editor, EditorEvent, MultiBuffer, ToPoint,
|
||||||
|
actions::{GoToHunk, GoToPreviousHunk},
|
||||||
|
};
|
||||||
|
use gpui::{
|
||||||
|
Action, AnyElement, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, SharedString,
|
||||||
|
Subscription, Task, WeakEntity, Window, prelude::*,
|
||||||
|
};
|
||||||
|
use language::{Capability, DiskState, OffsetRangeExt, Point};
|
||||||
|
use multi_buffer::PathKey;
|
||||||
|
use project::{Project, ProjectPath};
|
||||||
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
ops::Range,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use ui::{IconButtonShape, KeyBinding, Tooltip, prelude::*};
|
||||||
|
use workspace::{
|
||||||
|
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||||
|
Workspace,
|
||||||
|
item::{BreadcrumbText, ItemEvent, TabContentParams},
|
||||||
|
searchable::SearchableItemHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AssistantDiff {
|
||||||
|
multibuffer: Entity<MultiBuffer>,
|
||||||
|
editor: Entity<Editor>,
|
||||||
|
thread: Entity<Thread>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
title: SharedString,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssistantDiff {
|
||||||
|
pub fn deploy(
|
||||||
|
thread: Entity<Thread>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Result<()> {
|
||||||
|
let existing_diff = workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.items_of_type::<AssistantDiff>(cx)
|
||||||
|
.find(|diff| diff.read(cx).thread == thread)
|
||||||
|
})?;
|
||||||
|
if let Some(existing_diff) = existing_diff {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.activate_item(&existing_diff, true, true, window, cx);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let assistant_diff =
|
||||||
|
cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx));
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.add_item_to_center(Box::new(assistant_diff), window, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
thread: Entity<Thread>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||||
|
|
||||||
|
let project = thread.read(cx).project().clone();
|
||||||
|
let render_diff_hunk_controls = Arc::new({
|
||||||
|
let assistant_diff = cx.entity();
|
||||||
|
move |row,
|
||||||
|
status: &DiffHunkStatus,
|
||||||
|
hunk_range,
|
||||||
|
is_created_file,
|
||||||
|
line_height,
|
||||||
|
_editor: &Entity<Editor>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App| {
|
||||||
|
render_diff_hunk_controls(
|
||||||
|
row,
|
||||||
|
status,
|
||||||
|
hunk_range,
|
||||||
|
is_created_file,
|
||||||
|
line_height,
|
||||||
|
&assistant_diff,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let editor = cx.new(|cx| {
|
||||||
|
let mut editor =
|
||||||
|
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
|
||||||
|
editor.disable_inline_diagnostics();
|
||||||
|
editor.set_expand_all_diff_hunks(cx);
|
||||||
|
editor.set_render_diff_hunk_controls(render_diff_hunk_controls, cx);
|
||||||
|
editor.register_addon(AssistantDiffAddon);
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
let action_log = thread.read(cx).action_log().clone();
|
||||||
|
let mut this = Self {
|
||||||
|
_subscriptions: vec![
|
||||||
|
cx.observe_in(&action_log, window, |this, _action_log, window, cx| {
|
||||||
|
this.update_excerpts(window, cx)
|
||||||
|
}),
|
||||||
|
cx.subscribe(&thread, |this, _thread, event, cx| {
|
||||||
|
this.handle_thread_event(event, cx)
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
title: SharedString::default(),
|
||||||
|
multibuffer,
|
||||||
|
editor,
|
||||||
|
thread,
|
||||||
|
focus_handle,
|
||||||
|
workspace,
|
||||||
|
};
|
||||||
|
this.update_excerpts(window, cx);
|
||||||
|
this.update_title(cx);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let thread = self.thread.read(cx);
|
||||||
|
let changed_buffers = thread.action_log().read(cx).changed_buffers(cx);
|
||||||
|
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
for (buffer, changed) in changed_buffers {
|
||||||
|
let Some(file) = buffer.read(cx).file().cloned() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_key = PathKey::namespaced("", file.full_path(cx).into());
|
||||||
|
paths_to_delete.remove(&path_key);
|
||||||
|
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let diff = changed.diff.read(cx);
|
||||||
|
let diff_hunk_ranges = diff
|
||||||
|
.hunks_intersecting_range(
|
||||||
|
language::Anchor::MIN..language::Anchor::MAX,
|
||||||
|
&snapshot,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let (was_empty, is_excerpt_newly_added) =
|
||||||
|
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||||
|
let was_empty = multibuffer.is_empty();
|
||||||
|
let is_excerpt_newly_added = multibuffer.set_excerpts_for_path(
|
||||||
|
path_key.clone(),
|
||||||
|
buffer.clone(),
|
||||||
|
diff_hunk_ranges,
|
||||||
|
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
multibuffer.add_diff(changed.diff.clone(), cx);
|
||||||
|
(was_empty, is_excerpt_newly_added)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
if was_empty {
|
||||||
|
editor.change_selections(None, window, cx, |selections| {
|
||||||
|
selections.select_ranges([0..0])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_excerpt_newly_added
|
||||||
|
&& buffer
|
||||||
|
.read(cx)
|
||||||
|
.file()
|
||||||
|
.map_or(false, |file| file.disk_state() == DiskState::Deleted)
|
||||||
|
{
|
||||||
|
editor.fold_buffer(snapshot.text.remote_id(), cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||||
|
for path in paths_to_delete {
|
||||||
|
multibuffer.remove_excerpts_for_path(path, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.multibuffer.read(cx).is_empty()
|
||||||
|
&& self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.focus_handle(cx)
|
||||||
|
.contains_focused(window, cx)
|
||||||
|
{
|
||||||
|
self.focus_handle.focus(window);
|
||||||
|
} else if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).is_empty() {
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.focus_handle(cx).focus(window);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_title(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let new_title = self
|
||||||
|
.thread
|
||||||
|
.read(cx)
|
||||||
|
.summary()
|
||||||
|
.unwrap_or("Assistant Changes".into());
|
||||||
|
if new_title != self.title {
|
||||||
|
self.title = new_title;
|
||||||
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) {
|
||||||
|
match event {
|
||||||
|
ThreadEvent::SummaryChanged => self.update_title(cx),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_keep(&mut self, _: &crate::ToggleKeep, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let ranges = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.selections
|
||||||
|
.disjoint_anchor_ranges()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let snapshot = self.multibuffer.read(cx).snapshot(cx);
|
||||||
|
let diff_hunks_in_ranges = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.diff_hunks_in_ranges(&ranges, &snapshot)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for hunk in diff_hunks_in_ranges {
|
||||||
|
let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id);
|
||||||
|
if let Some(buffer) = buffer {
|
||||||
|
self.thread.update(cx, |thread, cx| {
|
||||||
|
let accept = hunk.status().has_secondary_hunk();
|
||||||
|
thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reject(&mut self, _: &crate::Reject, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let ranges = self
|
||||||
|
.editor
|
||||||
|
.update(cx, |editor, cx| editor.selections.ranges(cx));
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.restore_hunks_in_ranges(ranges, window, cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reject_all(&mut self, _: &crate::RejectAll, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
let max_point = editor.buffer().read(cx).read(cx).max_point();
|
||||||
|
editor.restore_hunks_in_ranges(vec![Point::zero()..max_point], window, cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keep_all(&mut self, _: &crate::KeepAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.thread
|
||||||
|
.update(cx, |thread, cx| thread.keep_all_edits(cx));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn review_diff_hunks(
|
||||||
|
&mut self,
|
||||||
|
hunk_ranges: Vec<Range<editor::Anchor>>,
|
||||||
|
accept: bool,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let snapshot = self.multibuffer.read(cx).snapshot(cx);
|
||||||
|
let diff_hunks_in_ranges = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.diff_hunks_in_ranges(&hunk_ranges, &snapshot)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for hunk in diff_hunks_in_ranges {
|
||||||
|
let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id);
|
||||||
|
if let Some(buffer) = buffer {
|
||||||
|
self.thread.update(cx, |thread, cx| {
|
||||||
|
thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<EditorEvent> for AssistantDiff {}
|
||||||
|
|
||||||
|
impl Focusable for AssistantDiff {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
if self.multibuffer.read(cx).is_empty() {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
} else {
|
||||||
|
self.editor.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item for AssistantDiff {
|
||||||
|
type Event = EditorEvent;
|
||||||
|
|
||||||
|
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
||||||
|
Some(Icon::new(IconName::ZedAssistant).color(Color::Muted))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
|
||||||
|
Editor::to_item_events(event, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.editor
|
||||||
|
.update(cx, |editor, cx| editor.deactivated(window, cx));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn navigate(
|
||||||
|
&mut self,
|
||||||
|
data: Box<dyn Any>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> bool {
|
||||||
|
self.editor
|
||||||
|
.update(cx, |editor, cx| editor.navigate(data, window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> {
|
||||||
|
Some("Assistant Diff".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
|
||||||
|
let summary = self
|
||||||
|
.thread
|
||||||
|
.read(cx)
|
||||||
|
.summary()
|
||||||
|
.unwrap_or("Assistant Changes".into());
|
||||||
|
Label::new(format!("Review: {}", summary))
|
||||||
|
.color(if params.selected {
|
||||||
|
Color::Default
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
Some("Assistant Diff Opened")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
|
Some(Box::new(self.editor.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn for_each_project_item(
|
||||||
|
&self,
|
||||||
|
cx: &App,
|
||||||
|
f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),
|
||||||
|
) {
|
||||||
|
self.editor.for_each_project_item(cx, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_singleton(&self, _: &App) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_nav_history(
|
||||||
|
&mut self,
|
||||||
|
nav_history: ItemNavHistory,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.editor.update(cx, |editor, _| {
|
||||||
|
editor.set_nav_history(Some(nav_history));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_on_split(
|
||||||
|
&self,
|
||||||
|
_workspace_id: Option<workspace::WorkspaceId>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Option<Entity<Self>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dirty(&self, cx: &App) -> bool {
|
||||||
|
self.multibuffer.read(cx).is_dirty(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_conflict(&self, cx: &App) -> bool {
|
||||||
|
self.multibuffer.read(cx).has_conflict(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_save(&self, _: &App) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(
|
||||||
|
&mut self,
|
||||||
|
format: bool,
|
||||||
|
project: Entity<Project>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
self.editor.save(format, project, window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as(
|
||||||
|
&mut self,
|
||||||
|
_: Entity<Project>,
|
||||||
|
_: ProjectPath,
|
||||||
|
_window: &mut Window,
|
||||||
|
_: &mut Context<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload(
|
||||||
|
&mut self,
|
||||||
|
project: Entity<Project>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
self.editor.reload(project, window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn act_as_type<'a>(
|
||||||
|
&'a self,
|
||||||
|
type_id: TypeId,
|
||||||
|
self_handle: &'a Entity<Self>,
|
||||||
|
_: &'a App,
|
||||||
|
) -> Option<AnyView> {
|
||||||
|
if type_id == TypeId::of::<Self>() {
|
||||||
|
Some(self_handle.to_any())
|
||||||
|
} else if type_id == TypeId::of::<Editor>() {
|
||||||
|
Some(self.editor.to_any())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {
|
||||||
|
ToolbarItemLocation::PrimaryLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
|
||||||
|
self.editor.breadcrumbs(theme, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn added_to_workspace(
|
||||||
|
&mut self,
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.added_to_workspace(workspace, window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for AssistantDiff {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let is_empty = self.multibuffer.read(cx).is_empty();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.key_context(if is_empty {
|
||||||
|
"EmptyPane"
|
||||||
|
} else {
|
||||||
|
"AssistantDiff"
|
||||||
|
})
|
||||||
|
.on_action(cx.listener(Self::toggle_keep))
|
||||||
|
.on_action(cx.listener(Self::reject))
|
||||||
|
.on_action(cx.listener(Self::reject_all))
|
||||||
|
.on_action(cx.listener(Self::keep_all))
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.size_full()
|
||||||
|
.when(is_empty, |el| el.child("No changes to review"))
|
||||||
|
.when(!is_empty, |el| el.child(self.editor.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_diff_hunk_controls(
|
||||||
|
row: u32,
|
||||||
|
status: &DiffHunkStatus,
|
||||||
|
hunk_range: Range<editor::Anchor>,
|
||||||
|
is_created_file: bool,
|
||||||
|
line_height: Pixels,
|
||||||
|
assistant_diff: &Entity<AssistantDiff>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> AnyElement {
|
||||||
|
let editor = assistant_diff.read(cx).editor.clone();
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.h(line_height)
|
||||||
|
.mr_0p5()
|
||||||
|
.gap_1()
|
||||||
|
.px_0p5()
|
||||||
|
.pb_1()
|
||||||
|
.border_x_1()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.rounded_b_md()
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.gap_1()
|
||||||
|
.occlude()
|
||||||
|
.shadow_md()
|
||||||
|
.children(if status.has_secondary_hunk() {
|
||||||
|
vec![
|
||||||
|
Button::new("reject", "Reject")
|
||||||
|
.disabled(is_created_file)
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&crate::Reject,
|
||||||
|
&editor.read(cx).focus_handle(cx),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let editor = editor.clone();
|
||||||
|
move |_event, window, cx| {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let snapshot = editor.snapshot(window, cx);
|
||||||
|
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
|
||||||
|
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Button::new(("keep", row as u64), "Keep")
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&crate::ToggleKeep,
|
||||||
|
&editor.read(cx).focus_handle(cx),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let assistant_diff = assistant_diff.clone();
|
||||||
|
move |_event, _window, cx| {
|
||||||
|
assistant_diff.update(cx, |diff, cx| {
|
||||||
|
diff.review_diff_hunks(
|
||||||
|
vec![hunk_range.start..hunk_range.start],
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
Button::new(("review", row as u64), "Review")
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&ToggleKeep,
|
||||||
|
&editor.read(cx).focus_handle(cx),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click({
|
||||||
|
let assistant_diff = assistant_diff.clone();
|
||||||
|
move |_event, _window, cx| {
|
||||||
|
assistant_diff.update(cx, |diff, cx| {
|
||||||
|
diff.review_diff_hunks(
|
||||||
|
vec![hunk_range.start..hunk_range.start],
|
||||||
|
false,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.when(
|
||||||
|
!editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
|
||||||
|
|el| {
|
||||||
|
el.child(
|
||||||
|
IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
// .disabled(!has_multiple_hunks)
|
||||||
|
.tooltip({
|
||||||
|
let focus_handle = editor.focus_handle(cx);
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Next Hunk",
|
||||||
|
&GoToHunk,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click({
|
||||||
|
let editor = editor.clone();
|
||||||
|
move |_event, window, cx| {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let snapshot = editor.snapshot(window, cx);
|
||||||
|
let position =
|
||||||
|
hunk_range.end.to_point(&snapshot.buffer_snapshot);
|
||||||
|
editor.go_to_hunk_before_or_after_position(
|
||||||
|
&snapshot,
|
||||||
|
position,
|
||||||
|
Direction::Next,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.expand_selected_diff_hunks(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
// .disabled(!has_multiple_hunks)
|
||||||
|
.tooltip({
|
||||||
|
let focus_handle = editor.focus_handle(cx);
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Previous Hunk",
|
||||||
|
&GoToPreviousHunk,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click({
|
||||||
|
let editor = editor.clone();
|
||||||
|
move |_event, window, cx| {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let snapshot = editor.snapshot(window, cx);
|
||||||
|
let point =
|
||||||
|
hunk_range.start.to_point(&snapshot.buffer_snapshot);
|
||||||
|
editor.go_to_hunk_before_or_after_position(
|
||||||
|
&snapshot,
|
||||||
|
point,
|
||||||
|
Direction::Prev,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.expand_selected_diff_hunks(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssistantDiffAddon;
|
||||||
|
|
||||||
|
impl editor::Addon for AssistantDiffAddon {
|
||||||
|
fn to_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_key_context(&self, key_context: &mut gpui::KeyContext, _: &App) {
|
||||||
|
key_context.add("assistant_diff");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AssistantDiffToolbar {
|
||||||
|
assistant_diff: Option<WeakEntity<AssistantDiff>>,
|
||||||
|
_workspace: WeakEntity<Workspace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssistantDiffToolbar {
|
||||||
|
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
assistant_diff: None,
|
||||||
|
_workspace: workspace.weak_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assistant_diff(&self, _: &App) -> Option<Entity<AssistantDiff>> {
|
||||||
|
self.assistant_diff.as_ref()?.upgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some(assistant_diff) = self.assistant_diff(cx) {
|
||||||
|
assistant_diff.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
|
let action = action.boxed_clone();
|
||||||
|
cx.defer(move |cx| {
|
||||||
|
cx.dispatch_action(action.as_ref());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ToolbarItemEvent> for AssistantDiffToolbar {}
|
||||||
|
|
||||||
|
impl ToolbarItemView for AssistantDiffToolbar {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> ToolbarItemLocation {
|
||||||
|
self.assistant_diff = active_pane_item
|
||||||
|
.and_then(|item| item.act_as::<AssistantDiff>(cx))
|
||||||
|
.map(|entity| entity.downgrade());
|
||||||
|
if self.assistant_diff.is_some() {
|
||||||
|
ToolbarItemLocation::PrimaryRight
|
||||||
|
} else {
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pane_focus_update(
|
||||||
|
&mut self,
|
||||||
|
_pane_focused: bool,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for AssistantDiffToolbar {
|
||||||
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let assistant_diff = match self.assistant_diff(cx) {
|
||||||
|
Some(ad) => ad,
|
||||||
|
None => return div(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_empty = assistant_diff.read(cx).multibuffer.read(cx).is_empty();
|
||||||
|
|
||||||
|
if is_empty {
|
||||||
|
return div();
|
||||||
|
}
|
||||||
|
|
||||||
|
h_group_xl()
|
||||||
|
.my_neg_1()
|
||||||
|
.items_center()
|
||||||
|
.p_1()
|
||||||
|
.flex_wrap()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
h_group_sm()
|
||||||
|
.child(
|
||||||
|
Button::new("reject-all", "Reject All").on_click(cx.listener(
|
||||||
|
|this, _, window, cx| {
|
||||||
|
this.dispatch_action(&crate::RejectAll, window, cx)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(Button::new("keep-all", "Keep All").on_click(cx.listener(
|
||||||
|
|this, _, window, cx| this.dispatch_action(&crate::KeepAll, window, cx),
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use language_model_selector::{
|
|||||||
};
|
};
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
|
|
||||||
pub struct AssistantModelSelector {
|
pub struct AssistantModelSelector {
|
||||||
selector: Entity<LanguageModelSelector>,
|
selector: Entity<LanguageModelSelector>,
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use assistant_context_editor::{
|
use assistant_context_editor::{
|
||||||
make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError,
|
AssistantPanelDelegate, ConfigurationError, ContextEditor, SlashCommandCompletionProvider,
|
||||||
ContextEditor, SlashCommandCompletionProvider,
|
make_lsp_adapter_delegate, render_remaining_tokens,
|
||||||
};
|
};
|
||||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||||
use assistant_slash_command::SlashCommandWorkingSet;
|
use assistant_slash_command::SlashCommandWorkingSet;
|
||||||
use assistant_tool::ToolWorkingSet;
|
use assistant_tool::ToolWorkingSet;
|
||||||
|
|
||||||
use client::zed_urls;
|
use client::zed_urls;
|
||||||
use editor::Editor;
|
use editor::{Editor, MultiBuffer};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
|
Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle,
|
||||||
FocusHandle, Focusable, FontWeight, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
|
Focusable, FontWeight, KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
|
||||||
WeakEntity,
|
action_with_deprecated_aliases, prelude::*,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
|
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
|
||||||
|
use language_model_selector::ToggleModelSelector;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_library::{open_prompt_library, PromptLibrary};
|
use prompt_library::{PromptLibrary, open_prompt_library};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{Settings, update_settings_file};
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip};
|
use ui::{ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
use zed_actions::assistant::{DeployPromptLibrary, ToggleFocus};
|
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||||
|
use zed_actions::assistant::ToggleFocus;
|
||||||
|
|
||||||
use crate::active_thread::ActiveThread;
|
use crate::active_thread::ActiveThread;
|
||||||
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
||||||
@@ -38,7 +39,16 @@ use crate::message_editor::MessageEditor;
|
|||||||
use crate::thread::{Thread, ThreadError, ThreadId};
|
use crate::thread::{Thread, ThreadError, ThreadId};
|
||||||
use crate::thread_history::{PastContext, PastThread, ThreadHistory};
|
use crate::thread_history::{PastContext, PastThread, ThreadHistory};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::{InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory};
|
use crate::{
|
||||||
|
AssistantDiff, InlineAssistant, NewPromptEditor, NewThread, OpenActiveThreadAsMarkdown,
|
||||||
|
OpenAssistantDiff, OpenConfiguration, OpenHistory, ToggleContextPicker,
|
||||||
|
};
|
||||||
|
|
||||||
|
action_with_deprecated_aliases!(
|
||||||
|
assistant,
|
||||||
|
OpenPromptLibrary,
|
||||||
|
["assistant::DeployPromptLibrary"]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
cx.observe_new(
|
cx.observe_new(
|
||||||
@@ -56,17 +66,39 @@ pub fn init(cx: &mut App) {
|
|||||||
panel.update(cx, |panel, cx| panel.open_history(window, cx));
|
panel.update(cx, |panel, cx| panel.open_history(window, cx));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
|
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
|
||||||
|
}
|
||||||
|
})
|
||||||
.register_action(|workspace, _: &NewPromptEditor, window, cx| {
|
.register_action(|workspace, _: &NewPromptEditor, window, cx| {
|
||||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.register_action(|workspace, _: &OpenPromptLibrary, window, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.deploy_prompt_library(&OpenPromptLibrary, window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
||||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
|
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.register_action(|workspace, _: &OpenAssistantDiff, window, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.open_assistant_diff(&OpenAssistantDiff, window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -92,12 +124,11 @@ pub struct AssistantPanel {
|
|||||||
context_editor: Option<Entity<ContextEditor>>,
|
context_editor: Option<Entity<ContextEditor>>,
|
||||||
configuration: Option<Entity<AssistantConfiguration>>,
|
configuration: Option<Entity<AssistantConfiguration>>,
|
||||||
configuration_subscription: Option<Subscription>,
|
configuration_subscription: Option<Subscription>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
|
||||||
local_timezone: UtcOffset,
|
local_timezone: UtcOffset,
|
||||||
active_view: ActiveView,
|
active_view: ActiveView,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
history: Entity<ThreadHistory>,
|
history: Entity<ThreadHistory>,
|
||||||
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
height: Option<Pixels>,
|
height: Option<Pixels>,
|
||||||
}
|
}
|
||||||
@@ -108,19 +139,16 @@ impl AssistantPanel {
|
|||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: AsyncWindowContext,
|
cx: AsyncWindowContext,
|
||||||
) -> Task<Result<Entity<Self>>> {
|
) -> Task<Result<Entity<Self>>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(async move |cx| {
|
||||||
let tools = Arc::new(ToolWorkingSet::default());
|
let tools = Arc::new(ToolWorkingSet::default());
|
||||||
log::info!("[assistant2-debug] initializing ThreadStore");
|
let thread_store = workspace.update(cx, |workspace, cx| {
|
||||||
let thread_store = workspace.update(&mut cx, |workspace, cx| {
|
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
ThreadStore::new(project, tools.clone(), cx)
|
ThreadStore::new(project, tools.clone(), prompt_builder.clone(), cx)
|
||||||
})??;
|
})??;
|
||||||
log::info!("[assistant2-debug] finished initializing ThreadStore");
|
|
||||||
|
|
||||||
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
||||||
log::info!("[assistant2-debug] initializing ContextStore");
|
|
||||||
let context_store = workspace
|
let context_store = workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
assistant_context_editor::ContextStore::new(
|
assistant_context_editor::ContextStore::new(
|
||||||
project,
|
project,
|
||||||
@@ -130,10 +158,9 @@ impl AssistantPanel {
|
|||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
log::info!("[assistant2-debug] finished initializing ContextStore");
|
|
||||||
|
|
||||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
cx.new(|cx| Self::new(workspace, thread_store, context_store, tools, window, cx))
|
cx.new(|cx| Self::new(workspace, thread_store, context_store, window, cx))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -142,11 +169,9 @@ impl AssistantPanel {
|
|||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
log::info!("[assistant2-debug] AssistantPanel::new");
|
|
||||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||||
let fs = workspace.app_state().fs.clone();
|
let fs = workspace.app_state().fs.clone();
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
@@ -154,10 +179,14 @@ impl AssistantPanel {
|
|||||||
let workspace = workspace.weak_handle();
|
let workspace = workspace.weak_handle();
|
||||||
let weak_self = cx.entity().downgrade();
|
let weak_self = cx.entity().downgrade();
|
||||||
|
|
||||||
|
let message_editor_context_store =
|
||||||
|
cx.new(|_cx| crate::context_store::ContextStore::new(workspace.clone()));
|
||||||
|
|
||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
message_editor_context_store.clone(),
|
||||||
thread_store.downgrade(),
|
thread_store.downgrade(),
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
window,
|
window,
|
||||||
@@ -168,37 +197,38 @@ impl AssistantPanel {
|
|||||||
let history_store =
|
let history_store =
|
||||||
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
|
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
|
||||||
|
|
||||||
|
let thread = cx.new(|cx| {
|
||||||
|
ActiveThread::new(
|
||||||
|
thread.clone(),
|
||||||
|
thread_store.clone(),
|
||||||
|
language_registry.clone(),
|
||||||
|
message_editor_context_store.clone(),
|
||||||
|
workspace.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
active_view: ActiveView::Thread,
|
active_view: ActiveView::Thread,
|
||||||
workspace: workspace.clone(),
|
workspace,
|
||||||
project,
|
project: project.clone(),
|
||||||
fs: fs.clone(),
|
fs: fs.clone(),
|
||||||
language_registry: language_registry.clone(),
|
language_registry,
|
||||||
thread_store: thread_store.clone(),
|
thread_store: thread_store.clone(),
|
||||||
thread: cx.new(|cx| {
|
thread,
|
||||||
ActiveThread::new(
|
|
||||||
thread.clone(),
|
|
||||||
thread_store.clone(),
|
|
||||||
workspace,
|
|
||||||
language_registry,
|
|
||||||
tools.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
message_editor,
|
message_editor,
|
||||||
context_store,
|
context_store,
|
||||||
context_editor: None,
|
context_editor: None,
|
||||||
configuration: None,
|
configuration: None,
|
||||||
configuration_subscription: None,
|
configuration_subscription: None,
|
||||||
tools,
|
|
||||||
local_timezone: UtcOffset::from_whole_seconds(
|
local_timezone: UtcOffset::from_whole_seconds(
|
||||||
chrono::Local::now().offset().local_minus_utc(),
|
chrono::Local::now().offset().local_minus_utc(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
history_store: history_store.clone(),
|
history_store: history_store.clone(),
|
||||||
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, cx)),
|
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, cx)),
|
||||||
new_item_context_menu_handle: PopoverMenuHandle::default(),
|
assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
}
|
}
|
||||||
@@ -210,12 +240,12 @@ impl AssistantPanel {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
if workspace
|
||||||
if !settings.enabled {
|
.panel::<Self>(cx)
|
||||||
return;
|
.is_some_and(|panel| panel.read(cx).enabled(cx))
|
||||||
|
{
|
||||||
|
workspace.toggle_panel_focus::<Self>(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace.toggle_panel_focus::<Self>(window, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn local_timezone(&self) -> UtcOffset {
|
pub(crate) fn local_timezone(&self) -> UtcOffset {
|
||||||
@@ -242,13 +272,17 @@ impl AssistantPanel {
|
|||||||
.update(cx, |this, cx| this.create_thread(cx));
|
.update(cx, |this, cx| this.create_thread(cx));
|
||||||
|
|
||||||
self.active_view = ActiveView::Thread;
|
self.active_view = ActiveView::Thread;
|
||||||
|
|
||||||
|
let message_editor_context_store =
|
||||||
|
cx.new(|_cx| crate::context_store::ContextStore::new(self.workspace.clone()));
|
||||||
|
|
||||||
self.thread = cx.new(|cx| {
|
self.thread = cx.new(|cx| {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
self.thread_store.clone(),
|
self.thread_store.clone(),
|
||||||
self.workspace.clone(),
|
|
||||||
self.language_registry.clone(),
|
self.language_registry.clone(),
|
||||||
self.tools.clone(),
|
message_editor_context_store.clone(),
|
||||||
|
self.workspace.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -257,6 +291,7 @@ impl AssistantPanel {
|
|||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
self.fs.clone(),
|
self.fs.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
|
message_editor_context_store,
|
||||||
self.thread_store.downgrade(),
|
self.thread_store.downgrade(),
|
||||||
thread,
|
thread,
|
||||||
window,
|
window,
|
||||||
@@ -297,7 +332,7 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
fn deploy_prompt_library(
|
fn deploy_prompt_library(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &DeployPromptLibrary,
|
_: &OpenPromptLibrary,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
@@ -340,9 +375,9 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let context = context.await?;
|
let context = context.await?;
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let editor = cx.new(|cx| {
|
let editor = cx.new(|cx| {
|
||||||
ContextEditor::for_context(
|
ContextEditor::for_context(
|
||||||
context,
|
context,
|
||||||
@@ -373,17 +408,19 @@ impl AssistantPanel {
|
|||||||
.thread_store
|
.thread_store
|
||||||
.update(cx, |this, cx| this.open_thread(thread_id, cx));
|
.update(cx, |this, cx| this.open_thread(thread_id, cx));
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let thread = open_thread_task.await?;
|
let thread = open_thread_task.await?;
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.active_view = ActiveView::Thread;
|
this.active_view = ActiveView::Thread;
|
||||||
|
let message_editor_context_store =
|
||||||
|
cx.new(|_cx| crate::context_store::ContextStore::new(this.workspace.clone()));
|
||||||
this.thread = cx.new(|cx| {
|
this.thread = cx.new(|cx| {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
this.thread_store.clone(),
|
this.thread_store.clone(),
|
||||||
this.workspace.clone(),
|
|
||||||
this.language_registry.clone(),
|
this.language_registry.clone(),
|
||||||
this.tools.clone(),
|
message_editor_context_store.clone(),
|
||||||
|
this.workspace.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -392,6 +429,7 @@ impl AssistantPanel {
|
|||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
this.fs.clone(),
|
this.fs.clone(),
|
||||||
this.workspace.clone(),
|
this.workspace.clone(),
|
||||||
|
message_editor_context_store,
|
||||||
this.thread_store.downgrade(),
|
this.thread_store.downgrade(),
|
||||||
thread,
|
thread,
|
||||||
window,
|
window,
|
||||||
@@ -403,9 +441,24 @@ impl AssistantPanel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn open_assistant_diff(
|
||||||
|
&mut self,
|
||||||
|
_: &OpenAssistantDiff,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let thread = self.thread.read(cx).thread().clone();
|
||||||
|
AssistantDiff::deploy(thread, self.workspace.clone(), window, cx).log_err();
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let context_server_manager = self.thread_store.read(cx).context_server_manager();
|
||||||
|
let tools = self.thread_store.read(cx).tools();
|
||||||
|
|
||||||
self.active_view = ActiveView::Configuration;
|
self.active_view = ActiveView::Configuration;
|
||||||
self.configuration = Some(cx.new(|cx| AssistantConfiguration::new(window, cx)));
|
self.configuration = Some(
|
||||||
|
cx.new(|cx| AssistantConfiguration::new(context_server_manager, tools, window, cx)),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(configuration) = self.configuration.as_ref() {
|
if let Some(configuration) = self.configuration.as_ref() {
|
||||||
self.configuration_subscription = Some(cx.subscribe_in(
|
self.configuration_subscription = Some(cx.subscribe_in(
|
||||||
@@ -418,6 +471,65 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_active_thread_as_markdown(
|
||||||
|
&mut self,
|
||||||
|
_: &OpenActiveThreadAsMarkdown,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let Some(workspace) = self
|
||||||
|
.workspace
|
||||||
|
.upgrade()
|
||||||
|
.ok_or_else(|| anyhow!("workspace dropped"))
|
||||||
|
.log_err()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let markdown_language_task = workspace
|
||||||
|
.read(cx)
|
||||||
|
.app_state()
|
||||||
|
.languages
|
||||||
|
.language_for_name("Markdown");
|
||||||
|
let thread = self.active_thread(cx);
|
||||||
|
cx.spawn_in(window, async move |_this, cx| {
|
||||||
|
let markdown_language = markdown_language_task.await?;
|
||||||
|
|
||||||
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
|
let thread = thread.read(cx);
|
||||||
|
let markdown = thread.to_markdown(cx)?;
|
||||||
|
let thread_summary = thread
|
||||||
|
.summary()
|
||||||
|
.map(|summary| summary.to_string())
|
||||||
|
.unwrap_or_else(|| "Thread".to_string());
|
||||||
|
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let buffer = project.update(cx, |project, cx| {
|
||||||
|
project.create_local_buffer(&markdown, Some(markdown_language), cx)
|
||||||
|
});
|
||||||
|
let buffer = cx.new(|cx| {
|
||||||
|
MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.add_item_to_active_pane(
|
||||||
|
Box::new(cx.new(|cx| {
|
||||||
|
let mut editor =
|
||||||
|
Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
|
||||||
|
editor.set_breadcrumb_header(thread_summary);
|
||||||
|
editor
|
||||||
|
})),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_assistant_configuration_event(
|
fn handle_assistant_configuration_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_entity: &Entity<AssistantConfiguration>,
|
_entity: &Entity<AssistantConfiguration>,
|
||||||
@@ -550,12 +662,8 @@ impl Panel for AssistantPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
|
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
|
||||||
let settings = AssistantSettings::get_global(cx);
|
(self.enabled(cx) && AssistantSettings::get_global(cx).button)
|
||||||
if !settings.enabled || !settings.button {
|
.then_some(IconName::ZedAssistant)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(IconName::ZedAssistant)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
|
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
|
||||||
@@ -569,11 +677,16 @@ impl Panel for AssistantPanel {
|
|||||||
fn activation_priority(&self) -> u32 {
|
fn activation_priority(&self) -> u32 {
|
||||||
3
|
3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enabled(&self, cx: &App) -> bool {
|
||||||
|
AssistantSettings::get_global(cx).enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantPanel {
|
impl AssistantPanel {
|
||||||
fn render_toolbar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_toolbar(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let thread = self.thread.read(cx);
|
let thread = self.thread.read(cx);
|
||||||
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
let title = match self.active_view {
|
let title = match self.active_view {
|
||||||
ActiveView::Thread => {
|
ActiveView::Thread => {
|
||||||
@@ -593,7 +706,7 @@ impl AssistantPanel {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
|
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
|
||||||
ActiveView::History => "History".into(),
|
ActiveView::History => "History".into(),
|
||||||
ActiveView::Configuration => "Assistant Settings".into(),
|
ActiveView::Configuration => "Settings".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -633,57 +746,47 @@ impl AssistantPanel {
|
|||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.gap(DynamicSpacing::Base02.rems(cx))
|
.gap(DynamicSpacing::Base02.rems(cx))
|
||||||
.child(
|
.child(
|
||||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
IconButton::new("new", IconName::Plus)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"New Thread",
|
||||||
|
&NewThread,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_click(move |_event, window, cx| {
|
||||||
|
window.dispatch_action(NewThread.boxed_clone(), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
PopoverMenu::new("assistant-menu")
|
||||||
.trigger_with_tooltip(
|
.trigger_with_tooltip(
|
||||||
IconButton::new("new", IconName::Plus)
|
IconButton::new("new", IconName::Ellipsis)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.style(ButtonStyle::Subtle),
|
.style(ButtonStyle::Subtle),
|
||||||
Tooltip::text("New…"),
|
Tooltip::text("Toggle Assistant Menu"),
|
||||||
)
|
)
|
||||||
.anchor(Corner::TopRight)
|
.anchor(Corner::TopRight)
|
||||||
.with_handle(self.new_item_context_menu_handle.clone())
|
.with_handle(self.assistant_dropdown_menu_handle.clone())
|
||||||
.menu(move |window, cx| {
|
.menu(move |window, cx| {
|
||||||
Some(ContextMenu::build(
|
Some(ContextMenu::build(
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|menu, _window, _cx| {
|
|menu, _window, _cx| {
|
||||||
menu.action("New Thread", NewThread.boxed_clone())
|
menu.action(
|
||||||
.action(
|
"New Prompt Editor",
|
||||||
"New Prompt Editor",
|
NewPromptEditor.boxed_clone(),
|
||||||
NewPromptEditor.boxed_clone(),
|
)
|
||||||
)
|
.separator()
|
||||||
|
.action("History", OpenHistory.boxed_clone())
|
||||||
|
.action("Settings", OpenConfiguration.boxed_clone())
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("open-history", IconName::HistoryRerun)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = self.focus_handle(cx);
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"History",
|
|
||||||
&OpenHistory,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_click(move |_event, window, cx| {
|
|
||||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("configure-assistant", IconName::Settings)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.tooltip(Tooltip::text("Assistant Settings"))
|
|
||||||
.on_click(move |_event, window, cx| {
|
|
||||||
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -728,66 +831,150 @@ impl AssistantPanel {
|
|||||||
.history_store
|
.history_store
|
||||||
.update(cx, |this, cx| this.recent_entries(6, cx));
|
.update(cx, |this, cx| this.recent_entries(6, cx));
|
||||||
|
|
||||||
let create_welcome_heading = || {
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
|
|
||||||
};
|
|
||||||
|
|
||||||
let configuration_error = self.configuration_error(cx);
|
let configuration_error = self.configuration_error(cx);
|
||||||
let no_error = configuration_error.is_none();
|
let no_error = configuration_error.is_none();
|
||||||
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_1p5()
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.justify_end()
|
.when(recent_history.is_empty(), |this| {
|
||||||
.gap_1()
|
this.child(
|
||||||
.map(|parent| {
|
v_flex()
|
||||||
match configuration_error {
|
.size_full()
|
||||||
Some(ConfigurationError::ProviderNotAuthenticated)
|
.max_w_80()
|
||||||
| Some(ConfigurationError::NoProvider) => {
|
.mx_auto()
|
||||||
parent.child(
|
.justify_center()
|
||||||
v_flex()
|
.items_center()
|
||||||
.px_1p5()
|
.gap_1()
|
||||||
.gap_0p5()
|
.child(
|
||||||
.child(create_welcome_heading())
|
h_flex().child(
|
||||||
.child(
|
Headline::new("Welcome to the Assistant Panel")
|
||||||
Label::new(
|
|
||||||
"To start using the assistant, configure at least one LLM provider.",
|
|
||||||
)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex().mt_1().w_full().child(
|
|
||||||
Button::new("open-configuration", "Configure a Provider")
|
|
||||||
.size(ButtonSize::Compact)
|
|
||||||
.icon(Some(IconName::Sliders))
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.icon_position(IconPosition::Start)
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.open_configuration(window, cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
|
|
||||||
.child(v_flex().px_1p5().gap_0p5().child(create_welcome_heading()).children(
|
|
||||||
provider.render_accept_terms(
|
|
||||||
LanguageModelProviderTosView::ThreadEmptyState,
|
|
||||||
cx,
|
|
||||||
),
|
),
|
||||||
)),
|
)
|
||||||
None => parent,
|
.when(no_error, |parent| {
|
||||||
}
|
parent.child(
|
||||||
})
|
h_flex().child(
|
||||||
.when(recent_history.is_empty() && no_error, |parent| {
|
Label::new("Ask and build anything.")
|
||||||
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child(
|
.color(Color::Muted)
|
||||||
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
.mb_2p5(),
|
||||||
))
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("new-thread", "Start New Thread")
|
||||||
|
.icon(IconName::Plus)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.full_width()
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&NewThread,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(NewThread.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("context", "Add Context")
|
||||||
|
.icon(IconName::FileCode)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.full_width()
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&ToggleContextPicker,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("mode", "Switch Model")
|
||||||
|
.icon(IconName::DatabaseZap)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.full_width()
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&ToggleModelSelector,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("settings", "View Settings")
|
||||||
|
.icon(IconName::Settings)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.full_width()
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&OpenConfiguration,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|parent| {
|
||||||
|
match configuration_error {
|
||||||
|
Some(ConfigurationError::ProviderNotAuthenticated)
|
||||||
|
| Some(ConfigurationError::NoProvider) => {
|
||||||
|
parent
|
||||||
|
.child(
|
||||||
|
h_flex().child(
|
||||||
|
Label::new("To start using the assistant, configure at least one LLM provider.")
|
||||||
|
.color(Color::Muted)
|
||||||
|
.mb_2p5()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("settings", "Configure a Provider")
|
||||||
|
.icon(IconName::Settings)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.full_width()
|
||||||
|
.key_binding(KeyBinding::for_action_in(
|
||||||
|
&OpenConfiguration,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_event, window, cx| {
|
||||||
|
window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
|
||||||
|
.children(
|
||||||
|
provider.render_accept_terms(
|
||||||
|
LanguageModelProviderTosView::ThreadEmptyState,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
None => parent,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.when(!recent_history.is_empty(), |parent| {
|
.when(!recent_history.is_empty(), |parent| {
|
||||||
parent
|
parent
|
||||||
|
.p_1p5()
|
||||||
|
.justify_end()
|
||||||
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.pl_1p5()
|
.pl_1p5()
|
||||||
@@ -810,7 +997,7 @@ impl AssistantPanel {
|
|||||||
&self.focus_handle(cx),
|
&self.focus_handle(cx),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
))
|
).map(|kb| kb.size(rems_from_px(12.))),)
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click(move |_event, window, cx| {
|
||||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||||
}),
|
}),
|
||||||
@@ -818,7 +1005,7 @@ impl AssistantPanel {
|
|||||||
)
|
)
|
||||||
.child(v_flex().gap_1().children(
|
.child(v_flex().gap_1().children(
|
||||||
recent_history.into_iter().map(|entry| {
|
recent_history.into_iter().map(|entry| {
|
||||||
// TODO: Add keyboard navigation.
|
// TODO: Add keyboard navigation.
|
||||||
match entry {
|
match entry {
|
||||||
HistoryEntry::Thread(thread) => {
|
HistoryEntry::Thread(thread) => {
|
||||||
PastThread::new(thread, cx.entity().downgrade(), false)
|
PastThread::new(thread, cx.entity().downgrade(), false)
|
||||||
@@ -852,8 +1039,8 @@ impl AssistantPanel {
|
|||||||
ThreadError::MaxMonthlySpendReached => {
|
ThreadError::MaxMonthlySpendReached => {
|
||||||
self.render_max_monthly_spend_reached_error(cx)
|
self.render_max_monthly_spend_reached_error(cx)
|
||||||
}
|
}
|
||||||
ThreadError::Message(error_message) => {
|
ThreadError::Message { header, message } => {
|
||||||
self.render_error_message(&error_message, cx)
|
self.render_error_message(header, message, cx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_any(),
|
.into_any(),
|
||||||
@@ -956,7 +1143,8 @@ impl AssistantPanel {
|
|||||||
|
|
||||||
fn render_error_message(
|
fn render_error_message(
|
||||||
&self,
|
&self,
|
||||||
error_message: &SharedString,
|
header: SharedString,
|
||||||
|
message: SharedString,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -966,17 +1154,14 @@ impl AssistantPanel {
|
|||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.items_center()
|
.items_center()
|
||||||
.child(Icon::new(IconName::XCircle).color(Color::Error))
|
.child(Icon::new(IconName::XCircle).color(Color::Error))
|
||||||
.child(
|
.child(Label::new(header).weight(FontWeight::MEDIUM)),
|
||||||
Label::new("Error interacting with language model")
|
|
||||||
.weight(FontWeight::MEDIUM),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("error-message")
|
.id("error-message")
|
||||||
.max_h_32()
|
.max_h_32()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.child(Label::new(error_message.clone())),
|
.child(Label::new(message)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -1018,8 +1203,13 @@ impl Render for AssistantPanel {
|
|||||||
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
|
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
|
||||||
this.open_history(window, cx);
|
this.open_history(window, cx);
|
||||||
}))
|
}))
|
||||||
|
.on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
|
||||||
|
this.open_configuration(window, cx);
|
||||||
|
}))
|
||||||
|
.on_action(cx.listener(Self::open_active_thread_as_markdown))
|
||||||
.on_action(cx.listener(Self::deploy_prompt_library))
|
.on_action(cx.listener(Self::deploy_prompt_library))
|
||||||
.child(self.render_toolbar(cx))
|
.on_action(cx.listener(Self::open_assistant_diff))
|
||||||
|
.child(self.render_toolbar(window, cx))
|
||||||
.map(|parent| match self.active_view {
|
.map(|parent| match self.active_view {
|
||||||
ActiveView::Thread => parent
|
ActiveView::Thread => parent
|
||||||
.child(self.render_active_thread_or_empty_state(window, cx))
|
.child(self.render_active_thread_or_empty_state(window, cx))
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ use anyhow::{Context as _, Result};
|
|||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||||
use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, StreamExt};
|
use futures::{SinkExt, Stream, StreamExt, channel::mpsc, future::LocalBoxFuture, join};
|
||||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||||
use language::{line_diff, Buffer, IndentKind, Point, TransactionId};
|
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
LanguageModelTextStream, Role, report_assistant_event,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -367,7 +367,7 @@ impl CodegenAlternative {
|
|||||||
let request = self.build_request(user_prompt, cx)?;
|
let request = self.build_request(user_prompt, cx)?;
|
||||||
self.request = Some(request.clone());
|
self.request = Some(request.clone());
|
||||||
|
|
||||||
cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
|
cx.spawn(async move |_, cx| model.stream_completion_text(request, &cx).await)
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
};
|
};
|
||||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||||
@@ -414,7 +414,11 @@ impl CodegenAlternative {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(context_store) = &self.context_store {
|
if let Some(context_store) = &self.context_store {
|
||||||
attach_context_to_message(&mut request_message, context_store.read(cx).snapshot(cx));
|
attach_context_to_message(
|
||||||
|
&mut request_message,
|
||||||
|
context_store.read(cx).context().iter(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
request_message.content.push(prompt.into());
|
request_message.content.push(prompt.into());
|
||||||
@@ -480,213 +484,223 @@ impl CodegenAlternative {
|
|||||||
let completion = Arc::new(Mutex::new(String::new()));
|
let completion = Arc::new(Mutex::new(String::new()));
|
||||||
let completion_clone = completion.clone();
|
let completion_clone = completion.clone();
|
||||||
|
|
||||||
self.generation = cx.spawn(|codegen, mut cx| {
|
self.generation = cx.spawn(async move |codegen, cx| {
|
||||||
async move {
|
let stream = stream.await;
|
||||||
let stream = stream.await;
|
let token_usage = stream
|
||||||
let message_id = stream
|
.as_ref()
|
||||||
.as_ref()
|
.ok()
|
||||||
.ok()
|
.map(|stream| stream.last_token_usage.clone());
|
||||||
.and_then(|stream| stream.message_id.clone());
|
let message_id = stream
|
||||||
let generate = async {
|
.as_ref()
|
||||||
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
.ok()
|
||||||
let executor = cx.background_executor().clone();
|
.and_then(|stream| stream.message_id.clone());
|
||||||
let message_id = message_id.clone();
|
let generate = async {
|
||||||
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
let model_telemetry_id = model_telemetry_id.clone();
|
||||||
cx.background_spawn(async move {
|
let model_provider_id = model_provider_id.clone();
|
||||||
let mut response_latency = None;
|
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
||||||
let request_start = Instant::now();
|
let executor = cx.background_executor().clone();
|
||||||
let diff = async {
|
let message_id = message_id.clone();
|
||||||
let chunks = StripInvalidSpans::new(stream?.stream);
|
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
||||||
futures::pin_mut!(chunks);
|
cx.background_spawn(async move {
|
||||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
let mut response_latency = None;
|
||||||
let mut line_diff = LineDiff::default();
|
let request_start = Instant::now();
|
||||||
|
let diff = async {
|
||||||
|
let chunks = StripInvalidSpans::new(stream?.stream);
|
||||||
|
futures::pin_mut!(chunks);
|
||||||
|
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||||
|
let mut line_diff = LineDiff::default();
|
||||||
|
|
||||||
let mut new_text = String::new();
|
let mut new_text = String::new();
|
||||||
let mut base_indent = None;
|
let mut base_indent = None;
|
||||||
let mut line_indent = None;
|
let mut line_indent = None;
|
||||||
let mut first_line = true;
|
let mut first_line = true;
|
||||||
|
|
||||||
while let Some(chunk) = chunks.next().await {
|
while let Some(chunk) = chunks.next().await {
|
||||||
if response_latency.is_none() {
|
if response_latency.is_none() {
|
||||||
response_latency = Some(request_start.elapsed());
|
response_latency = Some(request_start.elapsed());
|
||||||
}
|
}
|
||||||
let chunk = chunk?;
|
let chunk = chunk?;
|
||||||
completion_clone.lock().push_str(&chunk);
|
completion_clone.lock().push_str(&chunk);
|
||||||
|
|
||||||
let mut lines = chunk.split('\n').peekable();
|
let mut lines = chunk.split('\n').peekable();
|
||||||
while let Some(line) = lines.next() {
|
while let Some(line) = lines.next() {
|
||||||
new_text.push_str(line);
|
new_text.push_str(line);
|
||||||
if line_indent.is_none() {
|
if line_indent.is_none() {
|
||||||
if let Some(non_whitespace_ch_ix) =
|
if let Some(non_whitespace_ch_ix) =
|
||||||
new_text.find(|ch: char| !ch.is_whitespace())
|
new_text.find(|ch: char| !ch.is_whitespace())
|
||||||
{
|
{
|
||||||
line_indent = Some(non_whitespace_ch_ix);
|
line_indent = Some(non_whitespace_ch_ix);
|
||||||
base_indent = base_indent.or(line_indent);
|
base_indent = base_indent.or(line_indent);
|
||||||
|
|
||||||
let line_indent = line_indent.unwrap();
|
let line_indent = line_indent.unwrap();
|
||||||
let base_indent = base_indent.unwrap();
|
let base_indent = base_indent.unwrap();
|
||||||
let indent_delta =
|
let indent_delta =
|
||||||
line_indent as i32 - base_indent as i32;
|
line_indent as i32 - base_indent as i32;
|
||||||
let mut corrected_indent_len = cmp::max(
|
let mut corrected_indent_len = cmp::max(
|
||||||
0,
|
0,
|
||||||
suggested_line_indent.len as i32 + indent_delta,
|
suggested_line_indent.len as i32 + indent_delta,
|
||||||
)
|
)
|
||||||
as usize;
|
as usize;
|
||||||
if first_line {
|
if first_line {
|
||||||
corrected_indent_len = corrected_indent_len
|
corrected_indent_len = corrected_indent_len
|
||||||
.saturating_sub(
|
.saturating_sub(
|
||||||
selection_start.column as usize,
|
selection_start.column as usize,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let indent_char = suggested_line_indent.char();
|
|
||||||
let mut indent_buffer = [0; 4];
|
|
||||||
let indent_str =
|
|
||||||
indent_char.encode_utf8(&mut indent_buffer);
|
|
||||||
new_text.replace_range(
|
|
||||||
..line_indent,
|
|
||||||
&indent_str.repeat(corrected_indent_len),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if line_indent.is_some() {
|
let indent_char = suggested_line_indent.char();
|
||||||
let char_ops = diff.push_new(&new_text);
|
let mut indent_buffer = [0; 4];
|
||||||
line_diff
|
let indent_str =
|
||||||
.push_char_operations(&char_ops, &selected_text);
|
indent_char.encode_utf8(&mut indent_buffer);
|
||||||
diff_tx
|
new_text.replace_range(
|
||||||
.send((char_ops, line_diff.line_operations()))
|
..line_indent,
|
||||||
.await?;
|
&indent_str.repeat(corrected_indent_len),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if line_indent.is_some() {
|
||||||
|
let char_ops = diff.push_new(&new_text);
|
||||||
|
line_diff.push_char_operations(&char_ops, &selected_text);
|
||||||
|
diff_tx
|
||||||
|
.send((char_ops, line_diff.line_operations()))
|
||||||
|
.await?;
|
||||||
|
new_text.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
let char_ops = diff.push_new("\n");
|
||||||
|
line_diff.push_char_operations(&char_ops, &selected_text);
|
||||||
|
diff_tx
|
||||||
|
.send((char_ops, line_diff.line_operations()))
|
||||||
|
.await?;
|
||||||
|
if line_indent.is_none() {
|
||||||
|
// Don't write out the leading indentation in empty lines on the next line
|
||||||
|
// This is the case where the above if statement didn't clear the buffer
|
||||||
new_text.clear();
|
new_text.clear();
|
||||||
}
|
}
|
||||||
|
line_indent = None;
|
||||||
if lines.peek().is_some() {
|
first_line = false;
|
||||||
let char_ops = diff.push_new("\n");
|
|
||||||
line_diff
|
|
||||||
.push_char_operations(&char_ops, &selected_text);
|
|
||||||
diff_tx
|
|
||||||
.send((char_ops, line_diff.line_operations()))
|
|
||||||
.await?;
|
|
||||||
if line_indent.is_none() {
|
|
||||||
// Don't write out the leading indentation in empty lines on the next line
|
|
||||||
// This is the case where the above if statement didn't clear the buffer
|
|
||||||
new_text.clear();
|
|
||||||
}
|
|
||||||
line_indent = None;
|
|
||||||
first_line = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut char_ops = diff.push_new(&new_text);
|
|
||||||
char_ops.extend(diff.finish());
|
|
||||||
line_diff.push_char_operations(&char_ops, &selected_text);
|
|
||||||
line_diff.finish(&selected_text);
|
|
||||||
diff_tx
|
|
||||||
.send((char_ops, line_diff.line_operations()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = diff.await;
|
|
||||||
|
|
||||||
let error_message =
|
|
||||||
result.as_ref().err().map(|error| error.to_string());
|
|
||||||
report_assistant_event(
|
|
||||||
AssistantEvent {
|
|
||||||
conversation_id: None,
|
|
||||||
message_id,
|
|
||||||
kind: AssistantKind::Inline,
|
|
||||||
phase: AssistantPhase::Response,
|
|
||||||
model: model_telemetry_id,
|
|
||||||
model_provider: model_provider_id.to_string(),
|
|
||||||
response_latency,
|
|
||||||
error_message,
|
|
||||||
language_name: language_name.map(|name| name.to_proto()),
|
|
||||||
},
|
|
||||||
telemetry,
|
|
||||||
http_client,
|
|
||||||
model_api_key,
|
|
||||||
&executor,
|
|
||||||
);
|
|
||||||
|
|
||||||
result?;
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
|
||||||
codegen.update(&mut cx, |codegen, cx| {
|
|
||||||
codegen.last_equal_ranges.clear();
|
|
||||||
|
|
||||||
let edits = char_ops
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|operation| match operation {
|
|
||||||
CharOperation::Insert { text } => {
|
|
||||||
let edit_start = snapshot.anchor_after(edit_start);
|
|
||||||
Some((edit_start..edit_start, text))
|
|
||||||
}
|
|
||||||
CharOperation::Delete { bytes } => {
|
|
||||||
let edit_end = edit_start + bytes;
|
|
||||||
let edit_range = snapshot.anchor_after(edit_start)
|
|
||||||
..snapshot.anchor_before(edit_end);
|
|
||||||
edit_start = edit_end;
|
|
||||||
Some((edit_range, String::new()))
|
|
||||||
}
|
|
||||||
CharOperation::Keep { bytes } => {
|
|
||||||
let edit_end = edit_start + bytes;
|
|
||||||
let edit_range = snapshot.anchor_after(edit_start)
|
|
||||||
..snapshot.anchor_before(edit_end);
|
|
||||||
edit_start = edit_end;
|
|
||||||
codegen.last_equal_ranges.push(edit_range);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if codegen.active {
|
|
||||||
codegen.apply_edits(edits.iter().cloned(), cx);
|
|
||||||
codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
|
|
||||||
}
|
}
|
||||||
codegen.edits.extend(edits);
|
|
||||||
codegen.line_operations = line_ops;
|
|
||||||
codegen.edit_position = Some(snapshot.anchor_after(edit_start));
|
|
||||||
|
|
||||||
cx.notify();
|
let mut char_ops = diff.push_new(&new_text);
|
||||||
})?;
|
char_ops.extend(diff.finish());
|
||||||
}
|
line_diff.push_char_operations(&char_ops, &selected_text);
|
||||||
|
line_diff.finish(&selected_text);
|
||||||
|
diff_tx
|
||||||
|
.send((char_ops, line_diff.line_operations()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
|
anyhow::Ok(())
|
||||||
// That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
|
};
|
||||||
// It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
|
|
||||||
let batch_diff_task =
|
|
||||||
codegen.update(&mut cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
|
|
||||||
let (line_based_stream_diff, ()) =
|
|
||||||
join!(line_based_stream_diff, batch_diff_task);
|
|
||||||
line_based_stream_diff?;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
let result = diff.await;
|
||||||
};
|
|
||||||
|
|
||||||
let result = generate.await;
|
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||||
let elapsed_time = start_time.elapsed().as_secs_f64();
|
report_assistant_event(
|
||||||
|
AssistantEvent {
|
||||||
|
conversation_id: None,
|
||||||
|
message_id,
|
||||||
|
kind: AssistantKind::Inline,
|
||||||
|
phase: AssistantPhase::Response,
|
||||||
|
model: model_telemetry_id,
|
||||||
|
model_provider: model_provider_id,
|
||||||
|
response_latency,
|
||||||
|
error_message,
|
||||||
|
language_name: language_name.map(|name| name.to_proto()),
|
||||||
|
},
|
||||||
|
telemetry,
|
||||||
|
http_client,
|
||||||
|
model_api_key,
|
||||||
|
&executor,
|
||||||
|
);
|
||||||
|
|
||||||
codegen
|
result?;
|
||||||
.update(&mut cx, |this, cx| {
|
Ok(())
|
||||||
this.message_id = message_id;
|
});
|
||||||
this.last_equal_ranges.clear();
|
|
||||||
if let Err(error) = result {
|
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
||||||
this.status = CodegenStatus::Error(error);
|
codegen.update(cx, |codegen, cx| {
|
||||||
} else {
|
codegen.last_equal_ranges.clear();
|
||||||
this.status = CodegenStatus::Done;
|
|
||||||
|
let edits = char_ops
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|operation| match operation {
|
||||||
|
CharOperation::Insert { text } => {
|
||||||
|
let edit_start = snapshot.anchor_after(edit_start);
|
||||||
|
Some((edit_start..edit_start, text))
|
||||||
|
}
|
||||||
|
CharOperation::Delete { bytes } => {
|
||||||
|
let edit_end = edit_start + bytes;
|
||||||
|
let edit_range = snapshot.anchor_after(edit_start)
|
||||||
|
..snapshot.anchor_before(edit_end);
|
||||||
|
edit_start = edit_end;
|
||||||
|
Some((edit_range, String::new()))
|
||||||
|
}
|
||||||
|
CharOperation::Keep { bytes } => {
|
||||||
|
let edit_end = edit_start + bytes;
|
||||||
|
let edit_range = snapshot.anchor_after(edit_start)
|
||||||
|
..snapshot.anchor_before(edit_end);
|
||||||
|
edit_start = edit_end;
|
||||||
|
codegen.last_equal_ranges.push(edit_range);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if codegen.active {
|
||||||
|
codegen.apply_edits(edits.iter().cloned(), cx);
|
||||||
|
codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
|
||||||
}
|
}
|
||||||
this.elapsed_time = Some(elapsed_time);
|
codegen.edits.extend(edits);
|
||||||
this.completion = Some(completion.lock().clone());
|
codegen.line_operations = line_ops;
|
||||||
cx.emit(CodegenEvent::Finished);
|
codegen.edit_position = Some(snapshot.anchor_after(edit_start));
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})?;
|
||||||
.ok();
|
}
|
||||||
}
|
|
||||||
|
// Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
|
||||||
|
// That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
|
||||||
|
// It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
|
||||||
|
let batch_diff_task =
|
||||||
|
codegen.update(cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
|
||||||
|
let (line_based_stream_diff, ()) = join!(line_based_stream_diff, batch_diff_task);
|
||||||
|
line_based_stream_diff?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = generate.await;
|
||||||
|
let elapsed_time = start_time.elapsed().as_secs_f64();
|
||||||
|
|
||||||
|
codegen
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.message_id = message_id;
|
||||||
|
this.last_equal_ranges.clear();
|
||||||
|
if let Err(error) = result {
|
||||||
|
this.status = CodegenStatus::Error(error);
|
||||||
|
} else {
|
||||||
|
this.status = CodegenStatus::Done;
|
||||||
|
}
|
||||||
|
this.elapsed_time = Some(elapsed_time);
|
||||||
|
this.completion = Some(completion.lock().clone());
|
||||||
|
if let Some(usage) = token_usage {
|
||||||
|
let usage = usage.lock();
|
||||||
|
telemetry::event!(
|
||||||
|
"Inline Assistant Completion",
|
||||||
|
model = model_telemetry_id,
|
||||||
|
model_provider = model_provider_id,
|
||||||
|
input_tokens = usage.input_tokens,
|
||||||
|
output_tokens = usage.output_tokens,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cx.emit(CodegenEvent::Finished);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -804,7 +818,7 @@ impl CodegenAlternative {
|
|||||||
let new_snapshot = self.buffer.read(cx).snapshot(cx);
|
let new_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
let new_range = self.range.to_point(&new_snapshot);
|
let new_range = self.range.to_point(&new_snapshot);
|
||||||
|
|
||||||
cx.spawn(|codegen, mut cx| async move {
|
cx.spawn(async move |codegen, cx| {
|
||||||
let (deleted_row_ranges, inserted_row_ranges) = cx
|
let (deleted_row_ranges, inserted_row_ranges) = cx
|
||||||
.background_spawn(async move {
|
.background_spawn(async move {
|
||||||
let old_text = old_snapshot
|
let old_text = old_snapshot
|
||||||
@@ -854,7 +868,7 @@ impl CodegenAlternative {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
codegen
|
codegen
|
||||||
.update(&mut cx, |codegen, cx| {
|
.update(cx, |codegen, cx| {
|
||||||
codegen.diff.deleted_row_ranges = deleted_row_ranges;
|
codegen.diff.deleted_row_ranges = deleted_row_ranges;
|
||||||
codegen.diff.inserted_row_ranges = inserted_row_ranges;
|
codegen.diff.inserted_row_ranges = inserted_row_ranges;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -1018,16 +1032,16 @@ impl Diff {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use futures::{
|
use futures::{
|
||||||
stream::{self},
|
|
||||||
Stream,
|
Stream,
|
||||||
|
stream::{self},
|
||||||
};
|
};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
|
Buffer, Language, LanguageConfig, LanguageMatcher, Point, language_settings,
|
||||||
Point,
|
tree_sitter_rust,
|
||||||
};
|
};
|
||||||
use language_model::LanguageModelRegistry;
|
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@@ -1411,6 +1425,7 @@ mod tests {
|
|||||||
future::ready(Ok(LanguageModelTextStream {
|
future::ready(Ok(LanguageModelTextStream {
|
||||||
message_id: None,
|
message_id: None,
|
||||||
stream: chunks_rx.map(Ok).boxed(),
|
stream: chunks_rx.map(Ok).boxed(),
|
||||||
|
last_token_usage: Arc::new(Mutex::new(TokenUsage::default())),
|
||||||
})),
|
})),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
use std::path::Path;
|
use std::{ops::Range, sync::Arc};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use file_icons::FileIcons;
|
|
||||||
use gpui::{App, Entity, SharedString};
|
use gpui::{App, Entity, SharedString};
|
||||||
use language::Buffer;
|
use language::{Buffer, File};
|
||||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||||
|
use project::ProjectPath;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use text::BufferId;
|
use text::{Anchor, BufferId};
|
||||||
use ui::IconName;
|
use ui::IconName;
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
|
|
||||||
use crate::{context_store::buffer_path_log_err, thread::Thread};
|
use crate::thread::Thread;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct ContextId(pub(crate) usize);
|
pub struct ContextId(pub(crate) usize);
|
||||||
@@ -21,51 +20,32 @@ impl ContextId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Some context attached to a message in a thread.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ContextSnapshot {
|
|
||||||
pub id: ContextId,
|
|
||||||
pub name: SharedString,
|
|
||||||
pub parent: Option<SharedString>,
|
|
||||||
pub tooltip: Option<SharedString>,
|
|
||||||
pub icon_path: Option<SharedString>,
|
|
||||||
pub kind: ContextKind,
|
|
||||||
/// Joining these strings separated by \n yields text for model. Not refreshed by `snapshot`.
|
|
||||||
pub text: Box<[SharedString]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ContextKind {
|
pub enum ContextKind {
|
||||||
File,
|
File,
|
||||||
Directory,
|
Directory,
|
||||||
|
Symbol,
|
||||||
FetchedUrl,
|
FetchedUrl,
|
||||||
Thread,
|
Thread,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextKind {
|
impl ContextKind {
|
||||||
pub fn label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ContextKind::File => "File",
|
|
||||||
ContextKind::Directory => "Folder",
|
|
||||||
ContextKind::FetchedUrl => "Fetch",
|
|
||||||
ContextKind::Thread => "Thread",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn icon(&self) -> IconName {
|
pub fn icon(&self) -> IconName {
|
||||||
match self {
|
match self {
|
||||||
ContextKind::File => IconName::File,
|
ContextKind::File => IconName::File,
|
||||||
ContextKind::Directory => IconName::Folder,
|
ContextKind::Directory => IconName::Folder,
|
||||||
|
ContextKind::Symbol => IconName::Code,
|
||||||
ContextKind::FetchedUrl => IconName::Globe,
|
ContextKind::FetchedUrl => IconName::Globe,
|
||||||
ContextKind::Thread => IconName::MessageCircle,
|
ContextKind::Thread => IconName::MessageBubbles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum AssistantContext {
|
pub enum AssistantContext {
|
||||||
File(FileContext),
|
File(FileContext),
|
||||||
Directory(DirectoryContext),
|
Directory(DirectoryContext),
|
||||||
|
Symbol(SymbolContext),
|
||||||
FetchedUrl(FetchedUrlContext),
|
FetchedUrl(FetchedUrlContext),
|
||||||
Thread(ThreadContext),
|
Thread(ThreadContext),
|
||||||
}
|
}
|
||||||
@@ -74,27 +54,34 @@ impl AssistantContext {
|
|||||||
pub fn id(&self) -> ContextId {
|
pub fn id(&self) -> ContextId {
|
||||||
match self {
|
match self {
|
||||||
Self::File(file) => file.id,
|
Self::File(file) => file.id,
|
||||||
Self::Directory(directory) => directory.snapshot.id,
|
Self::Directory(directory) => directory.id,
|
||||||
|
Self::Symbol(symbol) => symbol.id,
|
||||||
Self::FetchedUrl(url) => url.id,
|
Self::FetchedUrl(url) => url.id,
|
||||||
Self::Thread(thread) => thread.id,
|
Self::Thread(thread) => thread.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FileContext {
|
pub struct FileContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
pub context_buffer: ContextBuffer,
|
pub context_buffer: ContextBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DirectoryContext {
|
pub struct DirectoryContext {
|
||||||
pub path: Rc<Path>,
|
pub id: ContextId,
|
||||||
|
pub project_path: ProjectPath,
|
||||||
pub context_buffers: Vec<ContextBuffer>,
|
pub context_buffers: Vec<ContextBuffer>,
|
||||||
pub snapshot: ContextSnapshot,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SymbolContext {
|
||||||
|
pub id: ContextId,
|
||||||
|
pub context_symbol: ContextSymbol,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct FetchedUrlContext {
|
pub struct FetchedUrlContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
pub url: SharedString,
|
pub url: SharedString,
|
||||||
@@ -104,218 +91,129 @@ pub struct FetchedUrlContext {
|
|||||||
// TODO: Model<Thread> holds onto the thread even if the thread is deleted. Can either handle this
|
// TODO: Model<Thread> holds onto the thread even if the thread is deleted. Can either handle this
|
||||||
// explicitly or have a WeakModel<Thread> and remove during snapshot.
|
// explicitly or have a WeakModel<Thread> and remove during snapshot.
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThreadContext {
|
pub struct ThreadContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
pub thread: Entity<Thread>,
|
pub thread: Entity<Thread>,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ThreadContext {
|
||||||
|
pub fn summary(&self, cx: &App) -> SharedString {
|
||||||
|
self.thread
|
||||||
|
.read(cx)
|
||||||
|
.summary()
|
||||||
|
.unwrap_or("New thread".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Model<Buffer> holds onto the buffer even if the file is deleted and closed. Should remove
|
// TODO: Model<Buffer> holds onto the buffer even if the file is deleted and closed. Should remove
|
||||||
// the context from the message editor in this case.
|
// the context from the message editor in this case.
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ContextBuffer {
|
pub struct ContextBuffer {
|
||||||
pub id: BufferId,
|
pub id: BufferId,
|
||||||
pub buffer: Entity<Buffer>,
|
pub buffer: Entity<Buffer>,
|
||||||
|
pub file: Arc<dyn File>,
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantContext {
|
impl std::fmt::Debug for ContextBuffer {
|
||||||
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self {
|
f.debug_struct("ContextBuffer")
|
||||||
Self::File(file_context) => file_context.snapshot(cx),
|
.field("id", &self.id)
|
||||||
Self::Directory(directory_context) => Some(directory_context.snapshot()),
|
.field("buffer", &self.buffer)
|
||||||
Self::FetchedUrl(fetched_url_context) => Some(fetched_url_context.snapshot()),
|
.field("version", &self.version)
|
||||||
Self::Thread(thread_context) => Some(thread_context.snapshot(cx)),
|
.field("text", &self.text)
|
||||||
}
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileContext {
|
#[derive(Debug, Clone)]
|
||||||
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
|
pub struct ContextSymbol {
|
||||||
let buffer = self.context_buffer.buffer.read(cx);
|
pub id: ContextSymbolId,
|
||||||
let path = buffer_path_log_err(buffer)?;
|
pub buffer: Entity<Buffer>,
|
||||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
pub buffer_version: clock::Global,
|
||||||
let name = match path.file_name() {
|
/// The range that the symbol encloses, e.g. for function symbol, this will
|
||||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
/// include not only the signature, but also the body
|
||||||
None => full_path.clone(),
|
pub enclosing_range: Range<Anchor>,
|
||||||
};
|
pub text: SharedString,
|
||||||
let parent = path
|
|
||||||
.parent()
|
|
||||||
.and_then(|p| p.file_name())
|
|
||||||
.map(|p| p.to_string_lossy().into_owned().into());
|
|
||||||
|
|
||||||
let icon_path = FileIcons::get_icon(&path, cx);
|
|
||||||
|
|
||||||
Some(ContextSnapshot {
|
|
||||||
id: self.id,
|
|
||||||
name,
|
|
||||||
parent,
|
|
||||||
tooltip: Some(full_path),
|
|
||||||
icon_path,
|
|
||||||
kind: ContextKind::File,
|
|
||||||
text: Box::new([self.context_buffer.text.clone()]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectoryContext {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub fn new(
|
pub struct ContextSymbolId {
|
||||||
id: ContextId,
|
pub path: ProjectPath,
|
||||||
path: &Path,
|
pub name: SharedString,
|
||||||
context_buffers: Vec<ContextBuffer>,
|
pub range: Range<Anchor>,
|
||||||
) -> DirectoryContext {
|
|
||||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
|
||||||
|
|
||||||
let name = match path.file_name() {
|
|
||||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
|
||||||
None => full_path.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let parent = path
|
|
||||||
.parent()
|
|
||||||
.and_then(|p| p.file_name())
|
|
||||||
.map(|p| p.to_string_lossy().into_owned().into());
|
|
||||||
|
|
||||||
// TODO: include directory path in text?
|
|
||||||
let text = context_buffers
|
|
||||||
.iter()
|
|
||||||
.map(|b| b.text.clone())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into();
|
|
||||||
|
|
||||||
DirectoryContext {
|
|
||||||
path: path.into(),
|
|
||||||
context_buffers,
|
|
||||||
snapshot: ContextSnapshot {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
parent,
|
|
||||||
tooltip: Some(full_path),
|
|
||||||
icon_path: None,
|
|
||||||
kind: ContextKind::Directory,
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn snapshot(&self) -> ContextSnapshot {
|
|
||||||
self.snapshot.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchedUrlContext {
|
pub fn attach_context_to_message<'a>(
|
||||||
pub fn snapshot(&self) -> ContextSnapshot {
|
|
||||||
ContextSnapshot {
|
|
||||||
id: self.id,
|
|
||||||
name: self.url.clone(),
|
|
||||||
parent: None,
|
|
||||||
tooltip: None,
|
|
||||||
icon_path: None,
|
|
||||||
kind: ContextKind::FetchedUrl,
|
|
||||||
text: Box::new([self.text.clone()]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadContext {
|
|
||||||
pub fn snapshot(&self, cx: &App) -> ContextSnapshot {
|
|
||||||
let thread = self.thread.read(cx);
|
|
||||||
ContextSnapshot {
|
|
||||||
id: self.id,
|
|
||||||
name: thread.summary().unwrap_or("New thread".into()),
|
|
||||||
parent: None,
|
|
||||||
tooltip: None,
|
|
||||||
icon_path: None,
|
|
||||||
kind: ContextKind::Thread,
|
|
||||||
text: Box::new([self.text.clone()]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attach_context_to_message(
|
|
||||||
message: &mut LanguageModelRequestMessage,
|
message: &mut LanguageModelRequestMessage,
|
||||||
contexts: impl Iterator<Item = ContextSnapshot>,
|
contexts: impl Iterator<Item = &'a AssistantContext>,
|
||||||
|
cx: &App,
|
||||||
) {
|
) {
|
||||||
let mut file_context = Vec::new();
|
let mut file_context = Vec::new();
|
||||||
let mut directory_context = Vec::new();
|
let mut directory_context = Vec::new();
|
||||||
|
let mut symbol_context = Vec::new();
|
||||||
let mut fetch_context = Vec::new();
|
let mut fetch_context = Vec::new();
|
||||||
let mut thread_context = Vec::new();
|
let mut thread_context = Vec::new();
|
||||||
|
|
||||||
let mut capacity = 0;
|
|
||||||
for context in contexts {
|
for context in contexts {
|
||||||
capacity += context.text.len();
|
match context {
|
||||||
match context.kind {
|
AssistantContext::File(context) => file_context.push(context),
|
||||||
ContextKind::File => file_context.push(context),
|
AssistantContext::Directory(context) => directory_context.push(context),
|
||||||
ContextKind::Directory => directory_context.push(context),
|
AssistantContext::Symbol(context) => symbol_context.push(context),
|
||||||
ContextKind::FetchedUrl => fetch_context.push(context),
|
AssistantContext::FetchedUrl(context) => fetch_context.push(context),
|
||||||
ContextKind::Thread => thread_context.push(context),
|
AssistantContext::Thread(context) => thread_context.push(context),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !file_context.is_empty() {
|
|
||||||
capacity += 1;
|
|
||||||
}
|
|
||||||
if !directory_context.is_empty() {
|
|
||||||
capacity += 1;
|
|
||||||
}
|
|
||||||
if !fetch_context.is_empty() {
|
|
||||||
capacity += 1 + fetch_context.len();
|
|
||||||
}
|
|
||||||
if !thread_context.is_empty() {
|
|
||||||
capacity += 1 + thread_context.len();
|
|
||||||
}
|
|
||||||
if capacity == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut context_chunks = Vec::with_capacity(capacity);
|
let mut context_chunks = Vec::new();
|
||||||
|
|
||||||
if !file_context.is_empty() {
|
if !file_context.is_empty() {
|
||||||
context_chunks.push("The following files are available:\n");
|
context_chunks.push("The following files are available:\n");
|
||||||
for context in &file_context {
|
for context in file_context {
|
||||||
for chunk in &context.text {
|
context_chunks.push(&context.context_buffer.text);
|
||||||
context_chunks.push(&chunk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !directory_context.is_empty() {
|
if !directory_context.is_empty() {
|
||||||
context_chunks.push("The following directories are available:\n");
|
context_chunks.push("The following directories are available:\n");
|
||||||
for context in &directory_context {
|
for context in directory_context {
|
||||||
for chunk in &context.text {
|
for context_buffer in &context.context_buffers {
|
||||||
context_chunks.push(&chunk);
|
context_chunks.push(&context_buffer.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !symbol_context.is_empty() {
|
||||||
|
context_chunks.push("The following symbols are available:\n");
|
||||||
|
for context in symbol_context {
|
||||||
|
context_chunks.push(&context.context_symbol.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !fetch_context.is_empty() {
|
if !fetch_context.is_empty() {
|
||||||
context_chunks.push("The following fetched results are available:\n");
|
context_chunks.push("The following fetched results are available:\n");
|
||||||
for context in &fetch_context {
|
for context in &fetch_context {
|
||||||
context_chunks.push(&context.name);
|
context_chunks.push(&context.url);
|
||||||
for chunk in &context.text {
|
context_chunks.push(&context.text);
|
||||||
context_chunks.push(&chunk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Need to own the SharedString for summary so that it can be referenced.
|
||||||
|
let mut thread_context_chunks = Vec::new();
|
||||||
if !thread_context.is_empty() {
|
if !thread_context.is_empty() {
|
||||||
context_chunks.push("The following previous conversation threads are available:\n");
|
context_chunks.push("The following previous conversation threads are available:\n");
|
||||||
for context in &thread_context {
|
for context in &thread_context {
|
||||||
context_chunks.push(&context.name);
|
thread_context_chunks.push(context.summary(cx));
|
||||||
for chunk in &context.text {
|
thread_context_chunks.push(context.text.clone());
|
||||||
context_chunks.push(&chunk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for chunk in &thread_context_chunks {
|
||||||
debug_assert!(
|
context_chunks.push(chunk);
|
||||||
context_chunks.len() == capacity,
|
}
|
||||||
"attach_context_message calculated capacity of {}, but length was {}",
|
|
||||||
capacity,
|
|
||||||
context_chunks.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
if !context_chunks.is_empty() {
|
if !context_chunks.is_empty() {
|
||||||
message
|
message
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
mod directory_context_picker;
|
mod completion_provider;
|
||||||
mod fetch_context_picker;
|
mod fetch_context_picker;
|
||||||
mod file_context_picker;
|
mod file_context_picker;
|
||||||
|
mod symbol_context_picker;
|
||||||
mod thread_context_picker;
|
mod thread_context_picker;
|
||||||
|
|
||||||
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use editor::Editor;
|
use editor::display_map::{Crease, FoldId};
|
||||||
|
use editor::{Anchor, AnchorRangeExt as _, Editor, ExcerptId, FoldPlaceholder, ToOffset};
|
||||||
use file_context_picker::render_file_context_entry;
|
use file_context_picker::render_file_context_entry;
|
||||||
use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity};
|
use gpui::{
|
||||||
|
App, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity,
|
||||||
|
};
|
||||||
|
use multi_buffer::MultiBufferRow;
|
||||||
use project::ProjectPath;
|
use project::ProjectPath;
|
||||||
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
|
use symbol_context_picker::SymbolContextPicker;
|
||||||
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
use thread_context_picker::{ThreadContextEntry, render_thread_context_entry};
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use ui::{
|
||||||
|
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
||||||
|
};
|
||||||
|
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||||
|
|
||||||
use crate::context::ContextKind;
|
use crate::AssistantPanel;
|
||||||
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
|
pub use crate::context_picker::completion_provider::ContextPickerCompletionProvider;
|
||||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||||
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::AssistantPanel;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ConfirmBehavior {
|
pub enum ConfirmBehavior {
|
||||||
@@ -30,19 +38,69 @@ pub enum ConfirmBehavior {
|
|||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum ContextPickerMode {
|
enum ContextPickerMode {
|
||||||
|
File,
|
||||||
|
Symbol,
|
||||||
|
Fetch,
|
||||||
|
Thread,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for ContextPickerMode {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
"file" => Ok(Self::File),
|
||||||
|
"symbol" => Ok(Self::Symbol),
|
||||||
|
"fetch" => Ok(Self::Fetch),
|
||||||
|
"thread" => Ok(Self::Thread),
|
||||||
|
_ => Err(format!("Invalid context picker mode: {}", value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextPickerMode {
|
||||||
|
pub fn mention_prefix(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::File => "file",
|
||||||
|
Self::Symbol => "symbol",
|
||||||
|
Self::Fetch => "fetch",
|
||||||
|
Self::Thread => "thread",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::File => "Files & Directories",
|
||||||
|
Self::Symbol => "Symbols",
|
||||||
|
Self::Fetch => "Fetch",
|
||||||
|
Self::Thread => "Thread",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> IconName {
|
||||||
|
match self {
|
||||||
|
Self::File => IconName::File,
|
||||||
|
Self::Symbol => IconName::Code,
|
||||||
|
Self::Fetch => IconName::Globe,
|
||||||
|
Self::Thread => IconName::MessageBubbles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum ContextPickerState {
|
||||||
Default(Entity<ContextMenu>),
|
Default(Entity<ContextMenu>),
|
||||||
File(Entity<FileContextPicker>),
|
File(Entity<FileContextPicker>),
|
||||||
Directory(Entity<DirectoryContextPicker>),
|
Symbol(Entity<SymbolContextPicker>),
|
||||||
Fetch(Entity<FetchContextPicker>),
|
Fetch(Entity<FetchContextPicker>),
|
||||||
Thread(Entity<ThreadContextPicker>),
|
Thread(Entity<ThreadContextPicker>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct ContextPicker {
|
pub(super) struct ContextPicker {
|
||||||
mode: ContextPickerMode,
|
mode: ContextPickerState,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
@@ -53,13 +111,12 @@ impl ContextPicker {
|
|||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ContextPicker {
|
ContextPicker {
|
||||||
mode: ContextPickerMode::Default(ContextMenu::build(
|
mode: ContextPickerState::Default(ContextMenu::build(
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|menu, _window, _cx| menu,
|
|menu, _window, _cx| menu,
|
||||||
@@ -67,13 +124,12 @@ impl ContextPicker {
|
|||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
editor,
|
|
||||||
confirm_behavior,
|
confirm_behavior,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
|
self.mode = ContextPickerState::Default(self.build_menu(window, cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,14 +144,7 @@ impl ContextPicker {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
||||||
|
|
||||||
let mut context_kinds = vec![
|
let modes = supported_context_picker_modes(&self.thread_store);
|
||||||
ContextKind::File,
|
|
||||||
ContextKind::Directory,
|
|
||||||
ContextKind::FetchedUrl,
|
|
||||||
];
|
|
||||||
if self.allow_threads() {
|
|
||||||
context_kinds.push(ContextKind::Thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
let menu = menu
|
let menu = menu
|
||||||
.when(has_recent, |menu| {
|
.when(has_recent, |menu| {
|
||||||
@@ -112,15 +161,15 @@ impl ContextPicker {
|
|||||||
})
|
})
|
||||||
.extend(recent_entries)
|
.extend(recent_entries)
|
||||||
.when(has_recent, |menu| menu.separator())
|
.when(has_recent, |menu| menu.separator())
|
||||||
.extend(context_kinds.into_iter().map(|kind| {
|
.extend(modes.into_iter().map(|mode| {
|
||||||
let context_picker = context_picker.clone();
|
let context_picker = context_picker.clone();
|
||||||
|
|
||||||
ContextMenuEntry::new(kind.label())
|
ContextMenuEntry::new(mode.label())
|
||||||
.icon(kind.icon())
|
.icon(mode.icon())
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.handler(move |window, cx| {
|
.handler(move |window, cx| {
|
||||||
context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
|
context_picker.update(cx, |this, cx| this.select_mode(mode, window, cx))
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -143,16 +192,20 @@ impl ContextPicker {
|
|||||||
self.thread_store.is_some()
|
self.thread_store.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_mode(
|
||||||
|
&mut self,
|
||||||
|
mode: ContextPickerMode,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
let context_picker = cx.entity().downgrade();
|
let context_picker = cx.entity().downgrade();
|
||||||
|
|
||||||
match kind {
|
match mode {
|
||||||
ContextKind::File => {
|
ContextPickerMode::File => {
|
||||||
self.mode = ContextPickerMode::File(cx.new(|cx| {
|
self.mode = ContextPickerState::File(cx.new(|cx| {
|
||||||
FileContextPicker::new(
|
FileContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.editor.clone(),
|
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
self.confirm_behavior,
|
||||||
window,
|
window,
|
||||||
@@ -160,9 +213,9 @@ impl ContextPicker {
|
|||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::Directory => {
|
ContextPickerMode::Symbol => {
|
||||||
self.mode = ContextPickerMode::Directory(cx.new(|cx| {
|
self.mode = ContextPickerState::Symbol(cx.new(|cx| {
|
||||||
DirectoryContextPicker::new(
|
SymbolContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
@@ -172,8 +225,8 @@ impl ContextPicker {
|
|||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::FetchedUrl => {
|
ContextPickerMode::Fetch => {
|
||||||
self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
|
self.mode = ContextPickerState::Fetch(cx.new(|cx| {
|
||||||
FetchContextPicker::new(
|
FetchContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
@@ -184,9 +237,9 @@ impl ContextPicker {
|
|||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::Thread => {
|
ContextPickerMode::Thread => {
|
||||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||||
self.mode = ContextPickerMode::Thread(cx.new(|cx| {
|
self.mode = ContextPickerState::Thread(cx.new(|cx| {
|
||||||
ThreadContextPicker::new(
|
ThreadContextPicker::new(
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
@@ -224,6 +277,7 @@ impl ContextPicker {
|
|||||||
ElementId::NamedInteger("ctx-recent".into(), ix),
|
ElementId::NamedInteger("ctx-recent".into(), ix),
|
||||||
&path,
|
&path,
|
||||||
&path_prefix,
|
&path_prefix,
|
||||||
|
false,
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -267,13 +321,11 @@ impl ContextPicker {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let task = context_store.update(cx, |context_store, cx| {
|
let task = context_store.update(cx, |context_store, cx| {
|
||||||
context_store.add_file_from_path(project_path.clone(), cx)
|
context_store.add_file_from_path(project_path.clone(), true, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, |_, mut cx| async move {
|
cx.spawn_in(window, async move |_, cx| task.await.notify_async_err(cx))
|
||||||
task.await.notify_async_err(&mut cx)
|
.detach();
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -296,13 +348,13 @@ impl ContextPicker {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&thread.id, cx));
|
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&thread.id, cx));
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(async move |this, cx| {
|
||||||
let thread = open_thread_task.await?;
|
let thread = open_thread_task.await?;
|
||||||
context_store.update(&mut cx, |context_store, cx| {
|
context_store.update(cx, |context_store, cx| {
|
||||||
context_store.add_thread(thread, cx);
|
context_store.add_thread(thread, true, cx);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
this.update(&mut cx, |_this, cx| cx.notify())
|
this.update(cx, |_this, cx| cx.notify())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +371,7 @@ impl ContextPicker {
|
|||||||
|
|
||||||
let mut current_files = context_store.file_paths(cx);
|
let mut current_files = context_store.file_paths(cx);
|
||||||
|
|
||||||
if let Some(active_path) = Self::active_singleton_buffer_path(&workspace, cx) {
|
if let Some(active_path) = active_singleton_buffer_path(&workspace, cx) {
|
||||||
current_files.insert(active_path);
|
current_files.insert(active_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,16 +427,6 @@ impl ContextPicker {
|
|||||||
|
|
||||||
recent
|
recent
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_singleton_buffer_path(workspace: &Workspace, cx: &App) -> Option<PathBuf> {
|
|
||||||
let active_item = workspace.active_item(cx)?;
|
|
||||||
|
|
||||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
|
||||||
let buffer = editor.buffer().read(cx).as_singleton()?;
|
|
||||||
|
|
||||||
let path = buffer.read(cx).file()?.path().to_path_buf();
|
|
||||||
Some(path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for ContextPicker {}
|
impl EventEmitter<DismissEvent> for ContextPicker {}
|
||||||
@@ -392,11 +434,11 @@ impl EventEmitter<DismissEvent> for ContextPicker {}
|
|||||||
impl Focusable for ContextPicker {
|
impl Focusable for ContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
|
ContextPickerState::Default(menu) => menu.focus_handle(cx),
|
||||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
ContextPickerState::File(file_picker) => file_picker.focus_handle(cx),
|
||||||
ContextPickerMode::Directory(directory_picker) => directory_picker.focus_handle(cx),
|
ContextPickerState::Symbol(symbol_picker) => symbol_picker.focus_handle(cx),
|
||||||
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
ContextPickerState::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
||||||
ContextPickerMode::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
ContextPickerState::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,13 +449,11 @@ impl Render for ContextPicker {
|
|||||||
.w(px(400.))
|
.w(px(400.))
|
||||||
.min_w(px(400.))
|
.min_w(px(400.))
|
||||||
.map(|parent| match &self.mode {
|
.map(|parent| match &self.mode {
|
||||||
ContextPickerMode::Default(menu) => parent.child(menu.clone()),
|
ContextPickerState::Default(menu) => parent.child(menu.clone()),
|
||||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
ContextPickerState::File(file_picker) => parent.child(file_picker.clone()),
|
||||||
ContextPickerMode::Directory(directory_picker) => {
|
ContextPickerState::Symbol(symbol_picker) => parent.child(symbol_picker.clone()),
|
||||||
parent.child(directory_picker.clone())
|
ContextPickerState::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
||||||
}
|
ContextPickerState::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
||||||
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
|
||||||
ContextPickerMode::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,3 +464,216 @@ enum RecentEntry {
|
|||||||
},
|
},
|
||||||
Thread(ThreadContextEntry),
|
Thread(ThreadContextEntry),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supported_context_picker_modes(
|
||||||
|
thread_store: &Option<WeakEntity<ThreadStore>>,
|
||||||
|
) -> Vec<ContextPickerMode> {
|
||||||
|
let mut modes = vec![
|
||||||
|
ContextPickerMode::File,
|
||||||
|
ContextPickerMode::Symbol,
|
||||||
|
ContextPickerMode::Fetch,
|
||||||
|
];
|
||||||
|
if thread_store.is_some() {
|
||||||
|
modes.push(ContextPickerMode::Thread);
|
||||||
|
}
|
||||||
|
modes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_singleton_buffer_path(workspace: &Workspace, cx: &App) -> Option<PathBuf> {
|
||||||
|
let active_item = workspace.active_item(cx)?;
|
||||||
|
|
||||||
|
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||||
|
let buffer = editor.buffer().read(cx).as_singleton()?;
|
||||||
|
|
||||||
|
let path = buffer.read(cx).file()?.path().to_path_buf();
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recent_context_picker_entries(
|
||||||
|
context_store: Entity<ContextStore>,
|
||||||
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
workspace: Entity<Workspace>,
|
||||||
|
cx: &App,
|
||||||
|
) -> Vec<RecentEntry> {
|
||||||
|
let mut recent = Vec::with_capacity(6);
|
||||||
|
|
||||||
|
let mut current_files = context_store.read(cx).file_paths(cx);
|
||||||
|
|
||||||
|
let workspace = workspace.read(cx);
|
||||||
|
|
||||||
|
if let Some(active_path) = active_singleton_buffer_path(workspace, cx) {
|
||||||
|
current_files.insert(active_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let project = workspace.project().read(cx);
|
||||||
|
|
||||||
|
recent.extend(
|
||||||
|
workspace
|
||||||
|
.recent_navigation_history_iter(cx)
|
||||||
|
.filter(|(path, _)| !current_files.contains(&path.path.to_path_buf()))
|
||||||
|
.take(4)
|
||||||
|
.filter_map(|(project_path, _)| {
|
||||||
|
project
|
||||||
|
.worktree_for_id(project_path.worktree_id, cx)
|
||||||
|
.map(|worktree| RecentEntry::File {
|
||||||
|
project_path,
|
||||||
|
path_prefix: worktree.read(cx).root_name().into(),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut current_threads = context_store.read(cx).thread_ids();
|
||||||
|
|
||||||
|
if let Some(active_thread) = workspace
|
||||||
|
.panel::<AssistantPanel>(cx)
|
||||||
|
.map(|panel| panel.read(cx).active_thread(cx))
|
||||||
|
{
|
||||||
|
current_threads.insert(active_thread.read(cx).id().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(thread_store) = thread_store.and_then(|thread_store| thread_store.upgrade()) {
|
||||||
|
recent.extend(
|
||||||
|
thread_store
|
||||||
|
.read(cx)
|
||||||
|
.threads()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|thread| !current_threads.contains(&thread.id))
|
||||||
|
.take(2)
|
||||||
|
.map(|thread| {
|
||||||
|
RecentEntry::Thread(ThreadContextEntry {
|
||||||
|
id: thread.id,
|
||||||
|
summary: thread.summary,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
recent
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert_crease_for_mention(
|
||||||
|
excerpt_id: ExcerptId,
|
||||||
|
crease_start: text::Anchor,
|
||||||
|
content_len: usize,
|
||||||
|
crease_label: SharedString,
|
||||||
|
crease_icon_path: SharedString,
|
||||||
|
editor_entity: Entity<Editor>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
|
editor_entity.update(cx, |editor, cx| {
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
|
||||||
|
let Some(start) = snapshot.anchor_in_excerpt(excerpt_id, crease_start) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
|
||||||
|
|
||||||
|
let placeholder = FoldPlaceholder {
|
||||||
|
render: render_fold_icon_button(
|
||||||
|
crease_icon_path,
|
||||||
|
crease_label,
|
||||||
|
editor_entity.downgrade(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_trailer =
|
||||||
|
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
||||||
|
|
||||||
|
let crease = Crease::inline(
|
||||||
|
start..end,
|
||||||
|
placeholder.clone(),
|
||||||
|
fold_toggle("mention"),
|
||||||
|
render_trailer,
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.insert_creases(vec![crease.clone()], cx);
|
||||||
|
editor.fold_creases(vec![crease], false, window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_fold_icon_button(
|
||||||
|
icon_path: SharedString,
|
||||||
|
label: SharedString,
|
||||||
|
editor: WeakEntity<Editor>,
|
||||||
|
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
|
||||||
|
Arc::new({
|
||||||
|
move |fold_id, fold_range, cx| {
|
||||||
|
let is_in_text_selection = editor.upgrade().is_some_and(|editor| {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let snapshot = editor
|
||||||
|
.buffer()
|
||||||
|
.update(cx, |multi_buffer, cx| multi_buffer.snapshot(cx));
|
||||||
|
|
||||||
|
let is_in_pending_selection = || {
|
||||||
|
editor
|
||||||
|
.selections
|
||||||
|
.pending
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|pending_selection| {
|
||||||
|
pending_selection
|
||||||
|
.selection
|
||||||
|
.range()
|
||||||
|
.includes(&fold_range, &snapshot)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut is_in_complete_selection = || {
|
||||||
|
editor
|
||||||
|
.selections
|
||||||
|
.disjoint_in_range::<usize>(fold_range.clone(), cx)
|
||||||
|
.into_iter()
|
||||||
|
.any(|selection| {
|
||||||
|
// This is needed to cover a corner case, if we just check for an existing
|
||||||
|
// selection in the fold range, having a cursor at the start of the fold
|
||||||
|
// marks it as selected. Non-empty selections don't cause this.
|
||||||
|
let length = selection.end - selection.start;
|
||||||
|
length > 0
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
is_in_pending_selection() || is_in_complete_selection()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
ButtonLike::new(fold_id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
|
.toggle_state(is_in_text_selection)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Icon::from_path(icon_path.clone())
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label.clone())
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.single_line(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_toggle(
|
||||||
|
name: &'static str,
|
||||||
|
) -> impl Fn(
|
||||||
|
MultiBufferRow,
|
||||||
|
bool,
|
||||||
|
Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
|
||||||
|
&mut Window,
|
||||||
|
&mut App,
|
||||||
|
) -> AnyElement {
|
||||||
|
move |row, is_folded, fold, _window, _cx| {
|
||||||
|
Disclosure::new((name, row.0 as u64), !is_folded)
|
||||||
|
.toggle_state(is_folded)
|
||||||
|
.on_click(move |_e, window, cx| fold(!is_folded, window, cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1052
crates/assistant2/src/context_picker/completion_provider.rs
Normal file
@@ -1,269 +0,0 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use fuzzy::PathMatch;
|
|
||||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
|
||||||
use picker::{Picker, PickerDelegate};
|
|
||||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
|
||||||
use ui::{prelude::*, ListItem};
|
|
||||||
use util::ResultExt as _;
|
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
|
||||||
use crate::context_store::ContextStore;
|
|
||||||
|
|
||||||
pub struct DirectoryContextPicker {
|
|
||||||
picker: Entity<Picker<DirectoryContextPickerDelegate>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectoryContextPicker {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let delegate = DirectoryContextPickerDelegate::new(
|
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
);
|
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
|
||||||
|
|
||||||
Self { picker }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Focusable for DirectoryContextPicker {
|
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
|
||||||
self.picker.focus_handle(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for DirectoryContextPicker {
|
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
self.picker.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DirectoryContextPickerDelegate {
|
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
matches: Vec<PathMatch>,
|
|
||||||
selected_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectoryContextPickerDelegate {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
matches: Vec::new(),
|
|
||||||
selected_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
workspace: &Entity<Workspace>,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<Vec<PathMatch>> {
|
|
||||||
if query.is_empty() {
|
|
||||||
let workspace = workspace.read(cx);
|
|
||||||
let project = workspace.project().read(cx);
|
|
||||||
let directory_matches = project.worktrees(cx).flat_map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
|
||||||
worktree.directories(false, 0).map(move |entry| PathMatch {
|
|
||||||
score: 0.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: worktree.id().to_usize(),
|
|
||||||
path: entry.path.clone(),
|
|
||||||
path_prefix: path_prefix.clone(),
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
is_dir: true,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
Task::ready(directory_matches.collect())
|
|
||||||
} else {
|
|
||||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
|
||||||
let candidate_sets = worktrees
|
|
||||||
.into_iter()
|
|
||||||
.map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
|
|
||||||
PathMatchCandidateSet {
|
|
||||||
snapshot: worktree.snapshot(),
|
|
||||||
include_ignored: worktree
|
|
||||||
.root_entry()
|
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
|
||||||
include_root_name: true,
|
|
||||||
candidates: project::Candidates::Directories,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
cx.foreground_executor().spawn(async move {
|
|
||||||
fuzzy::match_path_sets(
|
|
||||||
candidate_sets.as_slice(),
|
|
||||||
query.as_str(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
100,
|
|
||||||
&cancellation_flag,
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PickerDelegate for DirectoryContextPickerDelegate {
|
|
||||||
type ListItem = ListItem;
|
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
|
||||||
self.matches.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
|
||||||
self.selected_index
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_selected_index(
|
|
||||||
&mut self,
|
|
||||||
ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
self.selected_index = ix;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
|
||||||
"Search folders…".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_matches(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
|
||||||
return Task::ready(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let mut paths = search_task.await;
|
|
||||||
let empty_path = Path::new("");
|
|
||||||
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
|
||||||
this.delegate.matches = paths;
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
|
||||||
let Some(mat) = self.matches.get(self.selected_index) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let project_path = ProjectPath {
|
|
||||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
|
||||||
path: mat.path.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(task) = self
|
|
||||||
.context_store
|
|
||||||
.update(cx, |context_store, cx| {
|
|
||||||
context_store.add_directory(project_path, cx)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let confirm_behavior = self.confirm_behavior;
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
|
||||||
match task.await.notify_async_err(&mut cx) {
|
|
||||||
None => anyhow::Ok(()),
|
|
||||||
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
|
|
||||||
ConfirmBehavior::KeepOpen => {}
|
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
|
||||||
self.context_picker
|
|
||||||
.update(cx, |_, cx| {
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
|
||||||
&self,
|
|
||||||
ix: usize,
|
|
||||||
selected: bool,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
|
||||||
let path_match = &self.matches[ix];
|
|
||||||
let directory_name = path_match.path.to_string_lossy().to_string();
|
|
||||||
|
|
||||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
|
||||||
context_store
|
|
||||||
.read(cx)
|
|
||||||
.includes_directory(&path_match.path)
|
|
||||||
.is_some()
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(
|
|
||||||
ListItem::new(ix)
|
|
||||||
.inset(true)
|
|
||||||
.toggle_state(selected)
|
|
||||||
.start_slot(
|
|
||||||
Icon::new(IconName::Folder)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(Label::new(directory_name))
|
|
||||||
.when(added, |el| {
|
|
||||||
el.end_slot(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::Check)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Success),
|
|
||||||
)
|
|
||||||
.child(Label::new("Added").size(LabelSize::Small)),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,13 @@ use std::cell::RefCell;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use futures::AsyncReadExt as _;
|
use futures::AsyncReadExt as _;
|
||||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
|
||||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use ui::{prelude::*, Context, ListItem, Window};
|
use ui::{Context, ListItem, Window, prelude::*};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
@@ -81,77 +81,80 @@ impl FetchContextPickerDelegate {
|
|||||||
url: String::new(),
|
url: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: String) -> Result<String> {
|
pub(crate) async fn fetch_url_content(
|
||||||
let url = if !url.starts_with("https://") && !url.starts_with("http://") {
|
http_client: Arc<HttpClientWithUrl>,
|
||||||
format!("https://{url}")
|
url: String,
|
||||||
} else {
|
) -> Result<String> {
|
||||||
url
|
let url = if !url.starts_with("https://") && !url.starts_with("http://") {
|
||||||
};
|
format!("https://{url}")
|
||||||
|
} else {
|
||||||
|
url
|
||||||
|
};
|
||||||
|
|
||||||
let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
|
let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
|
||||||
|
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
response
|
response
|
||||||
.body_mut()
|
.body_mut()
|
||||||
.read_to_end(&mut body)
|
.read_to_end(&mut body)
|
||||||
.await
|
.await
|
||||||
.context("error reading response body")?;
|
.context("error reading response body")?;
|
||||||
|
|
||||||
if response.status().is_client_error() {
|
if response.status().is_client_error() {
|
||||||
let text = String::from_utf8_lossy(body.as_slice());
|
let text = String::from_utf8_lossy(body.as_slice());
|
||||||
bail!(
|
bail!(
|
||||||
"status error {}, response: {text:?}",
|
"status error {}, response: {text:?}",
|
||||||
response.status().as_u16()
|
response.status().as_u16()
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(content_type) = response.headers().get("content-type") else {
|
||||||
|
bail!("missing Content-Type header");
|
||||||
|
};
|
||||||
|
let content_type = content_type
|
||||||
|
.to_str()
|
||||||
|
.context("invalid Content-Type header")?;
|
||||||
|
let content_type = match content_type {
|
||||||
|
"text/html" => ContentType::Html,
|
||||||
|
"text/plain" => ContentType::Plaintext,
|
||||||
|
"application/json" => ContentType::Json,
|
||||||
|
_ => ContentType::Html,
|
||||||
|
};
|
||||||
|
|
||||||
|
match content_type {
|
||||||
|
ContentType::Html => {
|
||||||
|
let mut handlers: Vec<TagHandler> = vec![
|
||||||
|
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
||||||
|
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
||||||
|
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
||||||
|
Rc::new(RefCell::new(markdown::ListHandler)),
|
||||||
|
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
||||||
|
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
||||||
|
];
|
||||||
|
if url.contains("wikipedia.org") {
|
||||||
|
use html_to_markdown::structure::wikipedia;
|
||||||
|
|
||||||
|
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
||||||
|
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
||||||
|
handlers.push(Rc::new(
|
||||||
|
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_html_to_markdown(&body[..], &mut handlers)
|
||||||
}
|
}
|
||||||
|
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
||||||
|
ContentType::Json => {
|
||||||
|
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
||||||
|
|
||||||
let Some(content_type) = response.headers().get("content-type") else {
|
Ok(format!(
|
||||||
bail!("missing Content-Type header");
|
"```json\n{}\n```",
|
||||||
};
|
serde_json::to_string_pretty(&json)?
|
||||||
let content_type = content_type
|
))
|
||||||
.to_str()
|
|
||||||
.context("invalid Content-Type header")?;
|
|
||||||
let content_type = match content_type {
|
|
||||||
"text/html" => ContentType::Html,
|
|
||||||
"text/plain" => ContentType::Plaintext,
|
|
||||||
"application/json" => ContentType::Json,
|
|
||||||
_ => ContentType::Html,
|
|
||||||
};
|
|
||||||
|
|
||||||
match content_type {
|
|
||||||
ContentType::Html => {
|
|
||||||
let mut handlers: Vec<TagHandler> = vec![
|
|
||||||
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
|
||||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
|
||||||
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
|
||||||
];
|
|
||||||
if url.contains("wikipedia.org") {
|
|
||||||
use html_to_markdown::structure::wikipedia;
|
|
||||||
|
|
||||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
|
||||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
|
||||||
handlers.push(Rc::new(
|
|
||||||
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
|
||||||
}
|
|
||||||
|
|
||||||
convert_html_to_markdown(&body[..], &mut handlers)
|
|
||||||
}
|
|
||||||
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
|
||||||
ContentType::Json => {
|
|
||||||
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
|
||||||
|
|
||||||
Ok(format!(
|
|
||||||
"```json\n{}\n```",
|
|
||||||
serde_json::to_string_pretty(&json)?
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,15 +163,11 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
type ListItem = ListItem;
|
type ListItem = ListItem;
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
if self.url.is_empty() {
|
if self.url.is_empty() { 0 } else { 1 }
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
|
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
|
||||||
"Enter the URL that you would like to fetch".into()
|
Some("Enter the URL that you would like to fetch".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
fn selected_index(&self) -> usize {
|
||||||
@@ -206,12 +205,12 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
let http_client = workspace.read(cx).client().http_client().clone();
|
let http_client = workspace.read(cx).client().http_client().clone();
|
||||||
let url = self.url.clone();
|
let url = self.url.clone();
|
||||||
let confirm_behavior = self.confirm_behavior;
|
let confirm_behavior = self.confirm_behavior;
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let text = cx
|
let text = cx
|
||||||
.background_spawn(Self::build_message(http_client, url.clone()))
|
.background_spawn(fetch_url_content(http_client, url.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.delegate
|
this.delegate
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, _cx| {
|
.update(cx, |context_store, _cx| {
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
use std::collections::BTreeSet;
|
|
||||||
use std::ops::Range;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use editor::actions::FoldAt;
|
|
||||||
use editor::display_map::{Crease, FoldId};
|
|
||||||
use editor::scroll::Autoscroll;
|
|
||||||
use editor::{Anchor, AnchorRangeExt, Editor, FoldPlaceholder, ToPoint};
|
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, AppContext, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful,
|
App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity,
|
||||||
Task, WeakEntity,
|
|
||||||
};
|
};
|
||||||
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||||
use rope::Point;
|
use ui::{ListItem, Tooltip, prelude::*};
|
||||||
use text::SelectionGoal;
|
|
||||||
use ui::{prelude::*, ButtonLike, Disclosure, ListItem, TintColor, Tooltip};
|
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
use crate::context_store::{ContextStore, FileInclusion};
|
use crate::context_store::{ContextStore, FileInclusion};
|
||||||
@@ -34,7 +24,6 @@ impl FileContextPicker {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
@@ -43,7 +32,6 @@ impl FileContextPicker {
|
|||||||
let delegate = FileContextPickerDelegate::new(
|
let delegate = FileContextPickerDelegate::new(
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
editor,
|
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
confirm_behavior,
|
||||||
);
|
);
|
||||||
@@ -68,7 +56,6 @@ impl Render for FileContextPicker {
|
|||||||
pub struct FileContextPickerDelegate {
|
pub struct FileContextPickerDelegate {
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
matches: Vec<PathMatch>,
|
matches: Vec<PathMatch>,
|
||||||
@@ -79,96 +66,18 @@ impl FileContextPickerDelegate {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
editor,
|
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
confirm_behavior,
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
workspace: &Entity<Workspace>,
|
|
||||||
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<Vec<PathMatch>> {
|
|
||||||
if query.is_empty() {
|
|
||||||
let workspace = workspace.read(cx);
|
|
||||||
let project = workspace.project().read(cx);
|
|
||||||
let recent_matches = workspace
|
|
||||||
.recent_navigation_history(Some(10), cx)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(project_path, _)| {
|
|
||||||
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
|
||||||
Some(PathMatch {
|
|
||||||
score: 0.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: project_path.worktree_id.to_usize(),
|
|
||||||
path: project_path.path,
|
|
||||||
path_prefix: worktree.read(cx).root_name().into(),
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
is_dir: false,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
|
||||||
worktree.files(false, 0).map(move |entry| PathMatch {
|
|
||||||
score: 0.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: worktree.id().to_usize(),
|
|
||||||
path: entry.path.clone(),
|
|
||||||
path_prefix: path_prefix.clone(),
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
is_dir: false,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
Task::ready(recent_matches.chain(file_matches).collect())
|
|
||||||
} else {
|
|
||||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
|
||||||
let candidate_sets = worktrees
|
|
||||||
.into_iter()
|
|
||||||
.map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
|
|
||||||
PathMatchCandidateSet {
|
|
||||||
snapshot: worktree.snapshot(),
|
|
||||||
include_ignored: worktree
|
|
||||||
.root_entry()
|
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
|
||||||
include_root_name: true,
|
|
||||||
candidates: project::Candidates::Files,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
cx.foreground_executor().spawn(async move {
|
|
||||||
fuzzy::match_path_sets(
|
|
||||||
candidate_sets.as_slice(),
|
|
||||||
query.as_str(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
100,
|
|
||||||
&cancellation_flag,
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for FileContextPickerDelegate {
|
impl PickerDelegate for FileContextPickerDelegate {
|
||||||
@@ -192,7 +101,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||||
"Search files…".into()
|
"Search files & directories…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(
|
||||||
@@ -205,13 +114,13 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
let search_task = search_paths(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
// TODO: This should be probably be run in the background.
|
// TODO: This should be probably be run in the background.
|
||||||
let paths = search_task.await;
|
let paths = search_task.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
this.delegate.matches = paths;
|
this.delegate.matches = paths;
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
@@ -223,114 +132,21 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(file_name) = mat
|
|
||||||
.path
|
|
||||||
.file_name()
|
|
||||||
.map(|os_str| os_str.to_string_lossy().into_owned())
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let full_path = mat.path.display().to_string();
|
|
||||||
|
|
||||||
let project_path = ProjectPath {
|
let project_path = ProjectPath {
|
||||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
||||||
path: mat.path.clone(),
|
path: mat.path.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(editor_entity) = self.editor.upgrade() else {
|
let is_directory = mat.is_dir;
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
editor_entity.update(cx, |editor, cx| {
|
|
||||||
editor.transact(window, cx, |editor, window, cx| {
|
|
||||||
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
|
|
||||||
{
|
|
||||||
let mut selections = editor.selections.all::<MultiBufferPoint>(cx);
|
|
||||||
|
|
||||||
for selection in selections.iter_mut() {
|
|
||||||
if selection.is_empty() {
|
|
||||||
let old_head = selection.head();
|
|
||||||
let new_head = MultiBufferPoint::new(
|
|
||||||
old_head.row,
|
|
||||||
old_head.column.saturating_sub(1),
|
|
||||||
);
|
|
||||||
selection.set_head(new_head, SelectionGoal::None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
|
||||||
s.select(selections)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let start_anchors = {
|
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
|
||||||
editor
|
|
||||||
.selections
|
|
||||||
.all::<Point>(cx)
|
|
||||||
.into_iter()
|
|
||||||
.map(|selection| snapshot.anchor_before(selection.start))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.insert(&full_path, window, cx);
|
|
||||||
|
|
||||||
let end_anchors = {
|
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
|
||||||
editor
|
|
||||||
.selections
|
|
||||||
.all::<Point>(cx)
|
|
||||||
.into_iter()
|
|
||||||
.map(|selection| snapshot.anchor_after(selection.end))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.insert("\n", window, cx); // Needed to end the fold
|
|
||||||
|
|
||||||
let file_icon = FileIcons::get_icon(&Path::new(&full_path), cx)
|
|
||||||
.unwrap_or_else(|| SharedString::new(""));
|
|
||||||
|
|
||||||
let placeholder = FoldPlaceholder {
|
|
||||||
render: render_fold_icon_button(
|
|
||||||
file_icon,
|
|
||||||
file_name.into(),
|
|
||||||
editor_entity.downgrade(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let render_trailer =
|
|
||||||
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
|
||||||
|
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
|
||||||
let mut rows_to_fold = BTreeSet::new();
|
|
||||||
let crease_iter = start_anchors
|
|
||||||
.into_iter()
|
|
||||||
.zip(end_anchors)
|
|
||||||
.map(|(start, end)| {
|
|
||||||
rows_to_fold.insert(MultiBufferRow(start.to_point(&buffer).row));
|
|
||||||
|
|
||||||
Crease::inline(
|
|
||||||
start..end,
|
|
||||||
placeholder.clone(),
|
|
||||||
fold_toggle("tool-use"),
|
|
||||||
render_trailer,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.insert_creases(crease_iter, cx);
|
|
||||||
|
|
||||||
for buffer_row in rows_to_fold {
|
|
||||||
editor.fold_at(&FoldAt { buffer_row }, window, cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let Some(task) = self
|
let Some(task) = self
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, cx| {
|
.update(cx, |context_store, cx| {
|
||||||
context_store.add_file_from_path(project_path, cx)
|
if is_directory {
|
||||||
|
context_store.add_directory(project_path, true, cx)
|
||||||
|
} else {
|
||||||
|
context_store.add_file_from_path(project_path, true, cx)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
else {
|
else {
|
||||||
@@ -338,10 +154,10 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let confirm_behavior = self.confirm_behavior;
|
let confirm_behavior = self.confirm_behavior;
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match task.await.notify_async_err(&mut cx) {
|
match task.await.notify_async_err(cx) {
|
||||||
None => anyhow::Ok(()),
|
None => anyhow::Ok(()),
|
||||||
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
|
Some(()) => this.update_in(cx, |this, window, cx| match confirm_behavior {
|
||||||
ConfirmBehavior::KeepOpen => {}
|
ConfirmBehavior::KeepOpen => {}
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
||||||
}),
|
}),
|
||||||
@@ -375,6 +191,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
||||||
&path_match.path,
|
&path_match.path,
|
||||||
&path_match.path_prefix,
|
&path_match.path_prefix,
|
||||||
|
path_match.is_dir,
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
@@ -382,15 +199,93 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_file_context_entry(
|
pub(crate) fn search_paths(
|
||||||
id: ElementId,
|
query: String,
|
||||||
path: &Path,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
path_prefix: &Arc<str>,
|
workspace: &Entity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Stateful<Div> {
|
) -> Task<Vec<PathMatch>> {
|
||||||
let (file_name, directory) = if path == Path::new("") {
|
if query.is_empty() {
|
||||||
(SharedString::from(path_prefix.clone()), None)
|
let workspace = workspace.read(cx);
|
||||||
|
let project = workspace.project().read(cx);
|
||||||
|
let recent_matches = workspace
|
||||||
|
.recent_navigation_history(Some(10), cx)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(project_path, _)| {
|
||||||
|
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
||||||
|
Some(PathMatch {
|
||||||
|
score: 0.,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: project_path.worktree_id.to_usize(),
|
||||||
|
path: project_path.path,
|
||||||
|
path_prefix: worktree.read(cx).root_name().into(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||||
|
let worktree = worktree.read(cx);
|
||||||
|
let path_prefix: Arc<str> = worktree.root_name().into();
|
||||||
|
worktree.entries(false, 0).map(move |entry| PathMatch {
|
||||||
|
score: 0.,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: worktree.id().to_usize(),
|
||||||
|
path: entry.path.clone(),
|
||||||
|
path_prefix: path_prefix.clone(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: entry.is_dir(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::ready(recent_matches.chain(file_matches).collect())
|
||||||
|
} else {
|
||||||
|
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
||||||
|
let candidate_sets = worktrees
|
||||||
|
.into_iter()
|
||||||
|
.map(|worktree| {
|
||||||
|
let worktree = worktree.read(cx);
|
||||||
|
|
||||||
|
PathMatchCandidateSet {
|
||||||
|
snapshot: worktree.snapshot(),
|
||||||
|
include_ignored: worktree
|
||||||
|
.root_entry()
|
||||||
|
.map_or(false, |entry| entry.is_ignored),
|
||||||
|
include_root_name: true,
|
||||||
|
candidates: project::Candidates::Entries,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
cx.foreground_executor().spawn(async move {
|
||||||
|
fuzzy::match_path_sets(
|
||||||
|
candidate_sets.as_slice(),
|
||||||
|
query.as_str(),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
100,
|
||||||
|
&cancellation_flag,
|
||||||
|
executor,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_file_name_and_directory(
|
||||||
|
path: &Path,
|
||||||
|
path_prefix: &str,
|
||||||
|
) -> (SharedString, Option<SharedString>) {
|
||||||
|
if path == Path::new("") {
|
||||||
|
(
|
||||||
|
SharedString::from(
|
||||||
|
path_prefix
|
||||||
|
.trim_end_matches(std::path::MAIN_SEPARATOR)
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let file_name = path
|
let file_name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -399,23 +294,46 @@ pub fn render_file_context_entry(
|
|||||||
.to_string()
|
.to_string()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let mut directory = format!("{}/", path_prefix);
|
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("")) {
|
if let Some(parent) = path.parent().filter(|parent| parent != &Path::new("")) {
|
||||||
directory.push_str(&parent.to_string_lossy());
|
directory.push_str(&parent.to_string_lossy());
|
||||||
directory.push('/');
|
directory.push('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
(file_name, Some(directory))
|
(file_name, Some(directory.into()))
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let added = context_store
|
pub fn render_file_context_entry(
|
||||||
.upgrade()
|
id: ElementId,
|
||||||
.and_then(|context_store| context_store.read(cx).will_include_file_path(path, cx));
|
path: &Path,
|
||||||
|
path_prefix: &Arc<str>,
|
||||||
|
is_directory: bool,
|
||||||
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
cx: &App,
|
||||||
|
) -> Stateful<Div> {
|
||||||
|
let (file_name, directory) = extract_file_name_and_directory(path, path_prefix);
|
||||||
|
|
||||||
let file_icon = FileIcons::get_icon(&path, cx)
|
let added = context_store.upgrade().and_then(|context_store| {
|
||||||
.map(Icon::from_path)
|
if is_directory {
|
||||||
.unwrap_or_else(|| Icon::new(IconName::File));
|
context_store.read(cx).includes_directory(path)
|
||||||
|
} else {
|
||||||
|
context_store.read(cx).will_include_file_path(path, cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let file_icon = if is_directory {
|
||||||
|
FileIcons::get_folder_icon(false, cx)
|
||||||
|
} else {
|
||||||
|
FileIcons::get_icon(&path, cx)
|
||||||
|
}
|
||||||
|
.map(Icon::from_path)
|
||||||
|
.unwrap_or_else(|| Icon::new(IconName::File));
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(id)
|
.id(id)
|
||||||
@@ -464,85 +382,3 @@ pub fn render_file_context_entry(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_fold_icon_button(
|
|
||||||
icon: SharedString,
|
|
||||||
label: SharedString,
|
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
|
|
||||||
Arc::new(move |fold_id, fold_range, cx| {
|
|
||||||
let is_in_text_selection = editor.upgrade().is_some_and(|editor| {
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
let snapshot = editor
|
|
||||||
.buffer()
|
|
||||||
.update(cx, |multi_buffer, cx| multi_buffer.snapshot(cx));
|
|
||||||
|
|
||||||
let is_in_pending_selection = || {
|
|
||||||
editor
|
|
||||||
.selections
|
|
||||||
.pending
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|pending_selection| {
|
|
||||||
pending_selection
|
|
||||||
.selection
|
|
||||||
.range()
|
|
||||||
.includes(&fold_range, &snapshot)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut is_in_complete_selection = || {
|
|
||||||
editor
|
|
||||||
.selections
|
|
||||||
.disjoint_in_range::<usize>(fold_range.clone(), cx)
|
|
||||||
.into_iter()
|
|
||||||
.any(|selection| {
|
|
||||||
// This is needed to cover a corner case, if we just check for an existing
|
|
||||||
// selection in the fold range, having a cursor at the start of the fold
|
|
||||||
// marks it as selected. Non-empty selections don't cause this.
|
|
||||||
let length = selection.end - selection.start;
|
|
||||||
length > 0
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
is_in_pending_selection() || is_in_complete_selection()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ButtonLike::new(fold_id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
|
||||||
.toggle_state(is_in_text_selection)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Icon::from_path(icon.clone())
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new(label.clone())
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.single_line(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fold_toggle(
|
|
||||||
name: &'static str,
|
|
||||||
) -> impl Fn(
|
|
||||||
MultiBufferRow,
|
|
||||||
bool,
|
|
||||||
Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
|
|
||||||
&mut Window,
|
|
||||||
&mut App,
|
|
||||||
) -> AnyElement {
|
|
||||||
move |row, is_folded, fold, _window, _cx| {
|
|
||||||
Disclosure::new((name, row.0 as u64), !is_folded)
|
|
||||||
.toggle_state(is_folded)
|
|
||||||
.on_click(move |_e, window, cx| fold(!is_folded, window, cx))
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
438
crates/assistant2/src/context_picker/symbol_context_picker.rs
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
use std::cmp::Reverse;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
|
use gpui::{
|
||||||
|
App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity,
|
||||||
|
};
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
use picker::{Picker, PickerDelegate};
|
||||||
|
use project::{DocumentSymbol, Symbol};
|
||||||
|
use text::OffsetRangeExt;
|
||||||
|
use ui::{ListItem, prelude::*};
|
||||||
|
use util::ResultExt as _;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
|
use crate::context_store::ContextStore;
|
||||||
|
|
||||||
|
pub struct SymbolContextPicker {
|
||||||
|
picker: Entity<Picker<SymbolContextPickerDelegate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SymbolContextPicker {
|
||||||
|
pub fn new(
|
||||||
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
confirm_behavior: ConfirmBehavior,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let delegate = SymbolContextPickerDelegate::new(
|
||||||
|
context_picker,
|
||||||
|
workspace,
|
||||||
|
context_store,
|
||||||
|
confirm_behavior,
|
||||||
|
);
|
||||||
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
|
Self { picker }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Focusable for SymbolContextPicker {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
self.picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for SymbolContextPicker {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
self.picker.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SymbolContextPickerDelegate {
|
||||||
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
confirm_behavior: ConfirmBehavior,
|
||||||
|
matches: Vec<SymbolEntry>,
|
||||||
|
selected_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SymbolContextPickerDelegate {
|
||||||
|
pub fn new(
|
||||||
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
confirm_behavior: ConfirmBehavior,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
context_picker,
|
||||||
|
workspace,
|
||||||
|
context_store,
|
||||||
|
confirm_behavior,
|
||||||
|
matches: Vec::new(),
|
||||||
|
selected_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickerDelegate for SymbolContextPickerDelegate {
|
||||||
|
type ListItem = ListItem;
|
||||||
|
|
||||||
|
fn match_count(&self) -> usize {
|
||||||
|
self.matches.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_index(&self) -> usize {
|
||||||
|
self.selected_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Picker<Self>>,
|
||||||
|
) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||||
|
"Search symbols…".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(
|
||||||
|
&mut self,
|
||||||
|
query: String,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Task<()> {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return Task::ready(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_task = search_symbols(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
||||||
|
let context_store = self.context_store.clone();
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let symbols = search_task
|
||||||
|
.await
|
||||||
|
.context("Failed to load symbols")
|
||||||
|
.log_err()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let symbol_entries = context_store
|
||||||
|
.read_with(cx, |context_store, cx| {
|
||||||
|
compute_symbol_entries(symbols, context_store, cx)
|
||||||
|
})
|
||||||
|
.log_err()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
this.update(cx, |this, _cx| {
|
||||||
|
this.delegate.matches = symbol_entries;
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
let Some(mat) = self.matches.get(self.selected_index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let confirm_behavior = self.confirm_behavior;
|
||||||
|
let add_symbol_task = add_symbol(
|
||||||
|
mat.symbol.clone(),
|
||||||
|
true,
|
||||||
|
workspace,
|
||||||
|
self.context_store.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
let selected_index = self.selected_index;
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let included = add_symbol_task.await?;
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
if let Some(mat) = this.delegate.matches.get_mut(selected_index) {
|
||||||
|
mat.is_included = included;
|
||||||
|
}
|
||||||
|
match confirm_behavior {
|
||||||
|
ConfirmBehavior::KeepOpen => {}
|
||||||
|
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
self.context_picker
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
selected: bool,
|
||||||
|
_window: &mut Window,
|
||||||
|
_: &mut Context<Picker<Self>>,
|
||||||
|
) -> Option<Self::ListItem> {
|
||||||
|
let mat = &self.matches[ix];
|
||||||
|
|
||||||
|
Some(ListItem::new(ix).inset(true).toggle_state(selected).child(
|
||||||
|
render_symbol_context_entry(
|
||||||
|
ElementId::NamedInteger("symbol-ctx-picker".into(), ix),
|
||||||
|
mat,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SymbolEntry {
|
||||||
|
pub symbol: Symbol,
|
||||||
|
pub is_included: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_symbol(
|
||||||
|
symbol: Symbol,
|
||||||
|
remove_if_exists: bool,
|
||||||
|
workspace: Entity<Workspace>,
|
||||||
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<bool>> {
|
||||||
|
let project = workspace.read(cx).project().clone();
|
||||||
|
let open_buffer_task = project.update(cx, |project, cx| {
|
||||||
|
project.open_buffer(symbol.path.clone(), cx)
|
||||||
|
});
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let buffer = open_buffer_task.await?;
|
||||||
|
let document_symbols = project
|
||||||
|
.update(cx, |project, cx| project.document_symbols(&buffer, cx))?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Try to find a matching document symbol. Document symbols include
|
||||||
|
// not only the symbol itself (e.g. function name), but they also
|
||||||
|
// include the context that they contain (e.g. function body).
|
||||||
|
let (name, range, enclosing_range) = if let Some(DocumentSymbol {
|
||||||
|
name,
|
||||||
|
range,
|
||||||
|
selection_range,
|
||||||
|
..
|
||||||
|
}) =
|
||||||
|
find_matching_symbol(&symbol, document_symbols.as_slice())
|
||||||
|
{
|
||||||
|
(name, selection_range, range)
|
||||||
|
} else {
|
||||||
|
// If we do not find a matching document symbol, fall back to
|
||||||
|
// just the symbol itself
|
||||||
|
(symbol.name, symbol.range.clone(), symbol.range)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (range, enclosing_range) = buffer.read_with(cx, |buffer, _| {
|
||||||
|
(
|
||||||
|
buffer.anchor_after(range.start)..buffer.anchor_before(range.end),
|
||||||
|
buffer.anchor_after(enclosing_range.start)
|
||||||
|
..buffer.anchor_before(enclosing_range.end),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
context_store
|
||||||
|
.update(cx, move |context_store, cx| {
|
||||||
|
context_store.add_symbol(
|
||||||
|
buffer,
|
||||||
|
name.into(),
|
||||||
|
range,
|
||||||
|
enclosing_range,
|
||||||
|
remove_if_exists,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_matching_symbol(symbol: &Symbol, candidates: &[DocumentSymbol]) -> Option<DocumentSymbol> {
|
||||||
|
let mut candidates = candidates.iter();
|
||||||
|
let mut candidate = candidates.next()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if candidate.range.start > symbol.range.end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if candidate.range.end < symbol.range.start {
|
||||||
|
candidate = candidates.next()?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if candidate.selection_range == symbol.range {
|
||||||
|
return Some(candidate.clone());
|
||||||
|
}
|
||||||
|
if candidate.range.start <= symbol.range.start && symbol.range.end <= candidate.range.end {
|
||||||
|
candidates = candidate.children.iter();
|
||||||
|
candidate = candidates.next()?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn search_symbols(
|
||||||
|
query: String,
|
||||||
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
workspace: &Entity<Workspace>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<Vec<(StringMatch, Symbol)>>> {
|
||||||
|
let symbols_task = workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.project()
|
||||||
|
.update(cx, |project, cx| project.symbols(&query, cx))
|
||||||
|
});
|
||||||
|
let project = workspace.read(cx).project().clone();
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let symbols = symbols_task.await?;
|
||||||
|
let (visible_match_candidates, external_match_candidates): (Vec<_>, Vec<_>) = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
symbols
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, symbol)| StringMatchCandidate::new(id, &symbol.label.filter_text()))
|
||||||
|
.partition(|candidate| {
|
||||||
|
project
|
||||||
|
.entry_for_path(&symbols[candidate.id].path, cx)
|
||||||
|
.map_or(false, |e| !e.is_ignored)
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
const MAX_MATCHES: usize = 100;
|
||||||
|
let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
|
||||||
|
&visible_match_candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
MAX_MATCHES,
|
||||||
|
&cancellation_flag,
|
||||||
|
cx.background_executor().clone(),
|
||||||
|
));
|
||||||
|
let mut external_matches = cx.background_executor().block(fuzzy::match_strings(
|
||||||
|
&external_match_candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
|
||||||
|
&cancellation_flag,
|
||||||
|
cx.background_executor().clone(),
|
||||||
|
));
|
||||||
|
let sort_key_for_match = |mat: &StringMatch| {
|
||||||
|
let symbol = &symbols[mat.candidate_id];
|
||||||
|
(Reverse(OrderedFloat(mat.score)), symbol.label.filter_text())
|
||||||
|
};
|
||||||
|
|
||||||
|
visible_matches.sort_unstable_by_key(sort_key_for_match);
|
||||||
|
external_matches.sort_unstable_by_key(sort_key_for_match);
|
||||||
|
let mut matches = visible_matches;
|
||||||
|
matches.append(&mut external_matches);
|
||||||
|
|
||||||
|
Ok(matches
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut mat| {
|
||||||
|
let symbol = symbols[mat.candidate_id].clone();
|
||||||
|
let filter_start = symbol.label.filter_range.start;
|
||||||
|
for position in &mut mat.positions {
|
||||||
|
*position += filter_start;
|
||||||
|
}
|
||||||
|
(mat, symbol)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_symbol_entries(
|
||||||
|
symbols: Vec<(StringMatch, Symbol)>,
|
||||||
|
context_store: &ContextStore,
|
||||||
|
cx: &App,
|
||||||
|
) -> Vec<SymbolEntry> {
|
||||||
|
let mut symbol_entries = Vec::with_capacity(symbols.len());
|
||||||
|
for (_, symbol) in symbols {
|
||||||
|
let symbols_for_path = context_store.included_symbols_by_path().get(&symbol.path);
|
||||||
|
let is_included = if let Some(symbols_for_path) = symbols_for_path {
|
||||||
|
let mut is_included = false;
|
||||||
|
for included_symbol_id in symbols_for_path {
|
||||||
|
if included_symbol_id.name.as_ref() == symbol.name.as_str() {
|
||||||
|
if let Some(buffer) = context_store.buffer_for_symbol(included_symbol_id) {
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let included_symbol_range =
|
||||||
|
included_symbol_id.range.to_point_utf16(&snapshot);
|
||||||
|
|
||||||
|
if included_symbol_range.start == symbol.range.start.0
|
||||||
|
&& included_symbol_range.end == symbol.range.end.0
|
||||||
|
{
|
||||||
|
is_included = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is_included
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
symbol_entries.push(SymbolEntry {
|
||||||
|
symbol,
|
||||||
|
is_included,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
symbol_entries
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful<Div> {
|
||||||
|
let path = entry
|
||||||
|
.symbol
|
||||||
|
.path
|
||||||
|
.path
|
||||||
|
.file_name()
|
||||||
|
.map(|s| s.to_string_lossy())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let symbol_location = format!("{} L{}", path, entry.symbol.range.start.0.row + 1);
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id(id)
|
||||||
|
.gap_1p5()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Code)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Label::new(&entry.symbol.name))
|
||||||
|
.child(
|
||||||
|
Label::new(symbol_location)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(entry.is_included, |el| {
|
||||||
|
el.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.justify_end()
|
||||||
|
.gap_0p5()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Check)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Success),
|
||||||
|
)
|
||||||
|
.child(Label::new("Added").size(LabelSize::Small)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use ui::{prelude::*, ListItem};
|
use ui::{ListItem, prelude::*};
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
use crate::context_store::{self, ContextStore};
|
use crate::context_store::{self, ContextStore};
|
||||||
@@ -110,48 +110,14 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let Ok(threads) = self.thread_store.update(cx, |this, _cx| {
|
let Some(threads) = self.thread_store.upgrade() else {
|
||||||
this.threads()
|
|
||||||
.into_iter()
|
|
||||||
.map(|thread| ThreadContextEntry {
|
|
||||||
id: thread.id,
|
|
||||||
summary: thread.summary,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}) else {
|
|
||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
let search_task = search_threads(query, threads, cx);
|
||||||
let search_task = cx.background_spawn(async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if query.is_empty() {
|
|
||||||
threads
|
|
||||||
} else {
|
|
||||||
let candidates = threads
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(id, thread)| StringMatchCandidate::new(id, &thread.summary))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let matches = fuzzy::match_strings(
|
|
||||||
&candidates,
|
|
||||||
&query,
|
|
||||||
false,
|
|
||||||
100,
|
|
||||||
&Default::default(),
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
matches
|
|
||||||
.into_iter()
|
|
||||||
.map(|mat| threads[mat.candidate_id].clone())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
|
||||||
let matches = search_task.await;
|
let matches = search_task.await;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.delegate.matches = matches;
|
this.delegate.matches = matches;
|
||||||
this.delegate.selected_index = 0;
|
this.delegate.selected_index = 0;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -171,12 +137,14 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let thread = open_thread_task.await?;
|
let thread = open_thread_task.await?;
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.delegate
|
this.delegate
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
|
.update(cx, |context_store, cx| {
|
||||||
|
context_store.add_thread(thread, true, cx)
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
match this.delegate.confirm_behavior {
|
match this.delegate.confirm_behavior {
|
||||||
@@ -223,13 +191,18 @@ pub fn render_thread_context_entry(
|
|||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::MessageCircle)
|
h_flex()
|
||||||
.size(IconSize::XSmall)
|
.gap_1p5()
|
||||||
.color(Color::Muted),
|
.max_w_72()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::MessageBubbles)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(Label::new(thread.summary.clone()).truncate()),
|
||||||
)
|
)
|
||||||
.child(Label::new(thread.summary.clone()))
|
|
||||||
.child(div().w_full())
|
|
||||||
.when(added, |el| {
|
.when(added, |el| {
|
||||||
el.child(
|
el.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -243,3 +216,46 @@ pub fn render_thread_context_entry(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn search_threads(
|
||||||
|
query: String,
|
||||||
|
thread_store: Entity<ThreadStore>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Vec<ThreadContextEntry>> {
|
||||||
|
let threads = thread_store.update(cx, |this, _cx| {
|
||||||
|
this.threads()
|
||||||
|
.into_iter()
|
||||||
|
.map(|thread| ThreadContextEntry {
|
||||||
|
id: thread.id,
|
||||||
|
summary: thread.summary,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
if query.is_empty() {
|
||||||
|
threads
|
||||||
|
} else {
|
||||||
|
let candidates = threads
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, thread)| StringMatchCandidate::new(id, &thread.summary))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let matches = fuzzy::match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
100,
|
||||||
|
&Default::default(),
|
||||||
|
executor,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
matches
|
||||||
|
.into_iter()
|
||||||
|
.map(|mat| threads[mat.candidate_id].clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use futures::{self, future, Future, FutureExt};
|
use futures::{self, Future, FutureExt, future};
|
||||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, SharedString, Task, WeakEntity};
|
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, SharedString, Task, WeakEntity};
|
||||||
use language::Buffer;
|
use language::{Buffer, File};
|
||||||
use project::{ProjectPath, Worktree};
|
use project::{ProjectItem, ProjectPath, Worktree};
|
||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
use text::BufferId;
|
use text::{Anchor, BufferId, OffsetRangeExt};
|
||||||
|
use util::{ResultExt, maybe};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context::{
|
use crate::context::{
|
||||||
AssistantContext, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext,
|
AssistantContext, ContextBuffer, ContextId, ContextSymbol, ContextSymbolId, DirectoryContext,
|
||||||
FetchedUrlContext, FileContext, ThreadContext,
|
FetchedUrlContext, FileContext, SymbolContext, ThreadContext,
|
||||||
};
|
};
|
||||||
use crate::context_strip::SuggestedContext;
|
use crate::context_strip::SuggestedContext;
|
||||||
use crate::thread::{Thread, ThreadId};
|
use crate::thread::{Thread, ThreadId};
|
||||||
@@ -25,6 +27,9 @@ pub struct ContextStore {
|
|||||||
next_context_id: ContextId,
|
next_context_id: ContextId,
|
||||||
files: BTreeMap<BufferId, ContextId>,
|
files: BTreeMap<BufferId, ContextId>,
|
||||||
directories: HashMap<PathBuf, ContextId>,
|
directories: HashMap<PathBuf, ContextId>,
|
||||||
|
symbols: HashMap<ContextSymbolId, ContextId>,
|
||||||
|
symbol_buffers: HashMap<ContextSymbolId, Entity<Buffer>>,
|
||||||
|
symbols_by_path: HashMap<ProjectPath, Vec<ContextSymbolId>>,
|
||||||
threads: HashMap<ThreadId, ContextId>,
|
threads: HashMap<ThreadId, ContextId>,
|
||||||
fetched_urls: HashMap<String, ContextId>,
|
fetched_urls: HashMap<String, ContextId>,
|
||||||
}
|
}
|
||||||
@@ -37,21 +42,22 @@ impl ContextStore {
|
|||||||
next_context_id: ContextId(0),
|
next_context_id: ContextId(0),
|
||||||
files: BTreeMap::default(),
|
files: BTreeMap::default(),
|
||||||
directories: HashMap::default(),
|
directories: HashMap::default(),
|
||||||
|
symbols: HashMap::default(),
|
||||||
|
symbol_buffers: HashMap::default(),
|
||||||
|
symbols_by_path: HashMap::default(),
|
||||||
threads: HashMap::default(),
|
threads: HashMap::default(),
|
||||||
fetched_urls: HashMap::default(),
|
fetched_urls: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ContextSnapshot> + 'a {
|
|
||||||
self.context()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|context| context.snapshot(cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn context(&self) -> &Vec<AssistantContext> {
|
pub fn context(&self) -> &Vec<AssistantContext> {
|
||||||
&self.context
|
&self.context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn context_for_id(&self, id: ContextId) -> Option<&AssistantContext> {
|
||||||
|
self.context().iter().find(|context| context.id() == id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.context.clear();
|
self.context.clear();
|
||||||
self.files.clear();
|
self.files.clear();
|
||||||
@@ -63,6 +69,7 @@ impl ContextStore {
|
|||||||
pub fn add_file_from_path(
|
pub fn add_file_from_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
|
remove_if_exists: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
@@ -74,18 +81,20 @@ impl ContextStore {
|
|||||||
return Task::ready(Err(anyhow!("failed to read project")));
|
return Task::ready(Err(anyhow!("failed to read project")));
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(async move |this, cx| {
|
||||||
let open_buffer_task = project.update(&mut cx, |project, cx| {
|
let open_buffer_task = project.update(cx, |project, cx| {
|
||||||
project.open_buffer(project_path.clone(), cx)
|
project.open_buffer(project_path.clone(), cx)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let buffer_entity = open_buffer_task.await?;
|
let buffer_entity = open_buffer_task.await?;
|
||||||
let buffer_id = this.update(&mut cx, |_, cx| buffer_entity.read(cx).remote_id())?;
|
let buffer_id = this.update(cx, |_, cx| buffer_entity.read(cx).remote_id())?;
|
||||||
|
|
||||||
let already_included = this.update(&mut cx, |this, _cx| {
|
let already_included = this.update(cx, |this, _cx| {
|
||||||
match this.will_include_buffer(buffer_id, &project_path.path) {
|
match this.will_include_buffer(buffer_id, &project_path.path) {
|
||||||
Some(FileInclusion::Direct(context_id)) => {
|
Some(FileInclusion::Direct(context_id)) => {
|
||||||
this.remove_context(context_id);
|
if remove_if_exists {
|
||||||
|
this.remove_context(context_id);
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Some(FileInclusion::InDirectory(_)) => true,
|
Some(FileInclusion::InDirectory(_)) => true,
|
||||||
@@ -97,19 +106,20 @@ impl ContextStore {
|
|||||||
return anyhow::Ok(());
|
return anyhow::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
|
let (buffer_info, text_task) = this.update(cx, |_, cx| {
|
||||||
let buffer = buffer_entity.read(cx);
|
let buffer = buffer_entity.read(cx);
|
||||||
collect_buffer_info_and_text(
|
collect_buffer_info_and_text(
|
||||||
project_path.path.clone(),
|
project_path.path.clone(),
|
||||||
buffer_entity,
|
buffer_entity,
|
||||||
buffer,
|
buffer,
|
||||||
|
None,
|
||||||
cx.to_async(),
|
cx.to_async(),
|
||||||
)
|
)
|
||||||
})?;
|
})??;
|
||||||
|
|
||||||
let text = text_task.await;
|
let text = text_task.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
this.insert_file(make_context_buffer(buffer_info, text));
|
this.insert_file(make_context_buffer(buffer_info, text));
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -122,23 +132,24 @@ impl ContextStore {
|
|||||||
buffer_entity: Entity<Buffer>,
|
buffer_entity: Entity<Buffer>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(async move |this, cx| {
|
||||||
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
|
let (buffer_info, text_task) = this.update(cx, |_, cx| {
|
||||||
let buffer = buffer_entity.read(cx);
|
let buffer = buffer_entity.read(cx);
|
||||||
let Some(file) = buffer.file() else {
|
let Some(file) = buffer.file() else {
|
||||||
return Err(anyhow!("Buffer has no path."));
|
return Err(anyhow!("Buffer has no path."));
|
||||||
};
|
};
|
||||||
Ok(collect_buffer_info_and_text(
|
collect_buffer_info_and_text(
|
||||||
file.path().clone(),
|
file.path().clone(),
|
||||||
buffer_entity,
|
buffer_entity,
|
||||||
buffer,
|
buffer,
|
||||||
|
None,
|
||||||
cx.to_async(),
|
cx.to_async(),
|
||||||
))
|
)
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
let text = text_task.await;
|
let text = text_task.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
this.insert_file(make_context_buffer(buffer_info, text))
|
this.insert_file(make_context_buffer(buffer_info, text))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -149,13 +160,16 @@ impl ContextStore {
|
|||||||
fn insert_file(&mut self, context_buffer: ContextBuffer) {
|
fn insert_file(&mut self, context_buffer: ContextBuffer) {
|
||||||
let id = self.next_context_id.post_inc();
|
let id = self.next_context_id.post_inc();
|
||||||
self.files.insert(context_buffer.id, id);
|
self.files.insert(context_buffer.id, id);
|
||||||
self.context
|
self.context.push(AssistantContext::File(FileContext {
|
||||||
.push(AssistantContext::File(FileContext { id, context_buffer }));
|
id,
|
||||||
|
context_buffer: context_buffer,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_directory(
|
pub fn add_directory(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
|
remove_if_exists: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
@@ -166,30 +180,33 @@ impl ContextStore {
|
|||||||
return Task::ready(Err(anyhow!("failed to read project")));
|
return Task::ready(Err(anyhow!("failed to read project")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let already_included = if let Some(context_id) = self.includes_directory(&project_path.path)
|
let already_included = match self.includes_directory(&project_path.path) {
|
||||||
{
|
Some(FileInclusion::Direct(context_id)) => {
|
||||||
self.remove_context(context_id);
|
if remove_if_exists {
|
||||||
true
|
self.remove_context(context_id);
|
||||||
} else {
|
}
|
||||||
false
|
true
|
||||||
|
}
|
||||||
|
Some(FileInclusion::InDirectory(_)) => true,
|
||||||
|
None => false,
|
||||||
};
|
};
|
||||||
if already_included {
|
if already_included {
|
||||||
return Task::ready(Ok(()));
|
return Task::ready(Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let worktree_id = project_path.worktree_id;
|
let worktree_id = project_path.worktree_id;
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(async move |this, cx| {
|
||||||
let worktree = project.update(&mut cx, |project, cx| {
|
let worktree = project.update(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.worktree_for_id(worktree_id, cx)
|
.worktree_for_id(worktree_id, cx)
|
||||||
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
|
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
let files = worktree.update(&mut cx, |worktree, _cx| {
|
let files = worktree.update(cx, |worktree, _cx| {
|
||||||
collect_files_in_path(worktree, &project_path.path)
|
collect_files_in_path(worktree, &project_path.path)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let open_buffers_task = project.update(&mut cx, |project, cx| {
|
let open_buffers_task = project.update(cx, |project, cx| {
|
||||||
let tasks = files.iter().map(|file_path| {
|
let tasks = files.iter().map(|file_path| {
|
||||||
project.open_buffer(
|
project.open_buffer(
|
||||||
ProjectPath {
|
ProjectPath {
|
||||||
@@ -206,19 +223,23 @@ impl ContextStore {
|
|||||||
|
|
||||||
let mut buffer_infos = Vec::new();
|
let mut buffer_infos = Vec::new();
|
||||||
let mut text_tasks = Vec::new();
|
let mut text_tasks = Vec::new();
|
||||||
this.update(&mut cx, |_, cx| {
|
this.update(cx, |_, cx| {
|
||||||
for (path, buffer_entity) in files.into_iter().zip(buffers) {
|
for (path, buffer_entity) in files.into_iter().zip(buffers) {
|
||||||
// Skip all binary files and other non-UTF8 files
|
// Skip all binary files and other non-UTF8 files
|
||||||
if let Ok(buffer_entity) = buffer_entity {
|
if let Ok(buffer_entity) = buffer_entity {
|
||||||
let buffer = buffer_entity.read(cx);
|
let buffer = buffer_entity.read(cx);
|
||||||
let (buffer_info, text_task) = collect_buffer_info_and_text(
|
if let Some((buffer_info, text_task)) = collect_buffer_info_and_text(
|
||||||
path,
|
path,
|
||||||
buffer_entity,
|
buffer_entity,
|
||||||
buffer,
|
buffer,
|
||||||
|
None,
|
||||||
cx.to_async(),
|
cx.to_async(),
|
||||||
);
|
)
|
||||||
buffer_infos.push(buffer_info);
|
.log_err()
|
||||||
text_tasks.push(text_task);
|
{
|
||||||
|
buffer_infos.push(buffer_info);
|
||||||
|
text_tasks.push(text_task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
@@ -232,32 +253,123 @@ impl ContextStore {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if context_buffers.is_empty() {
|
if context_buffers.is_empty() {
|
||||||
bail!("No text files found in {}", &project_path.path.display());
|
return Err(anyhow!(
|
||||||
|
"No text files found in {}",
|
||||||
|
&project_path.path.display()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(cx, |this, _| {
|
||||||
this.insert_directory(&project_path.path, context_buffers);
|
this.insert_directory(project_path, context_buffers);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_directory(&mut self, path: &Path, context_buffers: Vec<ContextBuffer>) {
|
fn insert_directory(&mut self, project_path: ProjectPath, context_buffers: Vec<ContextBuffer>) {
|
||||||
let id = self.next_context_id.post_inc();
|
let id = self.next_context_id.post_inc();
|
||||||
self.directories.insert(path.to_path_buf(), id);
|
self.directories.insert(project_path.path.to_path_buf(), id);
|
||||||
|
|
||||||
self.context
|
self.context
|
||||||
.push(AssistantContext::Directory(DirectoryContext::new(
|
.push(AssistantContext::Directory(DirectoryContext {
|
||||||
id,
|
id,
|
||||||
path,
|
project_path,
|
||||||
context_buffers,
|
context_buffers,
|
||||||
)));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
|
pub fn add_symbol(
|
||||||
|
&mut self,
|
||||||
|
buffer: Entity<Buffer>,
|
||||||
|
symbol_name: SharedString,
|
||||||
|
symbol_range: Range<Anchor>,
|
||||||
|
symbol_enclosing_range: Range<Anchor>,
|
||||||
|
remove_if_exists: bool,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<bool>> {
|
||||||
|
let buffer_ref = buffer.read(cx);
|
||||||
|
let Some(file) = buffer_ref.file() else {
|
||||||
|
return Task::ready(Err(anyhow!("Buffer has no path.")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(project_path) = buffer_ref.project_path(cx) else {
|
||||||
|
return Task::ready(Err(anyhow!("Buffer has no project path.")));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(symbols_for_path) = self.symbols_by_path.get(&project_path) {
|
||||||
|
let mut matching_symbol_id = None;
|
||||||
|
for symbol in symbols_for_path {
|
||||||
|
if &symbol.name == &symbol_name {
|
||||||
|
let snapshot = buffer_ref.snapshot();
|
||||||
|
if symbol.range.to_offset(&snapshot) == symbol_range.to_offset(&snapshot) {
|
||||||
|
matching_symbol_id = self.symbols.get(symbol).cloned();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(id) = matching_symbol_id {
|
||||||
|
if remove_if_exists {
|
||||||
|
self.remove_context(id);
|
||||||
|
}
|
||||||
|
return Task::ready(Ok(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (buffer_info, collect_content_task) = match collect_buffer_info_and_text(
|
||||||
|
file.path().clone(),
|
||||||
|
buffer,
|
||||||
|
buffer_ref,
|
||||||
|
Some(symbol_enclosing_range.clone()),
|
||||||
|
cx.to_async(),
|
||||||
|
) {
|
||||||
|
Ok((buffer_info, collect_context_task)) => (buffer_info, collect_context_task),
|
||||||
|
Err(err) => return Task::ready(Err(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let content = collect_content_task.await;
|
||||||
|
|
||||||
|
this.update(cx, |this, _cx| {
|
||||||
|
this.insert_symbol(make_context_symbol(
|
||||||
|
buffer_info,
|
||||||
|
project_path,
|
||||||
|
symbol_name,
|
||||||
|
symbol_range,
|
||||||
|
symbol_enclosing_range,
|
||||||
|
content,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
anyhow::Ok(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_symbol(&mut self, context_symbol: ContextSymbol) {
|
||||||
|
let id = self.next_context_id.post_inc();
|
||||||
|
self.symbols.insert(context_symbol.id.clone(), id);
|
||||||
|
self.symbols_by_path
|
||||||
|
.entry(context_symbol.id.path.clone())
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(context_symbol.id.clone());
|
||||||
|
self.symbol_buffers
|
||||||
|
.insert(context_symbol.id.clone(), context_symbol.buffer.clone());
|
||||||
|
self.context.push(AssistantContext::Symbol(SymbolContext {
|
||||||
|
id,
|
||||||
|
context_symbol,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_thread(
|
||||||
|
&mut self,
|
||||||
|
thread: Entity<Thread>,
|
||||||
|
remove_if_exists: bool,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
|
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
|
||||||
self.remove_context(context_id);
|
if remove_if_exists {
|
||||||
|
self.remove_context(context_id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.insert_thread(thread, cx);
|
self.insert_thread(thread, cx);
|
||||||
}
|
}
|
||||||
@@ -326,6 +438,19 @@ impl ContextStore {
|
|||||||
AssistantContext::Directory(_) => {
|
AssistantContext::Directory(_) => {
|
||||||
self.directories.retain(|_, context_id| *context_id != id);
|
self.directories.retain(|_, context_id| *context_id != id);
|
||||||
}
|
}
|
||||||
|
AssistantContext::Symbol(symbol) => {
|
||||||
|
if let Some(symbols_in_path) =
|
||||||
|
self.symbols_by_path.get_mut(&symbol.context_symbol.id.path)
|
||||||
|
{
|
||||||
|
symbols_in_path.retain(|s| {
|
||||||
|
self.symbols
|
||||||
|
.get(s)
|
||||||
|
.map_or(false, |context_id| *context_id != id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.symbol_buffers.remove(&symbol.context_symbol.id);
|
||||||
|
self.symbols.retain(|_, context_id| *context_id != id);
|
||||||
|
}
|
||||||
AssistantContext::FetchedUrl(_) => {
|
AssistantContext::FetchedUrl(_) => {
|
||||||
self.fetched_urls.retain(|_, context_id| *context_id != id);
|
self.fetched_urls.retain(|_, context_id| *context_id != id);
|
||||||
}
|
}
|
||||||
@@ -353,7 +478,7 @@ impl ContextStore {
|
|||||||
let found_file_context = self.context.iter().find(|context| match &context {
|
let found_file_context = self.context.iter().find(|context| match &context {
|
||||||
AssistantContext::File(file_context) => {
|
AssistantContext::File(file_context) => {
|
||||||
let buffer = file_context.context_buffer.buffer.read(cx);
|
let buffer = file_context.context_buffer.buffer.read(cx);
|
||||||
if let Some(file_path) = buffer_path_log_err(buffer) {
|
if let Some(file_path) = buffer_path_log_err(buffer, cx) {
|
||||||
*file_path == *path
|
*file_path == *path
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -385,8 +510,24 @@ impl ContextStore {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn includes_directory(&self, path: &Path) -> Option<ContextId> {
|
pub fn includes_directory(&self, path: &Path) -> Option<FileInclusion> {
|
||||||
self.directories.get(path).copied()
|
if let Some(context_id) = self.directories.get(path) {
|
||||||
|
return Some(FileInclusion::Direct(*context_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.will_include_file_path_via_directory(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn included_symbol(&self, symbol_id: &ContextSymbolId) -> Option<ContextId> {
|
||||||
|
self.symbols.get(symbol_id).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn included_symbols_by_path(&self) -> &HashMap<ProjectPath, Vec<ContextSymbolId>> {
|
||||||
|
&self.symbols_by_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer_for_symbol(&self, symbol_id: &ContextSymbolId) -> Option<Entity<Buffer>> {
|
||||||
|
self.symbol_buffers.get(symbol_id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn includes_thread(&self, thread_id: &ThreadId) -> Option<ContextId> {
|
pub fn includes_thread(&self, thread_id: &ThreadId) -> Option<ContextId> {
|
||||||
@@ -414,9 +555,10 @@ impl ContextStore {
|
|||||||
.filter_map(|context| match context {
|
.filter_map(|context| match context {
|
||||||
AssistantContext::File(file) => {
|
AssistantContext::File(file) => {
|
||||||
let buffer = file.context_buffer.buffer.read(cx);
|
let buffer = file.context_buffer.buffer.read(cx);
|
||||||
buffer_path_log_err(buffer).map(|p| p.to_path_buf())
|
buffer_path_log_err(buffer, cx).map(|p| p.to_path_buf())
|
||||||
}
|
}
|
||||||
AssistantContext::Directory(_)
|
AssistantContext::Directory(_)
|
||||||
|
| AssistantContext::Symbol(_)
|
||||||
| AssistantContext::FetchedUrl(_)
|
| AssistantContext::FetchedUrl(_)
|
||||||
| AssistantContext::Thread(_) => None,
|
| AssistantContext::Thread(_) => None,
|
||||||
})
|
})
|
||||||
@@ -436,6 +578,7 @@ pub enum FileInclusion {
|
|||||||
// ContextBuffer without text.
|
// ContextBuffer without text.
|
||||||
struct BufferInfo {
|
struct BufferInfo {
|
||||||
buffer_entity: Entity<Buffer>,
|
buffer_entity: Entity<Buffer>,
|
||||||
|
file: Arc<dyn File>,
|
||||||
id: BufferId,
|
id: BufferId,
|
||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
}
|
}
|
||||||
@@ -444,31 +587,62 @@ fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
|
|||||||
ContextBuffer {
|
ContextBuffer {
|
||||||
id: info.id,
|
id: info.id,
|
||||||
buffer: info.buffer_entity,
|
buffer: info.buffer_entity,
|
||||||
|
file: info.file,
|
||||||
version: info.version,
|
version: info.version,
|
||||||
text,
|
text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_context_symbol(
|
||||||
|
info: BufferInfo,
|
||||||
|
path: ProjectPath,
|
||||||
|
name: SharedString,
|
||||||
|
range: Range<Anchor>,
|
||||||
|
enclosing_range: Range<Anchor>,
|
||||||
|
text: SharedString,
|
||||||
|
) -> ContextSymbol {
|
||||||
|
ContextSymbol {
|
||||||
|
id: ContextSymbolId { name, range, path },
|
||||||
|
buffer_version: info.version,
|
||||||
|
enclosing_range,
|
||||||
|
buffer: info.buffer_entity,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_buffer_info_and_text(
|
fn collect_buffer_info_and_text(
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
buffer_entity: Entity<Buffer>,
|
buffer_entity: Entity<Buffer>,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
|
range: Option<Range<Anchor>>,
|
||||||
cx: AsyncApp,
|
cx: AsyncApp,
|
||||||
) -> (BufferInfo, Task<SharedString>) {
|
) -> Result<(BufferInfo, Task<SharedString>)> {
|
||||||
let buffer_info = BufferInfo {
|
let buffer_info = BufferInfo {
|
||||||
id: buffer.remote_id(),
|
id: buffer.remote_id(),
|
||||||
buffer_entity,
|
buffer_entity,
|
||||||
|
file: buffer
|
||||||
|
.file()
|
||||||
|
.context("buffer context must have a file")?
|
||||||
|
.clone(),
|
||||||
version: buffer.version(),
|
version: buffer.version(),
|
||||||
};
|
};
|
||||||
// Important to collect version at the same time as content so that staleness logic is correct.
|
// Important to collect version at the same time as content so that staleness logic is correct.
|
||||||
let content = buffer.as_rope().clone();
|
let content = if let Some(range) = range {
|
||||||
|
buffer.text_for_range(range).collect::<Rope>()
|
||||||
|
} else {
|
||||||
|
buffer.as_rope().clone()
|
||||||
|
};
|
||||||
let text_task = cx.background_spawn(async move { to_fenced_codeblock(&path, content) });
|
let text_task = cx.background_spawn(async move { to_fenced_codeblock(&path, content) });
|
||||||
(buffer_info, text_task)
|
Ok((buffer_info, text_task))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer_path_log_err(buffer: &Buffer) -> Option<Arc<Path>> {
|
pub fn buffer_path_log_err(buffer: &Buffer, cx: &App) -> Option<Arc<Path>> {
|
||||||
if let Some(file) = buffer.file() {
|
if let Some(file) = buffer.file() {
|
||||||
Some(file.path().clone())
|
let mut path = file.path().clone();
|
||||||
|
if path.as_os_str().is_empty() {
|
||||||
|
path = file.full_path(cx).into();
|
||||||
|
}
|
||||||
|
Some(path)
|
||||||
} else {
|
} else {
|
||||||
log::error!("Buffer that had a path unexpectedly no longer has a path.");
|
log::error!("Buffer that had a path unexpectedly no longer has a path.");
|
||||||
None
|
None
|
||||||
@@ -531,35 +705,68 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
|||||||
|
|
||||||
pub fn refresh_context_store_text(
|
pub fn refresh_context_store_text(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
|
changed_buffers: &HashSet<Entity<Buffer>>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> impl Future<Output = ()> {
|
) -> impl Future<Output = Vec<ContextId>> + use<> {
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
for context in &context_store.read(cx).context {
|
for context in &context_store.read(cx).context {
|
||||||
match context {
|
let id = context.id();
|
||||||
AssistantContext::File(file_context) => {
|
|
||||||
let context_store = context_store.clone();
|
let task = maybe!({
|
||||||
if let Some(task) = refresh_file_text(context_store, file_context, cx) {
|
match context {
|
||||||
tasks.push(task);
|
AssistantContext::File(file_context) => {
|
||||||
|
if changed_buffers.is_empty()
|
||||||
|
|| changed_buffers.contains(&file_context.context_buffer.buffer)
|
||||||
|
{
|
||||||
|
let context_store = context_store.clone();
|
||||||
|
return refresh_file_text(context_store, file_context, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
AssistantContext::Directory(directory_context) => {
|
||||||
AssistantContext::Directory(directory_context) => {
|
let should_refresh = changed_buffers.is_empty()
|
||||||
let context_store = context_store.clone();
|
|| changed_buffers.iter().any(|buffer| {
|
||||||
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
|
let buffer = buffer.read(cx);
|
||||||
tasks.push(task);
|
|
||||||
|
buffer_path_log_err(&buffer, cx).map_or(false, |path| {
|
||||||
|
path.starts_with(&directory_context.project_path.path)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if should_refresh {
|
||||||
|
let context_store = context_store.clone();
|
||||||
|
return refresh_directory_text(context_store, directory_context, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
AssistantContext::Symbol(symbol_context) => {
|
||||||
|
if changed_buffers.is_empty()
|
||||||
|
|| changed_buffers.contains(&symbol_context.context_symbol.buffer)
|
||||||
|
{
|
||||||
|
let context_store = context_store.clone();
|
||||||
|
return refresh_symbol_text(context_store, symbol_context, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssistantContext::Thread(thread_context) => {
|
||||||
|
if changed_buffers.is_empty() {
|
||||||
|
let context_store = context_store.clone();
|
||||||
|
return Some(refresh_thread_text(context_store, thread_context, cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful,
|
||||||
|
// and doing the caching properly could be tricky (unless it's already handled by
|
||||||
|
// the HttpClient?).
|
||||||
|
AssistantContext::FetchedUrl(_) => {}
|
||||||
}
|
}
|
||||||
AssistantContext::Thread(thread_context) => {
|
|
||||||
let context_store = context_store.clone();
|
None
|
||||||
tasks.push(refresh_thread_text(context_store, thread_context, cx));
|
});
|
||||||
}
|
|
||||||
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful,
|
if let Some(task) = task {
|
||||||
// and doing the caching properly could be tricky (unless it's already handled by
|
tasks.push(task.map(move |_| id));
|
||||||
// the HttpClient?).
|
|
||||||
AssistantContext::FetchedUrl(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
future::join_all(tasks).map(|_| ())
|
future::join_all(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_file_text(
|
fn refresh_file_text(
|
||||||
@@ -570,10 +777,10 @@ fn refresh_file_text(
|
|||||||
let id = file_context.id;
|
let id = file_context.id;
|
||||||
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
||||||
if let Some(task) = task {
|
if let Some(task) = task {
|
||||||
Some(cx.spawn(|mut cx| async move {
|
Some(cx.spawn(async move |cx| {
|
||||||
let context_buffer = task.await;
|
let context_buffer = task.await;
|
||||||
context_store
|
context_store
|
||||||
.update(&mut cx, |context_store, _| {
|
.update(cx, |context_store, _| {
|
||||||
let new_file_context = FileContext { id, context_buffer };
|
let new_file_context = FileContext { id, context_buffer };
|
||||||
context_store.replace_context(AssistantContext::File(new_file_context));
|
context_store.replace_context(AssistantContext::File(new_file_context));
|
||||||
})
|
})
|
||||||
@@ -609,19 +816,45 @@ fn refresh_directory_text(
|
|||||||
|
|
||||||
let context_buffers = future::join_all(futures);
|
let context_buffers = future::join_all(futures);
|
||||||
|
|
||||||
let id = directory_context.snapshot.id;
|
let id = directory_context.id;
|
||||||
let path = directory_context.path.clone();
|
let project_path = directory_context.project_path.clone();
|
||||||
Some(cx.spawn(|mut cx| async move {
|
Some(cx.spawn(async move |cx| {
|
||||||
let context_buffers = context_buffers.await;
|
let context_buffers = context_buffers.await;
|
||||||
context_store
|
context_store
|
||||||
.update(&mut cx, |context_store, _| {
|
.update(cx, |context_store, _| {
|
||||||
let new_directory_context = DirectoryContext::new(id, &path, context_buffers);
|
let new_directory_context = DirectoryContext {
|
||||||
|
id,
|
||||||
|
project_path,
|
||||||
|
context_buffers,
|
||||||
|
};
|
||||||
context_store.replace_context(AssistantContext::Directory(new_directory_context));
|
context_store.replace_context(AssistantContext::Directory(new_directory_context));
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refresh_symbol_text(
|
||||||
|
context_store: Entity<ContextStore>,
|
||||||
|
symbol_context: &SymbolContext,
|
||||||
|
cx: &App,
|
||||||
|
) -> Option<Task<()>> {
|
||||||
|
let id = symbol_context.id;
|
||||||
|
let task = refresh_context_symbol(&symbol_context.context_symbol, cx);
|
||||||
|
if let Some(task) = task {
|
||||||
|
Some(cx.spawn(async move |cx| {
|
||||||
|
let context_symbol = task.await;
|
||||||
|
context_store
|
||||||
|
.update(cx, |context_store, _| {
|
||||||
|
let new_symbol_context = SymbolContext { id, context_symbol };
|
||||||
|
context_store.replace_context(AssistantContext::Symbol(new_symbol_context));
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn refresh_thread_text(
|
fn refresh_thread_text(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
thread_context: &ThreadContext,
|
thread_context: &ThreadContext,
|
||||||
@@ -629,9 +862,9 @@ fn refresh_thread_text(
|
|||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let id = thread_context.id;
|
let id = thread_context.id;
|
||||||
let thread = thread_context.thread.clone();
|
let thread = thread_context.thread.clone();
|
||||||
cx.spawn(move |mut cx| async move {
|
cx.spawn(async move |cx| {
|
||||||
context_store
|
context_store
|
||||||
.update(&mut cx, |context_store, cx| {
|
.update(cx, |context_store, cx| {
|
||||||
let text = thread.read(cx).text().into();
|
let text = thread.read(cx).text().into();
|
||||||
context_store.replace_context(AssistantContext::Thread(ThreadContext {
|
context_store.replace_context(AssistantContext::Thread(ThreadContext {
|
||||||
id,
|
id,
|
||||||
@@ -646,18 +879,54 @@ fn refresh_thread_text(
|
|||||||
fn refresh_context_buffer(
|
fn refresh_context_buffer(
|
||||||
context_buffer: &ContextBuffer,
|
context_buffer: &ContextBuffer,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Option<impl Future<Output = ContextBuffer>> {
|
) -> Option<impl Future<Output = ContextBuffer> + use<>> {
|
||||||
let buffer = context_buffer.buffer.read(cx);
|
let buffer = context_buffer.buffer.read(cx);
|
||||||
let path = buffer_path_log_err(buffer)?;
|
let path = buffer_path_log_err(buffer, cx)?;
|
||||||
if buffer.version.changed_since(&context_buffer.version) {
|
if buffer.version.changed_since(&context_buffer.version) {
|
||||||
let (buffer_info, text_task) = collect_buffer_info_and_text(
|
let (buffer_info, text_task) = collect_buffer_info_and_text(
|
||||||
path,
|
path,
|
||||||
context_buffer.buffer.clone(),
|
context_buffer.buffer.clone(),
|
||||||
buffer,
|
buffer,
|
||||||
|
None,
|
||||||
cx.to_async(),
|
cx.to_async(),
|
||||||
);
|
)
|
||||||
|
.log_err()?;
|
||||||
Some(text_task.map(move |text| make_context_buffer(buffer_info, text)))
|
Some(text_task.map(move |text| make_context_buffer(buffer_info, text)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refresh_context_symbol(
|
||||||
|
context_symbol: &ContextSymbol,
|
||||||
|
cx: &App,
|
||||||
|
) -> Option<impl Future<Output = ContextSymbol> + use<>> {
|
||||||
|
let buffer = context_symbol.buffer.read(cx);
|
||||||
|
let path = buffer_path_log_err(buffer, cx)?;
|
||||||
|
let project_path = buffer.project_path(cx)?;
|
||||||
|
if buffer.version.changed_since(&context_symbol.buffer_version) {
|
||||||
|
let (buffer_info, text_task) = collect_buffer_info_and_text(
|
||||||
|
path,
|
||||||
|
context_symbol.buffer.clone(),
|
||||||
|
buffer,
|
||||||
|
Some(context_symbol.enclosing_range.clone()),
|
||||||
|
cx.to_async(),
|
||||||
|
)
|
||||||
|
.log_err()?;
|
||||||
|
let name = context_symbol.id.name.clone();
|
||||||
|
let range = context_symbol.id.range.clone();
|
||||||
|
let enclosing_range = context_symbol.enclosing_range.clone();
|
||||||
|
Some(text_task.map(move |text| {
|
||||||
|
make_context_symbol(
|
||||||
|
buffer_info,
|
||||||
|
project_path,
|
||||||
|
name,
|
||||||
|
range,
|
||||||
|
enclosing_range,
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ use collections::HashSet;
|
|||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
App, Bounds, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
WeakEntity,
|
Subscription, WeakEntity,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
|
use ui::{KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||||
|
|
||||||
use crate::context::ContextKind;
|
use crate::context::{ContextId, ContextKind};
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread::Thread;
|
use crate::thread::Thread;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::ui::ContextPill;
|
use crate::ui::{AddedContext, ContextPill};
|
||||||
use crate::{
|
use crate::{
|
||||||
AcceptSuggestedContext, AssistantPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
AcceptSuggestedContext, AssistantPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||||
RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
||||||
@@ -25,7 +25,7 @@ use crate::{
|
|||||||
|
|
||||||
pub struct ContextStrip {
|
pub struct ContextStrip {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
pub context_picker: Entity<ContextPicker>,
|
context_picker: Entity<ContextPicker>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
suggest_context_kind: SuggestContextKind,
|
suggest_context_kind: SuggestContextKind,
|
||||||
@@ -36,11 +36,9 @@ pub struct ContextStrip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContextStrip {
|
impl ContextStrip {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
suggest_context_kind: SuggestContextKind,
|
suggest_context_kind: SuggestContextKind,
|
||||||
@@ -52,7 +50,6 @@ impl ContextStrip {
|
|||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
editor.clone(),
|
|
||||||
ConfirmBehavior::KeepOpen,
|
ConfirmBehavior::KeepOpen,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -95,12 +92,12 @@ impl ContextStrip {
|
|||||||
let active_buffer_entity = editor.buffer().read(cx).as_singleton()?;
|
let active_buffer_entity = editor.buffer().read(cx).as_singleton()?;
|
||||||
let active_buffer = active_buffer_entity.read(cx);
|
let active_buffer = active_buffer_entity.read(cx);
|
||||||
|
|
||||||
let path = active_buffer.file()?.path();
|
let path = active_buffer.file()?.full_path(cx);
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.context_store
|
.context_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.will_include_buffer(active_buffer.remote_id(), path)
|
.will_include_buffer(active_buffer.remote_id(), &path)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
@@ -111,7 +108,7 @@ impl ContextStrip {
|
|||||||
None => path.to_string_lossy().into_owned().into(),
|
None => path.to_string_lossy().into_owned().into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon_path = FileIcons::get_icon(path, cx);
|
let icon_path = FileIcons::get_icon(&path, cx);
|
||||||
|
|
||||||
Some(SuggestedContext::File {
|
Some(SuggestedContext::File {
|
||||||
name,
|
name,
|
||||||
@@ -242,11 +239,7 @@ impl ContextStrip {
|
|||||||
let eraser = if bounds.len() < 3 { 0 } else { 1 };
|
let eraser = if bounds.len() < 3 { 0 } else { 1 };
|
||||||
let pills = &bounds[1..bounds.len() - eraser];
|
let pills = &bounds[1..bounds.len() - eraser];
|
||||||
|
|
||||||
if pills.is_empty() {
|
if pills.is_empty() { None } else { Some(pills) }
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(pills)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn last_pill_index(&self) -> Option<usize> {
|
fn last_pill_index(&self) -> Option<usize> {
|
||||||
@@ -280,6 +273,14 @@ impl ContextStrip {
|
|||||||
best.map(|(index, _, _)| index)
|
best.map(|(index, _, _)| index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_context(&mut self, id: ContextId, window: &mut Window, cx: &mut App) {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::active_thread::open_context(id, self.context_store.clone(), workspace, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_focused_context(
|
fn remove_focused_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &RemoveFocusedContext,
|
_: &RemoveFocusedContext,
|
||||||
@@ -336,12 +337,12 @@ impl ContextStrip {
|
|||||||
context_store.accept_suggested_context(&suggested, cx)
|
context_store.accept_suggested_context(&suggested, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match task.await.notify_async_err(&mut cx) {
|
match task.await.notify_async_err(cx) {
|
||||||
None => {}
|
None => {}
|
||||||
Some(()) => {
|
Some(()) => {
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = this.upgrade() {
|
||||||
this.update(&mut cx, |_, cx| cx.notify())?;
|
this.update(cx, |_, cx| cx.notify())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,19 +363,19 @@ impl Focusable for ContextStrip {
|
|||||||
impl Render for ContextStrip {
|
impl Render for ContextStrip {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let context_store = self.context_store.read(cx);
|
let context_store = self.context_store.read(cx);
|
||||||
let context = context_store
|
let context = context_store.context();
|
||||||
.context()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|context| context.snapshot(cx))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let context_picker = self.context_picker.clone();
|
let context_picker = self.context_picker.clone();
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
let suggested_context = self.suggested_context(cx);
|
let suggested_context = self.suggested_context(cx);
|
||||||
|
|
||||||
let dupe_names = context
|
let added_contexts = context
|
||||||
.iter()
|
.iter()
|
||||||
.map(|context| context.name.clone())
|
.map(|c| AddedContext::new(c, cx))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let dupe_names = added_contexts
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.name.clone())
|
||||||
.sorted()
|
.sorted()
|
||||||
.tuple_windows()
|
.tuple_windows()
|
||||||
.filter(|(a, b)| a == b)
|
.filter(|(a, b)| a == b)
|
||||||
@@ -460,27 +461,39 @@ impl Render for ContextStrip {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.children(context.iter().enumerate().map(|(i, context)| {
|
.children(
|
||||||
ContextPill::added(
|
added_contexts
|
||||||
context.clone(),
|
.into_iter()
|
||||||
dupe_names.contains(&context.name),
|
.enumerate()
|
||||||
self.focused_index == Some(i),
|
.map(|(i, added_context)| {
|
||||||
Some({
|
let name = added_context.name.clone();
|
||||||
let id = context.id;
|
let id = added_context.id;
|
||||||
let context_store = self.context_store.clone();
|
ContextPill::added(
|
||||||
Rc::new(cx.listener(move |_this, _event, _window, cx| {
|
added_context,
|
||||||
context_store.update(cx, |this, _cx| {
|
dupe_names.contains(&name),
|
||||||
this.remove_context(id);
|
self.focused_index == Some(i),
|
||||||
});
|
Some({
|
||||||
cx.notify();
|
let context_store = self.context_store.clone();
|
||||||
}))
|
Rc::new(cx.listener(move |_this, _event, _window, cx| {
|
||||||
|
context_store.update(cx, |this, _cx| {
|
||||||
|
this.remove_context(id);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
Rc::new(cx.listener(move |this, event: &ClickEvent, window, cx| {
|
||||||
|
if event.down.click_count > 1 {
|
||||||
|
this.open_context(id, window, cx);
|
||||||
|
} else {
|
||||||
|
this.focused_index = Some(i);
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.on_click(Rc::new(cx.listener(move |this, _, _window, cx| {
|
|
||||||
this.focused_index = Some(i);
|
|
||||||
cx.notify();
|
|
||||||
})))
|
|
||||||
}))
|
|
||||||
.when_some(suggested_context, |el, suggested| {
|
.when_some(suggested_context, |el, suggested| {
|
||||||
el.child(
|
el.child(
|
||||||
ContextPill::suggested(
|
ContextPill::suggested(
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use assistant_context_editor::SavedContextMetadata;
|
use assistant_context_editor::SavedContextMetadata;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use gpui::{prelude::*, Entity};
|
use gpui::{Entity, prelude::*};
|
||||||
|
|
||||||
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
|
use crate::thread_store::{SerializedThreadMetadata, ThreadStore};
|
||||||
|
|
||||||
pub enum HistoryEntry {
|
pub enum HistoryEntry {
|
||||||
Thread(SavedThreadMetadata),
|
Thread(SerializedThreadMetadata),
|
||||||
Context(SavedContextMetadata),
|
Context(SavedContextMetadata),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +43,11 @@ impl HistoryStore {
|
|||||||
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
||||||
let mut history_entries = Vec::new();
|
let mut history_entries = Vec::new();
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
||||||
|
return history_entries;
|
||||||
|
}
|
||||||
|
|
||||||
for thread in self.thread_store.update(cx, |this, _cx| this.threads()) {
|
for thread in self.thread_store.update(cx, |this, _cx| this.threads()) {
|
||||||
history_entries.push(HistoryEntry::Thread(thread));
|
history_entries.push(HistoryEntry::Thread(thread));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,45 +7,45 @@ use std::sync::Arc;
|
|||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||||
use editor::{
|
use editor::{
|
||||||
|
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||||
|
GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||||
actions::SelectAll,
|
actions::SelectAll,
|
||||||
display_map::{
|
display_map::{
|
||||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||||
ToDisplayPoint,
|
ToDisplayPoint,
|
||||||
},
|
},
|
||||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
|
||||||
GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
|
||||||
};
|
};
|
||||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagViewExt as _};
|
use feature_flags::{Assistant2FeatureFlag, FeatureFlagViewExt as _};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
point, App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task,
|
App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal,
|
||||||
UpdateGlobal, WeakEntity, Window,
|
WeakEntity, Window, point,
|
||||||
};
|
};
|
||||||
use language::{Buffer, Point, Selection, TransactionId};
|
use language::{Buffer, Point, Selection, TransactionId};
|
||||||
use language_model::{report_assistant_event, LanguageModelRegistry};
|
use language_model::{LanguageModelRegistry, report_assistant_event};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::ActionVariant;
|
use project::LspAction;
|
||||||
use project::{CodeAction, ProjectTransaction};
|
use project::{CodeAction, ProjectTransaction};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||||
use text::{OffsetRangeExt, ToPoint as _};
|
use text::{OffsetRangeExt, ToPoint as _};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::RangeExt;
|
use util::RangeExt;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{dock::Panel, ShowConfiguration};
|
use workspace::{ItemHandle, Toast, Workspace, notifications::NotificationId};
|
||||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
use workspace::{ShowConfiguration, dock::Panel};
|
||||||
|
|
||||||
|
use crate::AssistantPanel;
|
||||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
||||||
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::AssistantPanel;
|
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
@@ -276,7 +276,7 @@ impl InlineAssistant {
|
|||||||
if is_authenticated() {
|
if is_authenticated() {
|
||||||
handle_assist(window, cx);
|
handle_assist(window, cx);
|
||||||
} else {
|
} else {
|
||||||
cx.spawn_in(window, |_workspace, mut cx| async move {
|
cx.spawn_in(window, async move |_workspace, cx| {
|
||||||
let Some(task) = cx.update(|_, cx| {
|
let Some(task) = cx.update(|_, cx| {
|
||||||
LanguageModelRegistry::read_global(cx)
|
LanguageModelRegistry::read_global(cx)
|
||||||
.active_provider()
|
.active_provider()
|
||||||
@@ -324,7 +324,7 @@ impl InlineAssistant {
|
|||||||
) {
|
) {
|
||||||
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
||||||
(
|
(
|
||||||
editor.buffer().read(cx).snapshot(cx),
|
editor.snapshot(window, cx),
|
||||||
editor.selections.all::<Point>(cx),
|
editor.selections.all::<Point>(cx),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -338,7 +338,37 @@ impl InlineAssistant {
|
|||||||
if selection.end.column == 0 {
|
if selection.end.column == 0 {
|
||||||
selection.end.row -= 1;
|
selection.end.row -= 1;
|
||||||
}
|
}
|
||||||
selection.end.column = snapshot.line_len(MultiBufferRow(selection.end.row));
|
selection.end.column = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.line_len(MultiBufferRow(selection.end.row));
|
||||||
|
} else if let Some(fold) =
|
||||||
|
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||||
|
{
|
||||||
|
selection.start = fold.range().start;
|
||||||
|
selection.end = fold.range().end;
|
||||||
|
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
|
||||||
|
let chars = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.chars_at(Point::new(selection.end.row + 1, 0));
|
||||||
|
|
||||||
|
for c in chars {
|
||||||
|
if c == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if c.is_whitespace() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if snapshot
|
||||||
|
.language_at(selection.end)
|
||||||
|
.is_some_and(|language| language.config().brackets.is_closing_brace(c))
|
||||||
|
{
|
||||||
|
selection.end.row += 1;
|
||||||
|
selection.end.column = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.line_len(MultiBufferRow(selection.end.row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(prev_selection) = selections.last_mut() {
|
if let Some(prev_selection) = selections.last_mut() {
|
||||||
@@ -354,6 +384,7 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
selections.push(selection);
|
selections.push(selection);
|
||||||
}
|
}
|
||||||
|
let snapshot = &snapshot.buffer_snapshot;
|
||||||
let newest_selection = newest_selection.unwrap();
|
let newest_selection = newest_selection.unwrap();
|
||||||
|
|
||||||
let mut codegen_ranges = Vec::new();
|
let mut codegen_ranges = Vec::new();
|
||||||
@@ -480,7 +511,6 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn suggest_assist(
|
pub fn suggest_assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &Entity<Editor>,
|
editor: &Entity<Editor>,
|
||||||
@@ -1342,7 +1372,7 @@ impl InlineAssistant {
|
|||||||
});
|
});
|
||||||
|
|
||||||
enum DeletedLines {}
|
enum DeletedLines {}
|
||||||
let mut editor = Editor::for_multibuffer(multi_buffer, None, true, window, cx);
|
let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
|
||||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_gutter(false, cx);
|
editor.set_show_gutter(false, cx);
|
||||||
@@ -1451,16 +1481,15 @@ struct InlineAssistScrollLock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EditorInlineAssists {
|
impl EditorInlineAssists {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
|
fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
|
||||||
let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
|
let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
|
||||||
Self {
|
Self {
|
||||||
assist_ids: Vec::new(),
|
assist_ids: Vec::new(),
|
||||||
scroll_lock: None,
|
scroll_lock: None,
|
||||||
highlight_updates: highlight_updates_tx,
|
highlight_updates: highlight_updates_tx,
|
||||||
_update_highlights: cx.spawn(|cx| {
|
_update_highlights: cx.spawn({
|
||||||
let editor = editor.downgrade();
|
let editor = editor.downgrade();
|
||||||
async move {
|
async move |cx| {
|
||||||
while let Ok(()) = highlight_updates_rx.changed().await {
|
while let Ok(()) = highlight_updates_rx.changed().await {
|
||||||
let editor = editor.upgrade().context("editor was dropped")?;
|
let editor = editor.upgrade().context("editor was dropped")?;
|
||||||
cx.update_global(|assistant: &mut InlineAssistant, cx| {
|
cx.update_global(|assistant: &mut InlineAssistant, cx| {
|
||||||
@@ -1563,7 +1592,6 @@ pub struct InlineAssist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InlineAssist {
|
impl InlineAssist {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn new(
|
fn new(
|
||||||
assist_id: InlineAssistId,
|
assist_id: InlineAssistId,
|
||||||
group_id: InlineAssistGroupId,
|
group_id: InlineAssistGroupId,
|
||||||
@@ -1728,10 +1756,11 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
Task::ready(Ok(vec![CodeAction {
|
Task::ready(Ok(vec![CodeAction {
|
||||||
server_id: language::LanguageServerId(0),
|
server_id: language::LanguageServerId(0),
|
||||||
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
|
||||||
lsp_action: ActionVariant::Action(Box::new(lsp::CodeAction {
|
lsp_action: LspAction::Action(Box::new(lsp::CodeAction {
|
||||||
title: "Fix with Assistant".into(),
|
title: "Fix with Assistant".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
|
resolved: true,
|
||||||
}]))
|
}]))
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(Vec::new()))
|
Task::ready(Ok(Vec::new()))
|
||||||
@@ -1750,10 +1779,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let thread_store = self.thread_store.clone();
|
let thread_store = self.thread_store.clone();
|
||||||
window.spawn(cx, |mut cx| async move {
|
window.spawn(cx, async move |cx| {
|
||||||
let editor = editor.upgrade().context("editor was released")?;
|
let editor = editor.upgrade().context("editor was released")?;
|
||||||
let range = editor
|
let range = editor
|
||||||
.update(&mut cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
editor.buffer().update(cx, |multibuffer, cx| {
|
editor.buffer().update(cx, |multibuffer, cx| {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let multibuffer_snapshot = multibuffer.read(cx);
|
let multibuffer_snapshot = multibuffer.read(cx);
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ use crate::{RemoveAllContext, ToggleContextPicker};
|
|||||||
use client::ErrorExt;
|
use client::ErrorExt;
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{MoveDown, MoveUp},
|
|
||||||
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, GutterDimensions, MultiBuffer,
|
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, GutterDimensions, MultiBuffer,
|
||||||
|
actions::{MoveDown, MoveUp},
|
||||||
};
|
};
|
||||||
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, point, AnyElement, App, ClickEvent, Context, CursorStyle, Entity,
|
AnyElement, App, ClickEvent, Context, CursorStyle, Entity, EventEmitter, FocusHandle,
|
||||||
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
|
Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window, anchored, deferred, point,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||||
use language_model_selector::ToggleModelSelector;
|
use language_model_selector::ToggleModelSelector;
|
||||||
@@ -28,7 +28,7 @@ use std::sync::Arc;
|
|||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::utils::WithRemSize;
|
use ui::utils::WithRemSize;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip,
|
CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
@@ -455,47 +455,55 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
|
|
||||||
match codegen_status {
|
match codegen_status {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
vec![Button::new("start", mode.start_label())
|
vec![
|
||||||
.label_size(LabelSize::Small)
|
Button::new("start", mode.start_label())
|
||||||
.icon(IconName::Return)
|
.label_size(LabelSize::Small)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon(IconName::Return)
|
||||||
.icon_color(Color::Muted)
|
.icon_size(IconSize::XSmall)
|
||||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
|
.icon_color(Color::Muted)
|
||||||
.into_any_element()]
|
.on_click(
|
||||||
|
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
|
CodegenStatus::Pending => vec![
|
||||||
.icon_color(Color::Error)
|
IconButton::new("stop", IconName::Stop)
|
||||||
.shape(IconButtonShape::Square)
|
.icon_color(Color::Error)
|
||||||
.tooltip(move |window, cx| {
|
.shape(IconButtonShape::Square)
|
||||||
Tooltip::with_meta(
|
.tooltip(move |window, cx| {
|
||||||
mode.tooltip_interrupt(),
|
Tooltip::with_meta(
|
||||||
Some(&menu::Cancel),
|
mode.tooltip_interrupt(),
|
||||||
"Changes won't be discarded",
|
Some(&menu::Cancel),
|
||||||
window,
|
"Changes won't be discarded",
|
||||||
cx,
|
window,
|
||||||
)
|
cx,
|
||||||
})
|
)
|
||||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
})
|
||||||
.into_any_element()],
|
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||||
|
.into_any_element(),
|
||||||
|
],
|
||||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||||
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
|
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
|
||||||
if has_error || self.edited_since_done {
|
if has_error || self.edited_since_done {
|
||||||
vec![IconButton::new("restart", IconName::RotateCw)
|
vec![
|
||||||
.icon_color(Color::Info)
|
IconButton::new("restart", IconName::RotateCw)
|
||||||
.shape(IconButtonShape::Square)
|
.icon_color(Color::Info)
|
||||||
.tooltip(move |window, cx| {
|
.shape(IconButtonShape::Square)
|
||||||
Tooltip::with_meta(
|
.tooltip(move |window, cx| {
|
||||||
mode.tooltip_restart(),
|
Tooltip::with_meta(
|
||||||
Some(&menu::Confirm),
|
mode.tooltip_restart(),
|
||||||
"Changes will be discarded",
|
Some(&menu::Confirm),
|
||||||
window,
|
"Changes will be discarded",
|
||||||
cx,
|
window,
|
||||||
)
|
cx,
|
||||||
})
|
)
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
})
|
||||||
cx.emit(PromptEditorEvent::StartRequested);
|
.on_click(cx.listener(|_, _, _, cx| {
|
||||||
}))
|
cx.emit(PromptEditorEvent::StartRequested);
|
||||||
.into_any_element()]
|
}))
|
||||||
|
.into_any_element(),
|
||||||
|
]
|
||||||
} else {
|
} else {
|
||||||
let accept = IconButton::new("accept", IconName::Check)
|
let accept = IconButton::new("accept", IconName::Check)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
@@ -816,7 +824,6 @@ impl InlineAssistId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PromptEditor<BufferCodegen> {
|
impl PromptEditor<BufferCodegen> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn new_buffer(
|
pub fn new_buffer(
|
||||||
id: InlineAssistId,
|
id: InlineAssistId,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
@@ -844,7 +851,6 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
},
|
},
|
||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -863,7 +869,6 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
prompt_editor.downgrade(),
|
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
@@ -976,7 +981,6 @@ impl TerminalInlineAssistId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PromptEditor<TerminalCodegen> {
|
impl PromptEditor<TerminalCodegen> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn new_terminal(
|
pub fn new_terminal(
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
@@ -1003,7 +1007,6 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
},
|
},
|
||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -1018,7 +1021,6 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
prompt_editor.downgrade(),
|
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
|
|||||||
@@ -1,44 +1,54 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use collections::HashSet;
|
||||||
use editor::actions::MoveUp;
|
use editor::actions::MoveUp;
|
||||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
use editor::{ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorStyle};
|
||||||
|
use file_icons::FileIcons;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, TextStyle,
|
Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, TextStyle,
|
||||||
WeakEntity,
|
WeakEntity, linear_color_stop, linear_gradient, point,
|
||||||
};
|
};
|
||||||
use language_model::LanguageModelRegistry;
|
use language_model::LanguageModelRegistry;
|
||||||
use language_model_selector::ToggleModelSelector;
|
use language_model_selector::ToggleModelSelector;
|
||||||
use rope::Point;
|
use project::Project;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use text::Bias;
|
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch,
|
ButtonLike, Disclosure, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||||
Tooltip,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
use util::ResultExt;
|
||||||
use vim_mode_setting::VimModeSetting;
|
use vim_mode_setting::VimModeSetting;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::assistant_model_selector::AssistantModelSelector;
|
use crate::assistant_model_selector::AssistantModelSelector;
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker, ContextPickerCompletionProvider};
|
||||||
use crate::context_store::{refresh_context_store_text, ContextStore};
|
use crate::context_store::{ContextStore, refresh_context_store_text};
|
||||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||||
|
use crate::profile_selector::ProfileSelector;
|
||||||
use crate::thread::{RequestKind, Thread};
|
use crate::thread::{RequestKind, Thread};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker};
|
use crate::{
|
||||||
|
AssistantDiff, Chat, ChatMode, OpenAssistantDiff, RemoveAllContext, ThreadEvent,
|
||||||
|
ToggleContextPicker, ToggleProfileSelector,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
project: Entity<Project>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
context_strip: Entity<ContextStrip>,
|
context_strip: Entity<ContextStrip>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
inline_context_picker: Entity<ContextPicker>,
|
inline_context_picker: Entity<ContextPicker>,
|
||||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
model_selector: Entity<AssistantModelSelector>,
|
model_selector: Entity<AssistantModelSelector>,
|
||||||
use_tools: bool,
|
profile_selector: Entity<ProfileSelector>,
|
||||||
|
edits_expanded: bool,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +56,12 @@ impl MessageEditor {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
context_store: Entity<ContextStore>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
|
|
||||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||||
@@ -60,16 +70,30 @@ impl MessageEditor {
|
|||||||
let mut editor = Editor::auto_height(10, window, cx);
|
let mut editor = Editor::auto_height(10, window, cx);
|
||||||
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
|
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
|
editor.set_context_menu_options(ContextMenuOptions {
|
||||||
|
min_entries_visible: 12,
|
||||||
|
max_entries_visible: 12,
|
||||||
|
placement: Some(ContextMenuPlacement::Above),
|
||||||
|
});
|
||||||
|
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let editor_entity = editor.downgrade();
|
||||||
|
editor.update(cx, |editor, _| {
|
||||||
|
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
|
||||||
|
workspace.clone(),
|
||||||
|
context_store.downgrade(),
|
||||||
|
Some(thread_store.clone()),
|
||||||
|
editor_entity,
|
||||||
|
))));
|
||||||
|
});
|
||||||
|
|
||||||
let inline_context_picker = cx.new(|cx| {
|
let inline_context_picker = cx.new(|cx| {
|
||||||
ContextPicker::new(
|
ContextPicker::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
editor.downgrade(),
|
|
||||||
ConfirmBehavior::Close,
|
ConfirmBehavior::Close,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
@@ -80,7 +104,6 @@ impl MessageEditor {
|
|||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
editor.downgrade(),
|
|
||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::File,
|
SuggestContextKind::File,
|
||||||
@@ -90,7 +113,6 @@ impl MessageEditor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.subscribe_in(&editor, window, Self::handle_editor_event),
|
|
||||||
cx.subscribe_in(
|
cx.subscribe_in(
|
||||||
&inline_context_picker,
|
&inline_context_picker,
|
||||||
window,
|
window,
|
||||||
@@ -100,8 +122,10 @@ impl MessageEditor {
|
|||||||
];
|
];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
thread,
|
|
||||||
editor: editor.clone(),
|
editor: editor.clone(),
|
||||||
|
project: thread.read(cx).project().clone(),
|
||||||
|
thread,
|
||||||
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
context_strip,
|
context_strip,
|
||||||
context_picker_menu_handle,
|
context_picker_menu_handle,
|
||||||
@@ -109,20 +133,21 @@ impl MessageEditor {
|
|||||||
inline_context_picker_menu_handle,
|
inline_context_picker_menu_handle,
|
||||||
model_selector: cx.new(|cx| {
|
model_selector: cx.new(|cx| {
|
||||||
AssistantModelSelector::new(
|
AssistantModelSelector::new(
|
||||||
fs,
|
fs.clone(),
|
||||||
model_selector_menu_handle,
|
model_selector_menu_handle,
|
||||||
editor.focus_handle(cx),
|
editor.focus_handle(cx),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
use_tools: false,
|
edits_expanded: false,
|
||||||
|
profile_selector: cx
|
||||||
|
.new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)),
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
|
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.use_tools = !self.use_tools;
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +159,6 @@ impl MessageEditor {
|
|||||||
) {
|
) {
|
||||||
self.context_picker_menu_handle.toggle(window, cx);
|
self.context_picker_menu_handle.toggle(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_all_context(
|
pub fn remove_all_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &RemoveAllContext,
|
_: &RemoveAllContext,
|
||||||
@@ -146,6 +170,14 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
|
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.is_editor_empty(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.thread.read(cx).is_generating() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.send_to_model(RequestKind::Chat, window, cx);
|
self.send_to_model(RequestKind::Chat, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,52 +217,40 @@ impl MessageEditor {
|
|||||||
text
|
text
|
||||||
});
|
});
|
||||||
|
|
||||||
let refresh_task = refresh_context_store_text(self.context_store.clone(), cx);
|
let refresh_task =
|
||||||
|
refresh_context_store_text(self.context_store.clone(), &HashSet::default(), cx);
|
||||||
|
|
||||||
|
let system_prompt_context_task = self.thread.read(cx).load_system_prompt_context(cx);
|
||||||
|
|
||||||
let thread = self.thread.clone();
|
let thread = self.thread.clone();
|
||||||
let context_store = self.context_store.clone();
|
let context_store = self.context_store.clone();
|
||||||
let use_tools = self.use_tools;
|
let checkpoint = self.project.read(cx).git_store().read(cx).checkpoint(cx);
|
||||||
cx.spawn(move |_, mut cx| async move {
|
cx.spawn(async move |_, cx| {
|
||||||
|
let checkpoint = checkpoint.await.ok();
|
||||||
refresh_task.await;
|
refresh_task.await;
|
||||||
|
let (system_prompt_context, load_error) = system_prompt_context_task.await;
|
||||||
thread
|
thread
|
||||||
.update(&mut cx, |thread, cx| {
|
.update(cx, |thread, cx| {
|
||||||
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
|
thread.set_system_prompt_context(system_prompt_context);
|
||||||
thread.insert_user_message(user_message, context, cx);
|
if let Some(load_error) = load_error {
|
||||||
thread.send_to_model(model, request_kind, use_tools, cx);
|
cx.emit(ThreadEvent::ShowError(load_error));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
thread
|
||||||
|
.update(cx, |thread, cx| {
|
||||||
|
let context = context_store.read(cx).context().clone();
|
||||||
|
thread.action_log().update(cx, |action_log, cx| {
|
||||||
|
action_log.clear_reviewed_changes(cx);
|
||||||
|
});
|
||||||
|
thread.insert_user_message(user_message, context, checkpoint, cx);
|
||||||
|
thread.send_to_model(model, request_kind, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_editor_event(
|
|
||||||
&mut self,
|
|
||||||
editor: &Entity<Editor>,
|
|
||||||
event: &EditorEvent,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
EditorEvent::SelectionsChanged { .. } => {
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
|
||||||
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
|
||||||
if newest_cursor.column > 0 {
|
|
||||||
let behind_cursor = snapshot.clip_point(
|
|
||||||
Point::new(newest_cursor.row, newest_cursor.column - 1),
|
|
||||||
Bias::Left,
|
|
||||||
);
|
|
||||||
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
|
|
||||||
if char_behind_cursor == Some('@') {
|
|
||||||
self.inline_context_picker_menu_handle.show(window, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_inline_context_picker_event(
|
fn handle_inline_context_picker_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_inline_context_picker: &Entity<ContextPicker>,
|
_inline_context_picker: &Entity<ContextPicker>,
|
||||||
@@ -269,6 +289,10 @@ impl MessageEditor {
|
|||||||
self.context_strip.focus_handle(cx).focus(window);
|
self.context_strip.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_review_click(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
AssistantDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for MessageEditor {
|
impl Focusable for MessageEditor {
|
||||||
@@ -281,10 +305,11 @@ 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 {
|
||||||
let font_size = TextSize::Default.rems(cx);
|
let font_size = TextSize::Default.rems(cx);
|
||||||
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
|
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
|
||||||
|
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
let inline_context_picker = self.inline_context_picker.clone();
|
let inline_context_picker = self.inline_context_picker.clone();
|
||||||
let bg_color = cx.theme().colors().editor_background;
|
|
||||||
let is_streaming_completion = self.thread.read(cx).is_streaming();
|
let is_generating = self.thread.read(cx).is_generating();
|
||||||
let is_model_selected = self.is_model_selected(cx);
|
let is_model_selected = self.is_model_selected(cx);
|
||||||
let is_editor_empty = self.is_editor_empty(cx);
|
let is_editor_empty = self.is_editor_empty(cx);
|
||||||
let submit_label_color = if is_editor_empty {
|
let submit_label_color = if is_editor_empty {
|
||||||
@@ -303,9 +328,18 @@ impl Render for MessageEditor {
|
|||||||
px(64.)
|
px(64.)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let action_log = self.thread.read(cx).action_log();
|
||||||
|
let changed_buffers = action_log.read(cx).changed_buffers(cx);
|
||||||
|
let changed_buffers_count = changed_buffers.len();
|
||||||
|
|
||||||
|
let editor_bg_color = cx.theme().colors().editor_background;
|
||||||
|
let border_color = cx.theme().colors().border;
|
||||||
|
let active_color = cx.theme().colors().element_selected;
|
||||||
|
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.when(is_streaming_completion, |parent| {
|
.when(is_generating, |parent| {
|
||||||
let focus_handle = self.editor.focus_handle(cx).clone();
|
let focus_handle = self.editor.focus_handle(cx).clone();
|
||||||
parent.child(
|
parent.child(
|
||||||
h_flex().py_3().w_full().justify_center().child(
|
h_flex().py_3().w_full().justify_center().child(
|
||||||
@@ -314,7 +348,7 @@ impl Render for MessageEditor {
|
|||||||
.pl_2()
|
.pl_2()
|
||||||
.pr_1()
|
.pr_1()
|
||||||
.py_1()
|
.py_1()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(editor_bg_color)
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
@@ -363,10 +397,213 @@ impl Render for MessageEditor {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
.when(changed_buffers_count > 0, |parent| {
|
||||||
|
parent.child(
|
||||||
|
v_flex()
|
||||||
|
.mx_2()
|
||||||
|
.bg(bg_edit_files_disclosure)
|
||||||
|
.border_1()
|
||||||
|
.border_b_0()
|
||||||
|
.border_color(border_color)
|
||||||
|
.rounded_t_md()
|
||||||
|
.shadow(smallvec::smallvec![gpui::BoxShadow {
|
||||||
|
color: gpui::black().opacity(0.15),
|
||||||
|
offset: point(px(1.), px(-1.)),
|
||||||
|
blur_radius: px(3.),
|
||||||
|
spread_radius: px(0.),
|
||||||
|
}])
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.p_1p5()
|
||||||
|
.justify_between()
|
||||||
|
.when(self.edits_expanded, |this| {
|
||||||
|
this.border_b_1().border_color(border_color)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Disclosure::new(
|
||||||
|
"edits-disclosure",
|
||||||
|
self.edits_expanded,
|
||||||
|
)
|
||||||
|
.on_click(
|
||||||
|
cx.listener(|this, _ev, _window, cx| {
|
||||||
|
this.edits_expanded = !this.edits_expanded;
|
||||||
|
cx.notify();
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("Edits")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("•")
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(format!(
|
||||||
|
"{} {}",
|
||||||
|
changed_buffers_count,
|
||||||
|
if changed_buffers_count == 1 {
|
||||||
|
"file"
|
||||||
|
} else {
|
||||||
|
"files"
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("review", "Review Changes")
|
||||||
|
.label_size(LabelSize::Small)
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&OpenAssistantDiff,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.handle_review_click(window, cx)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(self.edits_expanded, |parent| {
|
||||||
|
parent.child(
|
||||||
|
v_flex().bg(cx.theme().colors().editor_background).children(
|
||||||
|
changed_buffers.into_iter().enumerate().flat_map(
|
||||||
|
|(index, (buffer, changed))| {
|
||||||
|
let file = buffer.read(cx).file()?;
|
||||||
|
let path = file.path();
|
||||||
|
|
||||||
|
let parent_label = path.parent().and_then(|parent| {
|
||||||
|
let parent_str = parent.to_string_lossy();
|
||||||
|
|
||||||
|
if parent_str.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
Label::new(format!(
|
||||||
|
"{}{}",
|
||||||
|
parent_str,
|
||||||
|
std::path::MAIN_SEPARATOR_STR
|
||||||
|
))
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let name_label = path.file_name().map(|name| {
|
||||||
|
Label::new(name.to_string_lossy().to_string())
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.buffer_font(cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let file_icon = FileIcons::get_icon(&path, cx)
|
||||||
|
.map(Icon::from_path)
|
||||||
|
.map(|icon| {
|
||||||
|
icon.color(Color::Muted).size(IconSize::Small)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
Icon::new(IconName::File)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
});
|
||||||
|
|
||||||
|
let element = div()
|
||||||
|
.relative()
|
||||||
|
.py_1()
|
||||||
|
.px_2()
|
||||||
|
.when(index + 1 < changed_buffers_count, |parent| {
|
||||||
|
parent.border_color(border_color).border_b_1()
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.id("file-container")
|
||||||
|
.pr_8()
|
||||||
|
.gap_1p5()
|
||||||
|
.max_w_full()
|
||||||
|
.overflow_x_scroll()
|
||||||
|
.child(file_icon)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.children(parent_label)
|
||||||
|
.children(name_label),
|
||||||
|
) // TODO: show lines changed
|
||||||
|
.child(
|
||||||
|
Label::new("+")
|
||||||
|
.color(Color::Created),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("-")
|
||||||
|
.color(Color::Deleted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(!changed.needs_review, |parent| {
|
||||||
|
parent.child(
|
||||||
|
Icon::new(IconName::Check)
|
||||||
|
.color(Color::Success),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.h_full()
|
||||||
|
.absolute()
|
||||||
|
.w_8()
|
||||||
|
.bottom_0()
|
||||||
|
.map(|this| {
|
||||||
|
if !changed.needs_review {
|
||||||
|
this.right_4()
|
||||||
|
} else {
|
||||||
|
this.right_0()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.bg(linear_gradient(
|
||||||
|
90.,
|
||||||
|
linear_color_stop(
|
||||||
|
editor_bg_color,
|
||||||
|
1.,
|
||||||
|
),
|
||||||
|
linear_color_stop(
|
||||||
|
editor_bg_color
|
||||||
|
.opacity(0.2),
|
||||||
|
0.,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(element)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("MessageEditor")
|
.key_context("MessageEditor")
|
||||||
.on_action(cx.listener(Self::chat))
|
.on_action(cx.listener(Self::chat))
|
||||||
|
.on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
|
||||||
|
this.profile_selector
|
||||||
|
.read(cx)
|
||||||
|
.menu_handle()
|
||||||
|
.toggle(window, cx);
|
||||||
|
}))
|
||||||
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
|
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
|
||||||
this.model_selector
|
this.model_selector
|
||||||
.update(cx, |model_selector, cx| model_selector.toggle(window, cx));
|
.update(cx, |model_selector, cx| model_selector.toggle(window, cx));
|
||||||
@@ -377,10 +614,10 @@ impl Render for MessageEditor {
|
|||||||
.on_action(cx.listener(Self::toggle_chat_mode))
|
.on_action(cx.listener(Self::toggle_chat_mode))
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.p_2()
|
.p_2()
|
||||||
.bg(bg_color)
|
.bg(editor_bg_color)
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.child(self.context_strip.clone())
|
.child(h_flex().justify_between().child(self.context_strip.clone()))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_5()
|
.gap_5()
|
||||||
@@ -389,6 +626,7 @@ impl Render for MessageEditor {
|
|||||||
let text_style = TextStyle {
|
let text_style = TextStyle {
|
||||||
color: cx.theme().colors().text,
|
color: cx.theme().colors().text,
|
||||||
font_family: settings.ui_font.family.clone(),
|
font_family: settings.ui_font.family.clone(),
|
||||||
|
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||||
font_features: settings.ui_font.features.clone(),
|
font_features: settings.ui_font.features.clone(),
|
||||||
font_size: font_size.into(),
|
font_size: font_size.into(),
|
||||||
font_weight: settings.ui_font.weight,
|
font_weight: settings.ui_font.weight,
|
||||||
@@ -399,9 +637,10 @@ impl Render for MessageEditor {
|
|||||||
EditorElement::new(
|
EditorElement::new(
|
||||||
&self.editor,
|
&self.editor,
|
||||||
EditorStyle {
|
EditorStyle {
|
||||||
background: bg_color,
|
background: editor_bg_color,
|
||||||
local_player: cx.theme().players().local(),
|
local_player: cx.theme().players().local(),
|
||||||
text: text_style,
|
text: text_style,
|
||||||
|
syntax: cx.theme().syntax().clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -427,25 +666,7 @@ impl Render for MessageEditor {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(h_flex().gap_2().child(self.profile_selector.clone()))
|
||||||
Switch::new("use-tools", self.use_tools.into())
|
|
||||||
.label("Tools")
|
|
||||||
.on_click(cx.listener(
|
|
||||||
|this, selection, _window, _cx| {
|
|
||||||
this.use_tools = match selection {
|
|
||||||
ToggleState::Selected => true,
|
|
||||||
ToggleState::Unselected
|
|
||||||
| ToggleState::Indeterminate => false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.key_binding(KeyBinding::for_action_in(
|
|
||||||
&ChatMode,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||||
ButtonLike::new("submit-message")
|
ButtonLike::new("submit-message")
|
||||||
@@ -454,7 +675,7 @@ impl Render for MessageEditor {
|
|||||||
.disabled(
|
.disabled(
|
||||||
is_editor_empty
|
is_editor_empty
|
||||||
|| !is_model_selected
|
|| !is_model_selected
|
||||||
|| is_streaming_completion,
|
|| is_generating,
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -489,7 +710,7 @@ impl Render for MessageEditor {
|
|||||||
"Type a message to submit",
|
"Type a message to submit",
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.when(is_streaming_completion, |button| {
|
.when(is_generating, |button| {
|
||||||
button.tooltip(Tooltip::text(
|
button.tooltip(Tooltip::text(
|
||||||
"Cancel to submit a new message",
|
"Cancel to submit a new message",
|
||||||
))
|
))
|
||||||
|
|||||||
172
crates/assistant2/src/profile_selector.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use assistant_settings::{AgentProfile, AssistantSettings};
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui::{Action, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use settings::{Settings as _, SettingsStore, update_settings_file};
|
||||||
|
use ui::{
|
||||||
|
ButtonLike, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use util::ResultExt as _;
|
||||||
|
|
||||||
|
use crate::{ManageProfiles, ThreadStore, ToggleProfileSelector};
|
||||||
|
|
||||||
|
pub struct ProfileSelector {
|
||||||
|
profiles: IndexMap<Arc<str>, AgentProfile>,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfileSelector {
|
||||||
|
pub fn new(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||||
|
this.refresh_profiles(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut this = Self {
|
||||||
|
profiles: IndexMap::default(),
|
||||||
|
fs,
|
||||||
|
thread_store,
|
||||||
|
focus_handle,
|
||||||
|
menu_handle: PopoverMenuHandle::default(),
|
||||||
|
_subscriptions: vec![settings_subscription],
|
||||||
|
};
|
||||||
|
this.refresh_profiles(cx);
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn menu_handle(&self) -> PopoverMenuHandle<ContextMenu> {
|
||||||
|
self.menu_handle.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
self.profiles = settings.profiles.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_context_menu(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Entity<ContextMenu> {
|
||||||
|
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
let icon_position = IconPosition::End;
|
||||||
|
|
||||||
|
menu = menu.header("Profiles");
|
||||||
|
for (profile_id, profile) in self.profiles.clone() {
|
||||||
|
menu = menu.toggleable_entry(
|
||||||
|
profile.name.clone(),
|
||||||
|
profile_id == settings.default_profile,
|
||||||
|
icon_position,
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
let thread_store = self.thread_store.clone();
|
||||||
|
move |_window, cx| {
|
||||||
|
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
|
||||||
|
let profile_id = profile_id.clone();
|
||||||
|
move |settings, _cx| {
|
||||||
|
settings.set_profile(profile_id.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread_store
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.load_profile_by_id(&profile_id, cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu = menu.separator();
|
||||||
|
menu = menu.header("Customize Current Profile");
|
||||||
|
menu = menu.item(ContextMenuEntry::new("Tools…").handler({
|
||||||
|
let profile_id = settings.default_profile.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
ManageProfiles::customize_tools(profile_id.clone()).boxed_clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
menu = menu.separator();
|
||||||
|
menu = menu.item(ContextMenuEntry::new("Configure Profiles…").handler(
|
||||||
|
move |window, cx| {
|
||||||
|
window.dispatch_action(ManageProfiles::default().boxed_clone(), cx);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
menu
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ProfileSelector {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
let profile_id = &settings.default_profile;
|
||||||
|
let profile = settings.profiles.get(profile_id);
|
||||||
|
|
||||||
|
let selected_profile = profile
|
||||||
|
.map(|profile| profile.name.clone())
|
||||||
|
.unwrap_or_else(|| "Unknown".into());
|
||||||
|
|
||||||
|
let icon = match profile_id.as_ref() {
|
||||||
|
"write" => IconName::Pencil,
|
||||||
|
"ask" => IconName::MessageBubbles,
|
||||||
|
_ => IconName::UserRoundPen,
|
||||||
|
};
|
||||||
|
|
||||||
|
let this = cx.entity().clone();
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
PopoverMenu::new("profile-selector")
|
||||||
|
.menu(move |window, cx| {
|
||||||
|
Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
|
||||||
|
})
|
||||||
|
.trigger(
|
||||||
|
ButtonLike::new("profile-selector-button").child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
|
||||||
|
.child(
|
||||||
|
Label::new(selected_profile)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::ChevronDown)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(div().opacity(0.5).children({
|
||||||
|
let focus_handle = focus_handle.clone();
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&ToggleProfileSelector,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(10.)))
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.anchor(gpui::Corner::BottomLeft)
|
||||||
|
.with_handle(self.menu_handle.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::inline_prompt_editor::CodegenStatus;
|
use crate::inline_prompt_editor::CodegenStatus;
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
||||||
use language_model::{report_assistant_event, LanguageModelRegistry, LanguageModelRequest};
|
use language_model::{LanguageModelRegistry, LanguageModelRequest, report_assistant_event};
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||||
use terminal::Terminal;
|
use terminal::Terminal;
|
||||||
@@ -40,7 +40,7 @@ impl TerminalCodegen {
|
|||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
self.status = CodegenStatus::Pending;
|
self.status = CodegenStatus::Pending;
|
||||||
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
||||||
self.generation = cx.spawn(|this, mut cx| async move {
|
self.generation = cx.spawn(async move |this, cx| {
|
||||||
let model_telemetry_id = model.telemetry_id();
|
let model_telemetry_id = model.telemetry_id();
|
||||||
let model_provider_id = model.provider_id();
|
let model_provider_id = model.provider_id();
|
||||||
let response = model.stream_completion_text(prompt, &cx).await;
|
let response = model.stream_completion_text(prompt, &cx).await;
|
||||||
@@ -97,12 +97,12 @@ impl TerminalCodegen {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(cx, |this, _| {
|
||||||
this.message_id = message_id;
|
this.message_id = message_id;
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
while let Some(hunk) = hunks_rx.next().await {
|
while let Some(hunk) = hunks_rx.next().await {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if let Some(transaction) = &mut this.transaction {
|
if let Some(transaction) = &mut this.transaction {
|
||||||
transaction.push(hunk, cx);
|
transaction.push(hunk, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -116,7 +116,7 @@ impl TerminalCodegen {
|
|||||||
|
|
||||||
let result = generate.await;
|
let result = generate.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
this.status = CodegenStatus::Error(error);
|
this.status = CodegenStatus::Error(error);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ use crate::context_store::ContextStore;
|
|||||||
use crate::inline_prompt_editor::{
|
use crate::inline_prompt_editor::{
|
||||||
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
||||||
};
|
};
|
||||||
use crate::terminal_codegen::{CodegenEvent, TerminalCodegen, CLEAR_INPUT};
|
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
use editor::{actions::SelectAll, MultiBuffer};
|
use editor::{MultiBuffer, actions::SelectAll};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||||
LanguageModelRequestMessage, Role,
|
report_assistant_event,
|
||||||
};
|
};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -22,7 +22,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
|||||||
use terminal_view::TerminalView;
|
use terminal_view::TerminalView;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{notifications::NotificationId, Toast, Workspace};
|
use workspace::{Toast, Workspace, notifications::NotificationId};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
@@ -252,7 +252,8 @@ impl TerminalInlineAssistant {
|
|||||||
|
|
||||||
attach_context_to_message(
|
attach_context_to_message(
|
||||||
&mut request_message,
|
&mut request_message,
|
||||||
assist.context_store.read(cx).snapshot(cx),
|
assist.context_store.read(cx).context().iter(),
|
||||||
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
request_message.content.push(prompt.into());
|
request_message.content.push(prompt.into());
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use assistant_context_editor::SavedContextMetadata;
|
use assistant_context_editor::SavedContextMetadata;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
|
App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle, WeakEntity,
|
||||||
WeakEntity,
|
uniform_list,
|
||||||
};
|
};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
use ui::{IconButtonShape, ListItem, ListItemSpacing, Tooltip, prelude::*};
|
||||||
|
|
||||||
use crate::history_store::{HistoryEntry, HistoryStore};
|
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||||
use crate::thread_store::SavedThreadMetadata;
|
use crate::thread_store::SerializedThreadMetadata;
|
||||||
use crate::{AssistantPanel, RemoveSelectedThread};
|
use crate::{AssistantPanel, RemoveSelectedThread};
|
||||||
|
|
||||||
pub struct ThreadHistory {
|
pub struct ThreadHistory {
|
||||||
@@ -221,14 +221,14 @@ impl Render for ThreadHistory {
|
|||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct PastThread {
|
pub struct PastThread {
|
||||||
thread: SavedThreadMetadata,
|
thread: SerializedThreadMetadata,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PastThread {
|
impl PastThread {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread: SavedThreadMetadata,
|
thread: SerializedThreadMetadata,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|||||||