Compare commits
606 Commits
github-tok
...
xcode-styl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06f4f46504 | ||
|
|
e5bcd720e1 | ||
|
|
41583fb066 | ||
|
|
521a223681 | ||
|
|
c8c6468f9c | ||
|
|
3f4098e87b | ||
|
|
1d684c8890 | ||
|
|
97c5c5a6e7 | ||
|
|
ba4fc1bcfc | ||
|
|
bbf16bda75 | ||
|
|
c56b8904cc | ||
|
|
695118d110 | ||
|
|
a675ca7a1e | ||
|
|
6e762d9c05 | ||
|
|
28380d714d | ||
|
|
f338c46bf7 | ||
|
|
5fbb7b0d40 | ||
|
|
f12b0dddf4 | ||
|
|
14bb10d783 | ||
|
|
c9ce4aec91 | ||
|
|
01dfb6fa82 | ||
|
|
f9987a1141 | ||
|
|
7432e947bc | ||
|
|
157199b65b | ||
|
|
d74f3f4ea6 | ||
|
|
9e2023bffc | ||
|
|
3ab4ad6de8 | ||
|
|
e3ce0618a3 | ||
|
|
865dd4c5fc | ||
|
|
2178f66af6 | ||
|
|
338a7395a7 | ||
|
|
4c2415b338 | ||
|
|
e6bc1308af | ||
|
|
6c46e1129d | ||
|
|
fbb5628ec6 | ||
|
|
8c9116daa5 | ||
|
|
20a3e613b8 | ||
|
|
ba1c05abf2 | ||
|
|
2823771c06 | ||
|
|
343f155ab9 | ||
|
|
2dece13d83 | ||
|
|
985dcf7523 | ||
|
|
b079871428 | ||
|
|
4983b01c89 | ||
|
|
35863c4302 | ||
|
|
a0bd25f218 | ||
|
|
8a1e795746 | ||
|
|
f4818b648e | ||
|
|
7031ed8b87 | ||
|
|
6073d2c93c | ||
|
|
00499aadd4 | ||
|
|
d1eb69c6cd | ||
|
|
40cbfb7eb2 | ||
|
|
5d0f02d356 | ||
|
|
ca8e213151 | ||
|
|
90c893747c | ||
|
|
d09c7eb317 | ||
|
|
1753432406 | ||
|
|
d9218b10ea | ||
|
|
dfdeb1bf51 | ||
|
|
b9f81c7ce7 | ||
|
|
b1450b6d71 | ||
|
|
1af9f98c1d | ||
|
|
1330cb7a1f | ||
|
|
dae4e84bc5 | ||
|
|
6fb5500ef2 | ||
|
|
8f9817173d | ||
|
|
aae4778b4e | ||
|
|
e5c812fbcb | ||
|
|
294147f473 | ||
|
|
4516b099e7 | ||
|
|
8e831ced5b | ||
|
|
3740eec5bf | ||
|
|
2a5a1814cd | ||
|
|
cc62125244 | ||
|
|
224de2ec6c | ||
|
|
c0acd8e8b1 | ||
|
|
91c9281cea | ||
|
|
84494ab26b | ||
|
|
93d670af13 | ||
|
|
7d087ea5d2 | ||
|
|
19c9fb3118 | ||
|
|
b0bab0bf9a | ||
|
|
630a326a07 | ||
|
|
6848073c38 | ||
|
|
eb51041154 | ||
|
|
308debe47f | ||
|
|
0905255fd1 | ||
|
|
59aeede50d | ||
|
|
18f1221a44 | ||
|
|
c979452c2d | ||
|
|
4396ac9dd6 | ||
|
|
c6ff58675f | ||
|
|
1c6b4712a3 | ||
|
|
108162423d | ||
|
|
098896146e | ||
|
|
96409965e4 | ||
|
|
014f93008a | ||
|
|
17774b17fb | ||
|
|
cf086544e3 | ||
|
|
aa330fcf2c | ||
|
|
be95716e94 | ||
|
|
f738fbd4f8 | ||
|
|
0c78a115de | ||
|
|
9427526a41 | ||
|
|
eec26c9a41 | ||
|
|
3c0475d182 | ||
|
|
fc1fc264ec | ||
|
|
800b925fd7 | ||
|
|
95cf153ad7 | ||
|
|
7be57baef0 | ||
|
|
39dc4b9040 | ||
|
|
deb2564b31 | ||
|
|
13f134448d | ||
|
|
94735aef69 | ||
|
|
0d70bcb88c | ||
|
|
360d73c763 | ||
|
|
668d5eef3b | ||
|
|
4cd4d28531 | ||
|
|
786e724684 | ||
|
|
24c94d474e | ||
|
|
21f985a018 | ||
|
|
2283ec5de2 | ||
|
|
371b7355d3 | ||
|
|
95f10fd187 | ||
|
|
324cbecb74 | ||
|
|
32df9256c3 | ||
|
|
36eebb7ba8 | ||
|
|
d34d4f2ef1 | ||
|
|
c610ebfb03 | ||
|
|
aabfea4c10 | ||
|
|
51059b6f50 | ||
|
|
6b0325b059 | ||
|
|
c9bd409732 | ||
|
|
a067c16c82 | ||
|
|
1a6c1b2159 | ||
|
|
8718019b52 | ||
|
|
a32505fcc2 | ||
|
|
980917bb7c | ||
|
|
bd8471bb40 | ||
|
|
16f1da1b7e | ||
|
|
272fc672af | ||
|
|
e68b95c61b | ||
|
|
28f56ad7ae | ||
|
|
7f44e4b89c | ||
|
|
e5ad2c2518 | ||
|
|
67ac80bd19 | ||
|
|
ac30a8b0df | ||
|
|
6b4c607331 | ||
|
|
595f61f0d6 | ||
|
|
1047d8adec | ||
|
|
21fd5c24bf | ||
|
|
336d2c41fa | ||
|
|
5244085bd0 | ||
|
|
af8f26dd34 | ||
|
|
3b9f504d75 | ||
|
|
0579bf73b0 | ||
|
|
534475d7aa | ||
|
|
8d05f6fdd3 | ||
|
|
3cb4342a76 | ||
|
|
6fb1081b61 | ||
|
|
0ed6b4ef1a | ||
|
|
d75e210e73 | ||
|
|
2b3e453d2f | ||
|
|
cb50f07d23 | ||
|
|
a713c66a9d | ||
|
|
76e3136369 | ||
|
|
834cdc1271 | ||
|
|
dfdd2b9558 | ||
|
|
b9838efaaa | ||
|
|
db8acfa9ff | ||
|
|
5c1dc8b2cc | ||
|
|
7e801dccb0 | ||
|
|
e0c0b6f95d | ||
|
|
ad76db7244 | ||
|
|
9f2c541ab0 | ||
|
|
9597c73f2b | ||
|
|
7812985d3c | ||
|
|
b0f192ec2e | ||
|
|
c9e5ff21a1 | ||
|
|
c02e249ecb | ||
|
|
a7bbbc0552 | ||
|
|
f8a0eb5a8c | ||
|
|
d97a58b20e | ||
|
|
10f0aabec6 | ||
|
|
2624950472 | ||
|
|
ca3f1d624a | ||
|
|
00fe195416 | ||
|
|
bca1a9145c | ||
|
|
b89ddf3a6e | ||
|
|
5c7e90d494 | ||
|
|
61abfd5930 | ||
|
|
ddaa8b3d02 | ||
|
|
3b31db1b1f | ||
|
|
1f736ed693 | ||
|
|
0b228ad12c | ||
|
|
e914d84f00 | ||
|
|
ec0f2fa79a | ||
|
|
1bd49a77e0 | ||
|
|
c8d49408d3 | ||
|
|
e202981f0c | ||
|
|
dec7baeb97 | ||
|
|
2839c2e492 | ||
|
|
0e94ca2a1a | ||
|
|
804b91aa8c | ||
|
|
526faf287d | ||
|
|
6e64628858 | ||
|
|
522d92fbe2 | ||
|
|
cec19aec7b | ||
|
|
c34b24b5fb | ||
|
|
05f944b83a | ||
|
|
629bd42276 | ||
|
|
ab189b898d | ||
|
|
d2ca68bd5d | ||
|
|
72a3292f8d | ||
|
|
ccb4644365 | ||
|
|
fdd307cf7a | ||
|
|
c1d0d72db9 | ||
|
|
db99d7131e | ||
|
|
99215f7660 | ||
|
|
73fee01c85 | ||
|
|
74aa227c09 | ||
|
|
d0e909e58d | ||
|
|
aa9dacad28 | ||
|
|
48491fa487 | ||
|
|
45b5b2e60d | ||
|
|
2f52e2d285 | ||
|
|
3e8a07f496 | ||
|
|
8e4031815d | ||
|
|
131f2857a5 | ||
|
|
65067dad9e | ||
|
|
d8eb341f9b | ||
|
|
70aab39e4f | ||
|
|
bfffc293a3 | ||
|
|
4da58188fb | ||
|
|
9bdfd1e98a | ||
|
|
aa1b2d74ee | ||
|
|
90aa99bb14 | ||
|
|
e47c48fd3b | ||
|
|
2f1d25d7f3 | ||
|
|
0cda28f786 | ||
|
|
a422345224 | ||
|
|
051fa06c7c | ||
|
|
0191f16ebc | ||
|
|
3c9fe363d5 | ||
|
|
8883885ecb | ||
|
|
dbc4ccd95a | ||
|
|
6223d04282 | ||
|
|
c7dad1cb19 | ||
|
|
d5472bc0ad | ||
|
|
b1e59b1371 | ||
|
|
82dfa82ba7 | ||
|
|
5405c2c2d3 | ||
|
|
a391d67366 | ||
|
|
dd850dcf13 | ||
|
|
c766f52f88 | ||
|
|
b686fb2917 | ||
|
|
6c7bcfe752 | ||
|
|
b9dc5f9061 | ||
|
|
3eca9ef3b4 | ||
|
|
f46957584f | ||
|
|
acb0210d26 | ||
|
|
4bbb7b5c2f | ||
|
|
a69ebf038a | ||
|
|
336c49b10d | ||
|
|
ed4b29f80c | ||
|
|
d4c9522da7 | ||
|
|
4b88090cca | ||
|
|
6ad9a66cf9 | ||
|
|
b13144eb1f | ||
|
|
c4355d2905 | ||
|
|
2f3acb6185 | ||
|
|
0e794fa0ac | ||
|
|
d92d52b508 | ||
|
|
109651e6e9 | ||
|
|
baf4abe101 | ||
|
|
2539d57ac7 | ||
|
|
c95e2a2f1d | ||
|
|
dfa7ed55be | ||
|
|
783412fa1d | ||
|
|
6d96f8be8e | ||
|
|
69e84c0c48 | ||
|
|
ffc6218349 | ||
|
|
1f457169ba | ||
|
|
701fa4daa8 | ||
|
|
7fb8ae0024 | ||
|
|
22a2ff4f12 | ||
|
|
0f0ff40c6d | ||
|
|
4383fee3c1 | ||
|
|
cfbc2d0972 | ||
|
|
6e04b9ef65 | ||
|
|
baad66c740 | ||
|
|
c7de817bf9 | ||
|
|
29cdef1ec8 | ||
|
|
6df4c537b9 | ||
|
|
92addb005a | ||
|
|
d7db4d4e0a | ||
|
|
c35f22dde0 | ||
|
|
fceba6c795 | ||
|
|
8df6ce2aac | ||
|
|
41e9f3148c | ||
|
|
b749d9302f | ||
|
|
d29e94b11c | ||
|
|
c72cdfd843 | ||
|
|
4733f188da | ||
|
|
1660438a2a | ||
|
|
ef61ebe049 | ||
|
|
6150c26bd2 | ||
|
|
3bed5b767f | ||
|
|
3595dbb155 | ||
|
|
61771e7e4a | ||
|
|
fd7a133d00 | ||
|
|
3810227759 | ||
|
|
02da4669f3 | ||
|
|
c0717bc613 | ||
|
|
f052a9e28c | ||
|
|
681c88d4e7 | ||
|
|
a994666888 | ||
|
|
0433b8859d | ||
|
|
a5ceef35fa | ||
|
|
afa70034d5 | ||
|
|
5d293ae8ac | ||
|
|
e8d495806f | ||
|
|
baefec3849 | ||
|
|
1edaeebae5 | ||
|
|
dc475dd292 | ||
|
|
feef68bec7 | ||
|
|
6650be8e0f | ||
|
|
4425d58d72 | ||
|
|
29fa6d1a4d | ||
|
|
aabce921e3 | ||
|
|
2948e18e0c | ||
|
|
1c135f99ef | ||
|
|
4370628e30 | ||
|
|
e59fb2e16a | ||
|
|
cf129aa19d | ||
|
|
628f91dd96 | ||
|
|
3fb28f695f | ||
|
|
2aa79a022e | ||
|
|
71dbe88459 | ||
|
|
d280c95d91 | ||
|
|
fcf5042007 | ||
|
|
cb9beb86bf | ||
|
|
29f3e62850 | ||
|
|
aa1cb9c1e1 | ||
|
|
d5b8c21a75 | ||
|
|
2c491d3a66 | ||
|
|
9427833fdf | ||
|
|
fc7c106b2a | ||
|
|
83cd1d2545 | ||
|
|
bc68455320 | ||
|
|
20793fc251 | ||
|
|
cb573172a3 | ||
|
|
9cc82212b5 | ||
|
|
babf846ef9 | ||
|
|
9a6e8a19b5 | ||
|
|
f63ae4388d | ||
|
|
bcd79331b9 | ||
|
|
9166e66519 | ||
|
|
0fe35f440d | ||
|
|
f227c2ff0c | ||
|
|
1078f929aa | ||
|
|
cef0c415f6 | ||
|
|
cd018da1ad | ||
|
|
d725371c42 | ||
|
|
7d708c14e4 | ||
|
|
e56a027bea | ||
|
|
1e244f4aff | ||
|
|
c13be165cd | ||
|
|
0ee6a90912 | ||
|
|
4236c9ed0e | ||
|
|
5923ba4992 | ||
|
|
bb5a763ef7 | ||
|
|
f54129461f | ||
|
|
dc8eb55b00 | ||
|
|
f14a923952 | ||
|
|
47af72bfe1 | ||
|
|
c7ee489c07 | ||
|
|
5e07d0f6de | ||
|
|
7ecad2bef9 | ||
|
|
2d4e427b45 | ||
|
|
4e4856f2c1 | ||
|
|
d1ca6db756 | ||
|
|
c7ee635853 | ||
|
|
106b98fbcb | ||
|
|
1a321b51df | ||
|
|
242af863f5 | ||
|
|
f428d54b74 | ||
|
|
3850da6bee | ||
|
|
13ee78c0b4 | ||
|
|
04223f304b | ||
|
|
1083c0ac53 | ||
|
|
2a63c5f951 | ||
|
|
9032ea9849 | ||
|
|
8cc5b04045 | ||
|
|
c4277681d1 | ||
|
|
0e0ac9b846 | ||
|
|
87f77db6d2 | ||
|
|
717bf35484 | ||
|
|
e8ba8bb1eb | ||
|
|
83eb6ffe1e | ||
|
|
027ce6889c | ||
|
|
65a1d09d24 | ||
|
|
7f150f7e0f | ||
|
|
ebd745cf2d | ||
|
|
06f7d791b7 | ||
|
|
2ecc24eb26 | ||
|
|
6c4728f00f | ||
|
|
a3cc063107 | ||
|
|
7d5a5d0984 | ||
|
|
4c3ada5753 | ||
|
|
b3a8816c0e | ||
|
|
6d9bcdb2af | ||
|
|
0852912fd6 | ||
|
|
47ac01842b | ||
|
|
5b22994d9f | ||
|
|
6c0ea88f5b | ||
|
|
fc4ca346be | ||
|
|
e9570eefbf | ||
|
|
72de3143c8 | ||
|
|
ad206a6a97 | ||
|
|
1e1bc7c373 | ||
|
|
84eca53319 | ||
|
|
b4e558ce3d | ||
|
|
00a8101016 | ||
|
|
444f797827 | ||
|
|
7a14987c02 | ||
|
|
5eb68f0ea4 | ||
|
|
9c513223c4 | ||
|
|
0c0933d1c0 | ||
|
|
a4c5a2d4d3 | ||
|
|
311e136e30 | ||
|
|
4f5433a180 | ||
|
|
295db79c47 | ||
|
|
71d5c57119 | ||
|
|
dd17fd3d5a | ||
|
|
e4f8c4fb4c | ||
|
|
e62e9facf0 | ||
|
|
3f419b32f8 | ||
|
|
5270844b42 | ||
|
|
f567bb52ff | ||
|
|
c55630889a | ||
|
|
e0ca4270b4 | ||
|
|
02dfaf7799 | ||
|
|
c9972ca532 | ||
|
|
9334e152b4 | ||
|
|
9c47c52de5 | ||
|
|
286b97c0de | ||
|
|
415d482395 | ||
|
|
a9d0eee2a9 | ||
|
|
e4e3409952 | ||
|
|
46f98b6001 | ||
|
|
c1a4a24bce | ||
|
|
eb5f59577d | ||
|
|
2dad48d8d9 | ||
|
|
16853acbb1 | ||
|
|
64d649245c | ||
|
|
08210b512d | ||
|
|
6070aea6c0 | ||
|
|
16b44d53f9 | ||
|
|
3bed830a1f | ||
|
|
ee2a329981 | ||
|
|
6d64058fc6 | ||
|
|
7c2822a020 | ||
|
|
3db00384f4 | ||
|
|
3dfbd9e57c | ||
|
|
b103d7621b | ||
|
|
ab70e524c8 | ||
|
|
f0ce62ead8 | ||
|
|
f0345df479 | ||
|
|
bbd2262a93 | ||
|
|
c4fd9e1a6b | ||
|
|
0b7583bae5 | ||
|
|
e4bd115a63 | ||
|
|
fa54fa80d0 | ||
|
|
de16f2bbe6 | ||
|
|
e3b13b54c9 | ||
|
|
2c5d2a58d8 | ||
|
|
3485b7704b | ||
|
|
6801b9137f | ||
|
|
3853e83da7 | ||
|
|
047a7f5d29 | ||
|
|
8332e60ca9 | ||
|
|
afab4b522e | ||
|
|
0cb7dd2972 | ||
|
|
387281fa5b | ||
|
|
72bcb0beb7 | ||
|
|
4ff41ba62e | ||
|
|
16e901fb8f | ||
|
|
54b4587f9a | ||
|
|
1fe10117b7 | ||
|
|
78fd2685d5 | ||
|
|
da9e958b15 | ||
|
|
3908ca9744 | ||
|
|
6fe58a2c4e | ||
|
|
79e7ccc1fe | ||
|
|
0bc9478b46 | ||
|
|
4ac7935589 | ||
|
|
c75ad2fd11 | ||
|
|
365997d79d | ||
|
|
c57a6263aa | ||
|
|
ebea734515 | ||
|
|
4fe05530b0 | ||
|
|
b15aef4310 | ||
|
|
23adff6ff2 | ||
|
|
866fe427b3 | ||
|
|
f7b2faf64f | ||
|
|
5187954711 | ||
|
|
cabd22f36b | ||
|
|
1552198b55 | ||
|
|
0da97b0c8b | ||
|
|
037df8cec5 | ||
|
|
05ac9f1f84 | ||
|
|
9ffb3c5176 | ||
|
|
72d787b3ae | ||
|
|
104f601413 | ||
|
|
46773ebbd8 | ||
|
|
65a93a0036 | ||
|
|
dc63138089 | ||
|
|
fa02bd71c3 | ||
|
|
6d95fd9167 | ||
|
|
899153d9a4 | ||
|
|
77ead25f8c | ||
|
|
9e5f89dc26 | ||
|
|
cf5e76b1b9 | ||
|
|
9775747ba9 | ||
|
|
b7c2d4876c | ||
|
|
2fe1293fba | ||
|
|
e0057ccd0f | ||
|
|
51585e770d | ||
|
|
52fa7ababb | ||
|
|
5ad51ca48e | ||
|
|
35a119d573 | ||
|
|
bbf3b20fc3 | ||
|
|
c1b997002a | ||
|
|
69e99b9f2f | ||
|
|
0fc85a020a | ||
|
|
974f724151 | ||
|
|
ca3f46588a | ||
|
|
d9efa2860f | ||
|
|
ac806d982b | ||
|
|
73cd6ef92c | ||
|
|
019a14bcde | ||
|
|
95d78ff8d5 | ||
|
|
454adfacae | ||
|
|
edd40566b7 | ||
|
|
a40ee74a1f | ||
|
|
2e883be4b5 | ||
|
|
6ea4d2b30d | ||
|
|
380d8c5662 | ||
|
|
508b604b67 | ||
|
|
3da1de2a48 | ||
|
|
8837e5564d | ||
|
|
709523bf36 | ||
|
|
7afee64119 | ||
|
|
cd0ef4b982 | ||
|
|
be6f29cc28 | ||
|
|
38b8e6549f | ||
|
|
b8c1b54f9e | ||
|
|
c304e964fe | ||
|
|
53abad5979 | ||
|
|
54e64b2407 | ||
|
|
ce8854007f | ||
|
|
3e8565ac25 | ||
|
|
d801b7b12e | ||
|
|
37fa42d5cc | ||
|
|
5c9b8e8321 | ||
|
|
96609151c6 | ||
|
|
e37c78bde7 | ||
|
|
920ca688a7 | ||
|
|
f62d76159b | ||
|
|
6a8fdbfd62 | ||
|
|
711a9e5753 | ||
|
|
6de5d29bff | ||
|
|
d7015e5b8f | ||
|
|
ddf70b3bb8 | ||
|
|
8bd8435887 | ||
|
|
4b297a9967 | ||
|
|
7aa70a4858 | ||
|
|
04cd3fcd23 | ||
|
|
d15d85830a | ||
|
|
ccc173ebb1 | ||
|
|
03a030fd00 | ||
|
|
894f3b9d15 | ||
|
|
f36143a461 | ||
|
|
8730d317a8 | ||
|
|
783b33b5c9 | ||
|
|
28da99cc06 | ||
|
|
d2c265c71f | ||
|
|
bbd431ae8c | ||
|
|
5b9d3ea097 | ||
|
|
738cfdff84 | ||
|
|
32d5a2cca0 | ||
|
|
fb877ddf4c | ||
|
|
5135326294 | ||
|
|
4acdb447cf | ||
|
|
1e91d68e08 | ||
|
|
4276901e28 | ||
|
|
44152c412f | ||
|
|
c19ff51465 | ||
|
|
f41747b422 | ||
|
|
f8d20986a1 | ||
|
|
82fa6d7e53 | ||
|
|
d5392cf53f | ||
|
|
a07a090b5a | ||
|
|
046dbba964 |
9
.github/ISSUE_TEMPLATE/01_bug_ai.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Bug Report (AI Related)
|
||||
name: Bug Report (AI)
|
||||
description: Zed Agent Panel Bugs
|
||||
type: "Bug"
|
||||
labels: ["ai"]
|
||||
@@ -19,15 +19,14 @@ body:
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
|
||||
### Model Provider Details
|
||||
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc)
|
||||
- Model Name:
|
||||
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
|
||||
- MCP Servers in-use:
|
||||
- Other Details:
|
||||
- Other Details (MCPs, other settings, etc):
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Bug Report (Edit Predictions)
|
||||
description: Zed Edit Predictions bugs
|
||||
type: "Bug"
|
||||
labels: ["ai", "inline completion", "zeta"]
|
||||
title: "Edit Predictions: <a short description of the Edit Prediction bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
<!-- Please include the LLM provider and model name you are using -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: copy system specs into clipboard"
|
||||
validations:
|
||||
required: true
|
||||
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Bug Report (Git)
|
||||
description: Zed Git-Related Bugs
|
||||
type: "Bug"
|
||||
labels: ["git"]
|
||||
title: "Git: <a short description of the Git bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: copy system specs into clipboard"
|
||||
validations:
|
||||
required: true
|
||||
4
.github/ISSUE_TEMPLATE/04_bug_debugger.yml
vendored
@@ -19,8 +19,8 @@ body:
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
@@ -18,14 +18,16 @@ body:
|
||||
- Issues with insufficient detail may be summarily closed.
|
||||
-->
|
||||
|
||||
DESCRIPTION_HERE
|
||||
|
||||
Steps to reproduce:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
Expected Behavior:
|
||||
Actual Behavior:
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
|
||||
<!-- Before Submitting, did you:
|
||||
1. Include settings.json, keymap.json, .editorconfig if relevant?
|
||||
|
||||
6
.github/actions/build_docs/action.yml
vendored
@@ -19,6 +19,12 @@ runs:
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: ./script/linux
|
||||
|
||||
- name: Check for broken links
|
||||
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
||||
with:
|
||||
args: --no-progress --exclude '^http' './docs/src/**/*'
|
||||
fail: true
|
||||
|
||||
- name: Build book
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
|
||||
8
.github/actions/run_tests_windows/action.yml
vendored
@@ -10,8 +10,8 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Rust
|
||||
shell: pwsh
|
||||
- name: Install test runner
|
||||
shell: powershell
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: cargo install cargo-nextest --locked
|
||||
|
||||
@@ -21,6 +21,6 @@ runs:
|
||||
node-version: "18"
|
||||
|
||||
- name: Run tests
|
||||
shell: pwsh
|
||||
shell: powershell
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: cargo nextest run --workspace --no-fail-fast --config='profile.dev.debug="limited"'
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
|
||||
192
.github/workflows/ci.yml
vendored
@@ -29,6 +29,7 @@ jobs:
|
||||
outputs:
|
||||
run_tests: ${{ steps.filter.outputs.run_tests }}
|
||||
run_license: ${{ steps.filter.outputs.run_license }}
|
||||
run_docs: ${{ steps.filter.outputs.run_docs }}
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
@@ -58,6 +59,11 @@ jobs:
|
||||
else
|
||||
echo "run_tests=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep '^docs/') ]]; then
|
||||
echo "run_docs=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "run_docs=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep '^Cargo.lock') ]]; then
|
||||
echo "run_license=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
@@ -183,6 +189,9 @@ jobs:
|
||||
- name: Check for todo! and FIXME comments
|
||||
run: script/check-todos
|
||||
|
||||
- name: Check modifier use in keymaps
|
||||
run: script/check-keymaps
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
@@ -195,7 +204,9 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: Check docs
|
||||
needs: [job_spec]
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
if: |
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
(needs.job_spec.outputs.run_tests == 'true' || needs.job_spec.outputs.run_docs == 'true')
|
||||
runs-on:
|
||||
- buildjet-8vcpu-ubuntu-2204
|
||||
steps:
|
||||
@@ -370,64 +381,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -rf ./../.cargo
|
||||
|
||||
windows_clippy:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Clippy
|
||||
needs: [job_spec]
|
||||
if: |
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
needs.job_spec.outputs.run_tests == 'true'
|
||||
runs-on: windows-2025-16
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
run: git config --system core.longpaths true
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Create Dev Drive using ReFS
|
||||
run: ./script/setup-dev-driver.ps1
|
||||
|
||||
# actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
|
||||
- name: Copy Git Repo to Dev Drive
|
||||
run: |
|
||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
workspaces: ${{ env.ZED_WORKSPACE }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
|
||||
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
|
||||
|
||||
- name: cargo clippy
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: ./script/clippy.ps1
|
||||
|
||||
- name: Check dev drive space
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
|
||||
run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
|
||||
|
||||
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: |
|
||||
if (Test-Path "${{ env.CARGO_HOME }}/config.toml") {
|
||||
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
|
||||
}
|
||||
|
||||
# Windows CI takes twice as long as our other platforms and fast github hosted runners are expensive.
|
||||
# But we still want to do CI, so let's only run tests on main and come back to this when we're
|
||||
# ready to self host our Windows CI (e.g. during the push for full Windows support)
|
||||
windows_tests:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Tests
|
||||
@@ -435,51 +388,45 @@ jobs:
|
||||
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' }}
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
run: git config --system core.longpaths true
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
$RunnerDir = Split-Path -Parent $env:RUNNER_WORKSPACE
|
||||
Write-Output `
|
||||
"RUSTUP_HOME=$RunnerDir\.rustup" `
|
||||
"CARGO_HOME=$RunnerDir\.cargo" `
|
||||
"PATH=$RunnerDir\.cargo\bin;$env:PATH" `
|
||||
>> $env:GITHUB_ENV
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Create Dev Drive using ReFS
|
||||
run: ./script/setup-dev-driver.ps1
|
||||
|
||||
# actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
|
||||
- name: Copy Git Repo to Dev Drive
|
||||
run: |
|
||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
workspaces: ${{ env.ZED_WORKSPACE }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: Configure CI
|
||||
- name: Setup Cargo and Rustup
|
||||
run: |
|
||||
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
|
||||
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
|
||||
.\script\install-rustup.ps1
|
||||
|
||||
- name: cargo clippy
|
||||
run: |
|
||||
.\script\clippy.ps1
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests_windows
|
||||
with:
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
|
||||
- name: Build Zed
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: cargo build
|
||||
|
||||
- name: Check dev drive space
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
|
||||
run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
|
||||
- name: Limit target directory size
|
||||
run: ./script/clear-target-dir-if-larger-than.ps1 250
|
||||
|
||||
# - name: Check dev drive space
|
||||
# working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# # `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
|
||||
# run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
|
||||
|
||||
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||
- name: Clean CI config file
|
||||
@@ -495,13 +442,13 @@ jobs:
|
||||
needs:
|
||||
- job_spec
|
||||
- style
|
||||
- check_docs
|
||||
- migration_checks
|
||||
# run_tests: If adding required tests, add them here and to script below.
|
||||
- workspace_hack
|
||||
- linux_tests
|
||||
- build_remote_server
|
||||
- macos_tests
|
||||
- windows_clippy
|
||||
- windows_tests
|
||||
if: |
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
@@ -512,15 +459,17 @@ jobs:
|
||||
# Check dependent jobs...
|
||||
RET_CODE=0
|
||||
# Always check style
|
||||
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
|
||||
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
|
||||
|
||||
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
fi
|
||||
# Only check test jobs if they were supposed to run
|
||||
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
||||
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
|
||||
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
|
||||
[[ "${{ needs.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"; }
|
||||
# This check is intentionally disabled. See: https://github.com/zed-industries/zed/pull/28431
|
||||
# [[ "${{ needs.migration_checks.result }}" != 'success' ]] && { RET_CODE=1; echo "Migration Checks failed"; }
|
||||
@@ -736,10 +685,69 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
freebsd:
|
||||
timeout-minutes: 60
|
||||
runs-on: github-8vcpu-ubuntu-2404
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
needs: [linux_tests]
|
||||
name: Build Zed on FreeBSD
|
||||
# env:
|
||||
# MYTOKEN : ${{ secrets.MYTOKEN }}
|
||||
# MYTOKEN2: "value2"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build FreeBSD remote-server
|
||||
id: freebsd-build
|
||||
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
|
||||
with:
|
||||
# envs: "MYTOKEN MYTOKEN2"
|
||||
usesh: true
|
||||
release: 13.5
|
||||
copyback: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash curl jq git \
|
||||
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
|
||||
run: |
|
||||
freebsd-version
|
||||
sysctl hw.model
|
||||
sysctl hw.ncpu
|
||||
sysctl hw.physmem
|
||||
sysctl hw.usermem
|
||||
git config --global --add safe.directory /home/runner/work/zed/zed
|
||||
rustup-init --profile minimal --default-toolchain none -y
|
||||
. "$HOME/.cargo/env"
|
||||
./script/bundle-freebsd
|
||||
mkdir -p out/
|
||||
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
|
||||
rm -rf target/
|
||||
cargo clean
|
||||
|
||||
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-freebsd.gz
|
||||
path: out/zed-remote-server-freebsd-x86_64.gz
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
files: |
|
||||
out/zed-remote-server-freebsd-x86_64.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
nix-build:
|
||||
name: Build with Nix
|
||||
uses: ./.github/workflows/nix.yml
|
||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||
secrets: inherit
|
||||
with:
|
||||
flake-output: debug
|
||||
# excludes the final package to only cache dependencies
|
||||
@@ -750,12 +758,12 @@ jobs:
|
||||
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, freebsd]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
steps:
|
||||
- name: gh release
|
||||
run: gh release edit $GITHUB_REF_NAME --draft=true
|
||||
run: gh release edit $GITHUB_REF_NAME --draft=false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
34
.github/workflows/community_delete_comments.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Delete Mediafire Comments
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
delete_comment:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for specific strings in comment
|
||||
id: check_comment
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
with:
|
||||
script: |
|
||||
const comment = context.payload.comment.body;
|
||||
const triggerStrings = ['www.mediafire.com'];
|
||||
return triggerStrings.some(triggerString => comment.includes(triggerString));
|
||||
|
||||
- name: Delete comment if it contains any of the specific strings
|
||||
if: steps.check_comment.outputs.result == 'true'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
with:
|
||||
script: |
|
||||
const commentId = context.payload.comment.id;
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: commentId
|
||||
});
|
||||
11
.github/workflows/eval.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
types: [synchronize, reopened, labeled]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -25,15 +25,6 @@ env:
|
||||
ZED_EVAL_TELEMETRY: 1
|
||||
|
||||
jobs:
|
||||
# This is a no-op job that we run to prevent GitHub from marking the workflow
|
||||
# as failed for PRs that don't have the `run-eval` label.
|
||||
noop:
|
||||
name: No-op
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: No-op
|
||||
run: echo "Nothing to do"
|
||||
|
||||
run_eval:
|
||||
timeout-minutes: 60
|
||||
name: Run Agent Eval
|
||||
|
||||
45
.github/workflows/release_nightly.yml
vendored
@@ -167,9 +167,54 @@ jobs:
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly linux-targz
|
||||
|
||||
freebsd:
|
||||
timeout-minutes: 60
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: github-8vcpu-ubuntu-2404
|
||||
needs: tests
|
||||
env:
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
name: Build Zed on FreeBSD
|
||||
# env:
|
||||
# MYTOKEN : ${{ secrets.MYTOKEN }}
|
||||
# MYTOKEN2: "value2"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build FreeBSD remote-server
|
||||
id: freebsd-build
|
||||
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
|
||||
with:
|
||||
# envs: "MYTOKEN MYTOKEN2"
|
||||
usesh: true
|
||||
release: 13.5
|
||||
copyback: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash curl jq git \
|
||||
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
|
||||
run: |
|
||||
freebsd-version
|
||||
sysctl hw.model
|
||||
sysctl hw.ncpu
|
||||
sysctl hw.physmem
|
||||
sysctl hw.usermem
|
||||
git config --global --add safe.directory /home/runner/work/zed/zed
|
||||
rustup-init --profile minimal --default-toolchain none -y
|
||||
. "$HOME/.cargo/env"
|
||||
./script/bundle-freebsd
|
||||
mkdir -p out/
|
||||
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
|
||||
rm -rf target/
|
||||
cargo clean
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly freebsd
|
||||
|
||||
bundle-nix:
|
||||
name: Build and cache Nix package
|
||||
needs: tests
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/nix.yml
|
||||
|
||||
update-nightly-tag:
|
||||
|
||||
5
.github/workflows/unit_evals.yml
vendored
@@ -19,6 +19,7 @@ env:
|
||||
|
||||
jobs:
|
||||
unit_evals:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
timeout-minutes: 60
|
||||
name: Run unit evals
|
||||
runs-on:
|
||||
@@ -62,11 +63,11 @@ jobs:
|
||||
|
||||
- name: Run unit evals
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)' --test-threads 1
|
||||
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)'
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
- name: Send the pull request link into the Slack channel
|
||||
- name: Send failure message to Slack channel if needed
|
||||
if: ${{ failure() }}
|
||||
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
||||
.flatpak-builder
|
||||
.idea
|
||||
.netrc
|
||||
*.pyc
|
||||
.pytest_cache
|
||||
.swiftpm
|
||||
.swiftpm/config/registries.json
|
||||
|
||||
4
.rules
@@ -100,9 +100,7 @@ Often event handlers will want to update the entity that's in the current `Conte
|
||||
|
||||
Actions are dispatched via user keyboard interaction or in code via `window.dispatch_action(SomeAction.boxed_clone(), cx)` or `focus_handle.dispatch_action(&SomeAction, window, cx)`.
|
||||
|
||||
Actions which have no data inside are created and registered with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call.
|
||||
|
||||
Actions that do have data must implement `Clone, Default, PartialEq, Deserialize, JsonSchema` and can be registered with an `impl_actions!(some_namespace, [SomeActionWithData])` macro call.
|
||||
Actions with no data defined with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call. Otherwise the `Action` derive macro is used. Doc comments on actions are displayed to the user.
|
||||
|
||||
Action handlers can be registered on an element via the event handler `.on_action(|action, window, cx| ...)`. Like other event handlers, this is often used with `cx.listener`.
|
||||
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
{
|
||||
"label": "Debug Zed (CodeLLDB)",
|
||||
"adapter": "CodeLLDB",
|
||||
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
|
||||
"build": {
|
||||
"label": "Build Zed",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Debug Zed (GDB)",
|
||||
"adapter": "GDB",
|
||||
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
|
||||
}
|
||||
"build": {
|
||||
"label": "Build Zed",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"file_types": {
|
||||
"Dockerfile": ["Dockerfile*[!dockerignore]"],
|
||||
"JSONC": ["assets/**/*.json", "renovate.json"],
|
||||
"Git Ignore": ["dockerignore"]
|
||||
},
|
||||
"hard_tabs": false,
|
||||
@@ -47,6 +48,7 @@
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
"ensure_final_newline_on_save": true,
|
||||
"file_scan_exclusions": [
|
||||
"crates/assistant_tools/src/edit_agent/evals/fixtures",
|
||||
"crates/eval/worktrees/",
|
||||
"crates/eval/repos/",
|
||||
"**/.git",
|
||||
|
||||
426
Cargo.lock
generated
34
Cargo.toml
@@ -2,12 +2,13 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/agent_ui",
|
||||
"crates/agent",
|
||||
"crates/agent_settings",
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"crates/assets",
|
||||
"crates/assistant_context_editor",
|
||||
"crates/assistant_context",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_slash_commands",
|
||||
"crates/assistant_tool",
|
||||
@@ -65,6 +66,7 @@ members = [
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui_tokio",
|
||||
|
||||
"crates/html_to_markdown",
|
||||
"crates/http_client",
|
||||
"crates/http_client_tls",
|
||||
@@ -93,6 +95,7 @@ members = [
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/svg_preview",
|
||||
"crates/migrator",
|
||||
"crates/mistral",
|
||||
"crates/multi_buffer",
|
||||
@@ -163,8 +166,10 @@ members = [
|
||||
"crates/ui_prompt",
|
||||
"crates/util",
|
||||
"crates/util_macros",
|
||||
"crates/vercel",
|
||||
"crates/vim",
|
||||
"crates/vim_mode_setting",
|
||||
"crates/watch",
|
||||
"crates/web_search",
|
||||
"crates/web_search_providers",
|
||||
"crates/welcome",
|
||||
@@ -212,12 +217,13 @@ edition = "2024"
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
agent = { path = "crates/agent" }
|
||||
agent_ui = { path = "crates/agent_ui" }
|
||||
agent_settings = { path = "crates/agent_settings" }
|
||||
ai = { path = "crates/ai" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||
assistant_context = { path = "crates/assistant_context" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
assistant_tool = { path = "crates/assistant_tool" }
|
||||
@@ -299,6 +305,7 @@ lmstudio = { path = "crates/lmstudio" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
svg_preview = { path = "crates/svg_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
migrator = { path = "crates/migrator" }
|
||||
@@ -371,8 +378,11 @@ ui_macros = { path = "crates/ui_macros" }
|
||||
ui_prompt = { path = "crates/ui_prompt" }
|
||||
util = { path = "crates/util" }
|
||||
util_macros = { path = "crates/util_macros" }
|
||||
vercel = { path = "crates/vercel" }
|
||||
vim = { path = "crates/vim" }
|
||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||
|
||||
watch = { path = "crates/watch" }
|
||||
web_search = { path = "crates/web_search" }
|
||||
web_search_providers = { path = "crates/web_search_providers" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
@@ -403,7 +413,6 @@ async-recursion = "1.0.0"
|
||||
async-tar = "0.5.0"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.29.1"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
|
||||
aws-credential-types = { version = "1.2.2", features = [
|
||||
@@ -416,9 +425,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
|
||||
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blake3 = "1.5.3"
|
||||
bytes = "1.0"
|
||||
cargo_metadata = "0.19"
|
||||
@@ -432,9 +441,10 @@ convert_case = "0.8.0"
|
||||
core-foundation = "0.10.0"
|
||||
core-foundation-sys = "0.8.6"
|
||||
core-video = { version = "0.4.3", features = ["metal"] }
|
||||
cpal = "0.16"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
ctor = "0.4.0"
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "68516de327fa1be15214133a0a2e52a12982ce75" }
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b40956a7f4d1939da67429d941389ee306a3a308" }
|
||||
dashmap = "6.0"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
@@ -451,7 +461,6 @@ futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
git2 = { version = "0.20.1", default-features = false }
|
||||
globset = "0.4"
|
||||
hashbrown = "0.15.3"
|
||||
handlebars = "4.3"
|
||||
heck = "0.5"
|
||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||
@@ -479,7 +488,6 @@ log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189f1c5dd53c624a419ce35bc77ad6a908d18" }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
metal = "0.29"
|
||||
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
|
||||
moka = { version = "0.12.10", features = ["sync"] }
|
||||
naga = { version = "25.0", features = ["wgsl-in"] }
|
||||
nanoid = "0.4"
|
||||
@@ -514,7 +522,6 @@ rand = "0.8.5"
|
||||
rayon = "1.8"
|
||||
ref-cast = "1.0.24"
|
||||
regex = "1.5"
|
||||
repair_json = "0.1.0"
|
||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c770a32f1998d6e999cef3e59e0013e6c4415", default-features = false, features = [
|
||||
"charset",
|
||||
"http2",
|
||||
@@ -546,7 +553,6 @@ serde_repr = "0.1"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
signal-hook = "0.3.17"
|
||||
simplelog = "0.12.2"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "2.0"
|
||||
@@ -591,7 +597,7 @@ tree-sitter-html = "0.23"
|
||||
tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-python = { git = "https://github.com/zed-industries/tree-sitter-python", rev = "218fcbf3fda3d029225f3dec005cb497d111b35e" }
|
||||
tree-sitter-regex = "0.24"
|
||||
tree-sitter-ruby = "0.23"
|
||||
tree-sitter-rust = "0.24"
|
||||
@@ -681,9 +687,7 @@ features = [
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
||||
[patch.crates-io]
|
||||
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
||||
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
|
||||
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
|
||||
|
||||
@@ -697,6 +701,8 @@ codegen-units = 16
|
||||
[profile.dev.package]
|
||||
taffy = { opt-level = 3 }
|
||||
cranelift-codegen = { opt-level = 3 }
|
||||
cranelift-codegen-meta = { opt-level = 3 }
|
||||
cranelift-codegen-shared = { opt-level = 3 }
|
||||
resvg = { opt-level = 3 }
|
||||
rustybuzz = { opt-level = 3 }
|
||||
ttf-parser = { opt-level = 3 }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.87-bookworm as builder
|
||||
FROM rust:1.88-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
16
assets/icons/ai_v_zero.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2639_570)">
|
||||
<g clip-path="url(#clip1_2639_570)">
|
||||
<path d="M9.85676 4H13.6675C15.2128 4 16.4654 5.25266 16.4654 6.7979V10.4322H14.9002V6.7979C14.9002 6.76067 14.8988 6.7237 14.8959 6.68706L11.0851 10.4316C11.098 10.432 11.1109 10.4322 11.1238 10.4322H14.9002V11.9105H11.1238C9.57856 11.9105 8.29152 10.6456 8.29152 9.10032V5.47569H9.85676V9.10032C9.85676 9.17012 9.86216 9.23908 9.87264 9.30672L13.7673 5.4798C13.7344 5.47708 13.7012 5.47569 13.6675 5.47569H9.85676V4Z" fill="black"/>
|
||||
<path d="M6.00752 11.6382L0.5 5.47504H2.71573L5.94924 9.09348V5.47504H7.6014V11.0298C7.6014 11.8682 6.56616 12.2634 6.00752 11.6382Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2639_570">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2639_570">
|
||||
<rect width="16" height="8" fill="white" transform="translate(0.5 4)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1015 B |
1
assets/icons/arrow_down10.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-down10-icon lucide-arrow-down-1-0"><path d="m3 16 4 4 4-4"/><path d="M7 20V4"/><path d="M17 10V4h-2"/><path d="M15 10h4"/><rect x="15" y="14" width="4" height="6" ry="2"/></svg>
|
||||
|
After Width: | Height: | Size: 386 B |
3
assets/icons/arrow_up_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 3.5L12.5 8M8 3.5L3.5 8M8 3.5V12.5" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 233 B |
@@ -1 +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-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-blocks-icon lucide-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>
|
||||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 386 B |
3
assets/icons/bolt_filled_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.75776 5.50003H8.49988C8.70769 5.50003 8.89518 5.62971 8.95455 5.82346C9.04049 6.01876 8.9858 6.23906 8.82956 6.37656L4.82971 9.87643C4.65315 10.0295 4.39488 10.042 4.20614 9.90455C4.01724 9.76705 3.94849 9.51706 4.04052 9.30301L5.24219 6.49999H3.48601C3.2918 6.49999 3.10524 6.37031 3.03197 6.17657C2.9587 5.98126 3.014 5.76096 3.1708 5.62346L7.17018 2.12375C7.34674 1.97001 7.60454 1.95829 7.7936 2.09547C7.98265 2.23275 8.0514 2.48218 7.95922 2.69695L6.75776 5.50003Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 601 B |
1
assets/icons/circle_help.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-help-icon lucide-circle-help"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
|
||||
|
After Width: | Height: | Size: 348 B |
3
assets/icons/file_icons/cairo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.51298 2C5.19374 2 2.5 4.6945 2.5 8.01298C2.5 9.41828 2.99491 10.7205 3.80143 11.7485C4.45214 10.8243 5.15479 9.95214 6.03997 9.23651C6.06594 9.21054 6.13086 9.17159 6.20876 9.11965C6.57307 8.84623 6.8335 8.44297 6.89842 7.98702V7.97403C7.11991 6.50306 7.71869 5.99593 9.3974 5.99593C9.54099 5.99593 9.70978 5.99593 9.86558 6.00891C10.7248 6.04786 11.2189 6.29532 11.2709 6.42515C11.3098 6.49007 11.2968 6.56874 11.2839 6.64664L11.2189 6.63366C10.6851 6.56874 9.87856 6.72454 9.76171 7.31034C9.69679 7.63569 9.77469 8 9.80066 8.32536C9.83961 8.6637 9.86558 9.01502 9.86558 9.35336C9.86558 9.37933 9.83961 9.50916 9.86558 9.52215C8.95443 8.64995 6.84572 9.74364 6.18203 10.2248C6.24695 10.1988 6.31263 10.1729 6.39053 10.1469C7.02826 9.92541 8.94145 9.35336 9.68304 9.99109C10.3078 10.7587 9.74796 12.1777 9.27979 12.8803C9.00636 13.2966 8.66802 13.6739 8.29073 14H8.51222C11.8315 14 14.5252 11.3055 14.5252 7.98702C14.5252 4.66853 11.8452 2 8.51298 2ZM9.20265 5.25356C8.55193 5.25356 8.01808 4.7197 8.01808 4.06899C8.01808 3.41828 8.55193 2.88442 9.20265 2.88442C9.85336 2.88442 10.3872 3.41828 10.3872 4.06899C10.3872 4.7197 9.86634 5.25356 9.20265 5.25356Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
12
assets/icons/lsp_debug.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 3L7 4" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 4L10 3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.002 6V5.51658C5.98992 5.32067 6.03266 5.12502 6.12762 4.94143C6.22259 4.75784 6.36781 4.59012 6.55453 4.44839C6.74125 4.30666 6.9656 4.19386 7.21403 4.1168C7.46246 4.03973 7.72983 4 8 4C8.27017 4 8.53754 4.03973 8.78597 4.1168C9.0344 4.19386 9.25875 4.30666 9.44547 4.44839C9.63219 4.59012 9.77741 4.75784 9.87238 4.94143C9.96734 5.12502 10.0101 5.32067 9.998 5.51658V6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 13C6.35 13 5 11.5462 5 9.76923V8.15385C5 7.58261 5.21071 7.03477 5.58579 6.63085C5.96086 6.22692 6.46957 6 7 6H9C9.53043 6 10.0391 6.22692 10.4142 6.63085C10.7893 7.03477 11 7.58261 11 8.15385V9.76923C11 11.5462 9.65 13 8 13Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 6.16663C3.90652 6.06663 3 5.21663 3 4.16663" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 9H3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 13C3 11.95 3.89474 11.05 5 11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 4C13 5.05 12.0857 5.9 11 6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 9H11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 11C12.1053 11.05 13 11.95 13 13" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
assets/icons/lsp_restart.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.84265 10.7778C4.39206 11.6001 5.17295 12.241 6.08658 12.6194C7.00021 12.9978 8.00555 13.0969 8.97545 12.9039C9.94535 12.711 10.8363 12.2348 11.5355 11.5355C12.2348 10.8363 12.711 9.94535 12.9039 8.97545C13.0969 8.00555 12.9978 7.00021 12.6194 6.08658C12.241 5.17295 11.6001 4.39206 10.7778 3.84265C9.9556 3.29324 8.9889 3 8 3C6.60219 3.00526 5.26054 3.55068 4.25556 4.52222L3 5.77778" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 3V6H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
4
assets/icons/lsp_stop.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 5L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 409 B |
1
assets/icons/scroll_text.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-scroll-text-icon lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
|
||||
|
After Width: | Height: | Size: 441 B |
1
assets/icons/split_alt.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-split-icon lucide-split"><path d="M16 3h5v5"/><path d="M8 3H3v5"/><path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3"/><path d="m15 9 6-6"/></svg>
|
||||
|
After Width: | Height: | Size: 345 B |
4
assets/icons/zed_mcp_custom.svg
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
4
assets/icons/zed_mcp_extension.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M9.62109 4.65039C9.80393 4.65039 9.98678 4.70637 10.1279 4.83203C10.2722 4.96055 10.3496 5.14167 10.3496 5.33691C10.3496 5.53215 10.2723 5.71329 10.1279 5.8418C9.98678 5.96744 9.80392 6.02344 9.62109 6.02344H7.1416V7.24902H9.46289C9.6423 7.24902 9.82303 7.30362 9.96387 7.42773C10.1085 7.55531 10.1875 7.73591 10.1875 7.93164C10.1874 8.12674 10.1072 8.30592 9.96484 8.43262C9.82536 8.55656 9.64499 8.61426 9.46289 8.61426H7.1416V9.97656H9.62109C9.80392 9.97656 9.98678 10.0326 10.1279 10.1582C10.2723 10.2867 10.3496 10.4678 10.3496 10.6631C10.3496 10.8583 10.2722 11.0394 10.1279 11.168C9.98678 11.2936 9.80393 11.3496 9.62109 11.3496H6.39648C6.20308 11.3496 6.00965 11.289 5.86328 11.1465C5.7159 11.0028 5.65039 10.8095 5.65039 10.6133V5.38672C5.65039 5.19054 5.7159 4.99723 5.86328 4.85352C6.00965 4.71101 6.20308 4.65039 6.39648 4.65039H9.62109ZM9.70215 10.9941C9.72618 10.9903 9.74882 10.9838 9.77051 10.9766C9.74884 10.9838 9.72616 10.9903 9.70215 10.9941ZM9.78809 10.9688C9.80381 10.9626 9.81877 10.9561 9.83301 10.9482C9.81877 10.9561 9.80381 10.9626 9.78809 10.9688ZM9.85059 10.9375C9.86364 10.9292 9.87617 10.9209 9.8877 10.9111C9.87616 10.9209 9.86365 10.9293 9.85059 10.9375ZM6.1123 10.8994C6.12346 10.9099 6.1358 10.9188 6.14844 10.9277C6.1358 10.9188 6.12346 10.9099 6.1123 10.8994ZM9.90039 10.8984C9.91122 10.8882 9.92149 10.8778 9.93066 10.8662C9.92147 10.8778 9.91123 10.8882 9.90039 10.8984ZM6.07129 10.8516C6.0792 10.8626 6.08754 10.8729 6.09668 10.8828C6.08754 10.8729 6.0792 10.8626 6.07129 10.8516ZM9.94434 10.8477C9.95104 10.8377 9.95638 10.8272 9.96191 10.8164C9.95637 10.8271 9.95106 10.8377 9.94434 10.8477ZM6.03418 10.7842C6.04066 10.7995 6.04735 10.8143 6.05566 10.8281C6.04735 10.8143 6.04066 10.7995 6.03418 10.7842ZM9.97559 10.7891C9.97984 10.7783 9.98321 10.7673 9.98633 10.7559C9.98321 10.7673 9.97984 10.7783 9.97559 10.7891ZM6.01367 10.7236C6.0173 10.7388 6.02121 10.7535 6.02637 10.7676C6.02121 10.7535 6.0173 10.7388 6.01367 10.7236ZM9.62109 10.3262C9.67716 10.3262 9.72942 10.3346 9.77539 10.3506C9.72927 10.3345 9.67733 10.3262 9.62109 10.3262ZM9.56641 8.25098C9.5802 8.24792 9.59348 8.24449 9.60645 8.24023C9.59348 8.2445 9.58021 8.24792 9.56641 8.25098ZM9.625 8.2334C9.64073 8.22735 9.65566 8.2207 9.66992 8.21289C9.65565 8.22072 9.64074 8.22733 9.625 8.2334ZM9.68848 8.20117C9.69832 8.19495 9.70781 8.18872 9.7168 8.18164C9.70781 8.18874 9.69832 8.19493 9.68848 8.20117ZM9.74121 8.16016C9.7511 8.15059 9.76008 8.14059 9.76855 8.12988C9.76009 8.1406 9.75109 8.15057 9.74121 8.16016ZM9.77734 8.11914C9.78753 8.10499 9.79605 8.09004 9.80371 8.07422C9.79606 8.09005 9.78751 8.10498 9.77734 8.11914ZM9.79883 5.6377C9.80686 5.6343 9.81463 5.63082 9.82227 5.62695C9.81463 5.63084 9.80687 5.63429 9.79883 5.6377ZM9.93457 5.53516C9.91124 5.56582 9.88273 5.59214 9.84863 5.61328C9.88277 5.5922 9.91121 5.56576 9.93457 5.53516ZM9.94434 5.52246C9.95363 5.50886 9.96171 5.4946 9.96875 5.47949C9.96172 5.4946 9.9536 5.50885 9.94434 5.52246ZM6.02637 5.23145C6.02118 5.24552 6.01733 5.26024 6.01367 5.27539C6.01734 5.26023 6.02118 5.24552 6.02637 5.23145ZM6.05566 5.17188C6.04766 5.18525 6.04049 5.19914 6.03418 5.21387C6.04049 5.19914 6.04765 5.18525 6.05566 5.17188ZM6.09961 5.11328C6.08938 5.124 6.08003 5.13538 6.07129 5.14746C6.08003 5.13538 6.08938 5.12399 6.09961 5.11328ZM6.14844 5.07129C6.13601 5.08003 6.12428 5.08938 6.11328 5.09961C6.12428 5.08938 6.13601 5.08003 6.14844 5.07129ZM6.23242 5.02637C6.21195 5.03382 6.19287 5.04334 6.1748 5.05371C6.19287 5.04335 6.21195 5.03382 6.23242 5.02637ZM6.1748 10.9453C6.19195 10.9552 6.21017 10.9635 6.22949 10.9707C6.21017 10.9635 6.19195 10.9552 6.1748 10.9453ZM6.30957 5.00684C6.28239 5.01136 6.25651 5.01759 6.23242 5.02637C6.25651 5.01759 6.28239 5.01136 6.30957 5.00684Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
890
assets/images/debugger_grid.svg
Normal file
@@ -0,0 +1,890 @@
|
||||
<svg width="400" height="92" viewBox="0 0 400 92" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_532_2318)">
|
||||
<mask id="mask0_532_2318" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="400" height="92">
|
||||
<path d="M400 0H0V92H400V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_532_2318)">
|
||||
<g clip-path="url(#clip1_532_2318)">
|
||||
<path d="M62.1878 1.24874C60.4044 1.12767 59.0444 -0.430533 59.1654 -2.21393L59.2755 -3.8352C59.3144 -4.40851 59.5795 -4.94289 60.0124 -5.32076C60.4453 -5.69863 61.0106 -5.88906 61.5839 -5.85013L63.7456 -5.70338C64.3189 -5.66446 64.8533 -5.39938 65.2312 -4.96647C65.6091 -4.53355 65.7995 -3.96825 65.7606 -3.39494L65.6505 -1.77367C65.5294 0.00972748 63.9712 1.36981 62.1878 1.24874Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M62.1879 1.24869L62.5181 -3.61511" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.2873 1.45893C57.3644 0.324045 58.3491 -0.586346 59.4877 -0.563343" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M65.108 -0.181763C66.2393 -0.0506748 67.0919 0.984456 67.0149 2.11934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip2_532_2318)">
|
||||
<path d="M87.1305 2.94204C85.3471 2.82097 83.987 1.26277 84.108 -0.52063L84.2181 -2.1419C84.257 -2.71521 84.5221 -3.24959 84.955 -3.62746C85.3879 -4.00534 85.9532 -4.19576 86.5266 -4.15684L88.6883 -4.01008C89.2616 -3.97116 89.7959 -3.70608 90.1738 -3.27317C90.5517 -2.84025 90.7421 -2.27495 90.7032 -1.70164L90.5931 -0.0803691C90.4721 1.70303 88.9139 3.06311 87.1305 2.94204Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M87.1305 2.94199L87.4607 -1.92181" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M82.23 3.15223C82.307 2.01734 83.2918 1.10695 84.4303 1.12996" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M92.7915 -0.474035L90.6298 -0.620789" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M90.0507 1.51154C91.1819 1.64262 92.0346 2.67775 91.9575 3.81264" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip3_532_2318)">
|
||||
<path d="M112.073 4.63534C110.29 4.51426 108.929 2.95606 109.051 1.17267L109.161 -0.4486C109.2 -1.02192 109.465 -1.55629 109.898 -1.93416C110.33 -2.31204 110.896 -2.50246 111.469 -2.46354L113.631 -2.31678C114.204 -2.27786 114.738 -2.01279 115.116 -1.57987C115.494 -1.14695 115.685 -0.581655 115.646 -0.00833894L115.536 1.61293C115.415 3.39632 113.856 4.75641 112.073 4.63534Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M112.073 4.63529L112.403 -0.228516" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M109.087 0.632227L106.926 0.485474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M107.172 4.84553C107.25 3.71064 108.234 2.80025 109.373 2.82325" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M117.734 1.21926L115.572 1.07251" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M114.993 3.20483C116.124 3.33592 116.977 4.37105 116.9 5.50594" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip4_532_2318)">
|
||||
<path d="M135.867 -0.736649L135.903 -1.27707C135.908 -1.49674 135.958 -1.71311 136.049 -1.91312C136.14 -2.11313 136.27 -2.29262 136.433 -2.44078C136.595 -2.58894 136.786 -2.70268 136.993 -2.77515C137.2 -2.84762 137.42 -2.8773 137.64 -2.86242C137.859 -2.84754 138.073 -2.78839 138.269 -2.68855C138.464 -2.58872 138.638 -2.45025 138.779 -2.28153C138.919 -2.1128 139.024 -1.9173 139.087 -1.70683C139.151 -1.49636 139.17 -1.27528 139.146 -1.05694L139.109 -0.516519" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M137.016 6.32869C135.232 6.20762 133.872 4.64942 133.993 2.86603L134.103 1.24476C134.142 0.671444 134.407 0.13707 134.84 -0.240804C135.273 -0.618678 135.838 -0.809099 136.412 -0.770178L138.573 -0.623424C139.147 -0.584503 139.681 -0.319426 140.059 0.113491C140.437 0.546408 140.627 1.1117 140.588 1.68502L140.478 3.30629C140.357 5.08968 138.799 6.44977 137.016 6.32869Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M137.016 6.32865L137.346 1.46484" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M134.463 0.183352C133.427 0.00445886 132.625 -0.972961 132.702 -2.10785" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M134.03 2.32559L131.868 2.17883" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M132.115 6.53889C132.192 5.404 133.177 4.49361 134.315 4.51661" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M142.413 -1.44856C142.336 -0.313668 141.409 0.546349 140.375 0.584726" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M142.677 2.91262L140.515 2.76587" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M139.936 4.89819C141.067 5.02928 141.92 6.06441 141.843 7.1993" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip5_532_2318)">
|
||||
<path d="M160.457 -1.85242L161.404 -0.767448" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M163.695 -0.611874L164.78 -1.55889" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M160.809 0.956649L160.846 0.416226C160.851 0.196553 160.9 -0.0198148 160.991 -0.219821C161.082 -0.419827 161.213 -0.599325 161.375 -0.747482C161.538 -0.895638 161.728 -1.00938 161.936 -1.08185C162.143 -1.15432 162.363 -1.184 162.582 -1.16912C162.801 -1.15424 163.015 -1.09509 163.211 -0.995255C163.407 -0.895417 163.58 -0.756956 163.721 -0.588227C163.862 -0.419498 163.967 -0.224001 164.03 -0.0135316C164.093 0.196937 164.113 0.418014 164.088 0.636357L164.052 1.17678" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M161.958 8.02199C160.175 7.90092 158.815 6.34272 158.936 4.55933L159.046 2.93806C159.085 2.36474 159.35 1.83037 159.783 1.45249C160.216 1.07462 160.781 0.884199 161.354 0.923121L163.516 1.06987C164.089 1.1088 164.624 1.37387 165.002 1.80679C165.379 2.23971 165.57 2.805 165.531 3.37832L165.421 4.99959C165.3 6.78298 163.742 8.14306 161.958 8.02199Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M161.958 8.02195L162.288 3.15814" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M159.406 1.87665C158.37 1.69776 157.568 0.720337 157.645 -0.414551" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M158.972 4.01888L156.811 3.87213" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M157.058 8.23219C157.135 7.0973 158.12 6.18691 159.258 6.20991" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M167.356 0.244742C167.279 1.37963 166.352 2.23965 165.318 2.27802" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M167.619 4.60592L165.458 4.45917" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M164.878 6.59149C166.01 6.72258 166.862 7.75771 166.785 8.8926" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip6_532_2318)">
|
||||
<path d="M185.399 -0.159119L186.346 0.92585" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M188.638 1.08142L189.723 0.134404" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M185.752 2.64995L185.788 2.10952C185.793 1.88985 185.843 1.67348 185.934 1.47348C186.025 1.27347 186.156 1.09397 186.318 0.945817C186.48 0.79766 186.671 0.683916 186.878 0.611449C187.086 0.538982 187.306 0.509294 187.525 0.524177C187.744 0.53906 187.958 0.598205 188.154 0.698043C188.349 0.797881 188.523 0.936343 188.664 1.10507C188.804 1.2738 188.91 1.4693 188.973 1.67977C189.036 1.89024 189.056 2.11131 189.031 2.32966L188.994 2.87008" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M186.901 9.71529C185.117 9.59422 183.757 8.03602 183.878 6.25262L183.988 4.63136C184.027 4.05804 184.292 3.52367 184.725 3.14579C185.158 2.76792 185.724 2.5775 186.297 2.61642L188.459 2.76317C189.032 2.80209 189.566 3.06717 189.944 3.50009C190.322 3.933 190.512 4.4983 190.474 5.07162L190.363 6.69289C190.242 8.47628 188.684 9.83636 186.901 9.71529Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M186.901 9.71525L187.231 4.85144" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M184.348 3.56995C183.313 3.39106 182.51 2.41364 182.587 1.27875" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M183.915 5.71218L181.753 5.56543" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M182 9.92549C182.077 8.7906 183.062 7.88021 184.201 7.90321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M192.299 1.93804C192.222 3.07293 191.295 3.93295 190.26 3.97132" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M192.562 6.29922L190.4 6.15247" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M189.821 8.28479C190.952 8.41588 191.805 9.45101 191.728 10.5859" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip7_532_2318)">
|
||||
<path d="M210.342 1.53418L211.289 2.61915" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M213.58 2.77472L214.665 1.8277" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M210.694 4.34325L210.731 3.80282C210.736 3.58315 210.786 3.36678 210.877 3.16678C210.968 2.96677 211.098 2.78727 211.26 2.63911C211.423 2.49096 211.613 2.37721 211.821 2.30475C212.028 2.23228 212.248 2.20259 212.467 2.21748C212.687 2.23236 212.9 2.2915 213.096 2.39134C213.292 2.49118 213.465 2.62964 213.606 2.79837C213.747 2.9671 213.852 3.1626 213.915 3.37307C213.978 3.58353 213.998 3.80461 213.973 4.02295L213.937 4.56338" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M211.843 11.4086C210.06 11.2875 208.7 9.72932 208.821 7.94592L208.931 6.32465C208.97 5.75134 209.235 5.21697 209.668 4.83909C210.101 4.46122 210.666 4.2708 211.239 4.30972L213.401 4.45647C213.974 4.49539 214.509 4.76047 214.887 5.19339C215.265 5.6263 215.455 6.1916 215.416 6.76492L215.306 8.38618C215.185 10.1696 213.627 11.5297 211.843 11.4086Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M211.843 11.4085L212.174 6.54474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M209.291 5.26325C208.255 5.08435 207.453 4.10693 207.53 2.97205" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M208.858 7.40548L206.696 7.25873" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206.943 11.6188C207.02 10.4839 208.005 9.5735 209.143 9.59651" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M217.241 3.63134C217.164 4.76623 216.237 5.62624 215.203 5.66462" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M217.504 7.99252L215.343 7.84576" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M214.764 9.97809C215.895 10.1092 216.748 11.1443 216.67 12.2792" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip8_532_2318)">
|
||||
<path d="M235.285 3.22748L236.232 4.31245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M238.523 4.46802L239.608 3.521" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M235.637 6.03654L235.674 5.49612C235.679 5.27645 235.728 5.06008 235.819 4.86007C235.91 4.66007 236.041 4.48057 236.203 4.33241C236.365 4.18426 236.556 4.07051 236.763 3.99805C236.971 3.92558 237.191 3.89589 237.41 3.91077C237.629 3.92566 237.843 3.9848 238.039 4.08464C238.235 4.18448 238.408 4.32294 238.549 4.49167C238.69 4.6604 238.795 4.85589 238.858 5.06636C238.921 5.27683 238.941 5.49791 238.916 5.71625L238.879 6.25667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M236.786 13.1019C235.003 12.9808 233.642 11.4226 233.764 9.63922L233.874 8.01795C233.913 7.44464 234.178 6.91026 234.611 6.53239C235.043 6.15452 235.609 5.96409 236.182 6.00302L238.344 6.14977C238.917 6.18869 239.451 6.45377 239.829 6.88668C240.207 7.3196 240.398 7.8849 240.359 8.45821L240.249 10.0795C240.128 11.8629 238.569 13.223 236.786 13.1019Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M236.786 13.1018L237.116 8.23804" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M234.233 6.95655C233.198 6.77765 232.395 5.80023 232.472 4.66534" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M233.8 9.09878L231.639 8.95203" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M231.885 13.3121C231.963 12.1772 232.947 11.2668 234.086 11.2898" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M242.184 5.32464C242.107 6.45953 241.18 7.31954 240.146 7.35792" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M242.447 9.68582L240.285 9.53906" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M239.706 11.6714C240.837 11.8025 241.69 12.8376 241.613 13.9725" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip9_532_2318)">
|
||||
<path d="M260.227 4.92084L261.174 6.00581" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M263.466 6.16138L264.551 5.21436" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M260.579 7.7299L260.616 7.18948C260.621 6.96981 260.671 6.75344 260.762 6.55343C260.853 6.35343 260.983 6.17393 261.146 6.02577C261.308 5.87762 261.498 5.76387 261.706 5.6914C261.913 5.61894 262.133 5.58925 262.353 5.60413C262.572 5.61902 262.786 5.67816 262.981 5.778C263.177 5.87784 263.351 6.0163 263.491 6.18503C263.632 6.35376 263.737 6.54925 263.8 6.75972C263.864 6.97019 263.883 7.19127 263.859 7.40961L263.822 7.95003" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M261.729 14.7952C259.945 14.6742 258.585 13.116 258.706 11.3326L258.816 9.71131C258.855 9.138 259.12 8.60362 259.553 8.22575C259.986 7.84788 260.551 7.65745 261.125 7.69638L263.286 7.84313C263.86 7.88205 264.394 8.14713 264.772 8.58004C265.15 9.01296 265.34 9.57826 265.301 10.1516L265.191 11.7728C265.07 13.5562 263.512 14.9163 261.729 14.7952Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M261.729 14.7952L262.059 9.9314" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M259.176 8.6499C258.14 8.47101 257.338 7.49359 257.415 6.3587" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M258.743 10.7921L256.581 10.6454" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M256.828 15.0054C256.905 13.8706 257.89 12.9602 259.028 12.9832" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M267.126 7.018C267.049 8.15288 266.122 9.0129 265.088 9.05128" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M267.39 11.3792L265.228 11.2324" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M264.649 13.3647C265.78 13.4958 266.633 14.531 266.556 15.6659" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip10_532_2318)">
|
||||
<path d="M285.17 6.61414L286.117 7.6991" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M288.408 7.85468L289.493 6.90766" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M285.522 9.4232L285.559 8.88278C285.564 8.66311 285.613 8.44674 285.704 8.24673C285.795 8.04673 285.926 7.86723 286.088 7.71907C286.25 7.57091 286.441 7.45717 286.648 7.3847C286.856 7.31224 287.076 7.28255 287.295 7.29743C287.514 7.31231 287.728 7.37146 287.924 7.4713C288.12 7.57114 288.293 7.7096 288.434 7.87833C288.575 8.04705 288.68 8.24255 288.743 8.45302C288.806 8.66349 288.826 8.88457 288.801 9.10291L288.765 9.64333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M286.671 16.4885C284.888 16.3675 283.528 14.8093 283.649 13.0259L283.759 11.4046C283.798 10.8313 284.063 10.2969 284.496 9.91905C284.929 9.54117 285.494 9.35075 286.067 9.38967L288.229 9.53643C288.802 9.57535 289.337 9.84042 289.714 10.2733C290.092 10.7063 290.283 11.2716 290.244 11.8449L290.134 13.4661C290.013 15.2495 288.455 16.6096 286.671 16.4885Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M286.671 16.4885L287.001 11.6247" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M284.119 10.3432C283.083 10.1643 282.281 9.18689 282.358 8.052" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M283.685 12.4854L281.524 12.3387" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M281.771 16.6987C281.848 15.5639 282.832 14.6535 283.971 14.6765" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M292.069 8.7113C291.992 9.84618 291.065 10.7062 290.031 10.7446" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M292.332 13.0725L290.17 12.9257" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M289.591 15.058C290.723 15.1891 291.575 16.2243 291.498 17.3592" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip11_532_2318)">
|
||||
<path d="M310.112 8.30743L311.059 9.3924" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M313.351 9.54798L314.436 8.60096" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M310.465 11.1165L310.501 10.5761C310.506 10.3564 310.556 10.14 310.647 9.94003C310.738 9.74002 310.868 9.56053 311.031 9.41237C311.193 9.26421 311.384 9.15047 311.591 9.078C311.799 9.00553 312.018 8.97585 312.238 8.99073C312.457 9.00561 312.671 9.06476 312.867 9.1646C313.062 9.26443 313.236 9.4029 313.377 9.57162C313.517 9.74035 313.622 9.93585 313.686 10.1463C313.749 10.3568 313.769 10.5779 313.744 10.7962L313.707 11.3366" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M311.614 18.1818C309.83 18.0608 308.47 16.5026 308.591 14.7192L308.701 13.0979C308.74 12.5246 309.005 11.9902 309.438 11.6123C309.871 11.2345 310.437 11.0441 311.01 11.083L313.172 11.2297C313.745 11.2686 314.279 11.5337 314.657 11.9666C315.035 12.3996 315.225 12.9649 315.186 13.5382L315.076 15.1594C314.955 16.9428 313.397 18.3029 311.614 18.1818Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M311.614 18.1818L311.944 13.318" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M309.061 12.0365C308.025 11.8576 307.223 10.8802 307.3 9.7453" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M308.628 14.1787L306.466 14.032" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M306.713 18.392C306.79 17.2572 307.775 16.3468 308.914 16.3698" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M317.012 10.4046C316.935 11.5395 316.008 12.3995 314.973 12.4379" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M317.275 14.7658L315.113 14.619" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M314.534 16.7513C315.665 16.8824 316.518 17.9176 316.441 19.0524" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip12_532_2318)">
|
||||
<path d="M335.055 10.0007L336.002 11.0857" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M338.293 11.2413L339.378 10.2943" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M335.407 12.8098L335.444 12.2694C335.449 12.0497 335.498 11.8333 335.589 11.6333C335.68 11.4333 335.811 11.2538 335.973 11.1057C336.136 10.9575 336.326 10.8438 336.534 10.7713C336.741 10.6988 336.961 10.6691 337.18 10.684C337.399 10.6989 337.613 10.7581 337.809 10.8579C338.005 10.9577 338.178 11.0962 338.319 11.2649C338.46 11.4337 338.565 11.6291 338.628 11.8396C338.691 12.0501 338.711 12.2712 338.686 12.4895L338.65 13.0299" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M336.556 19.8751C334.773 19.7541 333.413 18.1959 333.534 16.4125L333.644 14.7912C333.683 14.2179 333.948 13.6835 334.381 13.3056C334.814 12.9278 335.379 12.7373 335.952 12.7763L338.114 12.923C338.687 12.9619 339.222 13.227 339.6 13.6599C339.978 14.0929 340.168 14.6582 340.129 15.2315L340.019 16.8527C339.898 18.6361 338.34 19.9962 336.556 19.8751Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M336.556 19.8751L336.886 15.0113" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M334.004 13.7298C332.968 13.5509 332.166 12.5735 332.243 11.4386" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M333.571 15.872L331.409 15.7253" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M331.656 20.0853C331.733 18.9504 332.718 18.0401 333.856 18.0631" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M341.954 12.0979C341.877 13.2328 340.95 14.0928 339.916 14.1312" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M342.217 16.4591L340.056 16.3123" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M339.477 18.4446C340.608 18.5757 341.46 19.6109 341.383 20.7457" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip13_532_2318)">
|
||||
<path d="M359.998 11.694L360.945 12.779" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M363.236 12.9346L364.321 11.9876" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M360.35 14.5031L360.386 13.9627C360.392 13.743 360.441 13.5266 360.532 13.3266C360.623 13.1266 360.754 12.9471 360.916 12.799C361.078 12.6508 361.269 12.5371 361.476 12.4646C361.684 12.3921 361.904 12.3624 362.123 12.3773C362.342 12.3922 362.556 12.4514 362.752 12.5512C362.947 12.651 363.121 12.7895 363.262 12.9582C363.402 13.1269 363.508 13.3224 363.571 13.5329C363.634 13.7434 363.654 13.9645 363.629 14.1828L363.592 14.7232" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M361.499 21.5684C359.715 21.4474 358.355 19.8892 358.476 18.1058L358.587 16.4845C358.625 15.9112 358.891 15.3768 359.323 14.9989C359.756 14.6211 360.322 14.4306 360.895 14.4696L363.057 14.6163C363.63 14.6552 364.164 14.9203 364.542 15.3532C364.92 15.7862 365.111 16.3515 365.072 16.9248L364.962 18.546C364.84 20.3294 363.282 21.6895 361.499 21.5684Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M361.499 21.5684L361.829 16.7046" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M358.946 15.4231C357.911 15.2442 357.108 14.2668 357.185 13.1319" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M358.513 17.5653L356.351 17.4186" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M356.598 21.7786C356.675 20.6437 357.66 19.7334 358.799 19.7564" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M366.897 13.7912C366.82 14.9261 365.893 15.7861 364.859 15.8245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M367.16 18.1524L364.998 18.0056" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M364.419 20.1379C365.55 20.269 366.403 21.3042 366.326 22.439" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip14_532_2318)">
|
||||
<path d="M384.94 13.3874L385.887 14.4724" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M388.179 14.6279L389.264 13.6809" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M385.292 16.1965L385.329 15.656C385.334 15.4364 385.384 15.22 385.475 15.02C385.566 14.82 385.696 14.6405 385.859 14.4923C386.021 14.3442 386.211 14.2304 386.419 14.158C386.626 14.0855 386.846 14.0558 387.065 14.0707C387.285 14.0856 387.499 14.1447 387.694 14.2446C387.89 14.3444 388.064 14.4829 388.204 14.6516C388.345 14.8203 388.45 15.0158 388.513 15.2263C388.576 15.4367 388.596 15.6578 388.572 15.8762L388.535 16.4166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M386.441 23.2618C384.658 23.1407 383.298 21.5825 383.419 19.7991L383.529 18.1779C383.568 17.6045 383.833 17.0702 384.266 16.6923C384.699 16.3144 385.264 16.124 385.838 16.1629L387.999 16.3097C388.573 16.3486 389.107 16.6137 389.485 17.0466C389.863 17.4795 390.053 18.0448 390.014 18.6181L389.904 20.2394C389.783 22.0228 388.225 23.3829 386.441 23.2618Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M386.441 23.2618L386.772 18.3979" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M383.889 17.1165C382.853 16.9376 382.051 15.9601 382.128 14.8253" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M383.456 19.2587L381.294 19.1119" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M381.541 23.472C381.618 22.3371 382.603 21.4267 383.741 21.4497" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M391.839 15.4845C391.762 16.6194 390.835 17.4795 389.801 17.5178" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M392.103 19.8457L389.941 19.699" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M389.362 21.8313C390.493 21.9624 391.346 22.9975 391.269 24.1324" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip15_532_2318)">
|
||||
<path d="M59.2642 12.3261L60.2112 13.4111" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M62.5026 13.5667L63.5875 12.6196" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M59.6164 15.1352L59.6531 14.5948C59.6582 14.3751 59.7077 14.1587 59.7987 13.9587C59.8897 13.7587 60.0203 13.5792 60.1825 13.431C60.3448 13.2829 60.5354 13.1691 60.7429 13.0967C60.9503 13.0242 61.1703 12.9945 61.3895 13.0094C61.6087 13.0243 61.8227 13.0834 62.0184 13.1833C62.2141 13.2831 62.3876 13.4216 62.5284 13.5903C62.6691 13.759 62.7742 13.9545 62.8374 14.165C62.9005 14.3755 62.9203 14.5965 62.8957 14.8149L62.859 15.3553" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M60.7655 22.2005C58.9821 22.0794 57.622 20.5212 57.7431 18.7379L57.8531 17.1166C57.892 16.5433 58.1571 16.0089 58.59 15.631C59.023 15.2531 59.5883 15.0627 60.1616 15.1016L62.3233 15.2484C62.8966 15.2873 63.4309 15.5524 63.8088 15.9853C64.1867 16.4182 64.3771 16.9835 64.3382 17.5568L64.2281 19.1781C64.1071 20.9615 62.5489 22.3216 60.7655 22.2005Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M60.7655 22.2005L61.0957 17.3367" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M58.213 16.0552C57.1773 15.8763 56.375 14.8989 56.452 13.764" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.7797 18.1974L55.618 18.0507" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M55.865 22.4107C55.942 21.2758 56.9268 20.3654 58.0653 20.3884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.1633 14.4233C66.0863 15.5582 65.1592 16.4182 64.1251 16.4566" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.4265 18.7844L64.2648 18.6377" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M63.6857 20.77C64.8169 20.9011 65.6696 21.9362 65.5925 23.0711" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip16_532_2318)">
|
||||
<path d="M84.2068 14.0194L85.1538 15.1044" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M87.4452 15.26L88.5302 14.3129" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M84.5591 16.8285L84.5958 16.2881C84.6008 16.0684 84.6503 15.852 84.7413 15.652C84.8323 15.452 84.9629 15.2725 85.1252 15.1243C85.2874 14.9762 85.478 14.8624 85.6855 14.79C85.8929 14.7175 86.1129 14.6878 86.3321 14.7027C86.5513 14.7176 86.7653 14.7767 86.961 14.8766C87.1568 14.9764 87.3302 15.1149 87.471 15.2836C87.6118 15.4523 87.7169 15.6478 87.78 15.8583C87.8431 16.0688 87.863 16.2898 87.8383 16.5082L87.8016 17.0486" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M85.7081 23.8938C83.9247 23.7727 82.5646 22.2145 82.6857 20.4312L82.7958 18.8099C82.8347 18.2366 83.0997 17.7022 83.5327 17.3243C83.9656 16.9464 84.5309 16.756 85.1042 16.7949L87.2659 16.9417C87.8392 16.9806 88.3736 17.2457 88.7515 17.6786C89.1293 18.1115 89.3197 18.6768 89.2808 19.2501L89.1708 20.8714C89.0497 22.6548 87.4915 24.0149 85.7081 23.8938Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M85.7081 23.8938L86.0383 19.03" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M83.1556 17.7485C82.1199 17.5696 81.3176 16.5922 81.3947 15.4573" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M82.7224 19.8907L80.5607 19.744" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M80.8076 24.104C80.8846 22.9691 81.8694 22.0587 83.008 22.0817" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M91.106 16.1166C91.0289 17.2515 90.1019 18.1115 89.0677 18.1499" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M91.3691 20.4777L89.2074 20.331" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M88.6283 22.4633C89.7595 22.5944 90.6122 23.6295 90.5351 24.7644" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip17_532_2318)">
|
||||
<path d="M109.149 15.7127L110.096 16.7977" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M112.388 16.9533L113.473 16.0062" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M109.502 18.5218L109.538 17.9814C109.543 17.7617 109.593 17.5453 109.684 17.3453C109.775 17.1453 109.905 16.9658 110.068 16.8176C110.23 16.6695 110.421 16.5557 110.628 16.4833C110.835 16.4108 111.055 16.3811 111.275 16.396C111.494 16.4109 111.708 16.47 111.904 16.5699C112.099 16.6697 112.273 16.8082 112.414 16.9769C112.554 17.1456 112.659 17.3411 112.723 17.5516C112.786 17.7621 112.805 17.9831 112.781 18.2015L112.744 18.7419" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M110.651 25.5871C108.867 25.466 107.507 23.9078 107.628 22.1245L107.738 20.5032C107.777 19.9299 108.042 19.3955 108.475 19.0176C108.908 18.6397 109.473 18.4493 110.047 18.4882L112.208 18.635C112.782 18.6739 113.316 18.939 113.694 19.3719C114.072 19.8048 114.262 20.3701 114.223 20.9434L114.113 22.5647C113.992 24.3481 112.434 25.7082 110.651 25.5871Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M110.651 25.5871L110.981 20.7233" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M108.098 19.4418C107.062 19.2629 106.26 18.2855 106.337 17.1506" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M107.665 21.584L105.503 21.4373" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M105.75 25.7973C105.827 24.6624 106.812 23.752 107.95 23.775" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M116.048 17.8099C115.971 18.9448 115.044 19.8048 114.01 19.8431" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M116.312 22.171L114.15 22.0243" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M113.571 24.1566C114.702 24.2877 115.555 25.3228 115.478 26.4577" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip18_532_2318)">
|
||||
<path d="M134.092 17.4061L135.039 18.491" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M137.33 18.6466L138.415 17.6996" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M134.444 20.2151L134.481 19.6747C134.486 19.455 134.535 19.2387 134.626 19.0387C134.717 18.8387 134.848 18.6592 135.01 18.511C135.173 18.3628 135.363 18.2491 135.571 18.1766C135.778 18.1042 135.998 18.0745 136.217 18.0894C136.436 18.1042 136.65 18.1634 136.846 18.2632C137.042 18.3631 137.215 18.5015 137.356 18.6703C137.497 18.839 137.602 19.0345 137.665 19.245C137.728 19.4554 137.748 19.6765 137.723 19.8948L137.687 20.4353" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M135.593 27.2805C133.81 27.1594 132.45 25.6012 132.571 23.8178L132.681 22.1965C132.72 21.6232 132.985 21.0889 133.418 20.711C133.851 20.3331 134.416 20.1427 134.989 20.1816L137.151 20.3284C137.724 20.3673 138.259 20.6324 138.637 21.0653C139.014 21.4982 139.205 22.0635 139.166 22.6368L139.056 24.2581C138.935 26.0415 137.377 27.4015 135.593 27.2805Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M135.593 27.2804L135.923 22.4166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M133.041 21.1351C132.005 20.9562 131.203 19.9788 131.28 18.8439" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M132.607 23.2774L130.446 23.1306" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M130.693 27.4907C130.77 26.3558 131.755 25.4454 132.893 25.4684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M140.991 19.5032C140.914 20.6381 139.987 21.4981 138.953 21.5365" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M141.254 23.8644L139.093 23.7177" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M138.513 25.85C139.645 25.9811 140.497 27.0162 140.42 28.1511" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip19_532_2318)">
|
||||
<path d="M159.035 19.0994L159.982 20.1843" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M162.273 20.3399L163.358 19.3929" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M159.387 21.9084L159.424 21.368C159.429 21.1483 159.478 20.932 159.569 20.732C159.66 20.532 159.791 20.3525 159.953 20.2043C160.115 20.0561 160.306 19.9424 160.513 19.8699C160.721 19.7975 160.941 19.7678 161.16 19.7827C161.379 19.7975 161.593 19.8567 161.789 19.9565C161.985 20.0564 162.158 20.1948 162.299 20.3636C162.44 20.5323 162.545 20.7278 162.608 20.9383C162.671 21.1487 162.691 21.3698 162.666 21.5881L162.629 22.1286" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M160.536 28.9738C158.752 28.8527 157.392 27.2945 157.513 25.5111L157.624 23.8898C157.662 23.3165 157.928 22.7822 158.36 22.4043C158.793 22.0264 159.359 21.836 159.932 21.8749L162.094 22.0217C162.667 22.0606 163.201 22.3257 163.579 22.7586C163.957 23.1915 164.148 23.7568 164.109 24.3301L163.999 25.9514C163.877 27.7348 162.319 29.0948 160.536 28.9738Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M160.536 28.9737L160.866 24.1099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M157.983 22.8284C156.948 22.6495 156.145 21.6721 156.222 20.5372" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M157.55 24.9707L155.388 24.8239" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M155.635 29.184C155.712 28.0491 156.697 27.1387 157.836 27.1617" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M165.934 21.1965C165.857 22.3314 164.93 23.1914 163.896 23.2298" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M166.197 25.5577L164.035 25.4109" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M163.456 27.5433C164.587 27.6744 165.44 28.7095 165.363 29.8444" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip20_532_2318)">
|
||||
<path d="M183.977 20.7927L184.924 21.8776" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M187.216 22.0332L188.3 21.0862" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M184.329 23.6017L184.366 23.0613C184.371 22.8416 184.421 22.6253 184.512 22.4253C184.603 22.2253 184.733 22.0458 184.895 21.8976C185.058 21.7494 185.248 21.6357 185.456 21.5632C185.663 21.4908 185.883 21.4611 186.102 21.476C186.322 21.4908 186.536 21.55 186.731 21.6498C186.927 21.7497 187.101 21.8881 187.241 22.0569C187.382 22.2256 187.487 22.4211 187.55 22.6315C187.613 22.842 187.633 23.0631 187.609 23.2814L187.572 23.8219" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M185.478 30.6671C183.695 30.546 182.335 28.9878 182.456 27.2044L182.566 25.5831C182.605 25.0098 182.87 24.4754 183.303 24.0976C183.736 23.7197 184.301 23.5293 184.875 23.5682L187.036 23.715C187.61 23.7539 188.144 24.019 188.522 24.4519C188.9 24.8848 189.09 25.4501 189.051 26.0234L188.941 27.6447C188.82 29.4281 187.262 30.7881 185.478 30.6671Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M185.478 30.667L185.809 25.8032" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M182.926 24.5217C181.89 24.3428 181.088 23.3654 181.165 22.2305" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M182.493 26.664L180.331 26.5172" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M180.578 30.8773C180.655 29.7424 181.64 28.832 182.778 28.855" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M190.876 22.8898C190.799 24.0247 189.872 24.8847 188.838 24.9231" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M191.139 27.251L188.978 27.1042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M188.399 29.2366C189.53 29.3677 190.383 30.4028 190.306 31.5377" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip21_532_2318)">
|
||||
<path d="M208.92 22.486L209.867 23.5709" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M212.158 23.7265L213.243 22.7795" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M209.272 25.295L209.309 24.7546C209.314 24.5349 209.363 24.3186 209.454 24.1186C209.545 23.9186 209.676 23.7391 209.838 23.5909C210 23.4427 210.191 23.329 210.398 23.2565C210.606 23.1841 210.826 23.1544 211.045 23.1693C211.264 23.1841 211.478 23.2433 211.674 23.3431C211.87 23.443 212.043 23.5814 212.184 23.7502C212.325 23.9189 212.43 24.1144 212.493 24.3248C212.556 24.5353 212.576 24.7564 212.551 24.9747L212.514 25.5152" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M210.421 32.3604C208.638 32.2393 207.278 30.6811 207.399 28.8977L207.509 27.2764C207.548 26.7031 207.813 26.1687 208.246 25.7909C208.678 25.413 209.244 25.2226 209.817 25.2615L211.979 25.4083C212.552 25.4472 213.086 25.7123 213.464 26.1452C213.842 26.5781 214.033 27.1434 213.994 27.7167L213.884 29.338C213.763 31.1214 212.204 32.4814 210.421 32.3604Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M210.421 32.3603L210.751 27.4965" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M207.868 26.215C206.833 26.0361 206.03 25.0587 206.107 23.9238" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M207.435 28.3573L205.274 28.2105" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M205.52 32.5706C205.598 31.4357 206.582 30.5253 207.721 30.5483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M215.819 24.5831C215.742 25.718 214.815 26.578 213.781 26.6164" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M216.082 28.9443L213.92 28.7975" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M213.341 30.9299C214.472 31.061 215.325 32.0961 215.248 33.231" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip22_532_2318)">
|
||||
<path d="M233.862 24.1793L234.809 25.2642" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M237.101 25.4198L238.186 24.4728" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M234.215 26.9883L234.251 26.4479C234.256 26.2282 234.306 26.0119 234.397 25.8119C234.488 25.6118 234.618 25.4324 234.781 25.2842C234.943 25.136 235.134 25.0223 235.341 24.9498C235.548 24.8774 235.768 24.8477 235.988 24.8626C236.207 24.8774 236.421 24.9366 236.616 25.0364C236.812 25.1363 236.986 25.2747 237.126 25.4435C237.267 25.6122 237.372 25.8077 237.435 26.0181C237.499 26.2286 237.518 26.4497 237.494 26.668L237.457 27.2085" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M235.364 34.0537C233.58 33.9326 232.22 32.3744 232.341 30.591L232.451 28.9697C232.49 28.3964 232.755 27.862 233.188 27.4842C233.621 27.1063 234.186 26.9159 234.76 26.9548L236.921 27.1016C237.495 27.1405 238.029 27.4055 238.407 27.8385C238.785 28.2714 238.975 28.8367 238.936 29.41L238.826 31.0313C238.705 32.8147 237.147 34.1747 235.364 34.0537Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M235.364 34.0536L235.694 29.1898" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M232.811 27.9083C231.775 27.7294 230.973 26.752 231.05 25.6171" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M232.378 30.0506L230.216 29.9038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M230.463 34.2639C230.54 33.129 231.525 32.2186 232.663 32.2416" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M240.762 26.2764C240.684 27.4113 239.757 28.2713 238.723 28.3097" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M241.025 30.6376L238.863 30.4908" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M238.284 32.6232C239.415 32.7543 240.268 33.7894 240.191 34.9243" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip23_532_2318)">
|
||||
<path d="M258.805 25.8726L259.752 26.9576" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M262.043 27.1132L263.128 26.1661" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M259.157 28.6817L259.194 28.1413C259.199 27.9216 259.248 27.7052 259.339 27.5052C259.43 27.3052 259.561 27.1257 259.723 26.9776C259.885 26.8294 260.076 26.7157 260.284 26.6432C260.491 26.5707 260.711 26.541 260.93 26.5559C261.149 26.5708 261.363 26.6299 261.559 26.7298C261.755 26.8296 261.928 26.9681 262.069 27.1368C262.21 27.3055 262.315 27.501 262.378 27.7115C262.441 27.922 262.461 28.1431 262.436 28.3614L262.4 28.9018" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M260.306 35.747C258.523 35.626 257.163 34.0678 257.284 32.2844L257.394 30.6631C257.433 30.0898 257.698 29.5554 258.131 29.1775C258.564 28.7997 259.129 28.6092 259.702 28.6482L261.864 28.7949C262.437 28.8338 262.972 29.0989 263.35 29.5318C263.727 29.9647 263.918 30.53 263.879 31.1034L263.769 32.7246C263.648 34.508 262.09 35.8681 260.306 35.747Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M260.306 35.747L260.636 30.8832" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M257.754 29.6017C256.718 29.4228 255.916 28.4454 255.993 27.3105" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M257.32 31.7439L255.159 31.5972" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M255.406 35.9572C255.483 34.8223 256.467 33.9119 257.606 33.9349" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M265.704 27.9698C265.627 29.1047 264.7 29.9647 263.666 30.0031" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M265.967 32.331L263.806 32.1842" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M263.226 34.3165C264.358 34.4476 265.21 35.4827 265.133 36.6176" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip24_532_2318)">
|
||||
<path d="M283.747 27.5659L284.694 28.6509" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M286.986 28.8065L288.071 27.8594" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M284.1 30.375L284.136 29.8346C284.141 29.6149 284.191 29.3985 284.282 29.1985C284.373 28.9985 284.503 28.819 284.666 28.6709C284.828 28.5227 285.019 28.409 285.226 28.3365C285.434 28.264 285.653 28.2343 285.873 28.2492C286.092 28.2641 286.306 28.3232 286.502 28.4231C286.697 28.5229 286.871 28.6614 287.012 28.8301C287.152 28.9988 287.257 29.1943 287.321 29.4048C287.384 29.6153 287.404 29.8363 287.379 30.0547L287.342 30.5951" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M285.249 37.4403C283.465 37.3193 282.105 35.7611 282.226 33.9777L282.336 32.3564C282.375 31.7831 282.64 31.2487 283.073 30.8708C283.506 30.493 284.072 30.3025 284.645 30.3415L286.807 30.4882C287.38 30.5271 287.914 30.7922 288.292 31.2251C288.67 31.658 288.86 32.2233 288.821 32.7967L288.711 34.4179C288.59 36.2013 287.032 37.5614 285.249 37.4403Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M285.249 37.4403L285.579 32.5765" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M282.696 31.295C281.661 31.1161 280.858 30.1387 280.935 29.0038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M282.263 33.4372L280.101 33.2905" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M280.348 37.6505C280.425 36.5156 281.41 35.6052 282.549 35.6282" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M290.647 29.6631C290.57 30.798 289.643 31.658 288.608 31.6964" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M290.91 34.0243L288.748 33.8775" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M288.169 36.0098C289.3 36.1409 290.153 37.176 290.076 38.3109" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip25_532_2318)">
|
||||
<path d="M308.69 29.2592L309.637 30.3442" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M311.928 30.4998L313.013 29.5527" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M309.042 32.0683L309.079 31.5279C309.084 31.3082 309.134 31.0918 309.225 30.8918C309.316 30.6918 309.446 30.5123 309.608 30.3642C309.771 30.216 309.961 30.1023 310.169 30.0298C310.376 29.9573 310.596 29.9276 310.815 29.9425C311.035 29.9574 311.248 30.0165 311.444 30.1164C311.64 30.2162 311.813 30.3547 311.954 30.5234C312.095 30.6921 312.2 30.8876 312.263 31.0981C312.326 31.3086 312.346 31.5296 312.322 31.748L312.285 32.2884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M310.191 39.1336C308.408 39.0126 307.048 37.4544 307.169 35.671L307.279 34.0497C307.318 33.4764 307.583 32.942 308.016 32.5641C308.449 32.1863 309.014 31.9958 309.587 32.0348L311.749 32.1815C312.322 32.2204 312.857 32.4855 313.235 32.9184C313.613 33.3513 313.803 33.9166 313.764 34.49L313.654 36.1112C313.533 37.8946 311.975 39.2547 310.191 39.1336Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M310.191 39.1336L310.522 34.2698" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M307.639 32.9883C306.603 32.8094 305.801 31.832 305.878 30.6971" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M307.206 35.1305L305.044 34.9838" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M305.291 39.3438C305.368 38.2089 306.353 37.2985 307.491 37.3215" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M315.589 31.3564C315.512 32.4913 314.585 33.3513 313.551 33.3897" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M315.852 35.7176L313.691 35.5708" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M313.112 37.7031C314.243 37.8342 315.096 38.8693 315.018 40.0042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip26_532_2318)">
|
||||
<path d="M333.633 30.9525L334.58 32.0375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M336.871 32.1931L337.956 31.246" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M333.985 33.7616L334.022 33.2212C334.027 33.0015 334.076 32.7851 334.167 32.5851C334.258 32.3851 334.389 32.2056 334.551 32.0574C334.713 31.9093 334.904 31.7955 335.111 31.7231C335.319 31.6506 335.539 31.6209 335.758 31.6358C335.977 31.6507 336.191 31.7098 336.387 31.8097C336.583 31.9095 336.756 32.048 336.897 32.2167C337.038 32.3854 337.143 32.5809 337.206 32.7914C337.269 33.0019 337.289 33.2229 337.264 33.4413L337.227 33.9817" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M335.134 40.8269C333.351 40.7059 331.99 39.1477 332.112 37.3643L332.222 35.743C332.261 35.1697 332.526 34.6353 332.959 34.2574C333.391 33.8796 333.957 33.6891 334.53 33.7281L336.692 33.8748C337.265 33.9137 337.799 34.1788 338.177 34.6117C338.555 35.0446 338.746 35.6099 338.707 36.1833L338.597 37.8045C338.476 39.5879 336.917 40.948 335.134 40.8269Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M335.134 40.8269L335.464 35.9631" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M332.581 34.6816C331.546 34.5027 330.743 33.5253 330.82 32.3904" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M332.148 36.8238L329.987 36.6771" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M330.233 41.0371C330.31 39.9022 331.295 38.9918 332.434 39.0148" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M340.532 33.0497C340.455 34.1846 339.528 35.0446 338.494 35.083" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M340.795 37.4109L338.633 37.2641" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M338.054 39.3964C339.185 39.5275 340.038 40.5626 339.961 41.6975" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip27_532_2318)">
|
||||
<path d="M358.575 32.6458L359.522 33.7308" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M361.814 33.8864L362.899 32.9393" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M358.927 35.4549L358.964 34.9145C358.969 34.6948 359.019 34.4784 359.11 34.2784C359.201 34.0784 359.331 33.8989 359.494 33.7507C359.656 33.6026 359.846 33.4888 360.054 33.4164C360.261 33.3439 360.481 33.3142 360.7 33.3291C360.92 33.344 361.134 33.4031 361.329 33.503C361.525 33.6028 361.699 33.7413 361.839 33.91C361.98 34.0787 362.085 34.2742 362.148 34.4847C362.211 34.6952 362.231 34.9162 362.207 35.1346L362.17 35.675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M360.077 42.5202C358.293 42.3992 356.933 40.841 357.054 39.0576L357.164 37.4363C357.203 36.863 357.468 36.3286 357.901 35.9507C358.334 35.5729 358.899 35.3824 359.473 35.4214L361.634 35.5681C362.208 35.607 362.742 35.8721 363.12 36.305C363.498 36.7379 363.688 37.3032 363.649 37.8765L363.539 39.4978C363.418 41.2812 361.86 42.6413 360.077 42.5202Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M360.076 42.5202L360.407 37.6564" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M357.524 36.3749C356.488 36.196 355.686 35.2186 355.763 34.0837" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M357.091 38.5171L354.929 38.3704" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M355.176 42.7304C355.253 41.5955 356.238 40.6851 357.376 40.7081" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M365.474 34.743C365.397 35.8779 364.47 36.7379 363.436 36.7763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M365.738 39.1042L363.576 38.9574" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M362.997 41.0897C364.128 41.2208 364.981 42.2559 364.904 43.3908" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip28_532_2318)">
|
||||
<path d="M383.518 34.3392L384.465 35.4241" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M386.756 35.5797L387.841 34.6327" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M383.87 37.1482L383.907 36.6078C383.912 36.3881 383.961 36.1718 384.052 35.9718C384.143 35.7718 384.274 35.5923 384.436 35.4441C384.598 35.296 384.789 35.1822 384.996 35.1097C385.204 35.0373 385.424 35.0076 385.643 35.0225C385.862 35.0374 386.076 35.0965 386.272 35.1963C386.468 35.2962 386.641 35.4346 386.782 35.6034C386.923 35.7721 387.028 35.9676 387.091 36.1781C387.154 36.3885 387.174 36.6096 387.149 36.8279L387.113 37.3684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M385.019 44.2136C383.236 44.0925 381.876 42.5343 381.997 40.7509L382.107 39.1296C382.146 38.5563 382.411 38.022 382.844 37.6441C383.277 37.2662 383.842 37.0758 384.415 37.1147L386.577 37.2615C387.15 37.3004 387.685 37.5655 388.062 37.9984C388.44 38.4313 388.631 38.9966 388.592 39.5699L388.482 41.1912C388.361 42.9746 386.803 44.3347 385.019 44.2136Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M385.019 44.2135L385.349 39.3497" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M382.467 38.0682C381.431 37.8893 380.629 36.9119 380.706 35.777" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M382.033 40.2105L379.872 40.0637" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M380.119 44.4238C380.196 43.2889 381.18 42.3785 382.319 42.4015" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M390.417 36.4363C390.34 37.5712 389.413 38.4312 388.379 38.4696" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M390.68 40.7975L388.518 40.6508" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M387.939 42.7831C389.071 42.9142 389.923 43.9493 389.846 45.0842" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip29_532_2318)">
|
||||
<path d="M57.8418 33.2779L58.7888 34.3629" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M61.0802 34.5184L62.1652 33.5714" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M58.1941 36.087L58.2308 35.5465C58.2358 35.3269 58.2854 35.1105 58.3763 34.9105C58.4673 34.7105 58.5979 34.531 58.7602 34.3828C58.9225 34.2347 59.1131 34.1209 59.3205 34.0485C59.5279 33.976 59.7479 33.9463 59.9671 33.9612C60.1864 33.9761 60.4003 34.0352 60.596 34.1351C60.7918 34.2349 60.9653 34.3734 61.106 34.5421C61.2468 34.7108 61.3519 34.9063 61.415 35.1168C61.4781 35.3272 61.498 35.5483 61.4733 35.7667L61.4366 36.3071" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M59.3431 43.1523C57.5597 43.0312 56.1996 41.473 56.3207 39.6896L56.4308 38.0684C56.4697 37.4951 56.7348 36.9607 57.1677 36.5828C57.6006 36.2049 58.1659 36.0145 58.7392 36.0534L60.9009 36.2002C61.4742 36.2391 62.0086 36.5042 62.3865 36.9371C62.7643 37.37 62.9548 37.9353 62.9158 38.5086L62.8058 40.1299C62.6847 41.9133 61.1265 43.2734 59.3431 43.1523Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M59.3431 43.1523L59.6733 38.2885" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M56.7906 37.007C55.7549 36.8281 54.9526 35.8506 55.0297 34.7158" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M56.3574 39.1492L54.1957 39.0024" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M54.4426 43.3625C54.5196 42.2276 55.5044 41.3172 56.643 41.3402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M64.741 35.3751C64.6639 36.5099 63.7369 37.37 62.7027 37.4083" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M65.0041 39.7362L62.8424 39.5895" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M62.2633 41.7218C63.3945 41.8529 64.2472 42.888 64.1702 44.0229" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip30_532_2318)">
|
||||
<path d="M82.7844 34.9712L83.7314 36.0562" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M86.0228 36.2117L87.1078 35.2647" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M83.1367 37.7803L83.1734 37.2398C83.1785 37.0202 83.228 36.8038 83.319 36.6038C83.41 36.4038 83.5405 36.2243 83.7028 36.0761C83.8651 35.928 84.0557 35.8142 84.2631 35.7418C84.4706 35.6693 84.6905 35.6396 84.9098 35.6545C85.129 35.6694 85.3429 35.7285 85.5387 35.8284C85.7344 35.9282 85.9079 36.0667 86.0486 36.2354C86.1894 36.4041 86.2945 36.5996 86.3576 36.8101C86.4208 37.0205 86.4406 37.2416 86.4159 37.46L86.3792 38.0004" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M84.2857 44.8456C82.5023 44.7245 81.1423 43.1663 81.2633 41.3829L81.3734 39.7617C81.4123 39.1884 81.6774 38.654 82.1103 38.2761C82.5432 37.8982 83.1085 37.7078 83.6818 37.7467L85.8435 37.8935C86.4168 37.9324 86.9512 38.1975 87.3291 38.6304C87.707 39.0633 87.8974 39.6286 87.8585 40.2019L87.7484 41.8232C87.6273 43.6066 86.0691 44.9667 84.2857 44.8456Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M84.2858 44.8456L84.616 39.9818" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M81.7332 38.7003C80.6976 38.5214 79.8952 37.5439 79.9723 36.4091" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M81.3 40.8425L79.1383 40.6957" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M79.3852 45.0558C79.4623 43.9209 80.447 43.0105 81.5856 43.0335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M89.6836 37.0684C89.6065 38.2032 88.6795 39.0633 87.6454 39.1016" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M89.9467 41.4295L87.785 41.2828" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M87.2059 43.4151C88.3372 43.5462 89.1898 44.5813 89.1128 45.7162" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip31_532_2318)">
|
||||
<path d="M107.727 36.6645L108.674 37.7495" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M110.965 37.905L112.05 36.958" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M108.079 39.4736L108.116 38.9331C108.121 38.7135 108.17 38.4971 108.261 38.2971C108.352 38.0971 108.483 37.9176 108.645 37.7694C108.808 37.6213 108.998 37.5075 109.206 37.4351C109.413 37.3626 109.633 37.3329 109.852 37.3478C110.071 37.3627 110.285 37.4218 110.481 37.5217C110.677 37.6215 110.85 37.76 110.991 37.9287C111.132 38.0974 111.237 38.2929 111.3 38.5034C111.363 38.7138 111.383 38.9349 111.358 39.1533L111.322 39.6937" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M109.228 46.5389C107.445 46.4178 106.085 44.8596 106.206 43.0762L106.316 41.455C106.355 40.8816 106.62 40.3473 107.053 39.9694C107.486 39.5915 108.051 39.4011 108.624 39.44L110.786 39.5868C111.359 39.6257 111.894 39.8908 112.272 40.3237C112.649 40.7566 112.84 41.3219 112.801 41.8952L112.691 43.5165C112.57 45.2999 111.012 46.66 109.228 46.5389Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M109.228 46.5389L109.558 41.675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M106.676 40.3936C105.64 40.2147 104.838 39.2372 104.915 38.1024" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M106.243 42.5358L104.081 42.389" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M104.328 46.7491C104.405 45.6142 105.39 44.7038 106.528 44.7268" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M114.626 38.7616C114.549 39.8965 113.622 40.7566 112.588 40.7949" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M114.889 43.1228L112.728 42.9761" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M112.148 45.1084C113.28 45.2395 114.132 46.2746 114.055 47.4095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip32_532_2318)">
|
||||
<path d="M132.67 38.3578L133.617 39.4428" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M135.908 39.5984L136.993 38.6514" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M133.022 41.1669L133.059 40.6265C133.064 40.4068 133.113 40.1905 133.204 39.9904C133.295 39.7904 133.426 39.6109 133.588 39.4628C133.75 39.3146 133.941 39.2009 134.148 39.1284C134.356 39.0559 134.576 39.0263 134.795 39.0411C135.014 39.056 135.228 39.1152 135.424 39.215C135.62 39.3148 135.793 39.4533 135.934 39.622C136.075 39.7908 136.18 39.9863 136.243 40.1967C136.306 40.4072 136.326 40.6283 136.301 40.8466L136.264 41.387" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M134.171 48.2323C132.387 48.1112 131.027 46.553 131.148 44.7696L131.259 43.1483C131.297 42.575 131.563 42.0406 131.995 41.6628C132.428 41.2849 132.994 41.0945 133.567 41.1334L135.729 41.2801C136.302 41.3191 136.836 41.5841 137.214 42.0171C137.592 42.45 137.783 43.0153 137.744 43.5886L137.634 45.2099C137.512 46.9932 135.954 48.3533 134.171 48.2323Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M134.171 48.2322L134.501 43.3684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M131.618 42.0869C130.583 41.908 129.78 40.9306 129.857 39.7957" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M131.185 44.2292L129.023 44.0824" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M129.27 48.4425C129.347 47.3076 130.332 46.3972 131.471 46.4202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M139.569 40.455C139.492 41.5899 138.565 42.4499 137.53 42.4883" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M139.832 44.8162L137.67 44.6694" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M137.091 46.8018C138.222 46.9328 139.075 47.968 138.998 49.1029" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip33_532_2318)">
|
||||
<path d="M157.612 40.0511L158.559 41.1361" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M160.851 41.2917L161.936 40.3447" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M157.964 42.8602L158.001 42.3198C158.006 42.1001 158.056 41.8837 158.147 41.6837C158.238 41.4837 158.368 41.3042 158.531 41.1561C158.693 41.0079 158.883 40.8942 159.091 40.8217C159.298 40.7492 159.518 40.7196 159.738 40.7344C159.957 40.7493 160.171 40.8085 160.366 40.9083C160.562 41.0081 160.736 41.1466 160.876 41.3153C161.017 41.4841 161.122 41.6796 161.185 41.89C161.249 42.1005 161.268 42.3216 161.244 42.5399L161.207 43.0803" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M159.113 49.9256C157.33 49.8045 155.97 48.2463 156.091 46.4629L156.201 44.8416C156.24 44.2683 156.505 43.7339 156.938 43.3561C157.371 42.9782 157.936 42.7878 158.51 42.8267L160.671 42.9734C161.245 43.0124 161.779 43.2774 162.157 43.7104C162.535 44.1433 162.725 44.7086 162.686 45.2819L162.576 46.9032C162.455 48.6865 160.897 50.0466 159.113 49.9256Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M159.114 49.9255L159.444 45.0617" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M156.561 43.7802C155.525 43.6013 154.723 42.6239 154.8 41.489" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M156.128 45.9224L153.966 45.7757" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M154.213 50.1358C154.29 49.0009 155.275 48.0905 156.413 48.1135" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M164.511 42.1483C164.434 43.2832 163.507 44.1432 162.473 44.1816" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M164.775 46.5095L162.613 46.3627" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M162.034 48.4951C163.165 48.6261 164.018 49.6613 163.941 50.7962" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip34_532_2318)">
|
||||
<path d="M182.555 41.7444L183.502 42.8294" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M185.793 42.985L186.878 42.038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M182.907 44.5535L182.944 44.0131C182.949 43.7934 182.998 43.577 183.089 43.377C183.18 43.177 183.311 42.9975 183.473 42.8494C183.635 42.7012 183.826 42.5875 184.033 42.515C184.241 42.4425 184.461 42.4129 184.68 42.4277C184.899 42.4426 185.113 42.5018 185.309 42.6016C185.505 42.7014 185.678 42.8399 185.819 43.0086C185.96 43.1774 186.065 43.3729 186.128 43.5833C186.191 43.7938 186.211 44.0149 186.186 44.2332L186.149 44.7736" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M184.056 51.6189C182.273 51.4978 180.913 49.9396 181.034 48.1562L181.144 46.5349C181.183 45.9616 181.448 45.4272 181.881 45.0494C182.314 44.6715 182.879 44.4811 183.452 44.52L185.614 44.6667C186.187 44.7057 186.722 44.9707 187.099 45.4037C187.477 45.8366 187.668 46.4019 187.629 46.9752L187.519 48.5964C187.398 50.3798 185.839 51.7399 184.056 51.6189Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M184.056 51.6188L184.386 46.755" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M181.503 45.4735C180.468 45.2946 179.666 44.3172 179.743 43.1823" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M181.07 47.6157L178.909 47.469" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M179.156 51.829C179.233 50.6942 180.217 49.7838 181.356 49.8068" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M189.454 43.8416C189.377 44.9765 188.45 45.8365 187.416 45.8749" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M189.717 48.2028L187.555 48.056" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M186.976 50.1884C188.108 50.3194 188.96 51.3546 188.883 52.4895" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip35_532_2318)">
|
||||
<path d="M207.497 43.4377L208.444 44.5227" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M210.736 44.6783L211.821 43.7313" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M207.85 46.2468L207.886 45.7064C207.891 45.4867 207.941 45.2703 208.032 45.0703C208.123 44.8703 208.253 44.6908 208.416 44.5427C208.578 44.3945 208.769 44.2808 208.976 44.2083C209.183 44.1358 209.403 44.1062 209.623 44.121C209.842 44.1359 210.056 44.1951 210.251 44.2949C210.447 44.3947 210.621 44.5332 210.761 44.7019C210.902 44.8707 211.007 45.0662 211.07 45.2766C211.134 45.4871 211.153 45.7082 211.129 45.9265L211.092 46.4669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M208.999 53.3122C207.215 53.1911 205.855 51.6329 205.976 49.8495L206.086 48.2282C206.125 47.6549 206.39 47.1205 206.823 46.7427C207.256 46.3648 207.821 46.1744 208.395 46.2133L210.556 46.36C211.13 46.399 211.664 46.664 212.042 47.097C212.42 47.5299 212.61 48.0952 212.571 48.6685L212.461 50.2897C212.34 52.0731 210.782 53.4332 208.999 53.3122Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M208.999 53.3121L209.329 48.4483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206.446 47.1668C205.41 46.9879 204.608 46.0105 204.685 44.8756" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206.013 49.309L203.851 49.1623" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M204.098 53.5223C204.175 52.3875 205.16 51.4771 206.298 51.5001" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M214.397 45.5349C214.32 46.6698 213.392 47.5298 212.358 47.5682" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M214.66 49.8961L212.498 49.7493" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M211.919 51.8817C213.05 52.0127 213.903 53.0479 213.826 54.1828" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip36_532_2318)">
|
||||
<path d="M232.44 45.131L233.387 46.216" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M235.678 46.3716L236.763 45.4246" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M232.792 47.9401L232.829 47.3997C232.834 47.18 232.883 46.9636 232.974 46.7636C233.065 46.5636 233.196 46.3841 233.358 46.236C233.521 46.0878 233.711 45.9741 233.919 45.9016C234.126 45.8291 234.346 45.7995 234.565 45.8143C234.784 45.8292 234.998 45.8884 235.194 45.9882C235.39 46.088 235.563 46.2265 235.704 46.3952C235.845 46.564 235.95 46.7595 236.013 46.9699C236.076 47.1804 236.096 47.4015 236.071 47.6198L236.035 48.1602" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M233.941 55.0054C232.158 54.8844 230.798 53.3262 230.919 51.5428L231.029 49.9215C231.068 49.3482 231.333 48.8138 231.766 48.4359C232.199 48.058 232.764 47.8676 233.337 47.9065L235.499 48.0533C236.072 48.0922 236.607 48.3573 236.985 48.7902C237.362 49.2231 237.553 49.7884 237.514 50.3617L237.404 51.983C237.283 53.7664 235.725 55.1265 233.941 55.0054Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M233.941 55.0054L234.271 50.1416" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M231.389 48.8601C230.353 48.6812 229.551 47.7038 229.628 46.5689" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M230.956 51.0023L228.794 50.8556" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M229.041 55.2156C229.118 54.0808 230.103 53.1704 231.241 53.1934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M239.339 47.2282C239.262 48.3631 238.335 49.2231 237.301 49.2615" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M239.602 51.5894L237.441 51.4426" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M236.862 53.575C237.993 53.706 238.845 54.7412 238.768 55.8761" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip37_532_2318)">
|
||||
<path d="M257.383 46.8244L258.33 47.9094" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M260.621 48.0649L261.706 47.1179" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M257.735 49.6335L257.771 49.093C257.776 48.8734 257.826 48.657 257.917 48.457C258.008 48.257 258.139 48.0775 258.301 47.9293C258.463 47.7812 258.654 47.6674 258.861 47.595C259.069 47.5225 259.289 47.4928 259.508 47.5077C259.727 47.5226 259.941 47.5817 260.137 47.6816C260.332 47.7814 260.506 47.9199 260.647 48.0886C260.787 48.2573 260.893 48.4528 260.956 48.6633C261.019 48.8738 261.039 49.0948 261.014 49.3132L260.977 49.8536" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M258.884 56.6988C257.1 56.5777 255.74 55.0195 255.861 53.2361L255.971 51.6148C256.01 51.0415 256.275 50.5072 256.708 50.1293C257.141 49.7514 257.707 49.561 258.28 49.5999L260.442 49.7467C261.015 49.7856 261.549 50.0507 261.927 50.4836C262.305 50.9165 262.495 51.4818 262.457 52.0551L262.346 53.6764C262.225 55.4598 260.667 56.8199 258.884 56.6988Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M258.884 56.6988L259.214 51.835" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M256.331 50.5534C255.296 50.3745 254.493 49.3971 254.57 48.2622" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M255.898 52.6957L253.736 52.549" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M253.983 56.909C254.06 55.7741 255.045 54.8637 256.184 54.8867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M264.282 48.9215C264.205 50.0564 263.278 50.9164 262.244 50.9548" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M264.545 53.2827L262.383 53.136" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M261.804 55.2683C262.935 55.3994 263.788 56.4345 263.711 57.5694" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip38_532_2318)">
|
||||
<path d="M282.325 48.5177L283.272 49.6027" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M285.563 49.7582L286.648 48.8112" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M282.677 51.3268L282.714 50.7863C282.719 50.5667 282.769 50.3503 282.86 50.1503C282.951 49.9503 283.081 49.7708 283.243 49.6226C283.406 49.4745 283.596 49.3607 283.804 49.2883C284.011 49.2158 284.231 49.1861 284.45 49.201C284.67 49.2159 284.884 49.275 285.079 49.3749C285.275 49.4747 285.448 49.6132 285.589 49.7819C285.73 49.9506 285.835 50.1461 285.898 50.3566C285.961 50.5671 285.981 50.7881 285.957 51.0065L285.92 51.5469" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M283.826 58.3921C282.043 58.271 280.683 56.7128 280.804 54.9294L280.914 53.3081C280.953 52.7348 281.218 52.2005 281.651 51.8226C282.084 51.4447 282.649 51.2543 283.222 51.2932L285.384 51.44C285.957 51.4789 286.492 51.744 286.87 52.1769C287.248 52.6098 287.438 53.1751 287.399 53.7484L287.289 55.3697C287.168 57.1531 285.61 58.5132 283.826 58.3921Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M283.826 58.3921L284.157 53.5283" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M281.274 52.2467C280.238 52.0678 279.436 51.0904 279.513 49.9555" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M280.841 54.389L278.679 54.2422" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M278.926 58.6023C279.003 57.4674 279.988 56.557 281.126 56.58" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M289.224 50.6148C289.147 51.7497 288.22 52.6097 287.186 52.6481" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M289.487 54.976L287.326 54.8293" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M286.747 56.9616C287.878 57.0927 288.731 58.1278 288.653 59.2627" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip39_532_2318)">
|
||||
<path d="M307.268 50.211L308.215 51.296" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M310.506 51.4515L311.591 50.5045" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M307.62 53.0201L307.657 52.4796C307.662 52.26 307.711 52.0436 307.802 51.8436C307.893 51.6436 308.024 51.4641 308.186 51.3159C308.348 51.1678 308.539 51.054 308.746 50.9816C308.954 50.9091 309.174 50.8794 309.393 50.8943C309.612 50.9092 309.826 50.9683 310.022 51.0682C310.218 51.168 310.391 51.3065 310.532 51.4752C310.673 51.6439 310.778 51.8394 310.841 52.0499C310.904 52.2604 310.924 52.4814 310.899 52.6998L310.862 53.2402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M308.769 60.0854C306.986 59.9643 305.626 58.4061 305.747 56.6227L305.857 55.0014C305.896 54.4281 306.161 53.8938 306.594 53.5159C307.026 53.138 307.592 52.9476 308.165 52.9865L310.327 53.1333C310.9 53.1722 311.434 53.4373 311.812 53.8702C312.19 54.3031 312.381 54.8684 312.342 55.4417L312.232 57.063C312.111 58.8464 310.552 60.2064 308.769 60.0854Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M308.769 60.0854L309.099 55.2216" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M306.216 53.94C305.181 53.7611 304.378 52.7837 304.456 51.6488" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M305.783 56.0823L303.622 55.9355" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M303.869 60.2956C303.946 59.1607 304.93 58.2503 306.069 58.2733" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M314.167 52.3081C314.09 53.443 313.163 54.303 312.129 54.3414" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M314.43 56.6693L312.268 56.5226" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M311.689 58.6549C312.82 58.786 313.673 59.8211 313.596 60.956" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip40_532_2318)">
|
||||
<path d="M332.21 51.9043L333.157 52.9893" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M335.449 53.1448L336.534 52.1978" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M332.562 54.7134L332.599 54.1729C332.604 53.9533 332.654 53.7369 332.745 53.5369C332.836 53.3369 332.966 53.1574 333.129 53.0092C333.291 52.8611 333.481 52.7473 333.689 52.6749C333.896 52.6024 334.116 52.5727 334.336 52.5876C334.555 52.6025 334.769 52.6616 334.964 52.7615C335.16 52.8613 335.334 52.9998 335.474 53.1685C335.615 53.3372 335.72 53.5327 335.783 53.7432C335.847 53.9537 335.866 54.1747 335.842 54.3931L335.805 54.9335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M333.712 61.7787C331.928 61.6576 330.568 60.0994 330.689 58.316L330.799 56.6947C330.838 56.1214 331.103 55.5871 331.536 55.2092C331.969 54.8313 332.534 54.6409 333.108 54.6798L335.269 54.8266C335.843 54.8655 336.377 55.1306 336.755 55.5635C337.133 55.9964 337.323 56.5617 337.284 57.135L337.174 58.7563C337.053 60.5397 335.495 61.8997 333.712 61.7787Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M333.712 61.7787L334.042 56.9149" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M331.159 55.6333C330.123 55.4544 329.321 54.477 329.398 53.3421" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M330.726 57.7756L328.564 57.6288" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M328.811 61.9889C328.888 60.854 329.873 59.9436 331.011 59.9666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M339.109 54.0014C339.032 55.1363 338.105 55.9963 337.071 56.0347" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M339.373 58.3626L337.211 58.2159" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M336.632 60.3482C337.763 60.4793 338.616 61.5144 338.539 62.6493" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip41_532_2318)">
|
||||
<path d="M357.153 53.5977L358.1 54.6826" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M360.391 54.8382L361.476 53.8911" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M357.505 56.4067L357.542 55.8663C357.547 55.6466 357.596 55.4302 357.687 55.2302C357.778 55.0302 357.909 54.8507 358.071 54.7026C358.233 54.5544 358.424 54.4407 358.631 54.3682C358.839 54.2957 359.059 54.266 359.278 54.2809C359.497 54.2958 359.711 54.3549 359.907 54.4548C360.103 54.5546 360.276 54.6931 360.417 54.8618C360.558 55.0305 360.663 55.226 360.726 55.4365C360.789 55.647 360.809 55.8681 360.784 56.0864L360.748 56.6268" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M358.654 63.472C356.871 63.3509 355.511 61.7927 355.632 60.0093L355.742 58.3881C355.781 57.8148 356.046 57.2804 356.479 56.9025C356.912 56.5246 357.477 56.3342 358.05 56.3731L360.212 56.5199C360.785 56.5588 361.32 56.8239 361.697 57.2568C362.075 57.6897 362.266 58.255 362.227 58.8283L362.117 60.4496C361.996 62.233 360.438 63.5931 358.654 63.472Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M358.654 63.472L358.984 58.6082" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M356.102 57.3267C355.066 57.1478 354.264 56.1703 354.341 55.0355" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M355.668 59.4689L353.507 59.3222" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M353.754 63.6822C353.831 62.5473 354.815 61.637 355.954 61.66" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M364.052 55.6948C363.975 56.8296 363.048 57.6897 362.014 57.728" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M364.315 60.0559L362.154 59.9092" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M361.574 62.0415C362.706 62.1726 363.558 63.2078 363.481 64.3426" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip42_532_2318)">
|
||||
<path d="M382.095 55.291L383.042 56.3759" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M385.334 56.5315L386.419 55.5844" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M382.448 58.1L382.484 57.5596C382.489 57.3399 382.539 57.1235 382.63 56.9235C382.721 56.7235 382.852 56.544 383.014 56.3959C383.176 56.2477 383.367 56.134 383.574 56.0615C383.782 55.989 384.002 55.9593 384.221 55.9742C384.44 55.9891 384.654 56.0482 384.85 56.1481C385.045 56.2479 385.219 56.3864 385.36 56.5551C385.5 56.7238 385.605 56.9193 385.669 57.1298C385.732 57.3403 385.752 57.5614 385.727 57.7797L385.69 58.3201" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M383.597 65.1653C381.813 65.0442 380.453 63.486 380.574 61.7026L380.684 60.0814C380.723 59.5081 380.988 58.9737 381.421 58.5958C381.854 58.2179 382.42 58.0275 382.993 58.0664L385.155 58.2132C385.728 58.2521 386.262 58.5172 386.64 58.9501C387.018 59.383 387.208 59.9483 387.169 60.5216L387.059 62.1429C386.938 63.9263 385.38 65.2864 383.597 65.1653Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M383.597 65.1653L383.927 60.3015" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M381.044 59.02C380.009 58.8411 379.206 57.8636 379.283 56.7288" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M380.611 61.1622L378.449 61.0155" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M378.696 65.3755C378.773 64.2406 379.758 63.3302 380.897 63.3533" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M388.995 57.3881C388.918 58.5229 387.991 59.383 386.956 59.4213" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M389.258 61.7492L387.096 61.6025" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M386.517 63.7348C387.648 63.8659 388.501 64.9011 388.424 66.0359" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip43_532_2318)">
|
||||
<path d="M56.4194 54.2297L57.3665 55.3146" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M59.6578 55.4702L60.7428 54.5232" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M56.7717 57.0387L56.8084 56.4983C56.8135 56.2786 56.863 56.0622 56.954 55.8622C57.045 55.6622 57.1755 55.4827 57.3378 55.3346C57.5001 55.1864 57.6907 55.0727 57.8981 55.0002C58.1056 54.9277 58.3255 54.8981 58.5448 54.9129C58.764 54.9278 58.9779 54.987 59.1737 55.0868C59.3694 55.1866 59.5429 55.3251 59.6837 55.4938C59.8244 55.6626 59.9295 55.8581 59.9926 56.0685C60.0558 56.279 60.0756 56.5001 60.0509 56.7184L60.0142 57.2588" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.9207 64.104C56.1373 63.983 54.7773 62.4248 54.8983 60.6414L55.0084 59.0201C55.0473 58.4468 55.3124 57.9124 55.7453 57.5345C56.1782 57.1567 56.7435 56.9662 57.3168 57.0052L59.4785 57.1519C60.0518 57.1908 60.5862 57.4559 60.9641 57.8888C61.342 58.3217 61.5324 58.887 61.4935 59.4604L61.3834 61.0816C61.2623 62.865 59.7041 64.2251 57.9207 64.104Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.9208 64.104L58.251 59.2402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M55.3682 57.9587C54.3326 57.7798 53.5303 56.8024 53.6073 55.6675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M54.935 60.1009L52.7733 59.9542" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M53.0202 64.3142C53.0973 63.1794 54.082 62.269 55.2206 62.292" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M63.3186 56.3268C63.2416 57.4617 62.3145 58.3217 61.2804 58.3601" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M63.5817 60.688L61.42 60.5412" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M60.8409 62.6736C61.9722 62.8046 62.8248 63.8398 62.7478 64.9747" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip44_532_2318)">
|
||||
<path d="M81.3621 55.923L82.3091 57.0079" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M84.6005 57.1635L85.6854 56.2165" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M81.7143 58.732L81.751 58.1916C81.7561 57.9719 81.8056 57.7555 81.8966 57.5555C81.9876 57.3555 82.1182 57.176 82.2804 57.0279C82.4427 56.8797 82.6333 56.766 82.8408 56.6935C83.0482 56.621 83.2682 56.5914 83.4874 56.6062C83.7066 56.6211 83.9206 56.6803 84.1163 56.7801C84.312 56.8799 84.4855 57.0184 84.6263 57.1871C84.767 57.3559 84.8721 57.5514 84.9353 57.7618C84.9984 57.9723 85.0182 58.1934 84.9936 58.4117L84.9569 58.9521" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M82.8634 65.7973C81.08 65.6763 79.7199 64.1181 79.841 62.3347L79.951 60.7134C79.9899 60.1401 80.255 59.6057 80.6879 59.2278C81.1209 58.85 81.6862 58.6595 82.2595 58.6985L84.4212 58.8452C84.9945 58.8841 85.5288 59.1492 85.9067 59.5821C86.2846 60.015 86.475 60.5803 86.4361 61.1536L86.326 62.7749C86.205 64.5583 84.6468 65.9184 82.8634 65.7973Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M82.8634 65.7973L83.1936 60.9335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M80.3109 59.652C79.2752 59.4731 78.4729 58.4957 78.5499 57.3608" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M79.8776 61.7942L77.7159 61.6475" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M77.9629 66.0075C78.0399 64.8727 79.0247 63.9623 80.1632 63.9853" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M88.2612 58.0201C88.1842 59.155 87.2571 60.015 86.223 60.0534" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M88.5244 62.3813L86.3627 62.2345" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M85.7836 64.3669C86.9148 64.4979 87.7675 65.5331 87.6904 66.668" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip45_532_2318)">
|
||||
<path d="M106.305 57.6163L107.252 58.7013" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M109.543 58.8568L110.628 57.9098" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M106.657 60.4253L106.694 59.8849C106.699 59.6652 106.748 59.4489 106.839 59.2489C106.93 59.0489 107.061 58.8694 107.223 58.7212C107.385 58.5731 107.576 58.4593 107.783 58.3868C107.991 58.3144 108.211 58.2847 108.43 58.2996C108.649 58.3145 108.863 58.3736 109.059 58.4734C109.255 58.5733 109.428 58.7117 109.569 58.8805C109.71 59.0492 109.815 59.2447 109.878 59.4552C109.941 59.6656 109.961 59.8867 109.936 60.105L109.899 60.6455" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M107.806 67.4907C106.022 67.3696 104.662 65.8114 104.783 64.028L104.894 62.4067C104.932 61.8334 105.198 61.299 105.63 60.9212C106.063 60.5433 106.629 60.3529 107.202 60.3918L109.364 60.5385C109.937 60.5775 110.471 60.8425 110.849 61.2754C111.227 61.7084 111.418 62.2737 111.379 62.847L111.269 64.4682C111.147 66.2516 109.589 67.6117 107.806 67.4907Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M107.806 67.4907L108.136 62.6269" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M105.253 61.3453C104.218 61.1664 103.415 60.189 103.492 59.0541" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M104.82 63.4876L102.658 63.3408" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M102.905 67.7009C102.982 66.566 103.967 65.6556 105.106 65.6786" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M113.204 59.7134C113.127 60.8483 112.2 61.7083 111.166 61.7467" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M113.467 64.0746L111.305 63.9278" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M110.726 66.0602C111.857 66.1913 112.71 67.2264 112.633 68.3613" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip46_532_2318)">
|
||||
<path d="M131.247 59.3096L132.194 60.3946" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M134.486 60.5501L135.571 59.6031" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M131.599 62.1186L131.636 61.5782C131.641 61.3585 131.691 61.1422 131.782 60.9422C131.873 60.7422 132.003 60.5627 132.166 60.4145C132.328 60.2663 132.518 60.1526 132.726 60.0801C132.933 60.0077 133.153 59.978 133.373 59.9929C133.592 60.0077 133.806 60.0669 134.001 60.1667C134.197 60.2666 134.371 60.405 134.511 60.5738C134.652 60.7425 134.757 60.938 134.82 61.1485C134.884 61.3589 134.903 61.58 134.879 61.7983L134.842 62.3388" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M132.748 69.1839C130.965 69.0629 129.605 67.5047 129.726 65.7213L129.836 64.1C129.875 63.5267 130.14 62.9923 130.573 62.6145C131.006 62.2366 131.571 62.0462 132.145 62.0851L134.306 62.2318C134.88 62.2708 135.414 62.5358 135.792 62.9687C136.17 63.4017 136.36 63.967 136.321 64.5403L136.211 66.1615C136.09 67.9449 134.532 69.305 132.748 69.1839Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M132.749 69.184L133.079 64.3202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M130.196 63.0386C129.16 62.8597 128.358 61.8823 128.435 60.7474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M129.763 65.1809L127.601 65.0341" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M127.848 69.3942C127.925 68.2593 128.91 67.3489 130.048 67.3719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M138.146 61.4067C138.069 62.5416 137.142 63.4016 136.108 63.44" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M138.409 65.7679L136.248 65.6211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M135.669 67.7535C136.8 67.8846 137.653 68.9197 137.576 70.0546" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip47_532_2318)">
|
||||
<path d="M156.19 61.0029L157.137 62.0879" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M159.428 62.2434L160.513 61.2964" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M156.542 63.812L156.579 63.2715C156.584 63.0519 156.633 62.8355 156.724 62.6355C156.815 62.4355 156.946 62.256 157.108 62.1078C157.27 61.9597 157.461 61.8459 157.669 61.7735C157.876 61.701 158.096 61.6713 158.315 61.6862C158.534 61.7011 158.748 61.7602 158.944 61.8601C159.14 61.9599 159.313 62.0984 159.454 62.2671C159.595 62.4358 159.7 62.6313 159.763 62.8418C159.826 63.0523 159.846 63.2733 159.821 63.4917L159.785 64.0321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M157.691 70.8773C155.908 70.7562 154.548 69.198 154.669 67.4146L154.779 65.7933C154.818 65.22 155.083 64.6857 155.516 64.3078C155.949 63.9299 156.514 63.7395 157.087 63.7784L159.249 63.9252C159.822 63.9641 160.357 64.2292 160.734 64.6621C161.112 65.095 161.303 65.6603 161.264 66.2336L161.154 67.8549C161.033 69.6383 159.475 70.9984 157.691 70.8773Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M157.691 70.8773L158.021 66.0135" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M155.139 64.7319C154.103 64.553 153.301 63.5756 153.378 62.4407" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M154.705 66.8742L152.544 66.7274" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M152.791 71.0875C152.868 69.9526 153.852 69.0422 154.991 69.0652" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M163.089 63.1C163.012 64.2349 162.085 65.0949 161.051 65.1333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M163.352 67.4612L161.19 67.3145" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M160.611 69.4468C161.743 69.5779 162.595 70.613 162.518 71.7479" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip48_532_2318)">
|
||||
<path d="M181.132 62.6962L182.079 63.7812" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M184.371 63.9367L185.456 62.9897" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M181.485 65.5053L181.521 64.9648C181.526 64.7452 181.576 64.5288 181.667 64.3288C181.758 64.1288 181.888 63.9493 182.051 63.8011C182.213 63.653 182.404 63.5392 182.611 63.4668C182.818 63.3943 183.038 63.3646 183.258 63.3795C183.477 63.3944 183.691 63.4535 183.887 63.5534C184.082 63.6532 184.256 63.7917 184.397 63.9604C184.537 64.1291 184.642 64.3246 184.706 64.5351C184.769 64.7456 184.788 64.9666 184.764 65.185L184.727 65.7254" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M182.634 72.5706C180.85 72.4495 179.49 70.8913 179.611 69.1079L179.721 67.4866C179.76 66.9133 180.025 66.379 180.458 66.0011C180.891 65.6232 181.456 65.4328 182.03 65.4717L184.191 65.6185C184.765 65.6574 185.299 65.9225 185.677 66.3554C186.055 66.7883 186.245 67.3536 186.206 67.9269L186.096 69.5482C185.975 71.3316 184.417 72.6916 182.634 72.5706Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M182.634 72.5706L182.964 67.7068" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M180.081 66.4252C179.045 66.2463 178.243 65.2689 178.32 64.134" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M179.648 68.5675L177.486 68.4207" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M177.733 72.7808C177.81 71.6459 178.795 70.7355 179.934 70.7585" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M188.032 64.7933C187.955 65.9282 187.028 66.7882 185.993 66.8266" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M188.295 69.1545L186.133 69.0078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M185.554 71.1401C186.685 71.2712 187.538 72.3063 187.461 73.4412" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip49_532_2318)">
|
||||
<path d="M206.075 64.3895L207.022 65.4745" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M209.313 65.63L210.398 64.683" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M206.427 67.1986L206.464 66.6581C206.469 66.4385 206.518 66.2221 206.609 66.0221C206.7 65.8221 206.831 65.6426 206.993 65.4944C207.156 65.3463 207.346 65.2325 207.554 65.1601C207.761 65.0876 207.981 65.0579 208.2 65.0728C208.419 65.0877 208.633 65.1468 208.829 65.2467C209.025 65.3465 209.198 65.485 209.339 65.6537C209.48 65.8224 209.585 66.0179 209.648 66.2284C209.711 66.4389 209.731 66.6599 209.706 66.8783L209.67 67.4187" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M207.576 74.2639C205.793 74.1428 204.433 72.5846 204.554 70.8012L204.664 69.1799C204.703 68.6066 204.968 68.0723 205.401 67.6944C205.834 67.3165 206.399 67.1261 206.972 67.165L209.134 67.3118C209.707 67.3507 210.242 67.6158 210.62 68.0487C210.997 68.4816 211.188 69.0469 211.149 69.6202L211.039 71.2415C210.918 73.0249 209.36 74.3849 207.576 74.2639Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M207.576 74.2639L207.906 69.4001" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M205.024 68.1185C203.988 67.9396 203.186 66.9622 203.263 65.8273" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M204.591 70.2608L202.429 70.114" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M202.676 74.4741C202.753 73.3392 203.738 72.4288 204.876 72.4518" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M212.974 66.4866C212.897 67.6215 211.97 68.4815 210.936 68.5199" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M213.237 70.8478L211.076 70.701" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M210.497 72.8334C211.628 72.9645 212.48 73.9996 212.403 75.1345" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip50_532_2318)">
|
||||
<path d="M231.018 66.0829L231.965 67.1678" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M234.256 67.3234L235.341 66.3763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M231.37 68.8919L231.406 68.3515C231.412 68.1318 231.461 67.9154 231.552 67.7154C231.643 67.5154 231.774 67.3359 231.936 67.1878C232.098 67.0396 232.289 66.9259 232.496 66.8534C232.704 66.7809 232.924 66.7512 233.143 66.7661C233.362 66.781 233.576 66.8401 233.772 66.94C233.967 67.0398 234.141 67.1783 234.282 67.347C234.422 67.5157 234.528 67.7112 234.591 67.9217C234.654 68.1322 234.674 68.3533 234.649 68.5716L234.612 69.112" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M232.519 75.9572C230.735 75.8361 229.375 74.2779 229.496 72.4945L229.607 70.8733C229.645 70.3 229.911 69.7656 230.343 69.3877C230.776 69.0098 231.342 68.8194 231.915 68.8583L234.077 69.0051C234.65 69.044 235.184 69.3091 235.562 69.742C235.94 70.1749 236.131 70.7402 236.092 71.3135L235.982 72.9348C235.86 74.7182 234.302 76.0783 232.519 75.9572Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M232.519 75.9572L232.849 71.0934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M229.966 69.8119C228.931 69.633 228.128 68.6555 228.205 67.5207" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M229.533 71.9541L227.371 71.8074" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M227.618 76.1674C227.695 75.0325 228.68 74.1221 229.819 74.1452" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M237.917 68.18C237.84 69.3148 236.913 70.1749 235.879 70.2132" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M238.18 72.5411L236.018 72.3944" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M235.439 74.5267C236.57 74.6578 237.423 75.693 237.346 76.8278" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip51_532_2318)">
|
||||
<path d="M255.96 67.7762L256.907 68.8611" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M259.199 69.0167L260.284 68.0696" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M256.312 70.5852L256.349 70.0448C256.354 69.8251 256.404 69.6087 256.495 69.4087C256.586 69.2087 256.716 69.0292 256.878 68.8811C257.041 68.7329 257.231 68.6192 257.439 68.5467C257.646 68.4742 257.866 68.4445 258.085 68.4594C258.305 68.4743 258.519 68.5334 258.714 68.6333C258.91 68.7331 259.084 68.8716 259.224 69.0403C259.365 69.209 259.47 69.4045 259.533 69.615C259.596 69.8255 259.616 70.0466 259.592 70.2649L259.555 70.8053" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M257.461 77.6505C255.678 77.5294 254.318 75.9712 254.439 74.1878L254.549 72.5666C254.588 71.9933 254.853 71.4589 255.286 71.081C255.719 70.7031 256.284 70.5127 256.858 70.5516L259.019 70.6984C259.593 70.7373 260.127 71.0024 260.505 71.4353C260.883 71.8682 261.073 72.4335 261.034 73.0068L260.924 74.6281C260.803 76.4115 259.245 77.7716 257.461 77.6505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M257.461 77.6505L257.792 72.7867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M254.909 71.5052C253.873 71.3263 253.071 70.3488 253.148 69.214" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M254.476 73.6474L252.314 73.5007" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M252.561 77.8607C252.638 76.7258 253.623 75.8154 254.761 75.8385" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M262.859 69.8733C262.782 71.0081 261.855 71.8682 260.821 71.9065" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M263.123 74.2344L260.961 74.0877" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M260.382 76.22C261.513 76.3511 262.366 77.3863 262.289 78.5211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip52_532_2318)">
|
||||
<path d="M280.903 69.4695L281.85 70.5545" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M284.141 70.71L285.226 69.763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M281.255 72.2785L281.292 71.7381C281.297 71.5184 281.346 71.3021 281.437 71.102C281.528 70.902 281.659 70.7225 281.821 70.5744C281.983 70.4262 282.174 70.3125 282.381 70.24C282.589 70.1676 282.809 70.1379 283.028 70.1527C283.247 70.1676 283.461 70.2268 283.657 70.3266C283.853 70.4265 284.026 70.5649 284.167 70.7336C284.308 70.9024 284.413 71.0979 284.476 71.3083C284.539 71.5188 284.559 71.7399 284.534 71.9582L284.497 72.4986" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M282.404 79.3438C280.621 79.2228 279.261 77.6646 279.382 75.8812L279.492 74.2599C279.531 73.6866 279.796 73.1522 280.229 72.7743C280.662 72.3965 281.227 72.206 281.8 72.245L283.962 72.3917C284.535 72.4306 285.069 72.6957 285.447 73.1286C285.825 73.5615 286.016 74.1268 285.977 74.7002L285.867 76.3214C285.746 78.1048 284.187 79.4649 282.404 79.3438Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M282.404 79.3438L282.734 74.48" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M279.851 73.1985C278.816 73.0196 278.013 72.0422 278.091 70.9073" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M279.418 75.3408L277.257 75.194" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M277.504 79.5541C277.581 78.4192 278.565 77.5088 279.704 77.5318" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M287.802 71.5666C287.725 72.7015 286.798 73.5615 285.764 73.5999" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M288.065 75.9278L285.903 75.781" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M285.324 77.9134C286.455 78.0444 287.308 79.0796 287.231 80.2145" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip53_532_2318)">
|
||||
<path d="M305.845 71.1628L306.792 72.2477" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M309.084 72.4033L310.169 71.4563" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M306.198 73.9718L306.234 73.4314C306.239 73.2117 306.289 72.9954 306.38 72.7953C306.471 72.5953 306.601 72.4158 306.764 72.2677C306.926 72.1195 307.117 72.0058 307.324 71.9333C307.531 71.8609 307.751 71.8312 307.971 71.846C308.19 71.8609 308.404 71.9201 308.6 72.0199C308.795 72.1198 308.969 72.2582 309.109 72.4269C309.25 72.5957 309.355 72.7912 309.418 73.0016C309.482 73.2121 309.501 73.4332 309.477 73.6515L309.44 74.1919" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M307.347 81.0371C305.563 80.9161 304.203 79.3579 304.324 77.5745L304.434 75.9532C304.473 75.3799 304.738 74.8455 305.171 74.4676C305.604 74.0898 306.169 73.8993 306.743 73.9383L308.904 74.085C309.478 74.1239 310.012 74.389 310.39 74.8219C310.768 75.2548 310.958 75.8201 310.919 76.3935L310.809 78.0147C310.688 79.7981 309.13 81.1582 307.347 81.0371Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M307.347 81.0371L307.677 76.1733" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M304.794 74.8918C303.758 74.7129 302.956 73.7355 303.033 72.6006" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M304.361 77.0341L302.199 76.8873" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M302.446 81.2474C302.523 80.1125 303.508 79.2021 304.647 79.2251" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M312.745 73.2599C312.668 74.3948 311.74 75.2548 310.706 75.2932" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M313.008 77.6211L310.846 77.4743" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M310.267 79.6067C311.398 79.7377 312.251 80.7729 312.174 81.9078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip54_532_2318)">
|
||||
<path d="M330.788 72.8561L331.735 73.941" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M334.026 74.0966L335.111 73.1496" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M331.14 75.6651L331.177 75.1247C331.182 74.905 331.231 74.6887 331.322 74.4886C331.413 74.2886 331.544 74.1091 331.706 73.961C331.868 73.8128 332.059 73.6991 332.267 73.6266C332.474 73.5541 332.694 73.5245 332.913 73.5393C333.132 73.5542 333.346 73.6134 333.542 73.7132C333.738 73.813 333.911 73.9515 334.052 74.1202C334.193 74.289 334.298 74.4845 334.361 74.6949C334.424 74.9054 334.444 75.1265 334.419 75.3448L334.383 75.8852" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M332.289 82.7304C330.506 82.6094 329.146 81.0512 329.267 79.2678L329.377 77.6465C329.416 77.0732 329.681 76.5388 330.114 76.1609C330.547 75.7831 331.112 75.5926 331.685 75.6316L333.847 75.7783C334.42 75.8172 334.955 76.0823 335.333 76.5152C335.71 76.9481 335.901 77.5134 335.862 78.0868L335.752 79.708C335.631 81.4914 334.073 82.8515 332.289 82.7304Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M332.289 82.7304L332.619 77.8666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M329.737 76.5851C328.701 76.4062 327.899 75.4288 327.976 74.2939" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M329.303 78.7274L327.142 78.5806" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M327.389 82.9407C327.466 81.8058 328.451 80.8954 329.589 80.9184" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M337.687 74.9532C337.61 76.0881 336.683 76.9481 335.649 76.9865" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M337.95 79.3144L335.789 79.1676" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M335.209 81.3C336.341 81.431 337.193 82.4662 337.116 83.6011" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip55_532_2318)">
|
||||
<path d="M355.73 74.5494L356.677 75.6344" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M358.969 75.7899L360.054 74.8429" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M356.083 77.3584L356.119 76.818C356.124 76.5983 356.174 76.382 356.265 76.182C356.356 75.982 356.487 75.8025 356.649 75.6543C356.811 75.5062 357.002 75.3924 357.209 75.3199C357.417 75.2475 357.637 75.2178 357.856 75.2327C358.075 75.2476 358.289 75.3067 358.485 75.4065C358.68 75.5064 358.854 75.6448 358.995 75.8136C359.135 75.9823 359.24 76.1778 359.304 76.3883C359.367 76.5987 359.387 76.8198 359.362 77.0382L359.325 77.5786" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M357.232 84.4238C355.448 84.3027 354.088 82.7445 354.209 80.9611L354.319 79.3398C354.358 78.7665 354.623 78.2321 355.056 77.8543C355.489 77.4764 356.055 77.286 356.628 77.3249L358.79 77.4716C359.363 77.5106 359.897 77.7756 360.275 78.2086C360.653 78.6415 360.843 79.2068 360.805 79.7801L360.694 81.4014C360.573 83.1847 359.015 84.5448 357.232 84.4238Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M357.232 84.4238L357.562 79.56" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M354.679 78.2784C353.644 78.0995 352.841 77.1221 352.918 75.9872" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M354.246 80.4207L352.084 80.2739" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M352.331 84.634C352.408 83.4991 353.393 82.5887 354.532 82.6117" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M362.63 76.6465C362.553 77.7814 361.626 78.6414 360.591 78.6798" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M362.893 81.0077L360.731 80.8609" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M360.152 82.9933C361.283 83.1244 362.136 84.1595 362.059 85.2944" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g opacity="0.6" clip-path="url(#clip56_532_2318)">
|
||||
<path d="M380.673 76.2427L381.62 77.3277" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M383.911 77.4832L384.996 76.5362" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M381.025 79.0517L381.062 78.5113C381.067 78.2916 381.117 78.0753 381.208 77.8753C381.299 77.6753 381.429 77.4958 381.591 77.3476C381.754 77.1995 381.944 77.0857 382.152 77.0132C382.359 76.9408 382.579 76.9111 382.798 76.926C383.018 76.9409 383.232 77 383.427 77.0998C383.623 77.1997 383.796 77.3381 383.937 77.5069C384.078 77.6756 384.183 77.8711 384.246 78.0816C384.309 78.292 384.329 78.5131 384.305 78.7314L384.268 79.2719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M382.174 86.1171C380.391 85.996 379.031 84.4378 379.152 82.6544L379.262 81.0331C379.301 80.4598 379.566 79.9254 379.999 79.5476C380.432 79.1697 380.997 78.9793 381.571 79.0182L383.732 79.1649C384.306 79.2039 384.84 79.4689 385.218 79.9019C385.596 80.3348 385.786 80.9001 385.747 81.4734L385.637 83.0946C385.516 84.878 383.958 86.2381 382.174 86.1171Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M382.174 86.1171L382.505 81.2533" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M379.622 79.9717C378.586 79.7928 377.784 78.8154 377.861 77.6805" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M379.189 82.114L377.027 81.9672" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M377.274 86.3273C377.351 85.1924 378.336 84.282 379.474 84.305" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M387.572 78.3398C387.495 79.4747 386.568 80.3347 385.534 80.3731" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M387.835 82.701L385.674 82.5542" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M385.095 84.6866C386.226 84.8177 387.079 85.8528 387.002 86.9877" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_532_2318">
|
||||
<rect width="400" height="92" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(56.4365 -10) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip2_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(81.3792 -8.3067) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip3_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(106.322 -6.6134) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip4_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(131.264 -4.92004) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip5_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(156.207 -3.22675) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip6_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(181.149 -1.53345) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip7_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(206.092 0.159851) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip8_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(231.035 1.85315) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip9_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(255.977 3.54651) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip10_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(280.92 5.23981) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip11_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(305.862 6.93311) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip12_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(330.805 8.6264) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip13_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(355.748 10.3197) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip14_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(380.69 12.0131) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip15_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(55.0142 10.9518) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip16_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(79.9568 12.6451) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip17_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(104.899 14.3384) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip18_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(129.842 16.0317) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip19_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(154.785 17.725) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip20_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(179.727 19.4183) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip21_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(204.67 21.1116) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip22_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(229.612 22.8049) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip23_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(254.555 24.4983) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip24_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(279.497 26.1916) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip25_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(304.44 27.8849) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip26_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(329.383 29.5782) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip27_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(354.325 31.2715) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip28_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(379.268 32.9648) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip29_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(53.5918 31.9036) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip30_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(78.5344 33.5969) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip31_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(103.477 35.2902) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip32_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(128.42 36.9835) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip33_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(153.362 38.6768) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip34_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(178.305 40.3701) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip35_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(203.247 42.0634) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip36_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(228.19 43.7567) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip37_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(253.133 45.4501) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip38_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(278.075 47.1434) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip39_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(303.018 48.8367) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip40_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(327.96 50.53) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip41_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(352.903 52.2233) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip42_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(377.845 53.9166) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip43_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(52.1694 52.8553) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip44_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(77.1121 54.5486) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip45_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(102.055 56.2419) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip46_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(126.997 57.9352) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip47_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(151.94 59.6286) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip48_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(176.882 61.3219) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip49_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(201.825 63.0152) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip50_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(226.768 64.7085) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip51_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(251.71 66.4018) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip52_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(276.653 68.0951) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip53_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(301.595 69.7884) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip54_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(326.538 71.4817) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip55_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(351.48 73.175) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip56_532_2318">
|
||||
<rect width="13" height="13" fill="white" transform="translate(376.423 74.8683) rotate(3.88375)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 129 KiB |
@@ -10,8 +10,10 @@
|
||||
"pagedown": "menu::SelectLast",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"tab": "menu::SelectNext",
|
||||
"down": "menu::SelectNext",
|
||||
"ctrl-p": "menu::SelectPrevious",
|
||||
"shift-tab": "menu::SelectPrevious",
|
||||
"up": "menu::SelectPrevious",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
@@ -35,11 +37,12 @@
|
||||
"ctrl-shift-f5": "debugger::Restart",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"cmd-f11": "debugger::StepInto",
|
||||
"ctrl-f11": "debugger::StepInto",
|
||||
"shift-f11": "debugger::StepOut",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
||||
"ctrl-shift-i": "edit_prediction::ToggleMenu"
|
||||
"ctrl-shift-i": "edit_prediction::ToggleMenu",
|
||||
"ctrl-alt-l": "lsp_tool::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -59,7 +62,6 @@
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::Backtab",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
// "ctrl-t": "editor::Transpose",
|
||||
"ctrl-k ctrl-q": "editor::Rewrap",
|
||||
"ctrl-k q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
@@ -100,27 +102,23 @@
|
||||
"shift-down": "editor::SelectDown",
|
||||
"shift-left": "editor::SelectLeft",
|
||||
"shift-right": "editor::SelectRight",
|
||||
"ctrl-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
|
||||
"ctrl-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
||||
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
|
||||
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||
"ctrl-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-a": "editor::SelectAll",
|
||||
"ctrl-l": "editor::SelectLine",
|
||||
"ctrl-shift-i": "editor::Format",
|
||||
"alt-shift-o": "editor::OrganizeImports",
|
||||
// "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 }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
||||
"ctrl-;": "editor::ToggleLineNumbers",
|
||||
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
||||
"ctrl-\"": "editor::ExpandAllDiffHunks",
|
||||
"ctrl-i": "editor::ShowSignatureHelp",
|
||||
"alt-g b": "git::Blame",
|
||||
"alt-g m": "git::OpenModifiedFiles",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
"shift-f10": "editor::OpenContextMenu",
|
||||
"ctrl-shift-e": "editor::ToggleEditPrediction",
|
||||
@@ -140,7 +138,6 @@
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
||||
@@ -153,8 +150,7 @@
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"bindings": {
|
||||
"alt-]": "editor::NextEditPrediction",
|
||||
"alt-[": "editor::PreviousEditPrediction",
|
||||
"alt-right": "editor::AcceptPartialEditPrediction"
|
||||
"alt-[": "editor::PreviousEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -219,7 +215,6 @@
|
||||
"ctrl-enter": "assistant::Assist",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"save": "workspace::Save",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole",
|
||||
@@ -245,11 +240,12 @@
|
||||
"ctrl-shift-j": "agent::ToggleNavigationMenu",
|
||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
"alt-enter": "agent::ContinueWithBurnMode",
|
||||
"ctrl-alt-b": "agent::ToggleBurnMode"
|
||||
"super-ctrl-b": "agent::ToggleBurnMode",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -268,8 +264,8 @@
|
||||
{
|
||||
"context": "AgentPanel && prompt_editor",
|
||||
"bindings": {
|
||||
"cmd-n": "agent::NewTextThread",
|
||||
"cmd-alt-t": "agent::NewThread"
|
||||
"ctrl-n": "agent::NewTextThread",
|
||||
"ctrl-alt-t": "agent::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -495,13 +491,27 @@
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPreviousHunk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "svg::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
@@ -662,14 +672,16 @@
|
||||
"bindings": {
|
||||
"alt-tab": "editor::AcceptEditPrediction",
|
||||
"alt-l": "editor::AcceptEditPrediction",
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
"tab": "editor::AcceptEditPrediction",
|
||||
"alt-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction_conflict",
|
||||
"bindings": {
|
||||
"alt-tab": "editor::AcceptEditPrediction",
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
"alt-l": "editor::AcceptEditPrediction",
|
||||
"alt-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -897,14 +909,19 @@
|
||||
"right": "variable_list::ExpandSelectedEntry",
|
||||
"enter": "variable_list::EditVariable",
|
||||
"ctrl-c": "variable_list::CopyVariableValue",
|
||||
"ctrl-alt-c": "variable_list::CopyVariableName"
|
||||
"ctrl-alt-c": "variable_list::CopyVariableName",
|
||||
"delete": "variable_list::RemoveWatch",
|
||||
"backspace": "variable_list::RemoveWatch",
|
||||
"alt-enter": "variable_list::AddWatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -945,7 +962,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"context": "FileFinder || (FileFinder > Picker > Editor)",
|
||||
"bindings": {
|
||||
"ctrl-shift-a": "file_finder::ToggleSplitMenu",
|
||||
"ctrl-shift-i": "file_finder::ToggleFilterMenu"
|
||||
@@ -1040,7 +1057,8 @@
|
||||
"context": "DebugConsole > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "menu::Confirm"
|
||||
"enter": "menu::Confirm",
|
||||
"alt-enter": "console::WatchExpression"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"fn-f": "zed::ToggleFullScreen",
|
||||
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
||||
"ctrl-cmd-z": "edit_prediction::RateCompletions",
|
||||
"ctrl-cmd-i": "edit_prediction::ToggleMenu"
|
||||
"ctrl-cmd-i": "edit_prediction::ToggleMenu",
|
||||
"ctrl-cmd-l": "lsp_tool::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -139,6 +140,7 @@
|
||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "git::Blame",
|
||||
"cmd-alt-g m": "git::OpenModifiedFiles",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"f9": "editor::ToggleBreakpoint",
|
||||
"shift-f9": "editor::EditLogBreakpoint",
|
||||
@@ -181,8 +183,7 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::NextEditPrediction",
|
||||
"alt-shift-tab": "editor::PreviousEditPrediction",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
|
||||
"alt-shift-tab": "editor::PreviousEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -253,7 +254,6 @@
|
||||
"bindings": {
|
||||
"cmd-enter": "assistant::Assist",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole",
|
||||
@@ -280,11 +280,12 @@
|
||||
"cmd-shift-j": "agent::ToggleNavigationMenu",
|
||||
"cmd-shift-i": "agent::ToggleOptionsMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-ctrl-b": "agent::ToggleBurnMode",
|
||||
"cmd-shift-enter": "agent::ContinueThread",
|
||||
"alt-enter": "agent::ContinueWithBurnMode",
|
||||
"cmd-alt-b": "agent::ToggleBurnMode"
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -544,11 +545,25 @@
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
"cmd-k p": "editor::CopyPath",
|
||||
"cmd-\\": "pane::SplitRight",
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "svg::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
@@ -587,7 +602,7 @@
|
||||
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
|
||||
"ctrl-cmd-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true, "create_new_window": false }],
|
||||
"alt-cmd-b": "branches::OpenRecent",
|
||||
"cmd-ctrl-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd-k s": "workspace::SaveWithoutFormat",
|
||||
@@ -605,6 +620,7 @@
|
||||
"cmd-8": ["workspace::ActivatePane", 7],
|
||||
"cmd-9": ["workspace::ActivatePane", 8],
|
||||
"cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
@@ -719,14 +735,16 @@
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
"alt-tab": "editor::AcceptEditPrediction",
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
"tab": "editor::AcceptEditPrediction",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction_conflict",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-tab": "editor::AcceptEditPrediction"
|
||||
"alt-tab": "editor::AcceptEditPrediction",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -862,7 +880,10 @@
|
||||
"right": "variable_list::ExpandSelectedEntry",
|
||||
"enter": "variable_list::EditVariable",
|
||||
"cmd-c": "variable_list::CopyVariableValue",
|
||||
"cmd-alt-c": "variable_list::CopyVariableName"
|
||||
"cmd-alt-c": "variable_list::CopyVariableName",
|
||||
"delete": "variable_list::RemoveWatch",
|
||||
"backspace": "variable_list::RemoveWatch",
|
||||
"alt-enter": "variable_list::AddWatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -959,7 +980,9 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1006,7 +1029,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"context": "FileFinder || (FileFinder > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "file_finder::ToggleSplitMenu",
|
||||
@@ -1133,7 +1156,8 @@
|
||||
"context": "DebugConsole > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "menu::Confirm"
|
||||
"enter": "menu::Confirm",
|
||||
"alt-enter": "console::WatchExpression"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"context": "Editor && vim_mode == insert && !menu",
|
||||
"bindings": {
|
||||
// "j k": ["workspace::SendKeystrokes", "escape"]
|
||||
// "j k": "vim::SwitchToNormalMode"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase" // editor:lower-case
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-l": "language_selector::Toggle", // grammar-selector:show
|
||||
"ctrl-|": "pane::RevealInProjectPanel", // tree-view:reveal-active-file
|
||||
@@ -19,25 +26,20 @@
|
||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||
"alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
|
||||
"alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", // editor:lower-case
|
||||
"ctrl-j": "editor::JoinLines", // editor:join-lines
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
|
||||
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
|
||||
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
|
||||
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide" // markdown-preview:toggle
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide", // markdown-preview:toggle
|
||||
"ctrl-r": "outline::Toggle" // symbols-view:toggle-project-symbols
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
|
||||
"ctrl-shift-f3": "search::SelectPreviousMatch" // find-and-replace:find-previous-selected
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"ctrl-shift-i": "agent::ToggleFocus",
|
||||
"ctrl-l": "agent::ToggleFocus",
|
||||
"ctrl-shift-l": "agent::ToggleFocus",
|
||||
"ctrl-alt-b": "agent::ToggleFocus",
|
||||
"ctrl-shift-j": "agent::OpenConfiguration"
|
||||
}
|
||||
},
|
||||
@@ -42,7 +41,6 @@
|
||||
"ctrl-shift-i": "workspace::ToggleRightDock",
|
||||
"ctrl-l": "workspace::ToggleRightDock",
|
||||
"ctrl-shift-l": "workspace::ToggleRightDock",
|
||||
"ctrl-alt-b": "workspace::ToggleRightDock",
|
||||
"ctrl-w": "workspace::ToggleRightDock", // technically should close chat
|
||||
"ctrl-.": "agent::ToggleProfileSelector",
|
||||
"ctrl-/": "agent::ToggleModelSelector",
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -90,6 +91,13 @@
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrevious",
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"ctrl-shift-d": "editor::DuplicateSelection",
|
||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||
// "ctrl-f3": "", // find_under (cancels any selections)
|
||||
// "cmd-alt-shift-g": "" // find_under_prev (cancels any selections)
|
||||
// "ctrl-alt-shift-g": "" // find_under_prev (cancels any selections)
|
||||
"f9": "editor::SortLinesCaseSensitive",
|
||||
"ctrl-f9": "editor::SortLinesCaseInsensitive",
|
||||
"f12": "editor::GoToDefinition",
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-l": "language_selector::Toggle",
|
||||
"cmd-|": "pane::RevealInProjectPanel",
|
||||
@@ -19,26 +27,20 @@
|
||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
||||
"alt-enter": "editor::Newline",
|
||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-cmd-up": "editor::MoveLineUp",
|
||||
"ctrl-cmd-down": "editor::MoveLineDown",
|
||||
"cmd-\\": "workspace::ToggleLeftDock",
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-r": "outline::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||
"cmd-f3": "search::SelectNextMatch",
|
||||
"cmd-shift-f3": "search::SelectPreviousMatch"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"cmd-shift-i": "agent::ToggleFocus",
|
||||
"cmd-l": "agent::ToggleFocus",
|
||||
"cmd-shift-l": "agent::ToggleFocus",
|
||||
"cmd-alt-b": "agent::ToggleFocus",
|
||||
"cmd-shift-j": "agent::OpenConfiguration"
|
||||
}
|
||||
},
|
||||
@@ -28,7 +27,8 @@
|
||||
"context": "InlineAssistEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "editor::Cancel"
|
||||
"cmd-shift-backspace": "editor::Cancel",
|
||||
"cmd-enter": "menu::Confirm"
|
||||
// "alt-enter": // Quick Question
|
||||
// "cmd-shift-enter": // Full File Context
|
||||
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
|
||||
@@ -42,7 +42,6 @@
|
||||
"cmd-shift-i": "workspace::ToggleRightDock",
|
||||
"cmd-l": "workspace::ToggleRightDock",
|
||||
"cmd-shift-l": "workspace::ToggleRightDock",
|
||||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-w": "workspace::ToggleRightDock", // technically should close chat
|
||||
"cmd-.": "agent::ToggleProfileSelector",
|
||||
"cmd-/": "agent::ToggleModelSelector",
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -90,6 +91,13 @@
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrevious",
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
"[ shift-b": ["pane::ActivateItem", 0],
|
||||
"] space": "vim::InsertEmptyLineBelow",
|
||||
"[ space": "vim::InsertEmptyLineAbove",
|
||||
"[ e": "editor::MoveLineUp",
|
||||
"] e": "editor::MoveLineDown",
|
||||
|
||||
// Word motions
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
@@ -82,10 +85,10 @@
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||
"f": ["vim::PushFindForward", { "before": false }],
|
||||
"t": ["vim::PushFindForward", { "before": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true }],
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": false }],
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": false }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": false }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": false }],
|
||||
"m": "vim::PushMark",
|
||||
"'": ["vim::PushJump", { "line": true }],
|
||||
"`": ["vim::PushJump", { "line": false }],
|
||||
@@ -184,6 +187,8 @@
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"z shift-m": "editor::FoldAll",
|
||||
"z shift-r": "editor::UnfoldAll",
|
||||
"z l": "vim::ColumnRight",
|
||||
"z h": "vim::ColumnLeft",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
|
||||
// Count support
|
||||
@@ -363,6 +368,10 @@
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
"left": "vim::WrappingLeft",
|
||||
"right": "vim::WrappingRight",
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": "editor::Copy",
|
||||
@@ -380,6 +389,10 @@
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
@@ -395,6 +408,8 @@
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
".": "vim::Repeat",
|
||||
"alt-.": "vim::RepeatFind",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
@@ -421,6 +436,7 @@
|
||||
|
||||
"x": "editor::SelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"%": "editor::SelectAll",
|
||||
// Window mode
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
@@ -450,7 +466,8 @@
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::Substitute",
|
||||
"shift-c": "editor::AddSelectionBelow"
|
||||
"shift-c": "editor::AddSelectionBelow",
|
||||
"alt-shift-c": "editor::AddSelectionAbove"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -711,7 +728,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
|
||||
"context": "AgentPanel || GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
|
||||
"bindings": {
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w": null,
|
||||
|
||||
@@ -27,11 +27,11 @@ If you are unsure how to fulfill the user's request, gather more information wit
|
||||
If appropriate, use tool calls to explore the current project, which contains the following root directories:
|
||||
|
||||
{{#each worktrees}}
|
||||
- `{{root_name}}`
|
||||
- `{{abs_path}}`
|
||||
{{/each}}
|
||||
|
||||
- Bias towards not asking the user for help if you can find the answer yourself.
|
||||
- When providing paths to tools, the path should always begin with a path that starts with a project root directory listed above.
|
||||
- When providing paths to tools, the path should always start with the name of a project root directory listed above.
|
||||
- Before you read or edit a file, you must first find the full path. DO NOT ever guess a file path!
|
||||
{{# if (has_tool 'grep') }}
|
||||
- When looking for symbols in the project, prefer the `grep` tool.
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"inactive_opacity": 1.0
|
||||
},
|
||||
// Layout mode of the bottom dock. Defaults to "contained"
|
||||
// choices: contained, full, left_aligned, right_aligned
|
||||
"bottom_dock_layout": "contained",
|
||||
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||
"pane_split_direction_horizontal": "up",
|
||||
@@ -94,19 +95,22 @@
|
||||
// workspace when the centered layout is used.
|
||||
"right_padding": 0.2
|
||||
},
|
||||
// All settings related to the image viewer.
|
||||
// Image viewer settings
|
||||
"image_viewer": {
|
||||
// The unit for image file sizes.
|
||||
// By default we're setting it to binary.
|
||||
// The second option is decimal.
|
||||
// The unit for image file sizes: "binary" (KiB, MiB) or decimal (KB, MB)
|
||||
"unit": "binary"
|
||||
},
|
||||
// The key to use for adding multiple cursors
|
||||
// Currently "alt" or "cmd_or_ctrl" (also aliased as
|
||||
// "cmd" and "ctrl") are supported.
|
||||
// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
|
||||
//
|
||||
// 1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS:
|
||||
// "alt"
|
||||
// 2. Maps `Control` on Linux and Windows and to `Command` on MacOS:
|
||||
// "cmd_or_ctrl" (alias: "cmd", "ctrl")
|
||||
"multi_cursor_modifier": "alt",
|
||||
// Whether to enable vim modes and key bindings.
|
||||
"vim_mode": false,
|
||||
// Whether to enable helix mode and key bindings.
|
||||
"helix_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
"hover_popover_enabled": true,
|
||||
@@ -114,7 +118,14 @@
|
||||
"hover_popover_delay": 300,
|
||||
// Whether to confirm before quitting Zed.
|
||||
"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
|
||||
// May take 3 values:
|
||||
// 1. All workspaces open during last session
|
||||
// "restore_on_startup": "last_session"
|
||||
// 2. The workspace opened
|
||||
// "restore_on_startup": "last_workspace",
|
||||
// 3. Do not restore previous workspaces
|
||||
// "restore_on_startup": "none",
|
||||
"restore_on_startup": "last_session",
|
||||
// Whether to attempt to restore previous file's state when opening it again.
|
||||
// The state is stored per pane.
|
||||
@@ -127,7 +138,9 @@
|
||||
"restore_on_file_reopen": true,
|
||||
// Whether to automatically close files that have been deleted on disk.
|
||||
"close_on_file_delete": false,
|
||||
// Size of the drop target in the editor.
|
||||
// Relative size of the drop target in the editor that will open dropped file as a split pane (0-0.5)
|
||||
// E.g. 0.25 == If you drop onto the top/bottom quarter of the pane a new vertical split will be used
|
||||
// If you drop onto the left/right quarter of the pane a new horizontal split will be used
|
||||
"drop_target_size": 0.2,
|
||||
// Whether the window should be closed when using 'close active item' on a window with no tabs.
|
||||
// May take 3 values:
|
||||
@@ -214,6 +227,8 @@
|
||||
"show_signature_help_after_edits": false,
|
||||
// Whether to show code action button at start of buffer line.
|
||||
"inline_code_actions": true,
|
||||
// Whether to allow drag and drop text selection in buffer.
|
||||
"drag_and_drop_selection": true,
|
||||
// What to do when go to definition yields no results.
|
||||
//
|
||||
// 1. Do nothing: `none`
|
||||
@@ -302,6 +317,8 @@
|
||||
// "all"
|
||||
// 4. Draw whitespaces at boundaries only:
|
||||
// "boundary"
|
||||
// 5. Draw whitespaces only after non-whitespace characters:
|
||||
// "trailing"
|
||||
// For a whitespace to be on a boundary, any of the following conditions need to be met:
|
||||
// - It is a tab
|
||||
// - It is adjacent to an edge (start or end)
|
||||
@@ -393,6 +410,13 @@
|
||||
// 3. Never show the minimap:
|
||||
// "never" (default)
|
||||
"show": "never",
|
||||
// Where to show the minimap in the editor.
|
||||
// This setting can take two values:
|
||||
// 1. Show the minimap on the focused editor only:
|
||||
// "active_editor" (default)
|
||||
// 2. Show the minimap on all open editors:
|
||||
// "all_editors"
|
||||
"display_in": "active_editor",
|
||||
// When to show the minimap thumb.
|
||||
// This setting can take two values:
|
||||
// 1. Show the minimap thumb if the mouse is over the minimap:
|
||||
@@ -419,7 +443,9 @@
|
||||
// 1. `null` to inherit the editor `current_line_highlight` setting (default)
|
||||
// 2. "line" or "all" to highlight the current line in the minimap.
|
||||
// 3. "gutter" or "none" to not highlight the current line in the minimap.
|
||||
"current_line_highlight": null
|
||||
"current_line_highlight": null,
|
||||
// Maximum number of columns to display in the minimap.
|
||||
"max_width_columns": 80
|
||||
},
|
||||
// Enable middle-click paste on Linux.
|
||||
"middle_click_paste": true,
|
||||
@@ -440,7 +466,9 @@
|
||||
// Whether to show breakpoints in the gutter.
|
||||
"breakpoints": true,
|
||||
// Whether to show fold buttons in the gutter.
|
||||
"folds": true
|
||||
"folds": true,
|
||||
// Minimum number of characters to reserve space for in the gutter.
|
||||
"min_line_number_digits": 4
|
||||
},
|
||||
"indent_guides": {
|
||||
// Whether to show indent guides in the editor.
|
||||
@@ -465,7 +493,7 @@
|
||||
},
|
||||
// Whether the editor will scroll beyond the last line.
|
||||
"scroll_beyond_last_line": "one_page",
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
// The number of lines to keep above/below the cursor when scrolling with the keyboard
|
||||
"vertical_scroll_margin": 3,
|
||||
// Whether to scroll when clicking near the edge of the visible text area.
|
||||
"autoscroll_on_clicks": false,
|
||||
@@ -599,7 +627,9 @@
|
||||
// 2. Never show indent guides:
|
||||
// "never"
|
||||
"show": "always"
|
||||
}
|
||||
},
|
||||
// Whether to hide the root entry when only one folder is open in the window.
|
||||
"hide_root": false
|
||||
},
|
||||
"outline_panel": {
|
||||
// Whether to show the outline panel button in the status bar
|
||||
@@ -679,23 +709,27 @@
|
||||
"default_width": 360,
|
||||
// Style of the git status indicator in the panel.
|
||||
//
|
||||
// Choices: label_color, icon
|
||||
// Default: icon
|
||||
"status_style": "icon",
|
||||
// What branch name to use if init.defaultBranch
|
||||
// is not set
|
||||
// What branch name to use if `init.defaultBranch` is not set
|
||||
//
|
||||
// Default: main
|
||||
"fallback_branch_name": "main",
|
||||
// Whether to sort entries in the panel by path
|
||||
// or by status (the default).
|
||||
// Whether to sort entries in the panel by path or by status (the default).
|
||||
//
|
||||
// Default: false
|
||||
"sort_by_path": false,
|
||||
// Whether to collapse untracked files in the diff panel.
|
||||
//
|
||||
// Default: false
|
||||
"collapse_untracked_diff": false,
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the git panel.
|
||||
//
|
||||
// Choices: always, auto, never, system
|
||||
// Default: inherits editor scrollbar settings
|
||||
"show": null
|
||||
// "show": null
|
||||
}
|
||||
},
|
||||
"message_editor": {
|
||||
@@ -771,7 +805,6 @@
|
||||
"tools": {
|
||||
"copy_path": true,
|
||||
"create_directory": true,
|
||||
"create_file": true,
|
||||
"delete_path": true,
|
||||
"diagnostics": true,
|
||||
"edit_file": true,
|
||||
@@ -974,8 +1007,7 @@
|
||||
// Removes any lines containing only whitespace at the end of the file and
|
||||
// ensures just one newline at the end.
|
||||
"ensure_final_newline_on_save": true,
|
||||
// Whether or not to perform a buffer format before saving
|
||||
//
|
||||
// Whether or not to perform a buffer format before saving: [on, off, prettier, language_server]
|
||||
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
|
||||
"format_on_save": "on",
|
||||
// How to perform a buffer format. This setting can take 4 values:
|
||||
@@ -1028,12 +1060,33 @@
|
||||
// Automatically update Zed. This setting may be ignored on Linux if
|
||||
// installed through a package manager.
|
||||
"auto_update": true,
|
||||
// How to render LSP `textDocument/documentColor` colors in the editor.
|
||||
//
|
||||
// Possible values:
|
||||
//
|
||||
// 1. Do not query and render document colors.
|
||||
// "lsp_document_colors": "none",
|
||||
// 2. Render document colors as inlay hints near the color text (default).
|
||||
// "lsp_document_colors": "inlay",
|
||||
// 3. Draw a border around the color text.
|
||||
// "lsp_document_colors": "border",
|
||||
// 4. Draw a background behind the color text..
|
||||
// "lsp_document_colors": "background",
|
||||
"lsp_document_colors": "inlay",
|
||||
// Diagnostics configuration.
|
||||
"diagnostics": {
|
||||
// Whether to show the project diagnostics button in the status bar.
|
||||
"button": true,
|
||||
// Whether to show warnings or not by default.
|
||||
"include_warnings": true,
|
||||
// Settings for using LSP pull diagnostics mechanism in Zed.
|
||||
"lsp_pull_diagnostics": {
|
||||
// Whether to pull for diagnostics or not.
|
||||
"enabled": true,
|
||||
// Minimum time to wait before pulling diagnostics from the language server(s).
|
||||
// 0 turns the debounce off.
|
||||
"debounce_ms": 50
|
||||
},
|
||||
// Settings for inline diagnostics
|
||||
"inline": {
|
||||
// Whether to show diagnostics inline or not
|
||||
@@ -1135,6 +1188,12 @@
|
||||
// 2. Display predictions inline only when holding a modifier key (alt by default).
|
||||
// "mode": "subtle"
|
||||
"mode": "eager",
|
||||
// Copilot-specific settings
|
||||
// "copilot": {
|
||||
// "enterprise_uri": "",
|
||||
// "proxy": "",
|
||||
// "proxy_no_verify": false
|
||||
// },
|
||||
// Whether edit predictions are enabled when editing text threads.
|
||||
// This setting has no effect if globally disabled.
|
||||
"enabled_in_text_threads": true
|
||||
@@ -1300,6 +1359,8 @@
|
||||
// the terminal will default to matching the buffer's font fallbacks.
|
||||
// This will be merged with the platform's default font fallbacks
|
||||
// "font_fallbacks": ["FiraCode Nerd Fonts"],
|
||||
// The weight of the editor font in standard CSS units from 100 to 900.
|
||||
// "font_weight": 400
|
||||
// Sets the maximum number of lines in the terminal's scrollback buffer.
|
||||
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
@@ -1457,12 +1518,15 @@
|
||||
"language_servers": ["erlang-ls", "!elp", "..."]
|
||||
},
|
||||
"Git Commit": {
|
||||
"allow_rewrap": "anywhere"
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width",
|
||||
"preferred_line_length": 72
|
||||
},
|
||||
"Go": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"debuggers": ["Delve"]
|
||||
},
|
||||
"GraphQL": {
|
||||
"prettier": {
|
||||
@@ -1527,20 +1591,20 @@
|
||||
"Plain Text": {
|
||||
"allow_rewrap": "anywhere"
|
||||
},
|
||||
"Python": {
|
||||
"debuggers": ["Debugpy"]
|
||||
},
|
||||
"Ruby": {
|
||||
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
|
||||
},
|
||||
"Rust": {
|
||||
"debuggers": ["CodeLLDB"]
|
||||
},
|
||||
"SCSS": {
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"SQL": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["prettier-plugin-sql"]
|
||||
}
|
||||
},
|
||||
"Starlark": {
|
||||
"language_servers": ["starpls", "!buck2-lsp", "..."]
|
||||
},
|
||||
@@ -1656,6 +1720,11 @@
|
||||
// }
|
||||
// }
|
||||
},
|
||||
// Common language server settings.
|
||||
"global_lsp_settings": {
|
||||
// Whether to show the LSP servers button in the status bar.
|
||||
"button": true
|
||||
},
|
||||
// Jupyter settings
|
||||
"jupyter": {
|
||||
"enabled": true
|
||||
@@ -1670,7 +1739,6 @@
|
||||
"default_mode": "normal",
|
||||
"toggle_relative_line_numbers": false,
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false,
|
||||
"use_smartcase_find": false,
|
||||
"highlight_on_yank_duration": 200,
|
||||
"custom_digraphs": {},
|
||||
@@ -1753,6 +1821,7 @@
|
||||
"debugger": {
|
||||
"stepping_granularity": "line",
|
||||
"save_breakpoints": true,
|
||||
"dock": "bottom",
|
||||
"button": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Static tasks configuration.
|
||||
// Project tasks configuration. See https://zed.dev/docs/tasks for documentation.
|
||||
//
|
||||
// Example:
|
||||
[
|
||||
|
||||
@@ -261,6 +261,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#bfbdb6ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#d2a6ffff",
|
||||
"font_style": null,
|
||||
@@ -316,6 +321,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#d2a6ffff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#5ac1feff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#a9d94bff",
|
||||
"font_style": null,
|
||||
@@ -442,9 +457,9 @@
|
||||
"terminal.foreground": "#5c6166ff",
|
||||
"terminal.bright_foreground": "#5c6166ff",
|
||||
"terminal.dim_foreground": "#fcfcfcff",
|
||||
"terminal.ansi.black": "#fcfcfcff",
|
||||
"terminal.ansi.bright_black": "#bcbec0ff",
|
||||
"terminal.ansi.dim_black": "#5c6166ff",
|
||||
"terminal.ansi.black": "#5c6166ff",
|
||||
"terminal.ansi.bright_black": "#3b9ee5ff",
|
||||
"terminal.ansi.dim_black": "#9c9fa2ff",
|
||||
"terminal.ansi.red": "#ef7271ff",
|
||||
"terminal.ansi.bright_red": "#febab6ff",
|
||||
"terminal.ansi.dim_red": "#833538ff",
|
||||
@@ -463,9 +478,9 @@
|
||||
"terminal.ansi.cyan": "#4dbf99ff",
|
||||
"terminal.ansi.bright_cyan": "#ace0cbff",
|
||||
"terminal.ansi.dim_cyan": "#2a5f4aff",
|
||||
"terminal.ansi.white": "#5c6166ff",
|
||||
"terminal.ansi.bright_white": "#5c6166ff",
|
||||
"terminal.ansi.dim_white": "#9c9fa2ff",
|
||||
"terminal.ansi.white": "#fcfcfcff",
|
||||
"terminal.ansi.bright_white": "#fcfcfcff",
|
||||
"terminal.ansi.dim_white": "#bcbec0ff",
|
||||
"link_text.hover": "#3b9ee5ff",
|
||||
"conflict": "#f1ad49ff",
|
||||
"conflict.background": "#ffeedaff",
|
||||
@@ -632,6 +647,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#5c6166ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#a37accff",
|
||||
"font_style": null,
|
||||
@@ -687,6 +707,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#a37accff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#3b9ee5ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#86b300ff",
|
||||
"font_style": null,
|
||||
@@ -1003,6 +1033,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#cccac2ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#dfbfffff",
|
||||
"font_style": null,
|
||||
@@ -1058,6 +1093,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#dfbfffff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#72cffeff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#d4fe7fff",
|
||||
"font_style": null,
|
||||
|
||||
@@ -270,6 +270,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#d3869bff",
|
||||
"font_style": null,
|
||||
@@ -325,6 +330,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#fabd2eff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#b8bb25ff",
|
||||
"font_style": null,
|
||||
@@ -655,6 +670,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#d3869bff",
|
||||
"font_style": null,
|
||||
@@ -710,6 +730,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#fabd2eff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#b8bb25ff",
|
||||
"font_style": null,
|
||||
@@ -1040,6 +1070,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#d3869bff",
|
||||
"font_style": null,
|
||||
@@ -1095,6 +1130,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#fabd2eff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#b8bb25ff",
|
||||
"font_style": null,
|
||||
@@ -1227,9 +1272,9 @@
|
||||
"terminal.foreground": "#282828ff",
|
||||
"terminal.bright_foreground": "#282828ff",
|
||||
"terminal.dim_foreground": "#fbf1c7ff",
|
||||
"terminal.ansi.black": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_black": "#b0a189ff",
|
||||
"terminal.ansi.dim_black": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#0b6678ff",
|
||||
"terminal.ansi.dim_black": "#5f5650ff",
|
||||
"terminal.ansi.red": "#9d0308ff",
|
||||
"terminal.ansi.bright_red": "#db8b7aff",
|
||||
"terminal.ansi.dim_red": "#4e1207ff",
|
||||
@@ -1248,9 +1293,9 @@
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
"terminal.ansi.white": "#282828ff",
|
||||
"terminal.ansi.bright_white": "#282828ff",
|
||||
"terminal.ansi.dim_white": "#73675eff",
|
||||
"terminal.ansi.white": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#0b6678ff",
|
||||
"version_control.added": "#797410ff",
|
||||
"version_control.modified": "#b57615ff",
|
||||
@@ -1425,6 +1470,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#8f3e71ff",
|
||||
"font_style": null,
|
||||
@@ -1480,6 +1530,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#b57613ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#0b6678ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#79740eff",
|
||||
"font_style": null,
|
||||
@@ -1612,9 +1672,9 @@
|
||||
"terminal.foreground": "#282828ff",
|
||||
"terminal.bright_foreground": "#282828ff",
|
||||
"terminal.dim_foreground": "#f9f5d7ff",
|
||||
"terminal.ansi.black": "#f9f5d7ff",
|
||||
"terminal.ansi.bright_black": "#b0a189ff",
|
||||
"terminal.ansi.dim_black": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#f9f5d7ff",
|
||||
"terminal.ansi.red": "#9d0308ff",
|
||||
"terminal.ansi.bright_red": "#db8b7aff",
|
||||
"terminal.ansi.dim_red": "#4e1207ff",
|
||||
@@ -1633,9 +1693,9 @@
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
"terminal.ansi.white": "#282828ff",
|
||||
"terminal.ansi.bright_white": "#282828ff",
|
||||
"terminal.ansi.dim_white": "#73675eff",
|
||||
"terminal.ansi.white": "#f9f5d7ff",
|
||||
"terminal.ansi.bright_white": "#f9f5d7ff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#0b6678ff",
|
||||
"version_control.added": "#797410ff",
|
||||
"version_control.modified": "#b57615ff",
|
||||
@@ -1810,6 +1870,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#8f3e71ff",
|
||||
"font_style": null,
|
||||
@@ -1865,6 +1930,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#b57613ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#0b6678ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#79740eff",
|
||||
"font_style": null,
|
||||
@@ -1997,9 +2072,9 @@
|
||||
"terminal.foreground": "#282828ff",
|
||||
"terminal.bright_foreground": "#282828ff",
|
||||
"terminal.dim_foreground": "#f2e5bcff",
|
||||
"terminal.ansi.black": "#f2e5bcff",
|
||||
"terminal.ansi.bright_black": "#b0a189ff",
|
||||
"terminal.ansi.dim_black": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#f2e5bcff",
|
||||
"terminal.ansi.red": "#9d0308ff",
|
||||
"terminal.ansi.bright_red": "#db8b7aff",
|
||||
"terminal.ansi.dim_red": "#4e1207ff",
|
||||
@@ -2018,9 +2093,9 @@
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
"terminal.ansi.white": "#282828ff",
|
||||
"terminal.ansi.bright_white": "#282828ff",
|
||||
"terminal.ansi.dim_white": "#73675eff",
|
||||
"terminal.ansi.white": "#f2e5bcff",
|
||||
"terminal.ansi.bright_white": "#f2e5bcff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#0b6678ff",
|
||||
"version_control.added": "#797410ff",
|
||||
"version_control.modified": "#b57615ff",
|
||||
@@ -2195,6 +2270,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#8f3e71ff",
|
||||
"font_style": null,
|
||||
@@ -2250,6 +2330,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#b57613ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#0b6678ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#79740eff",
|
||||
"font_style": null,
|
||||
|
||||
@@ -99,6 +99,8 @@
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
"version_control.deleted": "#e06c76ff",
|
||||
"version_control.conflict_marker.ours": "#a1c1811a",
|
||||
"version_control.conflict_marker.theirs": "#74ade81a",
|
||||
"conflict": "#dec184ff",
|
||||
"conflict.background": "#dec1841a",
|
||||
"conflict.border": "#5d4c2fff",
|
||||
@@ -264,6 +266,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#dce0e5ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#bf956aff",
|
||||
"font_style": null,
|
||||
@@ -319,6 +326,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#dfc184ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#74ade8ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#a1c181ff",
|
||||
"font_style": null,
|
||||
@@ -450,9 +467,9 @@
|
||||
"terminal.foreground": "#242529ff",
|
||||
"terminal.bright_foreground": "#242529ff",
|
||||
"terminal.dim_foreground": "#fafafaff",
|
||||
"terminal.ansi.black": "#fafafaff",
|
||||
"terminal.ansi.bright_black": "#aaaaaaff",
|
||||
"terminal.ansi.dim_black": "#242529ff",
|
||||
"terminal.ansi.black": "#242529ff",
|
||||
"terminal.ansi.bright_black": "#242529ff",
|
||||
"terminal.ansi.dim_black": "#97979aff",
|
||||
"terminal.ansi.red": "#d36151ff",
|
||||
"terminal.ansi.bright_red": "#f0b0a4ff",
|
||||
"terminal.ansi.dim_red": "#6f312aff",
|
||||
@@ -471,9 +488,9 @@
|
||||
"terminal.ansi.cyan": "#3a82b7ff",
|
||||
"terminal.ansi.bright_cyan": "#a3bedaff",
|
||||
"terminal.ansi.dim_cyan": "#254058ff",
|
||||
"terminal.ansi.white": "#242529ff",
|
||||
"terminal.ansi.bright_white": "#242529ff",
|
||||
"terminal.ansi.dim_white": "#97979aff",
|
||||
"terminal.ansi.white": "#fafafaff",
|
||||
"terminal.ansi.bright_white": "#fafafaff",
|
||||
"terminal.ansi.dim_white": "#aaaaaaff",
|
||||
"link_text.hover": "#5c78e2ff",
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
@@ -584,7 +601,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#669f59ff",
|
||||
"color": "#c18401ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -643,6 +660,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"namespace": {
|
||||
"color": "#242529ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#ad6e25ff",
|
||||
"font_style": null,
|
||||
@@ -698,6 +720,16 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector": {
|
||||
"color": "#669f59ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"selector.pseudo": {
|
||||
"color": "#5c78e2ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#649f57ff",
|
||||
"font_style": null,
|
||||
|
||||
@@ -21,6 +21,7 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
smallvec.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -7,7 +7,10 @@ use gpui::{
|
||||
InteractiveElement as _, ParentElement as _, Render, SharedString, StatefulInteractiveElement,
|
||||
Styled, Transformation, Window, actions, percentage,
|
||||
};
|
||||
use language::{BinaryStatus, LanguageRegistry, LanguageServerId};
|
||||
use language::{
|
||||
BinaryStatus, LanguageRegistry, LanguageServerId, LanguageServerName,
|
||||
LanguageServerStatusUpdate, ServerHealth,
|
||||
};
|
||||
use project::{
|
||||
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
||||
ProjectEnvironmentEvent,
|
||||
@@ -16,6 +19,7 @@ use project::{
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
collections::HashSet,
|
||||
fmt::Write,
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
@@ -30,9 +34,9 @@ const GIT_OPERATION_DELAY: Duration = Duration::from_millis(0);
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
|
||||
pub enum Event {
|
||||
ShowError {
|
||||
server_name: SharedString,
|
||||
error: String,
|
||||
ShowStatus {
|
||||
server_name: LanguageServerName,
|
||||
status: SharedString,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -45,8 +49,8 @@ pub struct ActivityIndicator {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ServerStatus {
|
||||
name: SharedString,
|
||||
status: BinaryStatus,
|
||||
name: LanguageServerName,
|
||||
status: LanguageServerStatusUpdate,
|
||||
}
|
||||
|
||||
struct PendingWork<'a> {
|
||||
@@ -76,10 +80,13 @@ impl ActivityIndicator {
|
||||
let this = cx.new(|cx| {
|
||||
let mut status_events = languages.language_server_binary_statuses();
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Some((name, status)) = status_events.next().await {
|
||||
while let Some((name, binary_status)) = status_events.next().await {
|
||||
this.update(cx, |this: &mut ActivityIndicator, cx| {
|
||||
this.statuses.retain(|s| s.name != name);
|
||||
this.statuses.push(ServerStatus { name, status });
|
||||
this.statuses.push(ServerStatus {
|
||||
name,
|
||||
status: LanguageServerStatusUpdate::Binary(binary_status),
|
||||
});
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
@@ -108,8 +115,76 @@ impl ActivityIndicator {
|
||||
|
||||
cx.subscribe(
|
||||
&project.read(cx).lsp_store(),
|
||||
|_, _, event, cx| match event {
|
||||
LspStoreEvent::LanguageServerUpdate { .. } => cx.notify(),
|
||||
|activity_indicator, _, event, cx| match event {
|
||||
LspStoreEvent::LanguageServerUpdate { name, message, .. } => {
|
||||
if let proto::update_language_server::Variant::StatusUpdate(status_update) =
|
||||
message
|
||||
{
|
||||
let Some(name) = name.clone() else {
|
||||
return;
|
||||
};
|
||||
let status = match &status_update.status {
|
||||
Some(proto::status_update::Status::Binary(binary_status)) => {
|
||||
if let Some(binary_status) =
|
||||
proto::ServerBinaryStatus::from_i32(*binary_status)
|
||||
{
|
||||
let binary_status = match binary_status {
|
||||
proto::ServerBinaryStatus::None => BinaryStatus::None,
|
||||
proto::ServerBinaryStatus::CheckingForUpdate => {
|
||||
BinaryStatus::CheckingForUpdate
|
||||
}
|
||||
proto::ServerBinaryStatus::Downloading => {
|
||||
BinaryStatus::Downloading
|
||||
}
|
||||
proto::ServerBinaryStatus::Starting => {
|
||||
BinaryStatus::Starting
|
||||
}
|
||||
proto::ServerBinaryStatus::Stopping => {
|
||||
BinaryStatus::Stopping
|
||||
}
|
||||
proto::ServerBinaryStatus::Stopped => {
|
||||
BinaryStatus::Stopped
|
||||
}
|
||||
proto::ServerBinaryStatus::Failed => {
|
||||
let Some(error) = status_update.message.clone()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
BinaryStatus::Failed { error }
|
||||
}
|
||||
};
|
||||
LanguageServerStatusUpdate::Binary(binary_status)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(proto::status_update::Status::Health(health_status)) => {
|
||||
if let Some(health) =
|
||||
proto::ServerHealth::from_i32(*health_status)
|
||||
{
|
||||
let health = match health {
|
||||
proto::ServerHealth::Ok => ServerHealth::Ok,
|
||||
proto::ServerHealth::Warning => ServerHealth::Warning,
|
||||
proto::ServerHealth::Error => ServerHealth::Error,
|
||||
};
|
||||
LanguageServerStatusUpdate::Health(
|
||||
health,
|
||||
status_update.message.clone().map(SharedString::from),
|
||||
)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => return,
|
||||
};
|
||||
|
||||
activity_indicator.statuses.retain(|s| s.name != name);
|
||||
activity_indicator
|
||||
.statuses
|
||||
.push(ServerStatus { name, status });
|
||||
}
|
||||
cx.notify()
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
@@ -145,19 +220,19 @@ impl ActivityIndicator {
|
||||
});
|
||||
|
||||
cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
|
||||
Event::ShowError { server_name, error } => {
|
||||
Event::ShowStatus {
|
||||
server_name,
|
||||
status,
|
||||
} => {
|
||||
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||
let project = project.clone();
|
||||
let error = error.clone();
|
||||
let status = status.clone();
|
||||
let server_name = server_name.clone();
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let buffer = create_buffer.await?;
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
format!("Language server error: {}\n\n{}", server_name, error),
|
||||
)],
|
||||
[(0..0, format!("Language server {server_name}:\n\n{status}"))],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
@@ -166,7 +241,10 @@ impl ActivityIndicator {
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(cx.new(|cx| {
|
||||
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
|
||||
let mut editor =
|
||||
Editor::for_buffer(buffer, Some(project.clone()), window, cx);
|
||||
editor.set_read_only(true);
|
||||
editor
|
||||
})),
|
||||
None,
|
||||
true,
|
||||
@@ -185,19 +263,34 @@ impl ActivityIndicator {
|
||||
}
|
||||
|
||||
fn show_error_message(&mut self, _: &ShowErrorMessage, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.statuses.retain(|status| {
|
||||
if let BinaryStatus::Failed { error } = &status.status {
|
||||
cx.emit(Event::ShowError {
|
||||
let mut status_message_shown = false;
|
||||
self.statuses.retain(|status| match &status.status {
|
||||
LanguageServerStatusUpdate::Binary(BinaryStatus::Failed { error })
|
||||
if !status_message_shown =>
|
||||
{
|
||||
cx.emit(Event::ShowStatus {
|
||||
server_name: status.name.clone(),
|
||||
error: error.clone(),
|
||||
status: SharedString::from(error),
|
||||
});
|
||||
status_message_shown = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
LanguageServerStatusUpdate::Health(
|
||||
ServerHealth::Error | ServerHealth::Warning,
|
||||
status_string,
|
||||
) if !status_message_shown => match status_string {
|
||||
Some(error) => {
|
||||
cx.emit(Event::ShowStatus {
|
||||
server_name: status.name.clone(),
|
||||
status: error.clone(),
|
||||
});
|
||||
status_message_shown = true;
|
||||
false
|
||||
}
|
||||
None => false,
|
||||
},
|
||||
_ => true,
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn dismiss_error_message(
|
||||
@@ -206,9 +299,23 @@ impl ActivityIndicator {
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(updater) = &self.auto_updater {
|
||||
updater.update(cx, |updater, cx| updater.dismiss_error(cx));
|
||||
let error_dismissed = if let Some(updater) = &self.auto_updater {
|
||||
updater.update(cx, |updater, cx| updater.dismiss_error(cx))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if error_dismissed {
|
||||
return;
|
||||
}
|
||||
|
||||
self.project.update(cx, |project, cx| {
|
||||
if project.last_formatting_failure(cx).is_some() {
|
||||
project.reset_last_formatting_failure(cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn pending_language_server_work<'a>(
|
||||
@@ -267,48 +374,52 @@ impl ActivityIndicator {
|
||||
});
|
||||
}
|
||||
// Show any language server has pending activity.
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some(PendingWork {
|
||||
progress_token,
|
||||
progress,
|
||||
..
|
||||
}) = pending_work.next()
|
||||
{
|
||||
let mut message = progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(progress_token)
|
||||
.to_string();
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some(PendingWork {
|
||||
progress_token,
|
||||
progress,
|
||||
..
|
||||
}) = pending_work.next()
|
||||
{
|
||||
let mut message = progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(progress_token)
|
||||
.to_string();
|
||||
|
||||
if let Some(percentage) = progress.percentage {
|
||||
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||
if let Some(percentage) = progress.percentage {
|
||||
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||
}
|
||||
|
||||
if let Some(progress_message) = progress.message.as_ref() {
|
||||
message.push_str(": ");
|
||||
message.push_str(progress_message);
|
||||
}
|
||||
|
||||
let additional_work_count = pending_work.count();
|
||||
if additional_work_count > 0 {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
message,
|
||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||
tooltip_message: None,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(progress_message) = progress.message.as_ref() {
|
||||
message.push_str(": ");
|
||||
message.push_str(progress_message);
|
||||
}
|
||||
|
||||
let additional_work_count = pending_work.count();
|
||||
if additional_work_count > 0 {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
message,
|
||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||
tooltip_message: None,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(session) = self
|
||||
@@ -369,14 +480,44 @@ impl ActivityIndicator {
|
||||
let mut downloading = SmallVec::<[_; 3]>::new();
|
||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||
let mut failed = SmallVec::<[_; 3]>::new();
|
||||
let mut health_messages = SmallVec::<[_; 3]>::new();
|
||||
let mut servers_to_clear_statuses = HashSet::<LanguageServerName>::default();
|
||||
for status in &self.statuses {
|
||||
match status.status {
|
||||
BinaryStatus::CheckingForUpdate => checking_for_update.push(status.name.clone()),
|
||||
BinaryStatus::Downloading => downloading.push(status.name.clone()),
|
||||
BinaryStatus::Failed { .. } => failed.push(status.name.clone()),
|
||||
BinaryStatus::None => {}
|
||||
match &status.status {
|
||||
LanguageServerStatusUpdate::Binary(
|
||||
BinaryStatus::Starting | BinaryStatus::Stopping,
|
||||
) => {}
|
||||
LanguageServerStatusUpdate::Binary(BinaryStatus::Stopped) => {
|
||||
servers_to_clear_statuses.insert(status.name.clone());
|
||||
}
|
||||
LanguageServerStatusUpdate::Binary(BinaryStatus::CheckingForUpdate) => {
|
||||
checking_for_update.push(status.name.clone());
|
||||
}
|
||||
LanguageServerStatusUpdate::Binary(BinaryStatus::Downloading) => {
|
||||
downloading.push(status.name.clone());
|
||||
}
|
||||
LanguageServerStatusUpdate::Binary(BinaryStatus::Failed { .. }) => {
|
||||
failed.push(status.name.clone());
|
||||
}
|
||||
LanguageServerStatusUpdate::Binary(BinaryStatus::None) => {}
|
||||
LanguageServerStatusUpdate::Health(health, server_status) => match server_status {
|
||||
Some(server_status) => {
|
||||
health_messages.push((status.name.clone(), *health, server_status.clone()));
|
||||
}
|
||||
None => {
|
||||
servers_to_clear_statuses.insert(status.name.clone());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
self.statuses
|
||||
.retain(|status| !servers_to_clear_statuses.contains(&status.name));
|
||||
|
||||
health_messages.sort_by_key(|(_, health, _)| match health {
|
||||
ServerHealth::Error => 2,
|
||||
ServerHealth::Warning => 1,
|
||||
ServerHealth::Ok => 0,
|
||||
});
|
||||
|
||||
if !downloading.is_empty() {
|
||||
return Some(Content {
|
||||
@@ -457,7 +598,7 @@ impl ActivityIndicator {
|
||||
}),
|
||||
),
|
||||
on_click: Some(Arc::new(|this, window, cx| {
|
||||
this.show_error_message(&Default::default(), window, cx)
|
||||
this.show_error_message(&ShowErrorMessage, window, cx)
|
||||
})),
|
||||
tooltip_message: None,
|
||||
});
|
||||
@@ -471,7 +612,7 @@ impl ActivityIndicator {
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||
message: format!("Formatting failed: {failure}. Click to see logs."),
|
||||
on_click: Some(Arc::new(|indicator, window, cx| {
|
||||
indicator.project.update(cx, |project, cx| {
|
||||
project.reset_last_formatting_failure(cx);
|
||||
@@ -482,6 +623,56 @@ impl ActivityIndicator {
|
||||
});
|
||||
}
|
||||
|
||||
// Show any health messages for the language servers
|
||||
if let Some((server_name, health, message)) = health_messages.pop() {
|
||||
let health_str = match health {
|
||||
ServerHealth::Ok => format!("({server_name}) "),
|
||||
ServerHealth::Warning => format!("({server_name}) Warning: "),
|
||||
ServerHealth::Error => format!("({server_name}) Error: "),
|
||||
};
|
||||
let single_line_message = message
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let line = line.trim();
|
||||
if line.is_empty() { None } else { Some(line) }
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let mut altered_message = single_line_message != message;
|
||||
let truncated_message = truncate_and_trailoff(
|
||||
&single_line_message,
|
||||
MAX_MESSAGE_LEN.saturating_sub(health_str.len()),
|
||||
);
|
||||
altered_message |= truncated_message != single_line_message;
|
||||
let final_message = format!("{health_str}{truncated_message}");
|
||||
|
||||
let tooltip_message = if altered_message {
|
||||
Some(format!("{health_str}{message}"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: final_message,
|
||||
tooltip_message,
|
||||
on_click: Some(Arc::new(move |activity_indicator, window, cx| {
|
||||
if altered_message {
|
||||
activity_indicator.show_error_message(&ShowErrorMessage, window, cx)
|
||||
} else {
|
||||
activity_indicator
|
||||
.statuses
|
||||
.retain(|status| status.name != server_name);
|
||||
cx.notify();
|
||||
}
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
// Show any application auto-update info.
|
||||
if let Some(updater) = &self.auto_updater {
|
||||
return match &updater.read(cx).status() {
|
||||
|
||||
@@ -21,92 +21,59 @@ test-support = [
|
||||
[dependencies]
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
audio.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
context_server.workspace = true
|
||||
convert_case.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
heed.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
icons.workspace = true
|
||||
indoc.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
inventory.workspace = true
|
||||
itertools.workspace = true
|
||||
jsonschema.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
notifications.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
ref-cast.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rules_library.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
sqlez.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
ui_input.workspace = true
|
||||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
zstd.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
assistant_tools.workspace = true
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
|
||||
@@ -1,293 +1,20 @@
|
||||
mod active_thread;
|
||||
mod agent_configuration;
|
||||
mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod buffer_codegen;
|
||||
mod context;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_server_tool;
|
||||
mod context_store;
|
||||
mod context_strip;
|
||||
mod debug;
|
||||
mod history_store;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command_settings;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
mod thread_history;
|
||||
mod thread_store;
|
||||
mod tool_compatibility;
|
||||
mod tool_use;
|
||||
mod ui;
|
||||
pub mod agent_profile;
|
||||
pub mod context;
|
||||
pub mod context_server_tool;
|
||||
pub mod context_store;
|
||||
pub mod history_store;
|
||||
pub mod thread;
|
||||
pub mod thread_store;
|
||||
pub mod tool_use;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, actions, impl_actions};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
|
||||
};
|
||||
use prompt_store::PromptBuilder;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use thread::ThreadId;
|
||||
|
||||
pub use crate::active_thread::ActiveThread;
|
||||
use crate::agent_configuration::{AddContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::context::{ContextLoadResult, LoadedContext};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
|
||||
pub use crate::thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use context::{AgentContext, ContextId, ContextLoadResult};
|
||||
pub use context_store::ContextStore;
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
pub use thread::{
|
||||
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
||||
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
|
||||
};
|
||||
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||
|
||||
actions!(
|
||||
agent,
|
||||
[
|
||||
NewTextThread,
|
||||
ToggleContextPicker,
|
||||
ToggleNavigationMenu,
|
||||
ToggleOptionsMenu,
|
||||
DeleteRecentlyOpenThread,
|
||||
ToggleProfileSelector,
|
||||
RemoveAllContext,
|
||||
ExpandMessageEditor,
|
||||
OpenHistory,
|
||||
AddContextServer,
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
ChatWithFollow,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
FocusUp,
|
||||
FocusDown,
|
||||
FocusLeft,
|
||||
FocusRight,
|
||||
RemoveFocusedContext,
|
||||
AcceptSuggestedContext,
|
||||
OpenActiveThreadAsMarkdown,
|
||||
OpenAgentDiff,
|
||||
Keep,
|
||||
Reject,
|
||||
RejectAll,
|
||||
KeepAll,
|
||||
Follow,
|
||||
ResetTrialUpsell,
|
||||
ResetTrialEndUpsell,
|
||||
ContinueThread,
|
||||
ContinueWithBurnMode,
|
||||
ToggleBurnMode,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct NewThread {
|
||||
#[serde(default)]
|
||||
from_thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||
pub struct ManageProfiles {
|
||||
#[serde(default)]
|
||||
pub customize_tools: Option<AgentProfileId>,
|
||||
}
|
||||
|
||||
impl ManageProfiles {
|
||||
pub fn customize_tools(profile_id: AgentProfileId) -> Self {
|
||||
Self {
|
||||
customize_tools: Some(profile_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_actions!(agent, [NewThread, ManageProfiles]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ModelUsageContext {
|
||||
Thread(Entity<Thread>),
|
||||
InlineAssistant,
|
||||
}
|
||||
|
||||
impl ModelUsageContext {
|
||||
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||
match self {
|
||||
Self::Thread(thread) => thread.read(cx).configured_model(),
|
||||
Self::InlineAssistant => {
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
self.configured_model(cx)
|
||||
.map(|configured_model| configured_model.model)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the `agent` crate.
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
is_eval: bool,
|
||||
cx: &mut App,
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
if !is_eval {
|
||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||
// we're not running inside of the eval.
|
||||
init_language_model_settings(cx);
|
||||
}
|
||||
assistant_slash_command::init(cx);
|
||||
pub fn init(cx: &mut gpui::App) {
|
||||
thread_store::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry, cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
terminal_inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
indexed_docs::init(cx);
|
||||
cx.observe_new(AddContextServerModal::register).detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
}
|
||||
|
||||
fn init_language_model_settings(cx: &mut App) {
|
||||
update_active_language_model_from_settings(cx);
|
||||
|
||||
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
|
||||
.detach();
|
||||
cx.subscribe(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|_, event: &language_model::Event, cx| match event {
|
||||
language_model::Event::ProviderStateChanged
|
||||
| language_model::Event::AddedProvider(_)
|
||||
| language_model::Event::RemovedProvider(_) => {
|
||||
update_active_language_model_from_settings(cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
|
||||
language_model::SelectedModel {
|
||||
provider: LanguageModelProviderId::from(selection.provider.0.clone()),
|
||||
model: LanguageModelId::from(selection.model.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
let default = to_selected_model(&settings.default_model);
|
||||
let inline_assistant = settings
|
||||
.inline_assistant_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let commit_message = settings
|
||||
.commit_message_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let thread_summary = settings
|
||||
.thread_summary_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let inline_alternatives = settings
|
||||
.inline_alternatives
|
||||
.iter()
|
||||
.map(to_selected_model)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_default_model(Some(&default), cx);
|
||||
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
|
||||
registry.select_commit_message_model(commit_message.as_ref(), cx);
|
||||
registry.select_thread_summary_model(thread_summary.as_ref(), cx);
|
||||
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn register_slash_commands(cx: &mut App) {
|
||||
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::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(
|
||||
assistant_slash_commands::StreamingExampleSlashCommand,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
update_slash_commands_from_settings(cx);
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_slash_commands_from_settings(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let settings = SlashCommandSettings::get_global(cx);
|
||||
|
||||
if settings.docs.enabled {
|
||||
slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
|
||||
}
|
||||
|
||||
if settings.cargo_workspace.enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry
|
||||
.unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
use context_server::ContextServerCommand;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
||||
use project::project_settings::{ContextServerConfiguration, ProjectSettings};
|
||||
use serde_json::json;
|
||||
use settings::update_settings_file;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::AddContextServer;
|
||||
|
||||
pub struct AddContextServerModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
name_editor: Entity<SingleLineInput>,
|
||||
command_editor: Entity<SingleLineInput>,
|
||||
}
|
||||
|
||||
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| SingleLineInput::new(window, cx, "my-custom-server").label("Name"));
|
||||
let command_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, "Command").label("Command to run the MCP server")
|
||||
});
|
||||
|
||||
Self {
|
||||
name_editor,
|
||||
command_editor,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut Context<Self>) {
|
||||
let name = self
|
||||
.name_editor
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
let command = self
|
||||
.command_editor
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if name.is_empty() || command.is_empty() {
|
||||
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::<ProjectSettings>(fs.clone(), cx, |settings, _| {
|
||||
settings.context_servers.insert(
|
||||
name.into(),
|
||||
ContextServerConfiguration {
|
||||
command: Some(ContextServerCommand {
|
||||
path,
|
||||
args,
|
||||
env: None,
|
||||
}),
|
||||
settings: Some(json!({})),
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, 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).is_empty(cx);
|
||||
let is_command_empty = self.command_editor.read(cx).is_empty(cx);
|
||||
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
.key_context("AddContextServerModal")
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
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 MCP Server"))
|
||||
.section(
|
||||
Section::new().child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(self.name_editor.clone())
|
||||
.child(self.command_editor.clone()),
|
||||
),
|
||||
)
|
||||
.footer(
|
||||
ModalFooter::new().end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("cancel", "Cancel")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.cancel(&menu::Cancel, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("add-server", "Add Server")
|
||||
.disabled(is_name_empty || is_command_empty)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.map(|button| {
|
||||
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(&menu::Confirm, cx)
|
||||
})),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use context_server::ContextServerId;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
Animation, AnimationExt, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task,
|
||||
TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, percentage,
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerStatus, ContextServerStore},
|
||||
project_settings::{ContextServerConfiguration, ProjectSettings},
|
||||
};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
pub(crate) struct ConfigureContextServerModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
context_servers_to_setup: Vec<ContextServerSetup>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
}
|
||||
|
||||
enum Configuration {
|
||||
NotAvailable,
|
||||
Required(ConfigurationRequiredState),
|
||||
}
|
||||
|
||||
struct ConfigurationRequiredState {
|
||||
installation_instructions: Entity<markdown::Markdown>,
|
||||
settings_validator: Option<jsonschema::Validator>,
|
||||
settings_editor: Entity<Editor>,
|
||||
last_error: Option<SharedString>,
|
||||
waiting_for_context_server: bool,
|
||||
}
|
||||
|
||||
struct ContextServerSetup {
|
||||
id: ContextServerId,
|
||||
repository_url: Option<SharedString>,
|
||||
configuration: Configuration,
|
||||
}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
pub fn new(
|
||||
configurations: impl Iterator<Item = crate::context_server_configuration::Configuration>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
jsonc_language: Option<Arc<Language>>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let context_servers_to_setup = configurations
|
||||
.map(|config| match config {
|
||||
crate::context_server_configuration::Configuration::NotAvailable(
|
||||
context_server_id,
|
||||
repository_url,
|
||||
) => ContextServerSetup {
|
||||
id: context_server_id,
|
||||
repository_url,
|
||||
configuration: Configuration::NotAvailable,
|
||||
},
|
||||
crate::context_server_configuration::Configuration::Required(
|
||||
context_server_id,
|
||||
repository_url,
|
||||
config,
|
||||
) => {
|
||||
let jsonc_language = jsonc_language.clone();
|
||||
let settings_validator = jsonschema::validator_for(&config.settings_schema)
|
||||
.context("Failed to load JSON schema for context server settings")
|
||||
.log_err();
|
||||
let state = ConfigurationRequiredState {
|
||||
installation_instructions: cx.new(|cx| {
|
||||
Markdown::new(
|
||||
config.installation_instructions.clone().into(),
|
||||
Some(language_registry.clone()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
settings_validator,
|
||||
settings_editor: cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(16, window, cx);
|
||||
editor.set_text(config.default_settings.trim(), window, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_soft_wrap_mode(
|
||||
language::language_settings::SoftWrap::None,
|
||||
cx,
|
||||
);
|
||||
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(jsonc_language, cx)
|
||||
})
|
||||
}
|
||||
editor
|
||||
}),
|
||||
waiting_for_context_server: false,
|
||||
last_error: None,
|
||||
};
|
||||
ContextServerSetup {
|
||||
id: context_server_id,
|
||||
repository_url,
|
||||
configuration: Configuration::Required(state),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
workspace,
|
||||
focus_handle: cx.focus_handle(),
|
||||
context_servers_to_setup,
|
||||
context_server_store,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
pub fn confirm(&mut self, cx: &mut Context<Self>) {
|
||||
if self.context_servers_to_setup.is_empty() {
|
||||
self.dismiss(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let id = self.context_servers_to_setup[0].id.clone();
|
||||
let configuration = match &mut self.context_servers_to_setup[0].configuration {
|
||||
Configuration::NotAvailable => {
|
||||
self.context_servers_to_setup.remove(0);
|
||||
if self.context_servers_to_setup.is_empty() {
|
||||
self.dismiss(cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Configuration::Required(state) => state,
|
||||
};
|
||||
|
||||
configuration.last_error.take();
|
||||
if configuration.waiting_for_context_server {
|
||||
return;
|
||||
}
|
||||
|
||||
let settings_value = match serde_json_lenient::from_str::<serde_json::Value>(
|
||||
&configuration.settings_editor.read(cx).text(cx),
|
||||
) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
configuration.last_error = Some(error.to_string().into());
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(validator) = configuration.settings_validator.as_ref() {
|
||||
if let Err(error) = validator.validate(&settings_value) {
|
||||
configuration.last_error = Some(error.to_string().into());
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let id = id.clone();
|
||||
|
||||
let settings_changed = ProjectSettings::get_global(cx)
|
||||
.context_servers
|
||||
.get(&id.0)
|
||||
.map_or(true, |config| {
|
||||
config.settings.as_ref() != Some(&settings_value)
|
||||
});
|
||||
|
||||
let is_running = self.context_server_store.read(cx).status_for_server(&id)
|
||||
== Some(ContextServerStatus::Running);
|
||||
|
||||
if !settings_changed && is_running {
|
||||
self.complete_setup(id, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
configuration.waiting_for_context_server = true;
|
||||
|
||||
let task = wait_for_context_server(&self.context_server_store, id.clone(), cx);
|
||||
cx.spawn({
|
||||
let id = id.clone();
|
||||
async move |this, cx| {
|
||||
let result = task.await;
|
||||
this.update(cx, |this, cx| match result {
|
||||
Ok(_) => {
|
||||
this.complete_setup(id, cx);
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(setup) = this.context_servers_to_setup.get_mut(0) {
|
||||
match &mut setup.configuration {
|
||||
Configuration::NotAvailable => {}
|
||||
Configuration::Required(state) => {
|
||||
state.last_error = Some(err.into());
|
||||
state.waiting_for_context_server = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.dismiss(cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// When we write the settings to the file, the context server will be restarted.
|
||||
update_settings_file::<ProjectSettings>(workspace.read(cx).app_state().fs.clone(), cx, {
|
||||
let id = id.clone();
|
||||
|settings, _| {
|
||||
if let Some(server_config) = settings.context_servers.get_mut(&id.0) {
|
||||
server_config.settings = Some(settings_value);
|
||||
} else {
|
||||
settings.context_servers.insert(
|
||||
id.0,
|
||||
ContextServerConfiguration {
|
||||
settings: Some(settings_value),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn complete_setup(&mut self, id: ContextServerId, cx: &mut Context<Self>) {
|
||||
self.context_servers_to_setup.remove(0);
|
||||
cx.notify();
|
||||
|
||||
if !self.context_servers_to_setup.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.workspace
|
||||
.update(cx, {
|
||||
|workspace, cx| {
|
||||
let status_toast = StatusToast::new(
|
||||
format!("{} configured successfully.", id),
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Hammer).color(Color::Muted))
|
||||
.action("Dismiss", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
|
||||
self.dismiss(cx);
|
||||
}
|
||||
|
||||
fn dismiss(&self, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_context_server(
|
||||
context_server_store: &Entity<ContextServerStore>,
|
||||
context_server_id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(), Arc<str>>> {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let tx = Arc::new(Mutex::new(Some(tx)));
|
||||
|
||||
let subscription = cx.subscribe(context_server_store, move |_, event, _cx| match event {
|
||||
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
|
||||
match status {
|
||||
ContextServerStatus::Running => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Stopped => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err("Context server stopped running".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Error(error) => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err(error.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(async move |_cx| {
|
||||
let result = rx.await.unwrap();
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
impl Render for ConfigureContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let Some(setup) = self.context_servers_to_setup.first() else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(42.))
|
||||
.key_context("ConfigureContextServerModal")
|
||||
.track_focus(&focus_handle)
|
||||
.on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
|
||||
.on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.dismiss(cx)))
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
this.focus_handle(cx).focus(window);
|
||||
}))
|
||||
.child(
|
||||
Modal::new("configure-context-server", None)
|
||||
.header(ModalHeader::new().headline(format!("Configure {}", setup.id)))
|
||||
.section(match &setup.configuration {
|
||||
Configuration::NotAvailable => Section::new().child(
|
||||
Label::new(
|
||||
"No configuration options available for this context server. Visit the Repository for any further instructions.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
Configuration::Required(configuration) => Section::new()
|
||||
.child(div().pb_2().text_sm().child(MarkdownElement::new(
|
||||
configuration.installation_instructions.clone(),
|
||||
default_markdown_style(window, cx),
|
||||
)))
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.gap_1()
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(
|
||||
settings.buffer_line_height.value(),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
&configuration.settings_editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.when_some(configuration.last_error.clone(), |this, error| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.child(
|
||||
div().w_full().child(
|
||||
Label::new(error)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(configuration.waiting_for_context_server, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Label::new("Waiting for Context Server")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
}),
|
||||
})
|
||||
.footer(
|
||||
ModalFooter::new()
|
||||
.when_some(setup.repository_url.clone(), |this, repository_url| {
|
||||
this.start_slot(
|
||||
h_flex().w_full().child(
|
||||
Button::new("open-repository", "Open Repository")
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip({
|
||||
let repository_url = repository_url.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open Repository",
|
||||
None,
|
||||
repository_url.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, _, cx| cx.open_url(&repository_url)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.end_slot(match &setup.configuration {
|
||||
Configuration::NotAvailable => Button::new("dismiss", "Dismiss")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _event, _window, cx| this.dismiss(cx)),
|
||||
)
|
||||
.into_any_element(),
|
||||
Configuration::Required(state) => h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("cancel", "Cancel")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.dismiss(cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("configure-server", "Configure MCP")
|
||||
.disabled(state.waiting_for_context_server)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.confirm(cx)
|
||||
})),
|
||||
)
|
||||
.into_any_element(),
|
||||
}),
|
||||
),
|
||||
).into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let colors = cx.theme().colors();
|
||||
let mut text_style = window.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||
font_size: Some(TextSize::XSmall.rems(cx).into()),
|
||||
color: Some(colors.text_muted),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(colors.text_accent.opacity(0.5)),
|
||||
thickness: px(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for ConfigureContextServerModal {}
|
||||
impl EventEmitter<DismissEvent> for ConfigureContextServerModal {}
|
||||
impl Focusable for ConfigureContextServerModal {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
if let Some(current) = self.context_servers_to_setup.first() {
|
||||
match ¤t.configuration {
|
||||
Configuration::NotAvailable => self.focus_handle.clone(),
|
||||
Configuration::Required(configuration) => {
|
||||
configuration.settings_editor.read(cx).focus_handle(cx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
341
crates/agent/src/agent_profile.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentProfileId, AgentProfileSettings, AgentSettings};
|
||||
use assistant_tool::{Tool, ToolSource, ToolWorkingSet};
|
||||
use collections::IndexMap;
|
||||
use convert_case::{Case, Casing};
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, SharedString};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct AgentProfile {
|
||||
id: AgentProfileId,
|
||||
tool_set: Entity<ToolWorkingSet>,
|
||||
}
|
||||
|
||||
pub type AvailableProfiles = IndexMap<AgentProfileId, SharedString>;
|
||||
|
||||
impl AgentProfile {
|
||||
pub fn new(id: AgentProfileId, tool_set: Entity<ToolWorkingSet>) -> Self {
|
||||
Self { id, tool_set }
|
||||
}
|
||||
|
||||
/// Saves a new profile to the settings.
|
||||
pub fn create(
|
||||
name: String,
|
||||
base_profile_id: Option<AgentProfileId>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &App,
|
||||
) -> AgentProfileId {
|
||||
let id = AgentProfileId(name.to_case(Case::Kebab).into());
|
||||
|
||||
let base_profile =
|
||||
base_profile_id.and_then(|id| AgentSettings::get_global(cx).profiles.get(&id).cloned());
|
||||
|
||||
let profile_settings = AgentProfileSettings {
|
||||
name: name.into(),
|
||||
tools: base_profile
|
||||
.as_ref()
|
||||
.map(|profile| profile.tools.clone())
|
||||
.unwrap_or_default(),
|
||||
enable_all_context_servers: base_profile
|
||||
.as_ref()
|
||||
.map(|profile| profile.enable_all_context_servers)
|
||||
.unwrap_or_default(),
|
||||
context_servers: base_profile
|
||||
.map(|profile| profile.context_servers)
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
update_settings_file::<AgentSettings>(fs, cx, {
|
||||
let id = id.clone();
|
||||
move |settings, _cx| {
|
||||
settings.create_profile(id, profile_settings).log_err();
|
||||
}
|
||||
});
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Returns a map of AgentProfileIds to their names
|
||||
pub fn available_profiles(cx: &App) -> AvailableProfiles {
|
||||
let mut profiles = AvailableProfiles::default();
|
||||
for (id, profile) in AgentSettings::get_global(cx).profiles.iter() {
|
||||
profiles.insert(id.clone(), profile.name.clone());
|
||||
}
|
||||
profiles
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &AgentProfileId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn enabled_tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
|
||||
let Some(settings) = AgentSettings::get_global(cx).profiles.get(&self.id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
self.tool_set
|
||||
.read(cx)
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.filter(|tool| Self::is_enabled(settings, tool.source(), tool.name()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn is_tool_enabled(&self, source: ToolSource, tool_name: String, cx: &App) -> bool {
|
||||
let Some(settings) = AgentSettings::get_global(cx).profiles.get(&self.id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
return Self::is_enabled(settings, source, tool_name);
|
||||
}
|
||||
|
||||
fn is_enabled(settings: &AgentProfileSettings, source: ToolSource, name: String) -> bool {
|
||||
match source {
|
||||
ToolSource::Native => *settings.tools.get(name.as_str()).unwrap_or(&false),
|
||||
ToolSource::ContextServer { id } => {
|
||||
if settings.enable_all_context_servers {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(preset) = settings.context_servers.get(id.as_ref()) else {
|
||||
return false;
|
||||
};
|
||||
*preset.tools.get(name.as_str()).unwrap_or(&false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use agent_settings::ContextServerPreset;
|
||||
use assistant_tool::ToolRegistry;
|
||||
use collections::IndexMap;
|
||||
use gpui::SharedString;
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use http_client::FakeHttpClient;
|
||||
use project::Project;
|
||||
use settings::{Settings, SettingsStore};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_enabled_built_in_tools_for_profile(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let id = AgentProfileId::default();
|
||||
let profile_settings = cx.read(|cx| {
|
||||
AgentSettings::get_global(cx)
|
||||
.profiles
|
||||
.get(&id)
|
||||
.unwrap()
|
||||
.clone()
|
||||
});
|
||||
let tool_set = default_tool_set(cx);
|
||||
|
||||
let profile = AgentProfile::new(id.clone(), tool_set);
|
||||
|
||||
let mut enabled_tools = cx
|
||||
.read(|cx| profile.enabled_tools(cx))
|
||||
.into_iter()
|
||||
.map(|tool| tool.name())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort();
|
||||
|
||||
let mut expected_tools = profile_settings
|
||||
.tools
|
||||
.into_iter()
|
||||
.filter_map(|(tool, enabled)| enabled.then_some(tool.to_string()))
|
||||
// Provider dependent
|
||||
.filter(|tool| tool != "web_search")
|
||||
.collect::<Vec<_>>();
|
||||
// Plus all registered MCP tools
|
||||
expected_tools.extend(["enabled_mcp_tool".into(), "disabled_mcp_tool".into()]);
|
||||
expected_tools.sort();
|
||||
|
||||
assert_eq!(enabled_tools, expected_tools);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_custom_mcp_settings(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let id = AgentProfileId("custom_mcp".into());
|
||||
let profile_settings = cx.read(|cx| {
|
||||
AgentSettings::get_global(cx)
|
||||
.profiles
|
||||
.get(&id)
|
||||
.unwrap()
|
||||
.clone()
|
||||
});
|
||||
let tool_set = default_tool_set(cx);
|
||||
|
||||
let profile = AgentProfile::new(id.clone(), tool_set);
|
||||
|
||||
let mut enabled_tools = cx
|
||||
.read(|cx| profile.enabled_tools(cx))
|
||||
.into_iter()
|
||||
.map(|tool| tool.name())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort();
|
||||
|
||||
let mut expected_tools = profile_settings.context_servers["mcp"]
|
||||
.tools
|
||||
.iter()
|
||||
.filter_map(|(key, enabled)| enabled.then(|| key.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
expected_tools.sort();
|
||||
|
||||
assert_eq!(enabled_tools, expected_tools);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_only_built_in(cx: &mut TestAppContext) {
|
||||
init_test_settings(cx);
|
||||
|
||||
let id = AgentProfileId("write_minus_mcp".into());
|
||||
let profile_settings = cx.read(|cx| {
|
||||
AgentSettings::get_global(cx)
|
||||
.profiles
|
||||
.get(&id)
|
||||
.unwrap()
|
||||
.clone()
|
||||
});
|
||||
let tool_set = default_tool_set(cx);
|
||||
|
||||
let profile = AgentProfile::new(id.clone(), tool_set);
|
||||
|
||||
let mut enabled_tools = cx
|
||||
.read(|cx| profile.enabled_tools(cx))
|
||||
.into_iter()
|
||||
.map(|tool| tool.name())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort();
|
||||
|
||||
let mut expected_tools = profile_settings
|
||||
.tools
|
||||
.into_iter()
|
||||
.filter_map(|(tool, enabled)| enabled.then_some(tool.to_string()))
|
||||
// Provider dependent
|
||||
.filter(|tool| tool != "web_search")
|
||||
.collect::<Vec<_>>();
|
||||
expected_tools.sort();
|
||||
|
||||
assert_eq!(enabled_tools, expected_tools);
|
||||
}
|
||||
|
||||
fn init_test_settings(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
Project::init_settings(cx);
|
||||
AgentSettings::register(cx);
|
||||
language_model::init_settings(cx);
|
||||
ToolRegistry::default_global(cx);
|
||||
assistant_tools::init(FakeHttpClient::with_404_response(), cx);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
let mut agent_settings = AgentSettings::get_global(cx).clone();
|
||||
agent_settings.profiles.insert(
|
||||
AgentProfileId("write_minus_mcp".into()),
|
||||
AgentProfileSettings {
|
||||
name: "write_minus_mcp".into(),
|
||||
enable_all_context_servers: false,
|
||||
..agent_settings.profiles[&AgentProfileId::default()].clone()
|
||||
},
|
||||
);
|
||||
agent_settings.profiles.insert(
|
||||
AgentProfileId("custom_mcp".into()),
|
||||
AgentProfileSettings {
|
||||
name: "mcp".into(),
|
||||
tools: IndexMap::default(),
|
||||
enable_all_context_servers: false,
|
||||
context_servers: IndexMap::from_iter([("mcp".into(), context_server_preset())]),
|
||||
},
|
||||
);
|
||||
AgentSettings::override_global(agent_settings, cx);
|
||||
})
|
||||
}
|
||||
|
||||
fn context_server_preset() -> ContextServerPreset {
|
||||
ContextServerPreset {
|
||||
tools: IndexMap::from_iter([
|
||||
("enabled_mcp_tool".into(), true),
|
||||
("disabled_mcp_tool".into(), false),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_tool_set(cx: &mut TestAppContext) -> Entity<ToolWorkingSet> {
|
||||
cx.new(|_| {
|
||||
let mut tool_set = ToolWorkingSet::default();
|
||||
tool_set.insert(Arc::new(FakeTool::new("enabled_mcp_tool", "mcp")));
|
||||
tool_set.insert(Arc::new(FakeTool::new("disabled_mcp_tool", "mcp")));
|
||||
tool_set
|
||||
})
|
||||
}
|
||||
|
||||
struct FakeTool {
|
||||
name: String,
|
||||
source: SharedString,
|
||||
}
|
||||
|
||||
impl FakeTool {
|
||||
fn new(name: impl Into<String>, source: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
source: source.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tool for FakeTool {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn source(&self) -> ToolSource {
|
||||
ToolSource::ContextServer {
|
||||
id: self.source.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn icon(&self) -> icons::IconName {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn ui_text(&self, _input: &serde_json::Value) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: serde_json::Value,
|
||||
_request: Arc<language_model::LanguageModelRequest>,
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<assistant_tool::ActionLog>,
|
||||
_model: Arc<dyn language_model::LanguageModel>,
|
||||
_window: Option<gpui::AnyWindowHandle>,
|
||||
_cx: &mut App,
|
||||
) -> assistant_tool::ToolResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn may_perform_edits(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,25 @@
|
||||
use std::fmt::{self, Display, Formatter, Write as _};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
|
||||
use assistant_context_editor::AssistantContext;
|
||||
use crate::thread::Thread;
|
||||
use assistant_context::AssistantContext;
|
||||
use assistant_tool::outline;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::display_map::CreaseId;
|
||||
use editor::{Addon, Editor};
|
||||
use collections::HashSet;
|
||||
use futures::future;
|
||||
use futures::{FutureExt, future::Shared};
|
||||
use gpui::{App, AppContext as _, Entity, SharedString, Subscription, Task};
|
||||
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
|
||||
use icons::IconName;
|
||||
use language::{Buffer, ParseStatus};
|
||||
use language_model::{LanguageModelImage, LanguageModelRequestMessage, MessageContent};
|
||||
use project::{Project, ProjectEntryId, ProjectPath, Worktree};
|
||||
use prompt_store::{PromptStore, UserPromptId};
|
||||
use ref_cast::RefCast;
|
||||
use rope::Point;
|
||||
use std::fmt::{self, Display, Formatter, Write as _};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
use text::{Anchor, OffsetRangeExt as _};
|
||||
use ui::{Context, ElementId, IconName};
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
use util::{ResultExt as _, post_inc};
|
||||
|
||||
use crate::context_store::{ContextStore, ContextStoreEvent};
|
||||
use crate::thread::Thread;
|
||||
|
||||
pub const RULES_ICON: IconName = IconName::Context;
|
||||
|
||||
pub enum ContextKind {
|
||||
@@ -1117,69 +1112,6 @@ impl Hash for AgentContextKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContextCreasesAddon {
|
||||
creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Addon for ContextCreasesAddon {
|
||||
fn to_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextCreasesAddon {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
creases: HashMap::default(),
|
||||
_subscription: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_creases(
|
||||
&mut self,
|
||||
context_store: &Entity<ContextStore>,
|
||||
key: AgentContextKey,
|
||||
creases: impl IntoIterator<Item = (CreaseId, SharedString)>,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.creases.entry(key).or_default().extend(creases);
|
||||
self._subscription = Some(cx.subscribe(
|
||||
&context_store,
|
||||
|editor, _, event, cx| match event {
|
||||
ContextStoreEvent::ContextRemoved(key) => {
|
||||
let Some(this) = editor.addon_mut::<Self>() else {
|
||||
return;
|
||||
};
|
||||
let (crease_ids, replacement_texts): (Vec<_>, Vec<_>) = this
|
||||
.creases
|
||||
.remove(key)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let ranges = editor
|
||||
.remove_creases(crease_ids, cx)
|
||||
.into_iter()
|
||||
.map(|(_, range)| range)
|
||||
.collect::<Vec<_>>();
|
||||
editor.unfold_ranges(&ranges, false, false, cx);
|
||||
editor.edit(ranges.into_iter().zip(replacement_texts), cx);
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>> {
|
||||
self.creases
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use context_server::ContextServerId;
|
||||
use extension::{ContextServerConfiguration, ExtensionManifest};
|
||||
use gpui::Task;
|
||||
use language::LanguageRegistry;
|
||||
use project::context_server_store::registry::ContextServerDescriptorRegistry;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::agent_configuration::ConfigureContextServerModal;
|
||||
|
||||
pub(crate) fn init(language_registry: Arc<LanguageRegistry>, cx: &mut App) {
|
||||
cx.observe_new(move |_: &mut Workspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
|
||||
cx.subscribe_in(extension_events, window, {
|
||||
let language_registry = language_registry.clone();
|
||||
move |workspace, _, event, window, cx| match event {
|
||||
extension::Event::ExtensionInstalled(manifest) => {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ConfigureExtensionRequested(manifest) => {
|
||||
if !manifest.context_servers.is_empty() {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
log::info!(
|
||||
"No extension events global found. Skipping context server configuration wizard"
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub enum Configuration {
|
||||
NotAvailable(ContextServerId, Option<SharedString>),
|
||||
Required(
|
||||
ContextServerId,
|
||||
Option<SharedString>,
|
||||
ContextServerConfiguration,
|
||||
),
|
||||
}
|
||||
|
||||
fn show_configure_mcp_modal(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Workspace>,
|
||||
) {
|
||||
let context_server_store = workspace.project().read(cx).context_server_store();
|
||||
let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
|
||||
|
||||
let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
|
||||
let worktree_store = workspace.project().read(cx).worktree_store();
|
||||
let configuration_tasks = manifest
|
||||
.context_servers
|
||||
.keys()
|
||||
.cloned()
|
||||
.map({
|
||||
|key| {
|
||||
let Some(descriptor) = registry.context_server_descriptor(&key) else {
|
||||
return Task::ready(Configuration::NotAvailable(
|
||||
ContextServerId(key),
|
||||
repository.clone(),
|
||||
));
|
||||
};
|
||||
cx.spawn({
|
||||
let repository_url = repository.clone();
|
||||
let worktree_store = worktree_store.clone();
|
||||
async move |_, cx| {
|
||||
let configuration = descriptor
|
||||
.configuration(worktree_store.clone(), &cx)
|
||||
.await
|
||||
.context("Failed to resolve context server configuration")
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
match configuration {
|
||||
Some(config) => Configuration::Required(
|
||||
ContextServerId(key),
|
||||
repository_url,
|
||||
config,
|
||||
),
|
||||
None => {
|
||||
Configuration::NotAvailable(ContextServerId(key), repository_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let jsonc_language = language_registry.language_for_name("jsonc");
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let configurations = futures::future::join_all(configuration_tasks).await;
|
||||
let jsonc_language = jsonc_language.await.ok();
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let workspace = cx.entity().downgrade();
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
ConfigureContextServerModal::new(
|
||||
configurations.into_iter(),
|
||||
context_server_store,
|
||||
jsonc_language,
|
||||
language_registry,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -4,9 +4,9 @@ use anyhow::{Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
|
||||
use context_server::{ContextServerId, types};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use icons::IconName;
|
||||
use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
|
||||
use project::{Project, context_server_store::ContextServerStore};
|
||||
use ui::IconName;
|
||||
|
||||
pub struct ContextServerTool {
|
||||
store: Entity<ContextServerStore>,
|
||||
@@ -104,7 +104,15 @@ impl Tool for ContextServerTool {
|
||||
tool_name,
|
||||
arguments
|
||||
);
|
||||
let response = protocol.run_tool(tool_name, arguments).await?;
|
||||
let response = protocol
|
||||
.request::<context_server::types::requests::CallTool>(
|
||||
context_server::types::CallToolParams {
|
||||
name: tool_name,
|
||||
arguments,
|
||||
meta: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut result = String::new();
|
||||
for content in response.content {
|
||||
@@ -115,6 +123,9 @@ impl Tool for ContextServerTool {
|
||||
types::ToolResponseContent::Image { .. } => {
|
||||
log::warn!("Ignoring image content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Audio { .. } => {
|
||||
log::warn!("Ignoring audio content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Resource { .. } => {
|
||||
log::warn!("Ignoring resource content from tool response");
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
context::{
|
||||
AgentContextHandle, AgentContextKey, ContextId, ContextKind, DirectoryContextHandle,
|
||||
FetchedUrlContext, FileContextHandle, ImageContext, RulesContextHandle,
|
||||
SelectionContextHandle, SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
|
||||
},
|
||||
thread::{MessageId, Thread, ThreadId},
|
||||
thread_store::ThreadStore,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_context_editor::AssistantContext;
|
||||
use assistant_context::AssistantContext;
|
||||
use collections::{HashSet, IndexSet};
|
||||
use futures::{self, FutureExt};
|
||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||
use language::{Buffer, File as _};
|
||||
use language_model::LanguageModelImage;
|
||||
use project::image_store::is_image_file;
|
||||
use project::{Project, ProjectItem, ProjectPath, Symbol};
|
||||
use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file};
|
||||
use prompt_store::UserPromptId;
|
||||
use ref_cast::RefCast as _;
|
||||
use text::{Anchor, OffsetRangeExt};
|
||||
|
||||
use crate::ThreadStore;
|
||||
use crate::context::{
|
||||
AgentContextHandle, AgentContextKey, ContextId, DirectoryContextHandle, FetchedUrlContext,
|
||||
FileContextHandle, ImageContext, RulesContextHandle, SelectionContextHandle,
|
||||
SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
|
||||
use std::{
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use crate::context_strip::SuggestedContext;
|
||||
use crate::thread::{MessageId, Thread, ThreadId};
|
||||
use text::{Anchor, OffsetRangeExt};
|
||||
|
||||
pub struct ContextStore {
|
||||
project: WeakEntity<Project>,
|
||||
@@ -561,6 +561,49 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
name: SharedString,
|
||||
thread: WeakEntity<Thread>,
|
||||
},
|
||||
TextThread {
|
||||
name: SharedString,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
Self::Thread { name, .. } => name,
|
||||
Self::TextThread { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
Self::Thread { .. } => None,
|
||||
Self::TextThread { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ContextKind {
|
||||
match self {
|
||||
Self::File { .. } => ContextKind::File,
|
||||
Self::Thread { .. } => ContextKind::Thread,
|
||||
Self::TextThread { .. } => ContextKind::TextThread,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileInclusion {
|
||||
Direct,
|
||||
InDirectory { full_path: PathBuf },
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
use std::{collections::VecDeque, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use assistant_context_editor::{AssistantContext, SavedContextMetadata};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::future::{TryFutureExt as _, join_all};
|
||||
use gpui::{Entity, Task, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::future::FutureExt;
|
||||
use std::time::Duration;
|
||||
use ui::{App, SharedString, Window};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
Thread,
|
||||
thread::ThreadId,
|
||||
ThreadId,
|
||||
thread_store::{SerializedThreadMetadata, ThreadStore},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_context::SavedContextMetadata;
|
||||
use chrono::{DateTime, Utc};
|
||||
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||
use itertools::Itertools;
|
||||
use paths::contexts_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration};
|
||||
use util::ResultExt as _;
|
||||
|
||||
const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
|
||||
const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
|
||||
@@ -41,52 +36,34 @@ impl HistoryEntry {
|
||||
HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &SharedString {
|
||||
match self {
|
||||
HistoryEntry::Thread(thread) => &thread.summary,
|
||||
HistoryEntry::Context(context) => &context.title,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic identifier for a history entry.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum HistoryEntryId {
|
||||
Thread(ThreadId),
|
||||
Context(Arc<Path>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum RecentEntry {
|
||||
Thread(ThreadId, Entity<Thread>),
|
||||
Context(Entity<AssistantContext>),
|
||||
}
|
||||
|
||||
impl PartialEq for RecentEntry {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Thread(l0, _), Self::Thread(r0, _)) => l0 == r0,
|
||||
(Self::Context(l0), Self::Context(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RecentEntry {}
|
||||
|
||||
impl RecentEntry {
|
||||
pub(crate) fn summary(&self, cx: &App) -> SharedString {
|
||||
match self {
|
||||
RecentEntry::Thread(_, thread) => thread.read(cx).summary().or_default(),
|
||||
RecentEntry::Context(context) => context.read(cx).summary().or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum SerializedRecentEntry {
|
||||
enum SerializedRecentOpen {
|
||||
Thread(String),
|
||||
ContextName(String),
|
||||
/// Old format which stores the full path
|
||||
Context(String),
|
||||
}
|
||||
|
||||
pub struct HistoryStore {
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
recently_opened_entries: VecDeque<RecentEntry>,
|
||||
context_store: Entity<assistant_context::ContextStore>,
|
||||
recently_opened_entries: VecDeque<HistoryEntryId>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
_save_recently_opened_entries_task: Task<()>,
|
||||
}
|
||||
@@ -94,9 +71,8 @@ pub struct HistoryStore {
|
||||
impl HistoryStore {
|
||||
pub fn new(
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
initial_recent_entries: impl IntoIterator<Item = RecentEntry>,
|
||||
window: &mut Window,
|
||||
context_store: Entity<assistant_context::ContextStore>,
|
||||
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscriptions = vec![
|
||||
@@ -104,68 +80,20 @@ impl HistoryStore {
|
||||
cx.observe(&context_store, |_, _, cx| cx.notify()),
|
||||
];
|
||||
|
||||
window
|
||||
.spawn(cx, {
|
||||
let thread_store = thread_store.downgrade();
|
||||
let context_store = context_store.downgrade();
|
||||
let this = cx.weak_entity();
|
||||
async move |cx| {
|
||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
||||
let contents = cx
|
||||
.background_spawn(async move { std::fs::read_to_string(path) })
|
||||
.await
|
||||
.ok()?;
|
||||
let entries = serde_json::from_str::<Vec<SerializedRecentEntry>>(&contents)
|
||||
.context("deserializing persisted agent panel navigation history")
|
||||
.log_err()?
|
||||
.into_iter()
|
||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
||||
.map(|serialized| match serialized {
|
||||
SerializedRecentEntry::Thread(id) => thread_store
|
||||
.update_in(cx, |thread_store, window, cx| {
|
||||
let thread_id = ThreadId::from(id.as_str());
|
||||
thread_store
|
||||
.open_thread(&thread_id, window, cx)
|
||||
.map_ok(|thread| RecentEntry::Thread(thread_id, thread))
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
async {
|
||||
anyhow::bail!("no thread store");
|
||||
}
|
||||
.boxed()
|
||||
}),
|
||||
SerializedRecentEntry::Context(id) => context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store
|
||||
.open_local_context(Path::new(&id).into(), cx)
|
||||
.map_ok(RecentEntry::Context)
|
||||
.boxed()
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
async {
|
||||
anyhow::bail!("no context store");
|
||||
}
|
||||
.boxed()
|
||||
}),
|
||||
});
|
||||
let entries = join_all(entries)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|result| result.log_with_level(log::Level::Debug))
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.recently_opened_entries.extend(entries);
|
||||
this.recently_opened_entries
|
||||
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
|
||||
})
|
||||
.ok();
|
||||
|
||||
Some(())
|
||||
}
|
||||
cx.spawn(async move |this, cx| {
|
||||
let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
|
||||
this.update(cx, |this, _| {
|
||||
this.recently_opened_entries
|
||||
.extend(
|
||||
entries.into_iter().take(
|
||||
MAX_RECENTLY_OPENED_ENTRIES
|
||||
.saturating_sub(this.recently_opened_entries.len()),
|
||||
),
|
||||
);
|
||||
})
|
||||
.detach();
|
||||
.ok()
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
thread_store,
|
||||
@@ -184,19 +112,20 @@ impl HistoryStore {
|
||||
return history_entries;
|
||||
}
|
||||
|
||||
for thread in self
|
||||
.thread_store
|
||||
.update(cx, |this, _cx| this.reverse_chronological_threads())
|
||||
{
|
||||
history_entries.push(HistoryEntry::Thread(thread));
|
||||
}
|
||||
|
||||
for context in self
|
||||
.context_store
|
||||
.update(cx, |this, _cx| this.reverse_chronological_contexts())
|
||||
{
|
||||
history_entries.push(HistoryEntry::Context(context));
|
||||
}
|
||||
history_entries.extend(
|
||||
self.thread_store
|
||||
.read(cx)
|
||||
.reverse_chronological_threads()
|
||||
.cloned()
|
||||
.map(HistoryEntry::Thread),
|
||||
);
|
||||
history_entries.extend(
|
||||
self.context_store
|
||||
.read(cx)
|
||||
.unordered_contexts()
|
||||
.cloned()
|
||||
.map(HistoryEntry::Context),
|
||||
);
|
||||
|
||||
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
|
||||
history_entries
|
||||
@@ -206,15 +135,62 @@ impl HistoryStore {
|
||||
self.entries(cx).into_iter().take(limit).collect()
|
||||
}
|
||||
|
||||
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
|
||||
#[cfg(debug_assertions)]
|
||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let thread_entries = self
|
||||
.thread_store
|
||||
.read(cx)
|
||||
.reverse_chronological_threads()
|
||||
.flat_map(|thread| {
|
||||
self.recently_opened_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| match entry {
|
||||
HistoryEntryId::Thread(id) if &thread.id == id => {
|
||||
Some((index, HistoryEntry::Thread(thread.clone())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
|
||||
let context_entries =
|
||||
self.context_store
|
||||
.read(cx)
|
||||
.unordered_contexts()
|
||||
.flat_map(|context| {
|
||||
self.recently_opened_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| match entry {
|
||||
HistoryEntryId::Context(path) if &context.path == path => {
|
||||
Some((index, HistoryEntry::Context(context.clone())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
|
||||
thread_entries
|
||||
.chain(context_entries)
|
||||
// optimization to halt iteration early
|
||||
.take(self.recently_opened_entries.len())
|
||||
.sorted_unstable_by_key(|(index, _)| *index)
|
||||
.map(|(_, entry)| entry)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
|
||||
let serialized_entries = self
|
||||
.recently_opened_entries
|
||||
.iter()
|
||||
.filter_map(|entry| match entry {
|
||||
RecentEntry::Context(context) => Some(SerializedRecentEntry::Context(
|
||||
context.read(cx).path()?.to_str()?.to_owned(),
|
||||
)),
|
||||
RecentEntry::Thread(id, _) => Some(SerializedRecentEntry::Thread(id.to_string())),
|
||||
HistoryEntryId::Context(path) => path.file_name().map(|file| {
|
||||
SerializedRecentOpen::ContextName(file.to_string_lossy().to_string())
|
||||
}),
|
||||
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -233,7 +209,33 @@ impl HistoryStore {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn push_recently_opened_entry(&mut self, entry: RecentEntry, cx: &mut Context<Self>) {
|
||||
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> {
|
||||
cx.background_spawn(async move {
|
||||
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
|
||||
let contents = smol::fs::read_to_string(path).await?;
|
||||
let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
|
||||
.context("deserializing persisted agent panel navigation history")?
|
||||
.into_iter()
|
||||
.take(MAX_RECENTLY_OPENED_ENTRIES)
|
||||
.flat_map(|entry| match entry {
|
||||
SerializedRecentOpen::Thread(id) => {
|
||||
Some(HistoryEntryId::Thread(id.as_str().into()))
|
||||
}
|
||||
SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
|
||||
contexts_dir().join(file_name).into(),
|
||||
)),
|
||||
SerializedRecentOpen::Context(path) => {
|
||||
Path::new(&path).file_name().map(|file_name| {
|
||||
HistoryEntryId::Context(contexts_dir().join(file_name).into())
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(entries)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
|
||||
self.recently_opened_entries
|
||||
.retain(|old_entry| old_entry != &entry);
|
||||
self.recently_opened_entries.push_front(entry);
|
||||
@@ -244,24 +246,33 @@ impl HistoryStore {
|
||||
|
||||
pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
|
||||
self.recently_opened_entries.retain(|entry| match entry {
|
||||
RecentEntry::Thread(thread_id, _) if thread_id == &id => false,
|
||||
HistoryEntryId::Thread(thread_id) if thread_id == &id => false,
|
||||
_ => true,
|
||||
});
|
||||
self.save_recently_opened_entries(cx);
|
||||
}
|
||||
|
||||
pub fn remove_recently_opened_entry(&mut self, entry: &RecentEntry, cx: &mut Context<Self>) {
|
||||
pub fn replace_recently_opened_text_thread(
|
||||
&mut self,
|
||||
old_path: &Path,
|
||||
new_path: &Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
for entry in &mut self.recently_opened_entries {
|
||||
match entry {
|
||||
HistoryEntryId::Context(path) if path.as_ref() == old_path => {
|
||||
*entry = HistoryEntryId::Context(new_path.clone());
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.save_recently_opened_entries(cx);
|
||||
}
|
||||
|
||||
pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
|
||||
self.recently_opened_entries
|
||||
.retain(|old_entry| old_entry != entry);
|
||||
self.save_recently_opened_entries(cx);
|
||||
}
|
||||
|
||||
pub fn recently_opened_entries(&self, _cx: &mut Context<Self>) -> VecDeque<RecentEntry> {
|
||||
#[cfg(debug_assertions)]
|
||||
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
|
||||
return VecDeque::new();
|
||||
}
|
||||
|
||||
self.recently_opened_entries.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
These files changed since last read:
|
||||
@@ -1,22 +1,25 @@
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, CompletionMode};
|
||||
use crate::{
|
||||
context_server_tool::ContextServerTool,
|
||||
thread::{
|
||||
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||
},
|
||||
};
|
||||
use agent_settings::{AgentProfileId, CompletionMode};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::future::{self, BoxFuture, Shared};
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use futures::{
|
||||
FutureExt as _, StreamExt as _,
|
||||
channel::{mpsc, oneshot},
|
||||
future::{self, BoxFuture, Shared},
|
||||
};
|
||||
use gpui::{
|
||||
App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
|
||||
Subscription, Task, prelude::*,
|
||||
Subscription, Task, Window, prelude::*,
|
||||
};
|
||||
|
||||
use indoc::indoc;
|
||||
use language_model::{LanguageModelToolResultContent, LanguageModelToolUseId, Role, TokenUsage};
|
||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||
@@ -25,20 +28,18 @@ use prompt_store::{
|
||||
UserRulesContext, WorktreeContext,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use ui::Window;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::context_server_tool::ContextServerTool;
|
||||
use crate::thread::{
|
||||
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use sqlez::{
|
||||
bindable::{Bind, Column},
|
||||
connection::Connection,
|
||||
statement::Statement,
|
||||
};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DataType {
|
||||
@@ -70,7 +71,7 @@ impl Column for DataType {
|
||||
}
|
||||
}
|
||||
|
||||
const RULES_FILE_NAMES: [&'static str; 8] = [
|
||||
const RULES_FILE_NAMES: [&'static str; 9] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
@@ -79,6 +80,7 @@ const RULES_FILE_NAMES: [&'static str; 8] = [
|
||||
"CLAUDE.md",
|
||||
"AGENT.md",
|
||||
"AGENTS.md",
|
||||
"GEMINI.md",
|
||||
];
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
@@ -90,12 +92,12 @@ pub fn init(cx: &mut App) {
|
||||
pub struct SharedProjectContext(Rc<RefCell<Option<ProjectContext>>>);
|
||||
|
||||
impl SharedProjectContext {
|
||||
pub fn borrow(&self) -> Ref<Option<ProjectContext>> {
|
||||
pub fn borrow(&self) -> Ref<'_, Option<ProjectContext>> {
|
||||
self.0.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
pub type TextThreadStore = assistant_context_editor::ContextStore;
|
||||
pub type TextThreadStore = assistant_context::ContextStore;
|
||||
|
||||
pub struct ThreadStore {
|
||||
project: Entity<Project>,
|
||||
@@ -147,12 +149,7 @@ impl ThreadStore {
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Self, oneshot::Receiver<()>) {
|
||||
let mut subscriptions = vec![
|
||||
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
|
||||
this.load_default_profile(cx);
|
||||
}),
|
||||
cx.subscribe(&project, Self::handle_project_event),
|
||||
];
|
||||
let mut subscriptions = vec![cx.subscribe(&project, Self::handle_project_event)];
|
||||
|
||||
if let Some(prompt_store) = prompt_store.as_ref() {
|
||||
subscriptions.push(cx.subscribe(
|
||||
@@ -200,7 +197,6 @@ impl ThreadStore {
|
||||
_reload_system_prompt_task: reload_system_prompt_task,
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
this.load_default_profile(cx);
|
||||
this.register_context_server_handlers(cx);
|
||||
this.reload(cx).detach_and_log_err(cx);
|
||||
(this, ready_rx)
|
||||
@@ -312,17 +308,19 @@ impl ThreadStore {
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
|
||||
let root_name = worktree.read(cx).root_name().into();
|
||||
let tree = worktree.read(cx);
|
||||
let root_name = tree.root_name().into();
|
||||
let abs_path = tree.abs_path();
|
||||
|
||||
let mut context = WorktreeContext {
|
||||
root_name,
|
||||
abs_path,
|
||||
rules_file: None,
|
||||
};
|
||||
|
||||
let rules_task = Self::load_worktree_rules_file(worktree, project, cx);
|
||||
let Some(rules_task) = rules_task else {
|
||||
return Task::ready((
|
||||
WorktreeContext {
|
||||
root_name,
|
||||
rules_file: None,
|
||||
},
|
||||
None,
|
||||
));
|
||||
return Task::ready((context, None));
|
||||
};
|
||||
|
||||
cx.spawn(async move |_| {
|
||||
@@ -335,11 +333,8 @@ impl ThreadStore {
|
||||
}),
|
||||
),
|
||||
};
|
||||
let worktree_info = WorktreeContext {
|
||||
root_name,
|
||||
rules_file,
|
||||
};
|
||||
(worktree_info, rules_file_error)
|
||||
context.rules_file = rules_file;
|
||||
(context, rules_file_error)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -348,12 +343,12 @@ impl ThreadStore {
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<RulesFileContext>>> {
|
||||
let worktree_ref = worktree.read(cx);
|
||||
let worktree_id = worktree_ref.id();
|
||||
let worktree = worktree.read(cx);
|
||||
let worktree_id = worktree.id();
|
||||
let selected_rules_file = RULES_FILE_NAMES
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree_ref
|
||||
worktree
|
||||
.entry_for_path(name)
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| entry.path.clone())
|
||||
@@ -400,16 +395,11 @@ impl ThreadStore {
|
||||
self.threads.len()
|
||||
}
|
||||
|
||||
pub fn unordered_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
|
||||
pub fn reverse_chronological_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
|
||||
// ordering is from "ORDER BY" in `list_threads`
|
||||
self.threads.iter()
|
||||
}
|
||||
|
||||
pub fn reverse_chronological_threads(&self) -> Vec<SerializedThreadMetadata> {
|
||||
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
|
||||
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
|
||||
threads
|
||||
}
|
||||
|
||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
||||
cx.new(|cx| {
|
||||
Thread::new(
|
||||
@@ -520,92 +510,15 @@ impl ThreadStore {
|
||||
})
|
||||
}
|
||||
|
||||
fn load_default_profile(&self, cx: &mut Context<Self>) {
|
||||
let assistant_settings = AgentSettings::get_global(cx);
|
||||
|
||||
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
|
||||
}
|
||||
|
||||
pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
|
||||
let assistant_settings = AgentSettings::get_global(cx);
|
||||
|
||||
if let Some(profile) = assistant_settings.profiles.get(&profile_id) {
|
||||
self.load_profile(profile.clone(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_profile(&self, profile: AgentProfile, cx: &mut Context<Self>) {
|
||||
self.tools.update(cx, |tools, cx| {
|
||||
tools.disable_all_tools(cx);
|
||||
tools.enable(
|
||||
ToolSource::Native,
|
||||
&profile
|
||||
.tools
|
||||
.into_iter()
|
||||
.filter_map(|(tool, enabled)| enabled.then(|| tool))
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
if profile.enable_all_context_servers {
|
||||
for context_server_id in self
|
||||
.project
|
||||
.read(cx)
|
||||
.context_server_store()
|
||||
.read(cx)
|
||||
.all_server_ids()
|
||||
{
|
||||
self.tools.update(cx, |tools, cx| {
|
||||
tools.enable_source(
|
||||
ToolSource::ContextServer {
|
||||
id: context_server_id.0.into(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
// Enable all the tools from all context servers, but disable the ones that are explicitly disabled
|
||||
for (context_server_id, preset) in profile.context_servers {
|
||||
self.tools.update(cx, |tools, cx| {
|
||||
tools.disable(
|
||||
ToolSource::ContextServer {
|
||||
id: context_server_id.into(),
|
||||
},
|
||||
&preset
|
||||
.tools
|
||||
.into_iter()
|
||||
.filter_map(|(tool, enabled)| (!enabled).then(|| tool))
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for (context_server_id, preset) in profile.context_servers {
|
||||
self.tools.update(cx, |tools, cx| {
|
||||
tools.enable(
|
||||
ToolSource::ContextServer {
|
||||
id: context_server_id.into(),
|
||||
},
|
||||
&preset
|
||||
.tools
|
||||
.into_iter()
|
||||
.filter_map(|(tool, enabled)| enabled.then(|| tool))
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
||||
cx.subscribe(
|
||||
&self.project.read(cx).context_server_store(),
|
||||
Self::handle_context_server_event,
|
||||
)
|
||||
.detach();
|
||||
let context_server_store = self.project.read(cx).context_server_store();
|
||||
cx.subscribe(&context_server_store, Self::handle_context_server_event)
|
||||
.detach();
|
||||
|
||||
// Check for any servers that were already running before the handler was registered
|
||||
for server in context_server_store.read(cx).running_servers() {
|
||||
self.load_context_server_tools(server.id(), context_server_store.clone(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_context_server_event(
|
||||
@@ -618,71 +531,71 @@ impl ThreadStore {
|
||||
match event {
|
||||
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
|
||||
match status {
|
||||
ContextServerStatus::Starting => {}
|
||||
ContextServerStatus::Running => {
|
||||
if let Some(server) =
|
||||
context_server_store.read(cx).get_running_server(server_id)
|
||||
{
|
||||
let context_server_manager = context_server_store.clone();
|
||||
cx.spawn({
|
||||
let server = server.clone();
|
||||
let server_id = server_id.clone();
|
||||
async move |this, cx| {
|
||||
let Some(protocol) = server.client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||
let tool_ids = tool_working_set
|
||||
.update(cx, |tool_working_set, _| {
|
||||
tools
|
||||
.tools
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
log::info!(
|
||||
"registering context server tool: {:?}",
|
||||
tool.name
|
||||
);
|
||||
tool_working_set.insert(Arc::new(
|
||||
ContextServerTool::new(
|
||||
context_server_manager.clone(),
|
||||
server.id(),
|
||||
tool,
|
||||
),
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.log_err();
|
||||
|
||||
if let Some(tool_ids) = tool_ids {
|
||||
this.update(cx, |this, cx| {
|
||||
this.context_server_tool_ids
|
||||
.insert(server_id, tool_ids);
|
||||
this.load_default_profile(cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
self.load_context_server_tools(server_id.clone(), context_server_store, cx);
|
||||
}
|
||||
ContextServerStatus::Stopped | ContextServerStatus::Error(_) => {
|
||||
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
||||
tool_working_set.update(cx, |tool_working_set, _| {
|
||||
tool_working_set.remove(&tool_ids);
|
||||
});
|
||||
self.load_default_profile(cx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_context_server_tools(
|
||||
&self,
|
||||
server_id: ContextServerId,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(server) = context_server_store.read(cx).get_running_server(&server_id) else {
|
||||
return;
|
||||
};
|
||||
let tool_working_set = self.tools.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
let Some(protocol) = server.client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||
if let Some(response) = protocol
|
||||
.request::<context_server::types::requests::ListTools>(())
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
let tool_ids = tool_working_set
|
||||
.update(cx, |tool_working_set, _| {
|
||||
response
|
||||
.tools
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
log::info!("registering context server tool: {:?}", tool.name);
|
||||
tool_working_set.insert(Arc::new(ContextServerTool::new(
|
||||
context_server_store.clone(),
|
||||
server.id(),
|
||||
tool,
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.log_err();
|
||||
|
||||
if let Some(tool_ids) = tool_ids {
|
||||
this.update(cx, |this, _| {
|
||||
this.context_server_tool_ids.insert(server_id, tool_ids);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -692,7 +605,7 @@ pub struct SerializedThreadMetadata {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct SerializedThread {
|
||||
pub version: String,
|
||||
pub summary: SharedString,
|
||||
@@ -714,9 +627,11 @@ pub struct SerializedThread {
|
||||
pub completion_mode: Option<CompletionMode>,
|
||||
#[serde(default)]
|
||||
pub tool_use_limit_reached: bool,
|
||||
#[serde(default)]
|
||||
pub profile: Option<AgentProfileId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct SerializedLanguageModel {
|
||||
pub provider: String,
|
||||
pub model: String,
|
||||
@@ -777,11 +692,15 @@ impl SerializedThreadV0_1_0 {
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
SerializedThread { messages, ..self.0 }
|
||||
SerializedThread {
|
||||
messages,
|
||||
version: SerializedThread::VERSION.to_string(),
|
||||
..self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SerializedMessage {
|
||||
pub id: MessageId,
|
||||
pub role: Role,
|
||||
@@ -799,7 +718,7 @@ pub struct SerializedMessage {
|
||||
pub is_hidden: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SerializedMessageSegment {
|
||||
#[serde(rename = "text")]
|
||||
@@ -813,18 +732,18 @@ pub enum SerializedMessageSegment {
|
||||
signature: Option<String>,
|
||||
},
|
||||
RedactedThinking {
|
||||
data: Vec<u8>,
|
||||
data: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SerializedToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub name: SharedString,
|
||||
pub input: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SerializedToolResult {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub is_error: bool,
|
||||
@@ -856,6 +775,7 @@ impl LegacySerializedThread {
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -886,7 +806,7 @@ impl LegacySerializedMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SerializedCrease {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
@@ -1005,7 +925,7 @@ impl ThreadsDatabase {
|
||||
|
||||
fn bytes_encode(
|
||||
item: &Self::EItem,
|
||||
) -> Result<std::borrow::Cow<[u8]>, heed::BoxedError> {
|
||||
) -> Result<std::borrow::Cow<'_, [u8]>, heed::BoxedError> {
|
||||
serde_json::to_vec(&item.0)
|
||||
.map(std::borrow::Cow::Owned)
|
||||
.map_err(Into::into)
|
||||
@@ -1143,3 +1063,181 @@ impl ThreadsDatabase {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::thread::{DetailedSummaryState, MessageId};
|
||||
use chrono::Utc;
|
||||
use language_model::{Role, TokenUsage};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_legacy_serialized_thread_upgrade() {
|
||||
let updated_at = Utc::now();
|
||||
let legacy_thread = LegacySerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![LegacySerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
text: "Hello, world!".to_string(),
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
}],
|
||||
initial_project_snapshot: None,
|
||||
};
|
||||
|
||||
let upgraded = legacy_thread.upgrade();
|
||||
|
||||
assert_eq!(
|
||||
upgraded,
|
||||
SerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Hello, world!".to_string()
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false
|
||||
}],
|
||||
version: SerializedThread::VERSION.to_string(),
|
||||
initial_project_snapshot: None,
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
request_token_usage: vec![],
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialized_threadv0_1_0_upgrade() {
|
||||
let updated_at = Utc::now();
|
||||
let thread_v0_1_0 = SerializedThreadV0_1_0(SerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![
|
||||
SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Use tool_1".to_string(),
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
SerializedMessage {
|
||||
id: MessageId(2),
|
||||
role: Role::Assistant,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "I want to use a tool".to_string(),
|
||||
}],
|
||||
tool_uses: vec![SerializedToolUse {
|
||||
id: "abc".into(),
|
||||
name: "tool_1".into(),
|
||||
input: serde_json::Value::Null,
|
||||
}],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Here is the tool result".to_string(),
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![SerializedToolResult {
|
||||
tool_use_id: "abc".into(),
|
||||
is_error: false,
|
||||
content: LanguageModelToolResultContent::Text("abcdef".into()),
|
||||
output: Some(serde_json::Value::Null),
|
||||
}],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
],
|
||||
version: SerializedThreadV0_1_0::VERSION.to_string(),
|
||||
initial_project_snapshot: None,
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
request_token_usage: vec![],
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None,
|
||||
});
|
||||
let upgraded = thread_v0_1_0.upgrade();
|
||||
|
||||
assert_eq!(
|
||||
upgraded,
|
||||
SerializedThread {
|
||||
summary: "Test conversation".into(),
|
||||
updated_at,
|
||||
messages: vec![
|
||||
SerializedMessage {
|
||||
id: MessageId(1),
|
||||
role: Role::User,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "Use tool_1".to_string()
|
||||
}],
|
||||
tool_uses: vec![],
|
||||
tool_results: vec![],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false
|
||||
},
|
||||
SerializedMessage {
|
||||
id: MessageId(2),
|
||||
role: Role::Assistant,
|
||||
segments: vec![SerializedMessageSegment::Text {
|
||||
text: "I want to use a tool".to_string(),
|
||||
}],
|
||||
tool_uses: vec![SerializedToolUse {
|
||||
id: "abc".into(),
|
||||
name: "tool_1".into(),
|
||||
input: serde_json::Value::Null,
|
||||
}],
|
||||
tool_results: vec![SerializedToolResult {
|
||||
tool_use_id: "abc".into(),
|
||||
is_error: false,
|
||||
content: LanguageModelToolResultContent::Text("abcdef".into()),
|
||||
output: Some(serde_json::Value::Null),
|
||||
}],
|
||||
context: "".to_string(),
|
||||
creases: vec![],
|
||||
is_hidden: false,
|
||||
},
|
||||
],
|
||||
version: SerializedThread::VERSION.to_string(),
|
||||
initial_project_snapshot: None,
|
||||
cumulative_token_usage: TokenUsage::default(),
|
||||
request_token_usage: vec![],
|
||||
detailed_summary_state: DetailedSummaryState::default(),
|
||||
exceeded_window_error: None,
|
||||
model: None,
|
||||
completion_mode: None,
|
||||
tool_use_limit_reached: false,
|
||||
profile: None
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
thread::{MessageId, PromptId, ThreadId},
|
||||
thread_store::SerializedMessage,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{
|
||||
AnyToolCard, Tool, ToolResultContent, ToolResultOutput, ToolUseStatus, ToolWorkingSet,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use futures::FutureExt as _;
|
||||
use futures::future::Shared;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{App, Entity, SharedString, Task, Window};
|
||||
use icons::IconName;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelRequest, LanguageModelToolResult,
|
||||
LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, Role,
|
||||
};
|
||||
use project::Project;
|
||||
use ui::{IconName, Window};
|
||||
use std::sync::Arc;
|
||||
use util::truncate_lines_to_byte_limit;
|
||||
|
||||
use crate::thread::{MessageId, PromptId, ThreadId};
|
||||
use crate::thread_store::SerializedMessage;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
@@ -26,7 +25,7 @@ pub struct ToolUse {
|
||||
pub ui_text: SharedString,
|
||||
pub status: ToolUseStatus,
|
||||
pub input: serde_json::Value,
|
||||
pub icon: ui::IconName,
|
||||
pub icon: icons::IconName,
|
||||
pub needs_confirmation: bool,
|
||||
}
|
||||
|
||||
@@ -427,7 +426,7 @@ impl ToolUseState {
|
||||
|
||||
// Protect from overly large output
|
||||
let tool_output_limit = configured_model
|
||||
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.map(|model| model.model.max_token_count() as usize * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let content = match tool_result {
|
||||
|
||||
@@ -12,18 +12,10 @@ workspace = true
|
||||
path = "src/agent_settings.rs"
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
indexmap.workspace = true
|
||||
language_model.workspace = true
|
||||
lmstudio = { workspace = true, features = ["schemars"] }
|
||||
log.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
deepseek = { workspace = true, features = ["schemars"] }
|
||||
mistral = { workspace = true, features = ["schemars"] }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -17,29 +17,6 @@ pub mod builtin_profiles {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GroupedAgentProfiles {
|
||||
pub builtin: IndexMap<AgentProfileId, AgentProfile>,
|
||||
pub custom: IndexMap<AgentProfileId, AgentProfile>,
|
||||
}
|
||||
|
||||
impl GroupedAgentProfiles {
|
||||
pub fn from_settings(settings: &crate::AgentSettings) -> Self {
|
||||
let mut builtin = IndexMap::default();
|
||||
let mut custom = IndexMap::default();
|
||||
|
||||
for (profile_id, profile) in settings.profiles.clone() {
|
||||
if builtin_profiles::is_builtin(&profile_id) {
|
||||
builtin.insert(profile_id, profile);
|
||||
} else {
|
||||
custom.insert(profile_id, profile);
|
||||
}
|
||||
}
|
||||
|
||||
Self { builtin, custom }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AgentProfileId(pub Arc<str>);
|
||||
|
||||
@@ -63,7 +40,7 @@ impl Default for AgentProfileId {
|
||||
|
||||
/// A profile for the Zed Agent that controls its behavior.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AgentProfile {
|
||||
pub struct AgentProfileSettings {
|
||||
/// The name of the profile.
|
||||
pub name: SharedString,
|
||||
pub tools: IndexMap<Arc<str>, bool>,
|
||||
|
||||
@@ -2,16 +2,10 @@ mod agent_profile;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::open_ai::Model as OpenAiModel;
|
||||
use anthropic::Model as AnthropicModel;
|
||||
use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use deepseek::Model as DeepseekModel;
|
||||
use gpui::{App, Pixels, SharedString};
|
||||
use language_model::LanguageModel;
|
||||
use lmstudio::Model as LmStudioModel;
|
||||
use mistral::Model as MistralModel;
|
||||
use ollama::Model as OllamaModel;
|
||||
use schemars::{JsonSchema, schema::Schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
@@ -48,45 +42,6 @@ pub enum NotifyWhenAgentWaiting {
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(tag = "name", rename_all = "snake_case")]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub enum AgentProviderContentV1 {
|
||||
#[serde(rename = "zed.dev")]
|
||||
ZedDotDev { default_model: Option<String> },
|
||||
#[serde(rename = "openai")]
|
||||
OpenAi {
|
||||
default_model: Option<OpenAiModel>,
|
||||
api_url: Option<String>,
|
||||
available_models: Option<Vec<OpenAiModel>>,
|
||||
},
|
||||
#[serde(rename = "anthropic")]
|
||||
Anthropic {
|
||||
default_model: Option<AnthropicModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
#[serde(rename = "ollama")]
|
||||
Ollama {
|
||||
default_model: Option<OllamaModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
#[serde(rename = "lmstudio")]
|
||||
LmStudio {
|
||||
default_model: Option<LmStudioModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
#[serde(rename = "deepseek")]
|
||||
DeepSeek {
|
||||
default_model: Option<DeepseekModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
#[serde(rename = "mistral")]
|
||||
Mistral {
|
||||
default_model: Option<MistralModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct AgentSettings {
|
||||
pub enabled: bool,
|
||||
@@ -102,7 +57,7 @@ pub struct AgentSettings {
|
||||
pub using_outdated_settings_version: bool,
|
||||
pub default_profile: AgentProfileId,
|
||||
pub default_view: DefaultView,
|
||||
pub profiles: IndexMap<AgentProfileId, AgentProfile>,
|
||||
pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
|
||||
pub always_allow_tool_actions: bool,
|
||||
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
||||
pub play_sound_when_agent_done: bool,
|
||||
@@ -168,444 +123,96 @@ impl LanguageModelParameters {
|
||||
}
|
||||
}
|
||||
|
||||
/// Agent panel settings
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct AgentSettingsContent {
|
||||
#[serde(flatten)]
|
||||
pub inner: Option<AgentSettingsContentInner>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum AgentSettingsContentInner {
|
||||
Versioned(Box<VersionedAgentSettingsContent>),
|
||||
Legacy(LegacyAgentSettingsContent),
|
||||
}
|
||||
|
||||
impl AgentSettingsContentInner {
|
||||
fn for_v2(content: AgentSettingsContentV2) -> Self {
|
||||
AgentSettingsContentInner::Versioned(Box::new(VersionedAgentSettingsContent::V2(content)))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for AgentSettingsContent {
|
||||
fn schema_name() -> String {
|
||||
VersionedAgentSettingsContent::schema_name()
|
||||
}
|
||||
|
||||
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
VersionedAgentSettingsContent::json_schema(r#gen)
|
||||
}
|
||||
|
||||
fn is_referenceable() -> bool {
|
||||
VersionedAgentSettingsContent::is_referenceable()
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentSettingsContent {
|
||||
pub fn is_version_outdated(&self) -> bool {
|
||||
match &self.inner {
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(_) => true,
|
||||
VersionedAgentSettingsContent::V2(_) => false,
|
||||
},
|
||||
Some(AgentSettingsContentInner::Legacy(_)) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> AgentSettingsContentV2 {
|
||||
match &self.inner {
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(ref settings) => AgentSettingsContentV2 {
|
||||
enabled: settings.enabled,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
default_height: settings.default_width,
|
||||
default_model: settings
|
||||
.provider
|
||||
.clone()
|
||||
.and_then(|provider| match provider {
|
||||
AgentProviderContentV1::ZedDotDev { default_model } => default_model
|
||||
.map(|model| LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
model,
|
||||
}),
|
||||
AgentProviderContentV1::OpenAi { default_model, .. } => default_model
|
||||
.map(|model| LanguageModelSelection {
|
||||
provider: "openai".into(),
|
||||
model: model.id().to_string(),
|
||||
}),
|
||||
AgentProviderContentV1::Anthropic { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "anthropic".into(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AgentProviderContentV1::Ollama { default_model, .. } => default_model
|
||||
.map(|model| LanguageModelSelection {
|
||||
provider: "ollama".into(),
|
||||
model: model.id().to_string(),
|
||||
}),
|
||||
AgentProviderContentV1::LmStudio { default_model, .. } => default_model
|
||||
.map(|model| LanguageModelSelection {
|
||||
provider: "lmstudio".into(),
|
||||
model: model.id().to_string(),
|
||||
}),
|
||||
AgentProviderContentV1::DeepSeek { default_model, .. } => default_model
|
||||
.map(|model| LanguageModelSelection {
|
||||
provider: "deepseek".into(),
|
||||
model: model.id().to_string(),
|
||||
}),
|
||||
AgentProviderContentV1::Mistral { default_model, .. } => default_model
|
||||
.map(|model| LanguageModelSelection {
|
||||
provider: "mistral".into(),
|
||||
model: model.id().to_string(),
|
||||
}),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
default_profile: None,
|
||||
default_view: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
enable_feedback: None,
|
||||
play_sound_when_agent_done: None,
|
||||
},
|
||||
VersionedAgentSettingsContent::V2(ref settings) => settings.clone(),
|
||||
},
|
||||
Some(AgentSettingsContentInner::Legacy(settings)) => AgentSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
default_height: settings.default_height,
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "openai".into(),
|
||||
model: settings
|
||||
.default_open_ai_model
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.id()
|
||||
.to_string(),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
default_profile: None,
|
||||
default_view: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
enable_feedback: None,
|
||||
play_sound_when_agent_done: None,
|
||||
},
|
||||
None => AgentSettingsContentV2::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dock(&mut self, dock: AgentDockPosition) {
|
||||
match &mut self.inner {
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(ref mut settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
VersionedAgentSettingsContent::V2(ref mut settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
},
|
||||
Some(AgentSettingsContentInner::Legacy(settings)) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
None => {
|
||||
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
dock: Some(dock),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
self.dock = Some(dock);
|
||||
}
|
||||
|
||||
pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
|
||||
let model = language_model.id().0.to_string();
|
||||
let provider = language_model.provider_id().0.to_string();
|
||||
|
||||
match &mut self.inner {
|
||||
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAgentSettingsContent::V1(ref mut settings) => match provider.as_ref() {
|
||||
"zed.dev" => {
|
||||
log::warn!("attempted to set zed.dev model on outdated settings");
|
||||
}
|
||||
"anthropic" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AgentProviderContentV1::Anthropic { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AgentProviderContentV1::Anthropic {
|
||||
default_model: AnthropicModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"ollama" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AgentProviderContentV1::Ollama { api_url, .. }) => api_url.clone(),
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AgentProviderContentV1::Ollama {
|
||||
default_model: Some(ollama::Model::new(
|
||||
&model,
|
||||
None,
|
||||
None,
|
||||
Some(language_model.supports_tools()),
|
||||
Some(language_model.supports_images()),
|
||||
None,
|
||||
)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"lmstudio" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AgentProviderContentV1::LmStudio { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AgentProviderContentV1::LmStudio {
|
||||
default_model: Some(lmstudio::Model::new(&model, None, None, false)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"openai" => {
|
||||
let (api_url, available_models) = match &settings.provider {
|
||||
Some(AgentProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
available_models,
|
||||
..
|
||||
}) => (api_url.clone(), available_models.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
settings.provider = Some(AgentProviderContentV1::OpenAi {
|
||||
default_model: OpenAiModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
available_models,
|
||||
});
|
||||
}
|
||||
"deepseek" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AgentProviderContentV1::DeepSeek { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AgentProviderContentV1::DeepSeek {
|
||||
default_model: DeepseekModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
VersionedAgentSettingsContent::V2(ref mut settings) => {
|
||||
settings.default_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
},
|
||||
Some(AgentSettingsContentInner::Legacy(settings)) => {
|
||||
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
|
||||
settings.default_open_ai_model = Some(model);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
}),
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
}
|
||||
self.default_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
self.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn v2_setting(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut AgentSettingsContentV2) -> anyhow::Result<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
match self.inner.get_or_insert_with(|| {
|
||||
AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
..Default::default()
|
||||
})
|
||||
}) {
|
||||
AgentSettingsContentInner::Versioned(boxed) => {
|
||||
if let VersionedAgentSettingsContent::V2(ref mut settings) = **boxed {
|
||||
f(settings)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
self.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
self.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.always_allow_tool_actions = Some(allow);
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
self.always_allow_tool_actions = Some(allow);
|
||||
}
|
||||
|
||||
pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.play_sound_when_agent_done = Some(allow);
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
self.play_sound_when_agent_done = Some(allow);
|
||||
}
|
||||
|
||||
pub fn set_single_file_review(&mut self, allow: bool) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.single_file_review = Some(allow);
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
self.single_file_review = Some(allow);
|
||||
}
|
||||
|
||||
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
|
||||
self.v2_setting(|setting| {
|
||||
setting.default_profile = Some(profile_id);
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
self.default_profile = Some(profile_id);
|
||||
}
|
||||
|
||||
pub fn create_profile(
|
||||
&mut self,
|
||||
profile_id: AgentProfileId,
|
||||
profile: AgentProfile,
|
||||
profile_settings: AgentProfileSettings,
|
||||
) -> Result<()> {
|
||||
self.v2_setting(|settings| {
|
||||
let profiles = settings.profiles.get_or_insert_default();
|
||||
if profiles.contains_key(&profile_id) {
|
||||
bail!("profile with ID '{profile_id}' already exists");
|
||||
}
|
||||
let profiles = self.profiles.get_or_insert_default();
|
||||
if profiles.contains_key(&profile_id) {
|
||||
bail!("profile with ID '{profile_id}' already exists");
|
||||
}
|
||||
|
||||
profiles.insert(
|
||||
profile_id,
|
||||
AgentProfileContent {
|
||||
name: profile.name.into(),
|
||||
tools: profile.tools,
|
||||
enable_all_context_servers: Some(profile.enable_all_context_servers),
|
||||
context_servers: profile
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| {
|
||||
(
|
||||
server_id,
|
||||
ContextServerPresetContent {
|
||||
tools: preset.tools,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
profiles.insert(
|
||||
profile_id,
|
||||
AgentProfileContent {
|
||||
name: profile_settings.name.into(),
|
||||
tools: profile_settings.tools,
|
||||
enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
|
||||
context_servers: profile_settings
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| {
|
||||
(
|
||||
server_id,
|
||||
ContextServerPresetContent {
|
||||
tools: preset.tools,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "version")]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub enum VersionedAgentSettingsContent {
|
||||
#[serde(rename = "1")]
|
||||
V1(AgentSettingsContentV1),
|
||||
#[serde(rename = "2")]
|
||||
V2(AgentSettingsContentV2),
|
||||
}
|
||||
|
||||
impl Default for VersionedAgentSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self::V2(AgentSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
default_model: None,
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
default_profile: None,
|
||||
default_view: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
enable_feedback: None,
|
||||
play_sound_when_agent_done: None,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct AgentSettingsContentV2 {
|
||||
pub struct AgentSettingsContent {
|
||||
/// Whether the Agent is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
@@ -732,6 +339,7 @@ impl JsonSchema for LanguageModelProviderSetting {
|
||||
"deepseek".into(),
|
||||
"openrouter".into(),
|
||||
"mistral".into(),
|
||||
"vercel".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
@@ -776,65 +384,6 @@ pub struct ContextServerPresetContent {
|
||||
pub tools: IndexMap<Arc<str>, bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct AgentSettingsContentV1 {
|
||||
/// Whether the Agent is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
enabled: Option<bool>,
|
||||
/// Whether to show the Agent panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
button: Option<bool>,
|
||||
/// Where to dock the Agent.
|
||||
///
|
||||
/// Default: right
|
||||
dock: Option<AgentDockPosition>,
|
||||
/// Default width in pixels when the Agent is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
default_width: Option<f32>,
|
||||
/// Default height in pixels when the Agent is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The provider of the Agent service.
|
||||
///
|
||||
/// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
|
||||
/// each with their respective default models and configurations.
|
||||
provider: Option<AgentProviderContentV1>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct LegacyAgentSettingsContent {
|
||||
/// Whether to show the Agent panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
/// Where to dock the Agent.
|
||||
///
|
||||
/// Default: right
|
||||
pub dock: Option<AgentDockPosition>,
|
||||
/// Default width in pixels when the Agent is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
pub default_width: Option<f32>,
|
||||
/// Default height in pixels when the Agent is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
pub default_height: Option<f32>,
|
||||
/// The default OpenAI model to use when creating new chats.
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
/// OpenAI API base URL to use when creating new chats.
|
||||
///
|
||||
/// Default: <https://api.openai.com/v1>
|
||||
pub openai_api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings for AgentSettings {
|
||||
const KEY: Option<&'static str> = Some("agent");
|
||||
|
||||
@@ -851,11 +400,6 @@ impl Settings for AgentSettings {
|
||||
let mut settings = AgentSettings::default();
|
||||
|
||||
for value in sources.defaults_and_customizations() {
|
||||
if value.is_version_outdated() {
|
||||
settings.using_outdated_settings_version = true;
|
||||
}
|
||||
|
||||
let value = value.upgrade();
|
||||
merge(&mut settings.enabled, value.enabled);
|
||||
merge(&mut settings.button, value.button);
|
||||
merge(&mut settings.dock, value.dock);
|
||||
@@ -867,17 +411,23 @@ impl Settings for AgentSettings {
|
||||
&mut settings.default_height,
|
||||
value.default_height.map(Into::into),
|
||||
);
|
||||
merge(&mut settings.default_model, value.default_model);
|
||||
merge(&mut settings.default_model, value.default_model.clone());
|
||||
settings.inline_assistant_model = value
|
||||
.inline_assistant_model
|
||||
.clone()
|
||||
.or(settings.inline_assistant_model.take());
|
||||
settings.commit_message_model = value
|
||||
.clone()
|
||||
.commit_message_model
|
||||
.or(settings.commit_message_model.take());
|
||||
settings.thread_summary_model = value
|
||||
.clone()
|
||||
.thread_summary_model
|
||||
.or(settings.thread_summary_model.take());
|
||||
merge(&mut settings.inline_alternatives, value.inline_alternatives);
|
||||
merge(
|
||||
&mut settings.inline_alternatives,
|
||||
value.inline_alternatives.clone(),
|
||||
);
|
||||
merge(
|
||||
&mut settings.always_allow_tool_actions,
|
||||
value.always_allow_tool_actions,
|
||||
@@ -892,7 +442,7 @@ impl Settings for AgentSettings {
|
||||
);
|
||||
merge(&mut settings.stream_edits, value.stream_edits);
|
||||
merge(&mut settings.single_file_review, value.single_file_review);
|
||||
merge(&mut settings.default_profile, value.default_profile);
|
||||
merge(&mut settings.default_profile, value.default_profile.clone());
|
||||
merge(&mut settings.default_view, value.default_view);
|
||||
merge(
|
||||
&mut settings.preferred_completion_mode,
|
||||
@@ -904,24 +454,24 @@ impl Settings for AgentSettings {
|
||||
.model_parameters
|
||||
.extend_from_slice(&value.model_parameters);
|
||||
|
||||
if let Some(profiles) = value.profiles {
|
||||
if let Some(profiles) = value.profiles.as_ref() {
|
||||
settings
|
||||
.profiles
|
||||
.extend(profiles.into_iter().map(|(id, profile)| {
|
||||
(
|
||||
id,
|
||||
AgentProfile {
|
||||
name: profile.name.into(),
|
||||
tools: profile.tools,
|
||||
id.clone(),
|
||||
AgentProfileSettings {
|
||||
name: profile.name.clone().into(),
|
||||
tools: profile.tools.clone(),
|
||||
enable_all_context_servers: profile
|
||||
.enable_all_context_servers
|
||||
.unwrap_or_default(),
|
||||
context_servers: profile
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|(context_server_id, preset)| {
|
||||
(
|
||||
context_server_id,
|
||||
context_server_id.clone(),
|
||||
ContextServerPreset {
|
||||
tools: preset.tools.clone(),
|
||||
},
|
||||
@@ -942,28 +492,8 @@ impl Settings for AgentSettings {
|
||||
.read_value("chat.agent.enabled")
|
||||
.and_then(|b| b.as_bool())
|
||||
{
|
||||
match &mut current.inner {
|
||||
Some(AgentSettingsContentInner::Versioned(versioned)) => match versioned.as_mut() {
|
||||
VersionedAgentSettingsContent::V1(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
|
||||
VersionedAgentSettingsContent::V2(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
},
|
||||
Some(AgentSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
|
||||
None => {
|
||||
current.inner =
|
||||
Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
enabled: Some(b),
|
||||
button: Some(b),
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
}
|
||||
current.enabled = Some(b);
|
||||
current.button = Some(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -973,149 +503,3 @@ fn merge<T>(target: &mut T, value: Option<T>) {
|
||||
*target = value;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fs::Fs;
|
||||
use gpui::{ReadGlobal, TestAppContext};
|
||||
use settings::SettingsStore;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deserialize_agent_settings_with_version(cx: &mut TestAppContext) {
|
||||
let fs = fs::FakeFs::new(cx.executor().clone());
|
||||
fs.create_dir(paths::settings_file().parent().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.update(|cx| {
|
||||
let test_settings = settings::SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
AgentSettings::register(cx);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(!AgentSettings::get_global(cx).using_outdated_settings_version);
|
||||
assert_eq!(
|
||||
AgentSettings::get_global(cx).default_model,
|
||||
LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
model: "claude-sonnet-4".into(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
settings::SettingsStore::global(cx).update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
|settings, _| {
|
||||
*settings = AgentSettingsContent {
|
||||
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "test-provider".into(),
|
||||
model: "gpt-99".into(),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
default_profile: None,
|
||||
default_view: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
play_sound_when_agent_done: None,
|
||||
notify_when_agent_waiting: None,
|
||||
stream_edits: None,
|
||||
single_file_review: None,
|
||||
enable_feedback: None,
|
||||
model_parameters: Vec::new(),
|
||||
preferred_completion_mode: None,
|
||||
})),
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let raw_settings_value = fs.load(paths::settings_file()).await.unwrap();
|
||||
assert!(raw_settings_value.contains(r#""version": "2""#));
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AgentSettingsTest {
|
||||
agent: AgentSettingsContent,
|
||||
}
|
||||
|
||||
let agent_settings: AgentSettingsTest =
|
||||
serde_json_lenient::from_str(&raw_settings_value).unwrap();
|
||||
|
||||
assert!(!agent_settings.agent.is_version_outdated());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_load_settings_from_old_key(cx: &mut TestAppContext) {
|
||||
let fs = fs::FakeFs::new(cx.executor().clone());
|
||||
fs.create_dir(paths::settings_file().parent().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.update(|cx| {
|
||||
let mut test_settings = settings::SettingsStore::test(cx);
|
||||
let user_settings_content = r#"{
|
||||
"assistant": {
|
||||
"enabled": true,
|
||||
"version": "2",
|
||||
"default_model": {
|
||||
"provider": "zed.dev",
|
||||
"model": "gpt-99"
|
||||
},
|
||||
}}"#;
|
||||
test_settings
|
||||
.set_user_settings(user_settings_content, cx)
|
||||
.unwrap();
|
||||
cx.set_global(test_settings);
|
||||
AgentSettings::register(cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let agent_settings = cx.update(|cx| AgentSettings::get_global(cx).clone());
|
||||
assert!(agent_settings.enabled);
|
||||
assert!(!agent_settings.using_outdated_settings_version);
|
||||
assert_eq!(agent_settings.default_model.model, "gpt-99");
|
||||
|
||||
cx.update_global::<SettingsStore, _>(|settings_store, cx| {
|
||||
settings_store.update_user_settings::<AgentSettings>(cx, |settings| {
|
||||
*settings = AgentSettingsContent {
|
||||
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
|
||||
enabled: Some(false),
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "xai".to_owned().into(),
|
||||
model: "grok".to_owned(),
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AgentSettingsTest {
|
||||
assistant: AgentSettingsContent,
|
||||
agent: Option<serde_json_lenient::Value>,
|
||||
}
|
||||
|
||||
let agent_settings: AgentSettingsTest = serde_json::from_value(settings).unwrap();
|
||||
assert!(agent_settings.agent.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
110
crates/agent_ui/Cargo.toml
Normal file
@@ -0,0 +1,110 @@
|
||||
[package]
|
||||
name = "agent_ui"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/agent_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
"language/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
agent.workspace = true
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
audio.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
context_server.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
indoc.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
inventory.workspace = true
|
||||
itertools.workspace = true
|
||||
jsonschema.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
notifications.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rules_library.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
watch.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant_tools.workspace = true
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
@@ -1,4 +1,3 @@
|
||||
mod add_context_server_modal;
|
||||
mod configure_context_server_modal;
|
||||
mod manage_profiles_modal;
|
||||
mod tool_picker;
|
||||
@@ -9,22 +8,29 @@ use agent_settings::AgentSettings;
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
use extension::ExtensionManifest;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AnyView, App, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ScrollHandle, Subscription, pulsating_between,
|
||||
Action, Animation, AnimationExt as _, AnyView, App, Corner, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use ui::{
|
||||
Disclosure, ElevationIndex, Indicator, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip,
|
||||
prelude::*,
|
||||
ContextMenu, Disclosure, ElevationIndex, Indicator, PopoverMenu, Scrollbar, ScrollbarState,
|
||||
Switch, SwitchColor, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::ExtensionCategoryFilter;
|
||||
|
||||
pub(crate) use add_context_server_modal::AddContextServerModal;
|
||||
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
|
||||
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||
|
||||
@@ -32,6 +38,8 @@ use crate::AddContextServer;
|
||||
|
||||
pub struct AgentConfiguration {
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
@@ -48,6 +56,8 @@ impl AgentConfiguration {
|
||||
fs: Arc<dyn Fs>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -70,11 +80,16 @@ impl AgentConfiguration {
|
||||
},
|
||||
);
|
||||
|
||||
cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify())
|
||||
.detach();
|
||||
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
|
||||
let mut this = Self {
|
||||
fs,
|
||||
language_registry,
|
||||
workspace,
|
||||
focus_handle,
|
||||
configuration_views_by_provider: HashMap::default(),
|
||||
context_server_store,
|
||||
@@ -133,6 +148,8 @@ impl AgentConfiguration {
|
||||
) -> impl IntoElement + use<> {
|
||||
let provider_id = provider.id().0.clone();
|
||||
let provider_name = provider.name().0.clone();
|
||||
let provider_id_string = SharedString::from(format!("provider-disclosure-{provider_id}"));
|
||||
|
||||
let configuration_view = self
|
||||
.configuration_views_by_provider
|
||||
.get(&provider.id())
|
||||
@@ -145,72 +162,80 @@ impl AgentConfiguration {
|
||||
.unwrap_or(false);
|
||||
|
||||
v_flex()
|
||||
.pt_3()
|
||||
.py_2()
|
||||
.gap_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.6))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.id(provider_id_string.clone())
|
||||
.cursor_pointer()
|
||||
.py_0p5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.rounded_sm()
|
||||
.hover(|hover| hover.bg(cx.theme().colors().element_hover))
|
||||
.child(
|
||||
Icon::new(provider.icon())
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new(provider_name.clone()).size(LabelSize::Large))
|
||||
.when(provider.is_authenticated(cx) && !is_expanded, |parent| {
|
||||
parent.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.when(provider.is_authenticated(cx), |parent| {
|
||||
parent.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Start New Thread",
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(provider.icon())
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
)
|
||||
})
|
||||
.child(Label::new(provider_name.clone()).size(LabelSize::Large))
|
||||
.when(
|
||||
provider.is_authenticated(cx) && !is_expanded,
|
||||
|parent| {
|
||||
parent.child(
|
||||
Icon::new(IconName::Check).color(Color::Success),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Disclosure::new(
|
||||
SharedString::from(format!(
|
||||
"provider-disclosure-{provider_id}"
|
||||
)),
|
||||
is_expanded,
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener({
|
||||
let provider_id = provider.id().clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_expanded = this
|
||||
.expanded_provider_configurations
|
||||
.entry(provider_id.clone())
|
||||
.or_insert(false);
|
||||
Disclosure::new(provider_id_string, is_expanded)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let provider_id = provider.id().clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_expanded = this
|
||||
.expanded_provider_configurations
|
||||
.entry(provider_id.clone())
|
||||
.or_insert(false);
|
||||
|
||||
*is_expanded = !*is_expanded;
|
||||
}
|
||||
})),
|
||||
),
|
||||
),
|
||||
*is_expanded = !*is_expanded;
|
||||
}
|
||||
})),
|
||||
)
|
||||
.when(provider.is_authenticated(cx), |parent| {
|
||||
parent.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Start New Thread",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(is_expanded, |parent| match configuration_view {
|
||||
Some(configuration_view) => parent.child(configuration_view),
|
||||
@@ -229,11 +254,11 @@ impl AgentConfiguration {
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.pr(DynamicSpacing::Base20.rems(cx))
|
||||
.gap_4()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
v_flex()
|
||||
.mb_2p5()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("LLM Providers"))
|
||||
.child(
|
||||
@@ -460,9 +485,22 @@ impl AgentConfiguration {
|
||||
.read(cx)
|
||||
.status_for_server(&context_server_id)
|
||||
.unwrap_or(ContextServerStatus::Stopped);
|
||||
let server_configuration = self
|
||||
.context_server_store
|
||||
.read(cx)
|
||||
.configuration_for_server(&context_server_id);
|
||||
|
||||
let is_running = matches!(server_status, ContextServerStatus::Running);
|
||||
let item_id = SharedString::from(context_server_id.0.clone());
|
||||
let is_from_extension = server_configuration
|
||||
.as_ref()
|
||||
.map(|config| {
|
||||
matches!(
|
||||
config.as_ref(),
|
||||
ContextServerConfiguration::Extension { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let error = if let ContextServerStatus::Error(error) = server_status.clone() {
|
||||
Some(error)
|
||||
@@ -475,7 +513,6 @@ impl AgentConfiguration {
|
||||
.get(&context_server_id)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
let tools = tools_by_source
|
||||
.get(&ToolSource::ContextServer {
|
||||
id: context_server_id.0.clone().into(),
|
||||
@@ -484,25 +521,35 @@ impl AgentConfiguration {
|
||||
let tool_count = tools.len();
|
||||
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
let success_color = Color::Success.color(cx);
|
||||
|
||||
let (source_icon, source_tooltip) = if is_from_extension {
|
||||
(
|
||||
IconName::ZedMcpExtension,
|
||||
"This MCP server was installed from an extension.",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
IconName::ZedMcpCustom,
|
||||
"This custom MCP server was installed directly.",
|
||||
)
|
||||
};
|
||||
|
||||
let (status_indicator, tooltip_text) = match server_status {
|
||||
ContextServerStatus::Starting => (
|
||||
Indicator::dot()
|
||||
.color(Color::Success)
|
||||
Icon::new(IconName::LoadCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
SharedString::from(format!("{}-starting", context_server_id.0.clone(),)),
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 1.)),
|
||||
move |this, delta| this.color(success_color.alpha(delta).into()),
|
||||
Animation::new(Duration::from_secs(3)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element(),
|
||||
"Server is starting.",
|
||||
),
|
||||
ContextServerStatus::Running => (
|
||||
Indicator::dot().color(Color::Success).into_any_element(),
|
||||
"Server is running.",
|
||||
"Server is active.",
|
||||
),
|
||||
ContextServerStatus::Error(_) => (
|
||||
Indicator::dot().color(Color::Error).into_any_element(),
|
||||
@@ -514,6 +561,105 @@ impl AgentConfiguration {
|
||||
),
|
||||
};
|
||||
|
||||
let context_server_configuration_menu = PopoverMenu::new("context-server-config-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("context-server-config-menu", IconName::Settings)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small),
|
||||
Tooltip::text("Open MCP server options"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.menu({
|
||||
let fs = self.fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = self.language_registry.clone();
|
||||
let context_server_store = self.context_server_store.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.entry("Configure Server", None, {
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = language_registry.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |window, cx| {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
context_server_id.clone(),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.separator()
|
||||
.entry("Uninstall", None, {
|
||||
let fs = fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let context_server_store = context_server_store.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |_, cx| {
|
||||
let is_provided_by_extension = context_server_store
|
||||
.read(cx)
|
||||
.configuration_for_server(&context_server_id)
|
||||
.as_ref()
|
||||
.map(|config| {
|
||||
matches!(
|
||||
config.as_ref(),
|
||||
ContextServerConfiguration::Extension { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let uninstall_extension_task = match (
|
||||
is_provided_by_extension,
|
||||
resolve_extension_for_context_server(&context_server_id, cx),
|
||||
) {
|
||||
(true, Some((id, manifest))) => {
|
||||
if extension_only_provides_context_server(manifest.as_ref())
|
||||
{
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(id, cx)
|
||||
})
|
||||
} else {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
show_unable_to_uninstall_extension_with_context_server(workspace, context_server_id.clone(), cx);
|
||||
}).log_err();
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
_ => Task::ready(Ok(())),
|
||||
};
|
||||
|
||||
cx.spawn({
|
||||
let fs = fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |cx| {
|
||||
uninstall_extension_task.await?;
|
||||
cx.update(|cx| {
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id(item_id.clone())
|
||||
.border_1()
|
||||
@@ -526,12 +672,11 @@ impl AgentConfiguration {
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.when(
|
||||
error.is_some() || are_tools_expanded && tool_count > 1,
|
||||
error.is_some() || are_tools_expanded && tool_count >= 1,
|
||||
|element| element.border_b_1().border_color(border_color),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Disclosure::new(
|
||||
"tool-list-disclosure",
|
||||
@@ -551,12 +696,28 @@ impl AgentConfiguration {
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id(item_id.clone())
|
||||
h_flex()
|
||||
.id(SharedString::from(format!("tooltip-{}", item_id)))
|
||||
.h_full()
|
||||
.w_3()
|
||||
.mx_1()
|
||||
.justify_center()
|
||||
.tooltip(Tooltip::text(tooltip_text))
|
||||
.child(status_indicator),
|
||||
)
|
||||
.child(Label::new(context_server_id.0.clone()).ml_0p5())
|
||||
.child(Label::new(item_id).ml_0p5())
|
||||
.child(
|
||||
div()
|
||||
.id("extension-source")
|
||||
.mt_0p5()
|
||||
.mx_1()
|
||||
.tooltip(Tooltip::text(source_tooltip))
|
||||
.child(
|
||||
Icon::new(source_icon)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.when(is_running, |this| {
|
||||
this.child(
|
||||
Label::new(if tool_count == 1 {
|
||||
@@ -570,28 +731,72 @@ impl AgentConfiguration {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager = self.context_server_store.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
move |state, _window, cx| match state {
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
this.stop_server(&context_server_id, cx).log_err();
|
||||
});
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx).log_err();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(context_server_configuration_menu)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager =
|
||||
self.context_server_store.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let fs = self.fs.clone();
|
||||
|
||||
move |state, _window, cx| {
|
||||
let is_enabled = match state {
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => {
|
||||
context_server_manager.update(
|
||||
cx,
|
||||
|this, cx| {
|
||||
this.stop_server(
|
||||
&context_server_id,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
},
|
||||
);
|
||||
false
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(
|
||||
cx,
|
||||
|this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
}
|
||||
},
|
||||
);
|
||||
true
|
||||
}
|
||||
};
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.entry(context_server_id.0)
|
||||
.or_insert_with(|| {
|
||||
ContextServerSettings::Extension {
|
||||
enabled: is_enabled,
|
||||
settings: serde_json::json!({}),
|
||||
}
|
||||
})
|
||||
.set_enabled(is_enabled);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
@@ -701,3 +906,92 @@ impl Render for AgentConfiguration {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn extension_only_provides_context_server(manifest: &ExtensionManifest) -> bool {
|
||||
manifest.context_servers.len() == 1
|
||||
&& manifest.themes.is_empty()
|
||||
&& manifest.icon_themes.is_empty()
|
||||
&& manifest.languages.is_empty()
|
||||
&& manifest.grammars.is_empty()
|
||||
&& manifest.language_servers.is_empty()
|
||||
&& manifest.slash_commands.is_empty()
|
||||
&& manifest.indexed_docs_providers.is_empty()
|
||||
&& manifest.snippets.is_none()
|
||||
&& manifest.debug_locators.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_extension_for_context_server(
|
||||
id: &ContextServerId,
|
||||
cx: &App,
|
||||
) -> Option<(Arc<str>, Arc<ExtensionManifest>)> {
|
||||
ExtensionStore::global(cx)
|
||||
.read(cx)
|
||||
.installed_extensions()
|
||||
.iter()
|
||||
.find(|(_, entry)| entry.manifest.context_servers.contains_key(&id.0))
|
||||
.map(|(id, entry)| (id.clone(), entry.manifest.clone()))
|
||||
}
|
||||
|
||||
// This notification appears when trying to delete
|
||||
// an MCP server extension that not only provides
|
||||
// the server, but other things, too, like language servers and more.
|
||||
fn show_unable_to_uninstall_extension_with_context_server(
|
||||
workspace: &mut Workspace,
|
||||
id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let context_server_id = id.clone();
|
||||
|
||||
let status_toast = StatusToast::new(
|
||||
format!(
|
||||
"The {} extension provides more than just the MCP server. Proceed to uninstall anyway?",
|
||||
id.0
|
||||
),
|
||||
cx,
|
||||
move |this, _cx| {
|
||||
let workspace_handle = workspace_handle.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
|
||||
this.icon(ToastIcon::new(IconName::Warning).color(Color::Warning))
|
||||
.dismiss_button(true)
|
||||
.action("Uninstall", move |_, _cx| {
|
||||
if let Some((extension_id, _)) =
|
||||
resolve_extension_for_context_server(&context_server_id, _cx)
|
||||
{
|
||||
ExtensionStore::global(_cx).update(_cx, |store, cx| {
|
||||
store
|
||||
.uninstall_extension(extension_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
|
||||
workspace_handle
|
||||
.update(_cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
cx.spawn({
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |_workspace_handle, cx| {
|
||||
cx.update(|cx| {
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
},
|
||||
);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use context_server::{ContextServerCommand, ContextServerId};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
Animation, AnimationExt as _, AsyncWindowContext, DismissEvent, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle,
|
||||
WeakEntity, percentage, prelude::*,
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{
|
||||
ContextServerStatus, ContextServerStore, registry::ContextServerDescriptorRegistry,
|
||||
},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
worktree_store::WorktreeStore,
|
||||
};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::AddContextServer;
|
||||
|
||||
enum ConfigurationTarget {
|
||||
New,
|
||||
Existing {
|
||||
id: ContextServerId,
|
||||
command: ContextServerCommand,
|
||||
},
|
||||
Extension {
|
||||
id: ContextServerId,
|
||||
repository_url: Option<SharedString>,
|
||||
installation: Option<extension::ContextServerConfiguration>,
|
||||
},
|
||||
}
|
||||
|
||||
enum ConfigurationSource {
|
||||
New {
|
||||
editor: Entity<Editor>,
|
||||
},
|
||||
Existing {
|
||||
editor: Entity<Editor>,
|
||||
},
|
||||
Extension {
|
||||
id: ContextServerId,
|
||||
editor: Option<Entity<Editor>>,
|
||||
repository_url: Option<SharedString>,
|
||||
installation_instructions: Option<Entity<markdown::Markdown>>,
|
||||
settings_validator: Option<jsonschema::Validator>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConfigurationSource {
|
||||
fn has_configuration_options(&self) -> bool {
|
||||
!matches!(self, ConfigurationSource::Extension { editor: None, .. })
|
||||
}
|
||||
|
||||
fn is_new(&self) -> bool {
|
||||
matches!(self, ConfigurationSource::New { .. })
|
||||
}
|
||||
|
||||
fn from_target(
|
||||
target: ConfigurationTarget,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
jsonc_language: Option<Arc<Language>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
fn create_editor(
|
||||
json: String,
|
||||
jsonc_language: Option<Arc<Language>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Editor> {
|
||||
cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(4, 16, window, cx);
|
||||
editor.set_text(json, window, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
|
||||
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| buffer.set_language(jsonc_language, cx))
|
||||
}
|
||||
editor
|
||||
})
|
||||
}
|
||||
|
||||
match target {
|
||||
ConfigurationTarget::New => ConfigurationSource::New {
|
||||
editor: create_editor(context_server_input(None), jsonc_language, window, cx),
|
||||
},
|
||||
ConfigurationTarget::Existing { id, command } => ConfigurationSource::Existing {
|
||||
editor: create_editor(
|
||||
context_server_input(Some((id, command))),
|
||||
jsonc_language,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
},
|
||||
ConfigurationTarget::Extension {
|
||||
id,
|
||||
repository_url,
|
||||
installation,
|
||||
} => {
|
||||
let settings_validator = installation.as_ref().and_then(|installation| {
|
||||
jsonschema::validator_for(&installation.settings_schema)
|
||||
.context("Failed to load JSON schema for context server settings")
|
||||
.log_err()
|
||||
});
|
||||
let installation_instructions = installation.as_ref().map(|installation| {
|
||||
cx.new(|cx| {
|
||||
Markdown::new(
|
||||
installation.installation_instructions.clone().into(),
|
||||
Some(language_registry.clone()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
ConfigurationSource::Extension {
|
||||
id,
|
||||
repository_url,
|
||||
installation_instructions,
|
||||
settings_validator,
|
||||
editor: installation.map(|installation| {
|
||||
create_editor(installation.default_settings, jsonc_language, window, cx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn output(&self, cx: &mut App) -> Result<(ContextServerId, ContextServerSettings)> {
|
||||
match self {
|
||||
ConfigurationSource::New { editor } | ConfigurationSource::Existing { editor } => {
|
||||
parse_input(&editor.read(cx).text(cx)).map(|(id, command)| {
|
||||
(
|
||||
id,
|
||||
ContextServerSettings::Custom {
|
||||
enabled: true,
|
||||
command,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
ConfigurationSource::Extension {
|
||||
id,
|
||||
editor,
|
||||
settings_validator,
|
||||
..
|
||||
} => {
|
||||
let text = editor
|
||||
.as_ref()
|
||||
.context("No output available")?
|
||||
.read(cx)
|
||||
.text(cx);
|
||||
let settings = serde_json_lenient::from_str::<serde_json::Value>(&text)?;
|
||||
if let Some(settings_validator) = settings_validator {
|
||||
if let Err(error) = settings_validator.validate(&settings) {
|
||||
return Err(anyhow::anyhow!(error.to_string()));
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
id.clone(),
|
||||
ContextServerSettings::Extension {
|
||||
enabled: true,
|
||||
settings,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)>) -> String {
|
||||
let (name, path, args, env) = match existing {
|
||||
Some((id, cmd)) => {
|
||||
let args = serde_json::to_string(&cmd.args).unwrap();
|
||||
let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap();
|
||||
(id.0.to_string(), cmd.path, args, env)
|
||||
}
|
||||
None => (
|
||||
"some-mcp-server".to_string(),
|
||||
"".to_string(),
|
||||
"[]".to_string(),
|
||||
"{}".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"{{
|
||||
/// The name of your MCP server
|
||||
"{name}": {{
|
||||
"command": {{
|
||||
/// The path to the executable
|
||||
"path": "{path}",
|
||||
/// The arguments to pass to the executable
|
||||
"args": {args},
|
||||
/// The environment variables to set for the executable
|
||||
"env": {env}
|
||||
}}
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_context_server_extension(
|
||||
id: ContextServerId,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<ConfigurationTarget>> {
|
||||
let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
|
||||
|
||||
let Some(descriptor) = registry.context_server_descriptor(&id.0) else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
|
||||
let extension = crate::agent_configuration::resolve_extension_for_context_server(&id, cx);
|
||||
cx.spawn(async move |cx| {
|
||||
let installation = descriptor
|
||||
.configuration(worktree_store, cx)
|
||||
.await
|
||||
.context("Failed to resolve context server configuration")
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
Some(ConfigurationTarget::Extension {
|
||||
id,
|
||||
repository_url: extension
|
||||
.and_then(|(_, manifest)| manifest.repository.clone().map(SharedString::from)),
|
||||
installation,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Waiting,
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
pub struct ConfigureContextServerModal {
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
source: ConfigurationSource,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
pub fn register(
|
||||
workspace: &mut Workspace,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
_window: Option<&mut Window>,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.register_action({
|
||||
let language_registry = language_registry.clone();
|
||||
move |_workspace, _: &AddContextServer, window, cx| {
|
||||
let workspace_handle = cx.weak_entity();
|
||||
let language_registry = language_registry.clone();
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
Self::show_modal(
|
||||
ConfigurationTarget::New,
|
||||
language_registry,
|
||||
workspace_handle,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_modal_for_existing_server(
|
||||
server_id: ContextServerId,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(settings) = ProjectSettings::get_global(cx)
|
||||
.context_servers
|
||||
.get(&server_id.0)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
ContextServerDescriptorRegistry::default_global(cx)
|
||||
.read(cx)
|
||||
.context_server_descriptor(&server_id.0)
|
||||
.map(|_| ContextServerSettings::default_extension())
|
||||
})
|
||||
else {
|
||||
return Task::ready(Err(anyhow::anyhow!("Context server not found")));
|
||||
};
|
||||
|
||||
window.spawn(cx, async move |cx| {
|
||||
let target = match settings {
|
||||
ContextServerSettings::Custom {
|
||||
enabled: _,
|
||||
command,
|
||||
} => Some(ConfigurationTarget::Existing {
|
||||
id: server_id,
|
||||
command,
|
||||
}),
|
||||
ContextServerSettings::Extension { .. } => {
|
||||
match workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
resolve_context_server_extension(
|
||||
server_id,
|
||||
workspace.project().read(cx).worktree_store(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
{
|
||||
Some(task) => task.await,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match target {
|
||||
Some(target) => Self::show_modal(target, language_registry, workspace, cx).await,
|
||||
None => Err(anyhow::anyhow!("Failed to resolve context server")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn show_modal(
|
||||
target: ConfigurationTarget,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let jsonc_language = language_registry.language_for_name("jsonc").await.ok();
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let workspace_handle = cx.weak_entity();
|
||||
let context_server_store = workspace.project().read(cx).context_server_store();
|
||||
workspace.toggle_modal(window, cx, |window, cx| Self {
|
||||
context_server_store,
|
||||
workspace: workspace_handle,
|
||||
state: State::Idle,
|
||||
source: ConfigurationSource::from_target(
|
||||
target,
|
||||
language_registry,
|
||||
jsonc_language,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn set_error(&mut self, err: impl Into<SharedString>, cx: &mut Context<Self>) {
|
||||
self.state = State::Error(err.into());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut Context<Self>) {
|
||||
self.state = State::Idle;
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (id, settings) = match self.source.output(cx) {
|
||||
Ok(val) => val,
|
||||
Err(error) => {
|
||||
self.set_error(error.to_string(), cx);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.state = State::Waiting;
|
||||
let wait_for_context_server_task =
|
||||
wait_for_context_server(&self.context_server_store, id.clone(), cx);
|
||||
cx.spawn({
|
||||
let id = id.clone();
|
||||
async move |this, cx| {
|
||||
let result = wait_for_context_server_task.await;
|
||||
this.update(cx, |this, cx| match result {
|
||||
Ok(_) => {
|
||||
this.state = State::Idle;
|
||||
this.show_configured_context_server_toast(id, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
Err(err) => {
|
||||
this.set_error(err, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// When we write the settings to the file, the context server will be restarted.
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |project_settings, _| {
|
||||
project_settings.context_servers.insert(id.0, settings);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn show_configured_context_server_toast(&self, id: ContextServerId, cx: &mut App) {
|
||||
self.workspace
|
||||
.update(cx, {
|
||||
|workspace, cx| {
|
||||
let status_toast = StatusToast::new(
|
||||
format!("{} configured successfully.", id.0),
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Hammer).color(Color::Muted))
|
||||
.action("Dismiss", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_input(text: &str) -> Result<(ContextServerId, ContextServerCommand)> {
|
||||
let value: serde_json::Value = serde_json_lenient::from_str(text)?;
|
||||
let object = value.as_object().context("Expected object")?;
|
||||
anyhow::ensure!(object.len() == 1, "Expected exactly one key-value pair");
|
||||
let (context_server_name, value) = object.into_iter().next().unwrap();
|
||||
let command = value.get("command").context("Expected command")?;
|
||||
let command: ContextServerCommand = serde_json::from_value(command.clone())?;
|
||||
Ok((ContextServerId(context_server_name.clone().into()), command))
|
||||
}
|
||||
|
||||
impl ModalView for ConfigureContextServerModal {}
|
||||
|
||||
impl Focusable for ConfigureContextServerModal {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match &self.source {
|
||||
ConfigurationSource::New { editor } => editor.focus_handle(cx),
|
||||
ConfigurationSource::Existing { editor, .. } => editor.focus_handle(cx),
|
||||
ConfigurationSource::Extension { editor, .. } => editor
|
||||
.as_ref()
|
||||
.map(|editor| editor.focus_handle(cx))
|
||||
.unwrap_or_else(|| cx.focus_handle()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ConfigureContextServerModal {}
|
||||
|
||||
impl ConfigureContextServerModal {
|
||||
fn render_modal_header(&self) -> ModalHeader {
|
||||
let text: SharedString = match &self.source {
|
||||
ConfigurationSource::New { .. } => "Add MCP Server".into(),
|
||||
ConfigurationSource::Existing { .. } => "Configure MCP Server".into(),
|
||||
ConfigurationSource::Extension { id, .. } => format!("Configure {}", id.0).into(),
|
||||
};
|
||||
ModalHeader::new().headline(text)
|
||||
}
|
||||
|
||||
fn render_modal_description(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
const MODAL_DESCRIPTION: &'static str = "Visit the MCP server configuration docs to find all necessary arguments and environment variables.";
|
||||
|
||||
if let ConfigurationSource::Extension {
|
||||
installation_instructions: Some(installation_instructions),
|
||||
..
|
||||
} = &self.source
|
||||
{
|
||||
div()
|
||||
.pb_2()
|
||||
.text_sm()
|
||||
.child(MarkdownElement::new(
|
||||
installation_instructions.clone(),
|
||||
default_markdown_style(window, cx),
|
||||
))
|
||||
.into_any_element()
|
||||
} else {
|
||||
Label::new(MODAL_DESCRIPTION)
|
||||
.color(Color::Muted)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_modal_content(&self, cx: &App) -> AnyElement {
|
||||
let editor = match &self.source {
|
||||
ConfigurationSource::New { editor } => editor,
|
||||
ConfigurationSource::Existing { editor } => editor,
|
||||
ConfigurationSource::Extension { editor, .. } => {
|
||||
let Some(editor) = editor else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
editor
|
||||
}
|
||||
};
|
||||
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
let is_connecting = matches!(self.state, State::Waiting);
|
||||
|
||||
ModalFooter::new()
|
||||
.start_slot::<Button>(
|
||||
if let ConfigurationSource::Extension {
|
||||
repository_url: Some(repository_url),
|
||||
..
|
||||
} = &self.source
|
||||
{
|
||||
Some(
|
||||
Button::new("open-repository", "Open Repository")
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip({
|
||||
let repository_url = repository_url.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open Repository",
|
||||
None,
|
||||
repository_url.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
let repository_url = repository_url.clone();
|
||||
move |_, _, cx| cx.open_url(&repository_url)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new(
|
||||
"cancel",
|
||||
if self.source.has_configuration_options() {
|
||||
"Cancel"
|
||||
} else {
|
||||
"Dismiss"
|
||||
},
|
||||
)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _event, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
),
|
||||
)
|
||||
.children(self.source.has_configuration_options().then(|| {
|
||||
Button::new(
|
||||
"add-server",
|
||||
if self.source.is_new() {
|
||||
"Add Server"
|
||||
} else {
|
||||
"Configure Server"
|
||||
},
|
||||
)
|
||||
.disabled(is_connecting)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(|this, _event, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_waiting_for_context_server() -> Div {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Label::new("Waiting for Context Server")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_modal_error(error: SharedString) -> Div {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.child(Label::new(error).size(LabelSize::Small).color(Color::Muted)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ConfigureContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
.key_context("ConfigureContextServerModal")
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
this.focus_handle(cx).focus(window);
|
||||
}))
|
||||
.child(
|
||||
Modal::new("configure-context-server", None)
|
||||
.header(self.render_modal_header())
|
||||
.section(
|
||||
Section::new()
|
||||
.child(self.render_modal_description(window, cx))
|
||||
.child(self.render_modal_content(cx))
|
||||
.child(match &self.state {
|
||||
State::Idle => div(),
|
||||
State::Waiting => Self::render_waiting_for_context_server(),
|
||||
State::Error(error) => Self::render_modal_error(error.clone()),
|
||||
}),
|
||||
)
|
||||
.footer(self.render_modal_footer(window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_context_server(
|
||||
context_server_store: &Entity<ContextServerStore>,
|
||||
context_server_id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(), Arc<str>>> {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let tx = Arc::new(Mutex::new(Some(tx)));
|
||||
|
||||
let subscription = cx.subscribe(context_server_store, move |_, event, _cx| match event {
|
||||
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
|
||||
match status {
|
||||
ContextServerStatus::Running => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Stopped => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err("Context server stopped running".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextServerStatus::Error(error) => {
|
||||
if server_id == &context_server_id {
|
||||
if let Some(tx) = tx.lock().unwrap().take() {
|
||||
let _ = tx.send(Err(error.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(async move |_cx| {
|
||||
let result = rx.await.unwrap();
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let colors = cx.theme().colors();
|
||||
let mut text_style = window.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||
font_size: Some(TextSize::XSmall.rems(cx).into()),
|
||||
color: Some(colors.text_muted),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: colors.element_selection_background,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(colors.text_accent.opacity(0.5)),
|
||||
thickness: px(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,21 @@ mod profile_modal_header;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, builtin_profiles};
|
||||
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 gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
|
||||
use settings::Settings as _;
|
||||
use ui::{
|
||||
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
|
||||
use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
|
||||
use crate::{AgentPanel, ManageProfiles, ThreadStore};
|
||||
use crate::{AgentPanel, ManageProfiles};
|
||||
use agent::agent_profile::AgentProfile;
|
||||
|
||||
use super::tool_picker::ToolPickerMode;
|
||||
|
||||
@@ -103,7 +99,6 @@ pub struct NewProfileMode {
|
||||
pub struct ManageProfilesModal {
|
||||
fs: Arc<dyn Fs>,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
focus_handle: FocusHandle,
|
||||
mode: Mode,
|
||||
}
|
||||
@@ -119,9 +114,8 @@ impl ManageProfilesModal {
|
||||
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);
|
||||
let mut this = Self::new(fs, tools, window, cx);
|
||||
|
||||
if let Some(profile_id) = action.customize_tools.clone() {
|
||||
this.configure_builtin_tools(profile_id, window, cx);
|
||||
@@ -136,7 +130,6 @@ impl ManageProfilesModal {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -145,7 +138,6 @@ impl ManageProfilesModal {
|
||||
Self {
|
||||
fs,
|
||||
tools,
|
||||
thread_store,
|
||||
focus_handle,
|
||||
mode: Mode::choose_profile(window, cx),
|
||||
}
|
||||
@@ -206,7 +198,6 @@ impl ManageProfilesModal {
|
||||
ToolPickerMode::McpTools,
|
||||
self.fs.clone(),
|
||||
self.tools.clone(),
|
||||
self.thread_store.clone(),
|
||||
profile_id.clone(),
|
||||
profile,
|
||||
cx,
|
||||
@@ -244,7 +235,6 @@ impl ManageProfilesModal {
|
||||
ToolPickerMode::BuiltinTools,
|
||||
self.fs.clone(),
|
||||
self.tools.clone(),
|
||||
self.thread_store.clone(),
|
||||
profile_id.clone(),
|
||||
profile,
|
||||
cx,
|
||||
@@ -270,32 +260,10 @@ impl ManageProfilesModal {
|
||||
match &self.mode {
|
||||
Mode::ChooseProfile { .. } => {}
|
||||
Mode::NewProfile(mode) => {
|
||||
let settings = AgentSettings::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 = AgentProfileId(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(),
|
||||
enable_all_context_servers: base_profile
|
||||
.as_ref()
|
||||
.map(|profile| profile.enable_all_context_servers)
|
||||
.unwrap_or_default(),
|
||||
context_servers: base_profile
|
||||
.map(|profile| profile.context_servers)
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
self.create_profile(profile_id.clone(), profile, cx);
|
||||
let profile_id =
|
||||
AgentProfile::create(name, mode.base_profile_id.clone(), self.fs.clone(), cx);
|
||||
self.view_profile(profile_id, window, cx);
|
||||
}
|
||||
Mode::ViewProfile(_) => {}
|
||||
@@ -325,19 +293,6 @@ impl ManageProfilesModal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_profile(
|
||||
&self,
|
||||
profile_id: AgentProfileId,
|
||||
profile: AgentProfile,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||
move |settings, _cx| {
|
||||
settings.create_profile(profile_id, profile).log_err();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for ManageProfilesModal {}
|
||||
@@ -520,14 +475,13 @@ impl ManageProfilesModal {
|
||||
) -> impl IntoElement {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
let profile_id = &settings.default_profile;
|
||||
let profile_name = settings
|
||||
.profiles
|
||||
.get(&mode.profile_id)
|
||||
.map(|profile| profile.name.clone())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
let icon = match profile_id.as_str() {
|
||||
let icon = match mode.profile_id.as_str() {
|
||||
"write" => IconName::Pencil,
|
||||
"ask" => IconName::MessageBubbles,
|
||||
_ => IconName::UserRoundPen,
|
||||
@@ -1,19 +1,17 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use agent_settings::{
|
||||
AgentProfile, AgentProfileContent, AgentProfileId, AgentSettings, AgentSettingsContent,
|
||||
AgentProfileContent, AgentProfileId, AgentProfileSettings, AgentSettings, AgentSettingsContent,
|
||||
ContextServerPresetContent,
|
||||
};
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use fs::Fs;
|
||||
use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use settings::update_settings_file;
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::ThreadStore;
|
||||
|
||||
pub struct ToolPicker {
|
||||
picker: Entity<Picker<ToolPickerDelegate>>,
|
||||
}
|
||||
@@ -71,11 +69,10 @@ pub enum PickerItem {
|
||||
|
||||
pub struct ToolPickerDelegate {
|
||||
tool_picker: WeakEntity<ToolPicker>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
items: Arc<Vec<PickerItem>>,
|
||||
profile_id: AgentProfileId,
|
||||
profile: AgentProfile,
|
||||
profile_settings: AgentProfileSettings,
|
||||
filtered_items: Vec<PickerItem>,
|
||||
selected_index: usize,
|
||||
mode: ToolPickerMode,
|
||||
@@ -86,20 +83,18 @@ impl ToolPickerDelegate {
|
||||
mode: ToolPickerMode,
|
||||
fs: Arc<dyn Fs>,
|
||||
tool_set: Entity<ToolWorkingSet>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
profile_id: AgentProfileId,
|
||||
profile: AgentProfile,
|
||||
profile_settings: AgentProfileSettings,
|
||||
cx: &mut Context<ToolPicker>,
|
||||
) -> Self {
|
||||
let items = Arc::new(Self::resolve_items(mode, &tool_set, cx));
|
||||
|
||||
Self {
|
||||
tool_picker: cx.entity().downgrade(),
|
||||
thread_store,
|
||||
fs,
|
||||
items,
|
||||
profile_id,
|
||||
profile,
|
||||
profile_settings,
|
||||
filtered_items: Vec::new(),
|
||||
selected_index: 0,
|
||||
mode,
|
||||
@@ -249,67 +244,63 @@ impl PickerDelegate for ToolPickerDelegate {
|
||||
};
|
||||
|
||||
let is_currently_enabled = if let Some(server_id) = server_id.clone() {
|
||||
let preset = self.profile.context_servers.entry(server_id).or_default();
|
||||
let preset = self
|
||||
.profile_settings
|
||||
.context_servers
|
||||
.entry(server_id)
|
||||
.or_default();
|
||||
let is_enabled = *preset.tools.entry(tool_name.clone()).or_default();
|
||||
*preset.tools.entry(tool_name.clone()).or_default() = !is_enabled;
|
||||
is_enabled
|
||||
} else {
|
||||
let is_enabled = *self.profile.tools.entry(tool_name.clone()).or_default();
|
||||
*self.profile.tools.entry(tool_name.clone()).or_default() = !is_enabled;
|
||||
let is_enabled = *self
|
||||
.profile_settings
|
||||
.tools
|
||||
.entry(tool_name.clone())
|
||||
.or_default();
|
||||
*self
|
||||
.profile_settings
|
||||
.tools
|
||||
.entry(tool_name.clone())
|
||||
.or_default() = !is_enabled;
|
||||
is_enabled
|
||||
};
|
||||
|
||||
let active_profile_id = &AgentSettings::get_global(cx).default_profile;
|
||||
if active_profile_id == &self.profile_id {
|
||||
self.thread_store
|
||||
.update(cx, |this, cx| {
|
||||
this.load_profile(self.profile.clone(), cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||
let profile_id = self.profile_id.clone();
|
||||
let default_profile = self.profile.clone();
|
||||
let default_profile = self.profile_settings.clone();
|
||||
let server_id = server_id.clone();
|
||||
let tool_name = tool_name.clone();
|
||||
move |settings: &mut AgentSettingsContent, _cx| {
|
||||
settings
|
||||
.v2_setting(|v2_settings| {
|
||||
let profiles = v2_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,
|
||||
enable_all_context_servers: Some(
|
||||
default_profile.enable_all_context_servers,
|
||||
),
|
||||
context_servers: default_profile
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| {
|
||||
(
|
||||
server_id,
|
||||
ContextServerPresetContent {
|
||||
tools: preset.tools,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
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,
|
||||
enable_all_context_servers: Some(
|
||||
default_profile.enable_all_context_servers,
|
||||
),
|
||||
context_servers: default_profile
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| {
|
||||
(
|
||||
server_id,
|
||||
ContextServerPresetContent {
|
||||
tools: preset.tools,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
|
||||
if let Some(server_id) = server_id {
|
||||
let preset = profile.context_servers.entry(server_id).or_default();
|
||||
*preset.tools.entry(tool_name).or_default() = !is_currently_enabled;
|
||||
} else {
|
||||
*profile.tools.entry(tool_name).or_default() = !is_currently_enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
if let Some(server_id) = server_id {
|
||||
let preset = profile.context_servers.entry(server_id).or_default();
|
||||
*preset.tools.entry(tool_name).or_default() = !is_currently_enabled;
|
||||
} else {
|
||||
*profile.tools.entry(tool_name).or_default() = !is_currently_enabled;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -348,14 +339,18 @@ impl PickerDelegate for ToolPickerDelegate {
|
||||
),
|
||||
PickerItem::Tool { name, server_id } => {
|
||||
let is_enabled = if let Some(server_id) = server_id {
|
||||
self.profile
|
||||
self.profile_settings
|
||||
.context_servers
|
||||
.get(server_id.as_ref())
|
||||
.and_then(|preset| preset.tools.get(name))
|
||||
.copied()
|
||||
.unwrap_or(self.profile.enable_all_context_servers)
|
||||
.unwrap_or(self.profile_settings.enable_all_context_servers)
|
||||
} else {
|
||||
self.profile.tools.get(name).copied().unwrap_or(false)
|
||||
self.profile_settings
|
||||
.tools
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
Some(
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent};
|
||||
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
|
||||
use agent::{Thread, ThreadEvent};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint,
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot,
|
||||
SelectionEffects, ToPoint,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
@@ -31,7 +33,7 @@ use util::ResultExt;
|
||||
use workspace::{
|
||||
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||
Workspace,
|
||||
item::{BreadcrumbText, ItemEvent, TabContentParams},
|
||||
item::{BreadcrumbText, ItemEvent, SaveOptions, TabContentParams},
|
||||
searchable::SearchableItemHandle,
|
||||
};
|
||||
use zed_actions::assistant::ToggleFocus;
|
||||
@@ -170,15 +172,9 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
selections
|
||||
.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
},
|
||||
)
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +237,7 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
@@ -415,7 +411,7 @@ fn update_editor_selection(
|
||||
};
|
||||
|
||||
if let Some(target_hunk) = target_hunk {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
let next_hunk_start = target_hunk.multi_buffer_range().start;
|
||||
selections.select_anchor_ranges([next_hunk_start..next_hunk_start]);
|
||||
})
|
||||
@@ -532,12 +528,12 @@ impl Item for AgentDiffPane {
|
||||
|
||||
fn save(
|
||||
&mut self,
|
||||
format: bool,
|
||||
options: SaveOptions,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor.save(format, project, window, cx)
|
||||
self.editor.save(options, project, window, cx)
|
||||
}
|
||||
|
||||
fn save_as(
|
||||
@@ -1378,7 +1374,9 @@ impl AgentDiff {
|
||||
| ThreadEvent::CheckpointChanged
|
||||
| ThreadEvent::ToolConfirmationNeeded
|
||||
| ThreadEvent::ToolUseLimitReached
|
||||
| ThreadEvent::CancelEditing => {}
|
||||
| ThreadEvent::CancelEditing
|
||||
| ThreadEvent::RetriesFailed { .. }
|
||||
| ThreadEvent::ProfileChanged => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1512,7 +1510,7 @@ impl AgentDiff {
|
||||
multibuffer.add_diff(diff_handle.clone(), cx);
|
||||
});
|
||||
|
||||
let new_state = if thread.read(cx).has_pending_edit_tool_uses() {
|
||||
let new_state = if thread.read(cx).is_generating() {
|
||||
EditorState::Generating
|
||||
} else {
|
||||
EditorState::Reviewing
|
||||
@@ -1541,7 +1539,7 @@ impl AgentDiff {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::center()),
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -1747,7 +1745,8 @@ impl editor::Addon for EditorAgentDiffAddon {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Keep, ThreadStore, thread_store};
|
||||
use crate::Keep;
|
||||
use agent::thread_store::{self, ThreadStore};
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use editor::EditorSettings;
|
||||
@@ -1864,7 +1863,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
@@ -2120,7 +2119,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor1.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,17 @@
|
||||
use crate::{
|
||||
ModelUsageContext,
|
||||
language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Entity, FocusHandle, SharedString};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
|
||||
use crate::ModelUsageContext;
|
||||
use assistant_context_editor::language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
};
|
||||
use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
|
||||
pub struct AgentModelSelector {
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
@@ -91,21 +92,37 @@ impl AgentModelSelector {
|
||||
|
||||
impl Render for AgentModelSelector {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||
let model_name = model
|
||||
.as_ref()
|
||||
.map(|model| model.model.name().0)
|
||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||
let provider_icon = model
|
||||
.as_ref()
|
||||
.map(|model| model.provider.icon())
|
||||
.unwrap_or_else(|| IconName::Ai);
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
Button::new("active-model", model_name)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted),
|
||||
ButtonLike::new("active-model")
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
292
crates/agent_ui/src/agent_ui.rs
Normal file
@@ -0,0 +1,292 @@
|
||||
mod active_thread;
|
||||
mod agent_configuration;
|
||||
mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod buffer_codegen;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_strip;
|
||||
mod debug;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command;
|
||||
mod slash_command_picker;
|
||||
mod slash_command_settings;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod text_thread_editor;
|
||||
mod thread_history;
|
||||
mod tool_compatibility;
|
||||
mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::{Thread, ThreadId};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use fs::Fs;
|
||||
use gpui::{Action, App, Entity, actions};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
|
||||
};
|
||||
use prompt_store::PromptBuilder;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
|
||||
pub use crate::active_thread::ActiveThread;
|
||||
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
|
||||
actions!(
|
||||
agent,
|
||||
[
|
||||
NewTextThread,
|
||||
ToggleContextPicker,
|
||||
ToggleNavigationMenu,
|
||||
ToggleOptionsMenu,
|
||||
DeleteRecentlyOpenThread,
|
||||
ToggleProfileSelector,
|
||||
RemoveAllContext,
|
||||
ExpandMessageEditor,
|
||||
OpenHistory,
|
||||
AddContextServer,
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
ChatWithFollow,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
FocusUp,
|
||||
FocusDown,
|
||||
FocusLeft,
|
||||
FocusRight,
|
||||
RemoveFocusedContext,
|
||||
AcceptSuggestedContext,
|
||||
OpenActiveThreadAsMarkdown,
|
||||
OpenAgentDiff,
|
||||
Keep,
|
||||
Reject,
|
||||
RejectAll,
|
||||
KeepAll,
|
||||
Follow,
|
||||
ResetTrialUpsell,
|
||||
ResetTrialEndUpsell,
|
||||
ContinueThread,
|
||||
ContinueWithBurnMode,
|
||||
ToggleBurnMode,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
pub struct NewThread {
|
||||
#[serde(default)]
|
||||
from_thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
pub struct ManageProfiles {
|
||||
#[serde(default)]
|
||||
pub customize_tools: Option<AgentProfileId>,
|
||||
}
|
||||
|
||||
impl ManageProfiles {
|
||||
pub fn customize_tools(profile_id: AgentProfileId) -> Self {
|
||||
Self {
|
||||
customize_tools: Some(profile_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ModelUsageContext {
|
||||
Thread(Entity<Thread>),
|
||||
InlineAssistant,
|
||||
}
|
||||
|
||||
impl ModelUsageContext {
|
||||
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||
match self {
|
||||
Self::Thread(thread) => thread.read(cx).configured_model(),
|
||||
Self::InlineAssistant => {
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
|
||||
self.configured_model(cx)
|
||||
.map(|configured_model| configured_model.model)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the `agent` crate.
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
is_eval: bool,
|
||||
cx: &mut App,
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
if !is_eval {
|
||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||
// we're not running inside of the eval.
|
||||
init_language_model_settings(cx);
|
||||
}
|
||||
assistant_slash_command::init(cx);
|
||||
agent::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
TextThreadEditor::init(cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
terminal_inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
indexed_docs::init(cx);
|
||||
cx.observe_new(move |workspace, window, cx| {
|
||||
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
||||
})
|
||||
.detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
}
|
||||
|
||||
fn init_language_model_settings(cx: &mut App) {
|
||||
update_active_language_model_from_settings(cx);
|
||||
|
||||
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
|
||||
.detach();
|
||||
cx.subscribe(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|_, event: &language_model::Event, cx| match event {
|
||||
language_model::Event::ProviderStateChanged
|
||||
| language_model::Event::AddedProvider(_)
|
||||
| language_model::Event::RemovedProvider(_) => {
|
||||
update_active_language_model_from_settings(cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
|
||||
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
|
||||
language_model::SelectedModel {
|
||||
provider: LanguageModelProviderId::from(selection.provider.0.clone()),
|
||||
model: LanguageModelId::from(selection.model.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
let default = to_selected_model(&settings.default_model);
|
||||
let inline_assistant = settings
|
||||
.inline_assistant_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let commit_message = settings
|
||||
.commit_message_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let thread_summary = settings
|
||||
.thread_summary_model
|
||||
.as_ref()
|
||||
.map(to_selected_model);
|
||||
let inline_alternatives = settings
|
||||
.inline_alternatives
|
||||
.iter()
|
||||
.map(to_selected_model)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_default_model(Some(&default), cx);
|
||||
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
|
||||
registry.select_commit_message_model(commit_message.as_ref(), cx);
|
||||
registry.select_thread_summary_model(thread_summary.as_ref(), cx);
|
||||
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn register_slash_commands(cx: &mut App) {
|
||||
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::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(
|
||||
assistant_slash_commands::StreamingExampleSlashCommand,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
update_slash_commands_from_settings(cx);
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_slash_commands_from_settings(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let settings = SlashCommandSettings::get_global(cx);
|
||||
|
||||
if settings.docs.enabled {
|
||||
slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
|
||||
}
|
||||
|
||||
if settings.cargo_workspace.enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry
|
||||
.unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::context::ContextLoadResult;
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use crate::{context::load_context, context_store::ContextStore};
|
||||
use agent::{
|
||||
ContextStore,
|
||||
context::{ContextLoadResult, load_context},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
@@ -18,8 +20,7 @@ use language_model::{
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use prompt_store::PromptStore;
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use rope::Rope;
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
@@ -386,8 +387,10 @@ impl CodegenAlternative {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, cx)?;
|
||||
cx.spawn(async move |_, cx| model.stream_completion_text(request.await, &cx).await)
|
||||
.boxed_local()
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, &cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
};
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
Ok(())
|
||||
@@ -1091,15 +1094,9 @@ mod tests {
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
use settings::SettingsStore;
|
||||
use std::{future, sync::Arc};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DummyCompletionRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
init_test(cx);
|
||||
@@ -1,11 +1,11 @@
|
||||
use gpui::{Context, FontWeight, IntoElement, Render, Window};
|
||||
use ui::{prelude::*, tooltip_container};
|
||||
|
||||
pub struct MaxModeTooltip {
|
||||
pub struct BurnModeTooltip {
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl MaxModeTooltip {
|
||||
impl BurnModeTooltip {
|
||||
pub fn new() -> Self {
|
||||
Self { selected: false }
|
||||
}
|
||||
@@ -16,7 +16,7 @@ impl MaxModeTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MaxModeTooltip {
|
||||
impl Render for BurnModeTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (icon, color) = if self.selected {
|
||||
(IconName::ZedBurnModeOn, Color::Error)
|
||||
@@ -37,10 +37,12 @@ use uuid::Uuid;
|
||||
use workspace::{Workspace, notifications::NotifyResultExt};
|
||||
|
||||
use crate::AgentPanel;
|
||||
use crate::context::RULES_ICON;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
ThreadId,
|
||||
context::RULES_ICON,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ContextPickerEntry {
|
||||
@@ -659,7 +661,7 @@ fn recent_context_picker_entries(
|
||||
|
||||
let active_thread_id = workspace
|
||||
.panel::<AgentPanel>(cx)
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id()));
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread(cx)?.read(cx).id()));
|
||||
|
||||
if let Some((thread_store, text_thread_store)) = thread_store
|
||||
.and_then(|store| store.upgrade())
|
||||
@@ -928,8 +930,8 @@ impl MentionLink {
|
||||
format!(
|
||||
"[@{} ({}-{})]({}:{}:{}-{})",
|
||||
file_name,
|
||||
line_range.start,
|
||||
line_range.end,
|
||||
line_range.start + 1,
|
||||
line_range.end + 1,
|
||||
Self::SELECTION,
|
||||
full_path,
|
||||
line_range.start,
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use agent::context_store::ContextStore;
|
||||
use anyhow::Result;
|
||||
use editor::{CompletionProvider, Editor, ExcerptId, ToOffset as _};
|
||||
use file_icons::FileIcons;
|
||||
@@ -22,10 +21,11 @@ use ui::prelude::*;
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::Thread;
|
||||
use crate::context::{AgentContextHandle, AgentContextKey, ContextCreasesAddon, RULES_ICON};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
Thread,
|
||||
context::{AgentContextHandle, AgentContextKey, RULES_ICON},
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
use super::fetch_context_picker::fetch_url_content;
|
||||
use super::file_context_picker::{FileMatch, search_files};
|
||||
@@ -37,6 +37,7 @@ use super::{
|
||||
ContextPickerAction, ContextPickerEntry, ContextPickerMode, MentionLink, RecentEntry,
|
||||
available_context_picker_entries, recent_context_picker_entries, selection_ranges,
|
||||
};
|
||||
use crate::message_editor::ContextCreasesAddon;
|
||||
|
||||
pub(crate) enum Match {
|
||||
File(FileMatch),
|
||||
@@ -72,7 +73,7 @@ fn search(
|
||||
recent_entries: Vec<RecentEntry>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
text_thread_context_store: Option<WeakEntity<assistant_context_editor::ContextStore>>,
|
||||
text_thread_context_store: Option<WeakEntity<assistant_context::ContextStore>>,
|
||||
workspace: Entity<Workspace>,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<Match>> {
|
||||
@@ -216,6 +217,7 @@ fn search(
|
||||
&entry_candidates,
|
||||
&query,
|
||||
false,
|
||||
true,
|
||||
100,
|
||||
&Arc::new(AtomicBool::default()),
|
||||
executor,
|
||||
@@ -767,7 +769,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let source_range = snapshot.anchor_before(state.source_range.start)
|
||||
..snapshot.anchor_before(state.source_range.end);
|
||||
..snapshot.anchor_after(state.source_range.end);
|
||||
|
||||
let thread_store = self.thread_store.clone();
|
||||
let text_thread_store = self.text_thread_store.clone();
|
||||
@@ -912,16 +914,6 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
_buffer: Entity<Buffer>,
|
||||
_completion_indices: Vec<usize>,
|
||||
_completions: Rc<RefCell<Box<[Completion]>>>,
|
||||
_cx: &mut Context<Editor>,
|
||||
) -> Task<Result<bool>> {
|
||||
Task::ready(Ok(true))
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Entity<language::Buffer>,
|
||||
@@ -1077,8 +1069,8 @@ mod tests {
|
||||
use project::{Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::ops::Deref;
|
||||
use util::{path, separator};
|
||||
use std::{ops::Deref, rc::Rc};
|
||||
use util::path;
|
||||
use workspace::{AppState, Item};
|
||||
|
||||
#[test]
|
||||
@@ -1229,14 +1221,14 @@ mod tests {
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
|
||||
let paths = vec![
|
||||
separator!("a/one.txt"),
|
||||
separator!("a/two.txt"),
|
||||
separator!("a/three.txt"),
|
||||
separator!("a/four.txt"),
|
||||
separator!("b/five.txt"),
|
||||
separator!("b/six.txt"),
|
||||
separator!("b/seven.txt"),
|
||||
separator!("b/eight.txt"),
|
||||
path!("a/one.txt"),
|
||||
path!("a/two.txt"),
|
||||
path!("a/three.txt"),
|
||||
path!("a/four.txt"),
|
||||
path!("b/five.txt"),
|
||||
path!("b/six.txt"),
|
||||
path!("b/seven.txt"),
|
||||
path!("b/eight.txt"),
|
||||
];
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
@@ -2,6 +2,7 @@ use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::context_store::ContextStore;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||
@@ -12,7 +13,6 @@ use ui::{Context, ListItem, Window, prelude::*};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FetchContextPicker {
|
||||
picker: Entity<Picker<FetchContextPickerDelegate>>,
|
||||
@@ -14,7 +14,7 @@ use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::{ContextStore, FileInclusion};
|
||||
use agent::context_store::{ContextStore, FileInclusion};
|
||||
|
||||
pub struct FileContextPicker {
|
||||
picker: Entity<Picker<FileContextPickerDelegate>>,
|
||||
@@ -7,9 +7,9 @@ use prompt_store::{PromptId, PromptStore, UserPromptId};
|
||||
use ui::{ListItem, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::context::RULES_ICON;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::{self, ContextStore};
|
||||
use agent::context::RULES_ICON;
|
||||
use agent::context_store::{self, ContextStore};
|
||||
|
||||
pub struct RulesContextPicker {
|
||||
picker: Entity<Picker<RulesContextPickerDelegate>>,
|
||||
@@ -14,9 +14,9 @@ use ui::{ListItem, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::AgentContextHandle;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use agent::context::AgentContextHandle;
|
||||
use agent::context_store::ContextStore;
|
||||
|
||||
pub struct SymbolContextPicker {
|
||||
picker: Entity<Picker<SymbolContextPickerDelegate>>,
|
||||
@@ -307,6 +307,7 @@ pub(crate) fn search_symbols(
|
||||
&visible_match_candidates,
|
||||
&query,
|
||||
false,
|
||||
true,
|
||||
MAX_MATCHES,
|
||||
&cancellation_flag,
|
||||
cx.background_executor().clone(),
|
||||
@@ -315,6 +316,7 @@ pub(crate) fn search_symbols(
|
||||
&external_match_candidates,
|
||||
&query,
|
||||
false,
|
||||
true,
|
||||
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
|
||||
&cancellation_flag,
|
||||
cx.background_executor().clone(),
|
||||
@@ -9,9 +9,11 @@ use picker::{Picker, PickerDelegate};
|
||||
use ui::{ListItem, prelude::*};
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::{self, ContextStore};
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use agent::{
|
||||
ThreadId,
|
||||
context_store::{self, ContextStore},
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
pub struct ThreadContextPicker {
|
||||
picker: Entity<Picker<ThreadContextPickerDelegate>>,
|
||||
@@ -282,15 +284,18 @@ pub fn unordered_thread_entries(
|
||||
text_thread_store: Entity<TextThreadStore>,
|
||||
cx: &App,
|
||||
) -> impl Iterator<Item = (DateTime<Utc>, ThreadContextEntry)> {
|
||||
let threads = thread_store.read(cx).unordered_threads().map(|thread| {
|
||||
(
|
||||
thread.updated_at,
|
||||
ThreadContextEntry::Thread {
|
||||
id: thread.id.clone(),
|
||||
title: thread.summary.clone(),
|
||||
},
|
||||
)
|
||||
});
|
||||
let threads = thread_store
|
||||
.read(cx)
|
||||
.reverse_chronological_threads()
|
||||
.map(|thread| {
|
||||
(
|
||||
thread.updated_at,
|
||||
ThreadContextEntry::Thread {
|
||||
id: thread.id.clone(),
|
||||
title: thread.summary.clone(),
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let text_threads = text_thread_store
|
||||
.read(cx)
|
||||
@@ -300,7 +305,7 @@ pub fn unordered_thread_entries(
|
||||
context.mtime.to_utc(),
|
||||
ThreadContextEntry::Context {
|
||||
path: context.path.clone(),
|
||||
title: context.title.clone().into(),
|
||||
title: context.title.clone(),
|
||||
},
|
||||
)
|
||||
});
|
||||
@@ -339,6 +344,7 @@ pub(crate) fn search_threads(
|
||||
&candidates,
|
||||
&query,
|
||||
false,
|
||||
true,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
executor,
|
||||
116
crates/agent_ui/src/context_server_configuration.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use context_server::ContextServerId;
|
||||
use extension::ExtensionManifest;
|
||||
use fs::Fs;
|
||||
use gpui::WeakEntity;
|
||||
use language::LanguageRegistry;
|
||||
use project::project_settings::ProjectSettings;
|
||||
use settings::update_settings_file;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::agent_configuration::ConfigureContextServerModal;
|
||||
|
||||
pub(crate) fn init(language_registry: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
cx.observe_new(move |_: &mut Workspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
|
||||
cx.subscribe_in(extension_events, window, {
|
||||
let language_registry = language_registry.clone();
|
||||
let fs = fs.clone();
|
||||
move |_, _, event, window, cx| match event {
|
||||
extension::Event::ExtensionInstalled(manifest) => {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ExtensionUninstalled(manifest) => {
|
||||
remove_context_server_settings(
|
||||
manifest.context_servers.keys().cloned().collect(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
extension::Event::ConfigureExtensionRequested(manifest) => {
|
||||
if !manifest.context_servers.is_empty() {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
log::info!(
|
||||
"No extension events global found. Skipping context server configuration wizard"
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn remove_context_server_settings(
|
||||
context_server_ids: Vec<Arc<str>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
update_settings_file::<ProjectSettings>(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.retain(|server_id, _| !context_server_ids.contains(server_id));
|
||||
});
|
||||
}
|
||||
|
||||
fn show_configure_mcp_modal(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Workspace>,
|
||||
) {
|
||||
if !window.is_window_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ids = manifest.context_servers.keys().cloned().collect::<Vec<_>>();
|
||||
if ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
for id in ids {
|
||||
let Some(task) = cx
|
||||
.update(|window, cx| {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
ContextServerId(id.clone()),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
task.await.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use assistant_context_editor::AssistantContext;
|
||||
use crate::{
|
||||
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||
ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
||||
context_picker::ContextPicker,
|
||||
ui::{AddedContext, ContextPill},
|
||||
};
|
||||
use agent::context_store::SuggestedContext;
|
||||
use agent::{
|
||||
context::AgentContextHandle,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use collections::HashSet;
|
||||
use editor::Editor;
|
||||
use file_icons::FileIcons;
|
||||
@@ -10,22 +18,11 @@ use gpui::{
|
||||
Subscription, WeakEntity,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
use project::ProjectItem;
|
||||
use std::{path::Path, rc::Rc};
|
||||
use ui::{PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::{AgentContextHandle, ContextKind};
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread::Thread;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use crate::ui::{AddedContext, ContextPill};
|
||||
use crate::{
|
||||
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||
ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
||||
};
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Entity<ContextStore>,
|
||||
context_picker: Entity<ContextPicker>,
|
||||
@@ -164,7 +161,7 @@ impl ContextStrip {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
|
||||
|
||||
if let Some(active_thread) = panel.active_thread() {
|
||||
if let Some(active_thread) = panel.active_thread(cx) {
|
||||
let weak_active_thread = active_thread.downgrade();
|
||||
|
||||
let active_thread = active_thread.read(cx);
|
||||
@@ -575,46 +572,3 @@ pub enum SuggestContextKind {
|
||||
File,
|
||||
Thread,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
name: SharedString,
|
||||
thread: WeakEntity<Thread>,
|
||||
},
|
||||
TextThread {
|
||||
name: SharedString,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
Self::Thread { name, .. } => name,
|
||||
Self::TextThread { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
Self::Thread { .. } => None,
|
||||
Self::TextThread { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ContextKind {
|
||||
match self {
|
||||
Self::File { .. } => ContextKind::File,
|
||||
Self::Thread { .. } => ContextKind::Thread,
|
||||
Self::TextThread { .. } => ContextKind::TextThread,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(unused, dead_code)]
|
||||
|
||||
use client::{ModelRequestUsage, RequestUsage};
|
||||
use gpui::Global;
|
||||
use language_model::RequestUsage;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use ui::prelude::*;
|
||||
use zed_llm_client::{Plan, UsageLimit};
|
||||
@@ -17,7 +17,7 @@ pub struct DebugAccountState {
|
||||
pub enabled: bool,
|
||||
pub trial_expired: bool,
|
||||
pub plan: Plan,
|
||||
pub custom_prompt_usage: RequestUsage,
|
||||
pub custom_prompt_usage: ModelRequestUsage,
|
||||
pub usage_based_billing_enabled: bool,
|
||||
pub monthly_spending_cap: i32,
|
||||
pub custom_edit_prediction_usage: UsageLimit,
|
||||
@@ -43,7 +43,7 @@ impl DebugAccountState {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_custom_prompt_usage(&mut self, custom_prompt_usage: RequestUsage) -> &mut Self {
|
||||
pub fn set_custom_prompt_usage(&mut self, custom_prompt_usage: ModelRequestUsage) -> &mut Self {
|
||||
self.custom_prompt_usage = custom_prompt_usage;
|
||||
self
|
||||
}
|
||||
@@ -76,10 +76,10 @@ impl Default for DebugAccountState {
|
||||
enabled: false,
|
||||
trial_expired: false,
|
||||
plan: Plan::ZedFree,
|
||||
custom_prompt_usage: RequestUsage {
|
||||
custom_prompt_usage: ModelRequestUsage(RequestUsage {
|
||||
limit: UsageLimit::Unlimited,
|
||||
amount: 0,
|
||||
},
|
||||
}),
|
||||
usage_based_billing_enabled: false,
|
||||
// $50.00
|
||||
monthly_spending_cap: 5000,
|
||||
@@ -4,18 +4,28 @@ use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
AgentPanel,
|
||||
buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent},
|
||||
inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent},
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
};
|
||||
use agent::{
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::display_map::EditorMargins;
|
||||
use editor::SelectionEffects;
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
actions::SelectAll,
|
||||
display_map::{
|
||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
ToDisplayPoint,
|
||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
};
|
||||
use fs::Fs;
|
||||
@@ -24,33 +34,22 @@ use gpui::{
|
||||
WeakEntity, Window, point,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use language_model::ConfiguredModel;
|
||||
use language_model::{LanguageModelRegistry, report_assistant_event};
|
||||
use language_model::{
|
||||
ConfigurationError, ConfiguredModel, LanguageModelRegistry, report_assistant_event,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::LspAction;
|
||||
use project::Project;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use prompt_store::PromptBuilder;
|
||||
use prompt_store::PromptStore;
|
||||
use project::{CodeAction, LspAction, Project, ProjectTransaction};
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use ui::prelude::*;
|
||||
use util::RangeExt;
|
||||
use util::ResultExt;
|
||||
use util::{RangeExt, ResultExt, maybe};
|
||||
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
|
||||
use zed_actions::agent::OpenConfiguration;
|
||||
|
||||
use crate::AgentPanel;
|
||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
||||
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
||||
use crate::thread_store::TextThreadStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -233,10 +232,9 @@ impl InlineAssistant {
|
||||
return;
|
||||
};
|
||||
|
||||
let is_authenticated = || {
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.inline_assistant_model()
|
||||
.map_or(false, |model| model.provider.is_authenticated(cx))
|
||||
let configuration_error = || {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
model_registry.configuration_error(model_registry.inline_assistant_model(), cx)
|
||||
};
|
||||
|
||||
let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) else {
|
||||
@@ -284,20 +282,23 @@ impl InlineAssistant {
|
||||
}
|
||||
};
|
||||
|
||||
if is_authenticated() {
|
||||
handle_assist(window, cx);
|
||||
} else {
|
||||
cx.spawn_in(window, async move |_workspace, cx| {
|
||||
let Some(task) = cx.update(|_, cx| {
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.inline_assistant_model()
|
||||
.map_or(None, |model| Some(model.provider.authenticate(cx)))
|
||||
})?
|
||||
else {
|
||||
if let Some(error) = configuration_error() {
|
||||
if let ConfigurationError::ProviderNotAuthenticated(provider) = error {
|
||||
cx.spawn(async move |_, cx| {
|
||||
cx.update(|cx| provider.authenticate(cx))?.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
if configuration_error().is_none() {
|
||||
handle_assist(window, cx);
|
||||
}
|
||||
} else {
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
let answer = cx
|
||||
.prompt(
|
||||
gpui::PromptLevel::Warning,
|
||||
"No language model provider configured",
|
||||
&error.to_string(),
|
||||
None,
|
||||
&["Configure", "Cancel"],
|
||||
)
|
||||
@@ -311,17 +312,12 @@ impl InlineAssistant {
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
task.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
if is_authenticated() {
|
||||
handle_assist(window, cx);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
} else {
|
||||
handle_assist(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,9 +764,6 @@ impl InlineAssistant {
|
||||
PromptEditorEvent::CancelRequested => {
|
||||
self.finish_assist(assist_id, true, window, cx);
|
||||
}
|
||||
PromptEditorEvent::DismissRequested => {
|
||||
self.dismiss_assist(assist_id, window, cx);
|
||||
}
|
||||
PromptEditorEvent::Resized { .. } => {
|
||||
// This only matters for the terminal inline assistant
|
||||
}
|
||||
@@ -1011,7 +1004,7 @@ impl InlineAssistant {
|
||||
self.update_editor_highlights(&editor, cx);
|
||||
}
|
||||
} else {
|
||||
entry.get().highlight_updates.send(()).ok();
|
||||
entry.get_mut().highlight_updates.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,31 +1160,35 @@ impl InlineAssistant {
|
||||
|
||||
let position = assist.range.start;
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([position..position])
|
||||
});
|
||||
|
||||
let mut scroll_target_top;
|
||||
let mut scroll_target_bottom;
|
||||
let mut scroll_target_range = None;
|
||||
if let Some(decorations) = assist.decorations.as_ref() {
|
||||
scroll_target_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
scroll_target_bottom = editor
|
||||
.row_for_block(decorations.end_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
} else {
|
||||
scroll_target_range = maybe!({
|
||||
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
|
||||
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f32;
|
||||
Some((top, bottom))
|
||||
});
|
||||
if scroll_target_range.is_none() {
|
||||
log::error!("bug: failed to find blocks for scrolling to inline assist");
|
||||
}
|
||||
}
|
||||
let scroll_target_range = scroll_target_range.unwrap_or_else(|| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_row = assist
|
||||
.range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
scroll_target_top = start_row.0 as f32;
|
||||
scroll_target_bottom = scroll_target_top + 1.;
|
||||
}
|
||||
let top = start_row.0 as f32;
|
||||
let bottom = top + 1.0;
|
||||
(top, bottom)
|
||||
});
|
||||
let mut scroll_target_top = scroll_target_range.0;
|
||||
let mut scroll_target_bottom = scroll_target_range.1;
|
||||
|
||||
scroll_target_top -= editor.vertical_scroll_margin() as f32;
|
||||
scroll_target_bottom += editor.vertical_scroll_margin() as f32;
|
||||
|
||||
@@ -1331,7 +1328,7 @@ impl InlineAssistant {
|
||||
editor.clear_gutter_highlights::<GutterPendingRange>(cx);
|
||||
} else {
|
||||
editor.highlight_gutter::<GutterPendingRange>(
|
||||
&gutter_pending_ranges,
|
||||
gutter_pending_ranges,
|
||||
|cx| cx.theme().status().info_background,
|
||||
cx,
|
||||
)
|
||||
@@ -1342,7 +1339,7 @@ impl InlineAssistant {
|
||||
editor.clear_gutter_highlights::<GutterTransformedRange>(cx);
|
||||
} else {
|
||||
editor.highlight_gutter::<GutterTransformedRange>(
|
||||
&gutter_transformed_ranges,
|
||||
gutter_transformed_ranges,
|
||||
|cx| cx.theme().status().info,
|
||||
cx,
|
||||
)
|
||||
@@ -1519,7 +1516,7 @@ impl InlineAssistant {
|
||||
struct EditorInlineAssists {
|
||||
assist_ids: Vec<InlineAssistId>,
|
||||
scroll_lock: Option<InlineAssistScrollLock>,
|
||||
highlight_updates: async_watch::Sender<()>,
|
||||
highlight_updates: watch::Sender<()>,
|
||||
_update_highlights: Task<Result<()>>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
}
|
||||
@@ -1531,7 +1528,7 @@ struct InlineAssistScrollLock {
|
||||
|
||||
impl EditorInlineAssists {
|
||||
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) = watch::channel(());
|
||||
Self {
|
||||
assist_ids: Vec::new(),
|
||||
scroll_lock: None,
|
||||
@@ -1689,7 +1686,7 @@ impl InlineAssist {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
InlineAssistant::update_global(cx, |this, cx| {
|
||||
if let Some(editor_assists) =
|
||||
this.assists_by_editor.get(&editor.downgrade())
|
||||
this.assists_by_editor.get_mut(&editor.downgrade())
|
||||
{
|
||||
editor_assists.highlight_updates.send(()).ok();
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::agent_model_selector::AgentModelSelector;
|
||||
use crate::buffer_codegen::BufferCodegen;
|
||||
use crate::context::ContextCreasesAddon;
|
||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::message_editor::{extract_message_creases, insert_message_creases};
|
||||
use crate::language_model_selector::ToggleModelSelector;
|
||||
use crate::message_editor::{ContextCreasesAddon, extract_message_creases, insert_message_creases};
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker};
|
||||
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||
use agent::{
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use db::kvp::Dismissable;
|
||||
@@ -261,7 +262,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
|
||||
let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
|
||||
self.editor = cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, cx);
|
||||
let mut editor = Editor::auto_height(1, Self::MAX_LINES as usize, window, cx);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text("Add a prompt…", cx);
|
||||
editor.set_text(prompt, window, cx);
|
||||
@@ -403,9 +404,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
CodegenStatus::Idle => {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
cx.emit(PromptEditorEvent::DismissRequested);
|
||||
}
|
||||
CodegenStatus::Pending => {}
|
||||
CodegenStatus::Done => {
|
||||
if self.edited_since_done {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
@@ -831,7 +830,6 @@ pub enum PromptEditorEvent {
|
||||
StopRequested,
|
||||
ConfirmRequested { execute: bool },
|
||||
CancelRequested,
|
||||
DismissRequested,
|
||||
Resized { height_in_lines: u8 },
|
||||
}
|
||||
|
||||
@@ -872,7 +870,8 @@ impl PromptEditor<BufferCodegen> {
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
min_lines: 1,
|
||||
max_lines: Some(Self::MAX_LINES as usize),
|
||||
},
|
||||
prompt_buffer,
|
||||
None,
|
||||
@@ -1050,7 +1049,8 @@ impl PromptEditor<TerminalCodegen> {
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
min_lines: 1,
|
||||
max_lines: Some(Self::MAX_LINES as usize),
|
||||
},
|
||||
prompt_buffer,
|
||||
None,
|
||||
@@ -4,8 +4,7 @@ use collections::{HashSet, IndexMap};
|
||||
use feature_flags::ZedProFeatureFlag;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
||||
use gpui::{
|
||||
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task,
|
||||
action_with_deprecated_aliases,
|
||||
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task, actions,
|
||||
};
|
||||
use language_model::{
|
||||
AuthenticateError, ConfiguredModel, LanguageModel, LanguageModelProviderId,
|
||||
@@ -16,12 +15,11 @@ use picker::{Picker, PickerDelegate};
|
||||
use proto::Plan;
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
|
||||
action_with_deprecated_aliases!(
|
||||
actions!(
|
||||
agent,
|
||||
ToggleModelSelector,
|
||||
[
|
||||
"assistant::ToggleModelSelector",
|
||||
"assistant2::ToggleModelSelector"
|
||||
#[action(deprecated_aliases = ["assistant::ToggleModelSelector", "assistant2::ToggleModelSelector"])]
|
||||
ToggleModelSelector
|
||||
]
|
||||
);
|
||||
|
||||
@@ -310,6 +308,7 @@ impl ModelMatcher {
|
||||
&self.candidates,
|
||||
&query,
|
||||
false,
|
||||
true,
|
||||
100,
|
||||
&Default::default(),
|
||||
self.bg_executor.clone(),
|
||||
@@ -664,7 +663,7 @@ mod tests {
|
||||
format!("{}/{}", self.provider_id.0, self.name.0)
|
||||
}
|
||||
|
||||
fn max_token_count(&self) -> usize {
|
||||
fn max_token_count(&self) -> u64 {
|
||||
1000
|
||||
}
|
||||
|
||||
@@ -672,7 +671,7 @@ mod tests {
|
||||
&self,
|
||||
_: LanguageModelRequest,
|
||||
_: &App,
|
||||
) -> BoxFuture<'static, http_client::Result<usize>> {
|
||||
) -> BoxFuture<'static, http_client::Result<u64>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -682,11 +681,12 @@ mod tests {
|
||||
_: &AsyncApp,
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
http_client::Result<
|
||||
Result<
|
||||
BoxStream<
|
||||
'static,
|
||||
http_client::Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
>,
|
||||
LanguageModelCompletionError,
|
||||
>,
|
||||
> {
|
||||
unimplemented!()
|
||||
@@ -3,21 +3,25 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::agent_model_selector::AgentModelSelector;
|
||||
use crate::context::{AgentContextKey, ContextCreasesAddon, ContextLoadResult, load_context};
|
||||
use crate::language_model_selector::ToggleModelSelector;
|
||||
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
|
||||
use crate::ui::{
|
||||
MaxModeTooltip,
|
||||
preview::{AgentPreview, UsageCallout},
|
||||
};
|
||||
use agent::{
|
||||
context::{AgentContextKey, ContextLoadResult, load_context},
|
||||
context_store::ContextStoreEvent,
|
||||
};
|
||||
use agent_settings::{AgentSettings, CompletionMode};
|
||||
use assistant_context_editor::language_model_selector::ToggleModelSelector;
|
||||
use buffer_diff::BufferDiff;
|
||||
use client::UserStore;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::actions::{MoveUp, Paste};
|
||||
use editor::display_map::CreaseId;
|
||||
use editor::{
|
||||
AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorEvent,
|
||||
EditorMode, EditorStyle, MultiBuffer,
|
||||
Addon, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
|
||||
EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
};
|
||||
use file_icons::FileIcons;
|
||||
use fs::Fs;
|
||||
@@ -29,8 +33,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Buffer, Language, Point};
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModelRequestMessage, MessageContent, RequestUsage,
|
||||
ZED_CLOUD_PROVIDER_ID,
|
||||
ConfiguredModel, LanguageModelRequestMessage, MessageContent, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use multi_buffer;
|
||||
use project::Project;
|
||||
@@ -39,22 +42,26 @@ use proto::Plan;
|
||||
use settings::Settings;
|
||||
use std::time::Duration;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use util::{ResultExt as _, maybe};
|
||||
use ui::{
|
||||
Callout, Disclosure, Divider, DividerColor, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
use zed_llm_client::CompletionIntent;
|
||||
|
||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::profile_selector::ProfileSelector;
|
||||
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
||||
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||
use crate::{
|
||||
ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
|
||||
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
|
||||
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||
};
|
||||
use agent::{
|
||||
MessageCrease, Thread, TokenUsageRatio,
|
||||
context_store::ContextStore,
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct MessageEditor {
|
||||
@@ -74,11 +81,12 @@ pub struct MessageEditor {
|
||||
profile_selector: Entity<ProfileSelector>,
|
||||
edits_expanded: bool,
|
||||
editor_is_expanded: bool,
|
||||
last_estimated_token_count: Option<usize>,
|
||||
last_estimated_token_count: Option<u64>,
|
||||
update_token_count_task: Option<Task<()>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
const MIN_EDITOR_LINES: usize = 4;
|
||||
const MAX_EDITOR_LINES: usize = 8;
|
||||
|
||||
pub(crate) fn create_editor(
|
||||
@@ -86,6 +94,8 @@ pub(crate) fn create_editor(
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
text_thread_store: WeakEntity<TextThreadStore>,
|
||||
min_lines: usize,
|
||||
max_lines: Option<usize>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Editor> {
|
||||
@@ -102,7 +112,8 @@ pub(crate) fn create_editor(
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let mut editor = Editor::new(
|
||||
editor::EditorMode::AutoHeight {
|
||||
max_lines: MAX_EDITOR_LINES,
|
||||
min_lines,
|
||||
max_lines: max_lines,
|
||||
},
|
||||
buffer,
|
||||
None,
|
||||
@@ -157,6 +168,8 @@ impl MessageEditor {
|
||||
context_store.downgrade(),
|
||||
thread_store.clone(),
|
||||
text_thread_store.clone(),
|
||||
MIN_EDITOR_LINES,
|
||||
Some(MAX_EDITOR_LINES),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -175,8 +188,7 @@ impl MessageEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let incompatible_tools =
|
||||
cx.new(|cx| IncompatibleToolsState::new(thread.read(cx).tools().clone(), cx));
|
||||
let incompatible_tools = cx.new(|cx| IncompatibleToolsState::new(thread.clone(), cx));
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
|
||||
@@ -204,15 +216,8 @@ impl MessageEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let profile_selector = cx.new(|cx| {
|
||||
ProfileSelector::new(
|
||||
fs,
|
||||
thread.clone(),
|
||||
thread_store,
|
||||
editor.focus_handle(cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let profile_selector =
|
||||
cx.new(|cx| ProfileSelector::new(fs, thread.clone(), editor.focus_handle(cx), cx));
|
||||
|
||||
Self {
|
||||
editor: editor.clone(),
|
||||
@@ -241,6 +246,21 @@ impl MessageEditor {
|
||||
&self.context_store
|
||||
}
|
||||
|
||||
pub fn get_text(&self, cx: &App) -> String {
|
||||
self.editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
text: impl Into<Arc<str>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(text, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn expand_message_editor(
|
||||
&mut self,
|
||||
_: &ExpandMessageEditor,
|
||||
@@ -261,7 +281,8 @@ impl MessageEditor {
|
||||
})
|
||||
} else {
|
||||
editor.set_mode(EditorMode::AutoHeight {
|
||||
max_lines: MAX_EDITOR_LINES,
|
||||
min_lines: MIN_EDITOR_LINES,
|
||||
max_lines: Some(MAX_EDITOR_LINES),
|
||||
})
|
||||
}
|
||||
});
|
||||
@@ -304,6 +325,7 @@ impl MessageEditor {
|
||||
self.set_editor_is_expanded(false, cx);
|
||||
self.send_to_model(window, cx);
|
||||
|
||||
cx.emit(MessageEditorEvent::ScrollThreadToBottom);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -436,10 +458,6 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
fn handle_review_click(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.thread.read(cx).has_pending_edit_tool_uses() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.edits_expanded = true;
|
||||
AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
|
||||
cx.notify();
|
||||
@@ -514,10 +532,50 @@ impl MessageEditor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
fn handle_reject_file_changes(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.thread.read(cx).has_pending_edit_tool_uses() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
let buffer_snapshot = buffer.read(cx);
|
||||
let start = buffer_snapshot.anchor_before(Point::new(0, 0));
|
||||
let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
|
||||
thread
|
||||
.reject_edits_in_ranges(buffer, vec![start..end], cx)
|
||||
.detach();
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_accept_file_changes(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.thread.read(cx).has_pending_edit_tool_uses() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
let buffer_snapshot = buffer.read(cx);
|
||||
let start = buffer_snapshot.anchor_before(Point::new(0, 0));
|
||||
let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
|
||||
thread.keep_edits_in_range(buffer, start..end, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let thread = self.thread.read(cx);
|
||||
let model = thread.configured_model();
|
||||
if !model?.model.supports_max_mode() {
|
||||
if !model?.model.supports_burn_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -649,96 +707,87 @@ impl MessageEditor {
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.child(self.context_strip.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.when(focus_handle.is_focused(window), |this| {
|
||||
this.child(
|
||||
IconButton::new("toggle-height", expand_icon)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
let expand_label = if is_editor_expanded {
|
||||
"Minimize Message Editor".to_string()
|
||||
} else {
|
||||
"Expand Message Editor".to_string()
|
||||
};
|
||||
.when(focus_handle.is_focused(window), |this| {
|
||||
this.child(
|
||||
IconButton::new("toggle-height", expand_icon)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
let expand_label = if is_editor_expanded {
|
||||
"Minimize Message Editor".to_string()
|
||||
} else {
|
||||
"Expand Message Editor".to_string()
|
||||
};
|
||||
|
||||
Tooltip::for_action_in(
|
||||
expand_label,
|
||||
&ExpandMessageEditor,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window
|
||||
.dispatch_action(Box::new(ExpandMessageEditor), cx);
|
||||
})),
|
||||
)
|
||||
}),
|
||||
),
|
||||
Tooltip::for_action_in(
|
||||
expand_label,
|
||||
&ExpandMessageEditor,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(Box::new(ExpandMessageEditor), cx);
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_4()
|
||||
.gap_1()
|
||||
.when(is_editor_expanded, |this| {
|
||||
this.h(vh(0.8, window)).justify_between()
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.min_h_16()
|
||||
.when(is_editor_expanded, |this| this.h_full())
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let font_size = TextSize::Small
|
||||
.rems(cx)
|
||||
.to_pixels(settings.agent_font_size(cx));
|
||||
let line_height = settings.buffer_line_height.value() * font_size;
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let font_size = TextSize::Small
|
||||
.rems(cx)
|
||||
.to_pixels(settings.agent_font_size(cx));
|
||||
let line_height = settings.buffer_line_height.value() * font_size;
|
||||
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_size: font_size.into(),
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_size: font_size.into(),
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: editor_bg_color,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.into_any()
|
||||
}),
|
||||
)
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: editor_bg_color,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.into_any()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_none()
|
||||
.flex_wrap()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.child(self.render_follow_toggle(cx))
|
||||
.children(self.render_max_mode_toggle(cx)),
|
||||
.children(self.render_burn_mode_toggle(cx)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.flex_wrap()
|
||||
.when(!incompatible_tools.is_empty(), |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
@@ -882,7 +931,7 @@ impl MessageEditor {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_changed_buffers(
|
||||
fn render_edits_bar(
|
||||
&self,
|
||||
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
|
||||
window: &mut Window,
|
||||
@@ -1006,7 +1055,7 @@ impl MessageEditor {
|
||||
this.handle_review_click(window, cx)
|
||||
})),
|
||||
)
|
||||
.child(ui::Divider::vertical().color(ui::DividerColor::Border))
|
||||
.child(Divider::vertical().color(DividerColor::Border))
|
||||
.child(
|
||||
Button::new("reject-all-changes", "Reject All")
|
||||
.label_size(LabelSize::Small)
|
||||
@@ -1056,7 +1105,7 @@ impl MessageEditor {
|
||||
let file = buffer.read(cx).file()?;
|
||||
let path = file.path();
|
||||
|
||||
let parent_label = path.parent().and_then(|parent| {
|
||||
let file_path = path.parent().and_then(|parent| {
|
||||
let parent_str = parent.to_string_lossy();
|
||||
|
||||
if parent_str.is_empty() {
|
||||
@@ -1075,7 +1124,7 @@ impl MessageEditor {
|
||||
}
|
||||
});
|
||||
|
||||
let name_label = path.file_name().map(|name| {
|
||||
let file_name = path.file_name().map(|name| {
|
||||
Label::new(name.to_string_lossy().to_string())
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx)
|
||||
@@ -1090,36 +1139,22 @@ impl MessageEditor {
|
||||
.size(IconSize::Small)
|
||||
});
|
||||
|
||||
let hover_color = cx
|
||||
.theme()
|
||||
.colors()
|
||||
.element_background
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.025));
|
||||
|
||||
let overlay_gradient = linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(editor_bg_color, 1.),
|
||||
linear_color_stop(editor_bg_color.opacity(0.2), 0.),
|
||||
);
|
||||
|
||||
let overlay_gradient_hover = linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(hover_color, 1.),
|
||||
linear_color_stop(hover_color.opacity(0.2), 0.),
|
||||
);
|
||||
|
||||
let element = h_flex()
|
||||
.group("edited-code")
|
||||
.id(("file-container", index))
|
||||
.cursor_pointer()
|
||||
.relative()
|
||||
.py_1()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.hover(|style| style.bg(hover_color))
|
||||
.bg(editor_bg_color)
|
||||
.when(index < changed_buffers.len() - 1, |parent| {
|
||||
parent.border_color(border_color).border_b_1()
|
||||
})
|
||||
@@ -1134,47 +1169,75 @@ impl MessageEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.children(name_label)
|
||||
.children(parent_label),
|
||||
.children(file_name)
|
||||
.children(file_path),
|
||||
), // TODO: Implement line diff
|
||||
// .child(Label::new("+").color(Color::Created))
|
||||
// .child(Label::new("-").color(Color::Deleted)),
|
||||
)
|
||||
.child(
|
||||
div().visible_on_hover("edited-code").child(
|
||||
Button::new("review", "Review")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.handle_file_click(
|
||||
buffer.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}),
|
||||
),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.visible_on_hover("edited-code")
|
||||
.child(
|
||||
Button::new("review", "Review")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.handle_file_click(
|
||||
buffer.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Divider::vertical().color(DividerColor::BorderVariant),
|
||||
)
|
||||
.child(
|
||||
Button::new("reject-file", "Reject")
|
||||
.label_size(LabelSize::Small)
|
||||
.disabled(pending_edits)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.handle_reject_file_changes(
|
||||
buffer.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("accept-file", "Accept")
|
||||
.label_size(LabelSize::Small)
|
||||
.disabled(pending_edits)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.handle_accept_file_changes(
|
||||
buffer.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("gradient-overlay")
|
||||
.absolute()
|
||||
.h_5_6()
|
||||
.h_full()
|
||||
.w_12()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.right(px(52.))
|
||||
.bg(overlay_gradient)
|
||||
.group_hover("edited-code", |style| {
|
||||
style.bg(overlay_gradient_hover)
|
||||
}),
|
||||
)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.handle_file_click(buffer.clone(), window, cx);
|
||||
})
|
||||
});
|
||||
.right(px(152.))
|
||||
.bg(overlay_gradient),
|
||||
);
|
||||
|
||||
Some(element)
|
||||
},
|
||||
@@ -1183,15 +1246,17 @@ impl MessageEditor {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let is_using_zed_provider = self
|
||||
.thread
|
||||
fn is_using_zed_provider(&self, cx: &App) -> bool {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.configured_model()
|
||||
.map_or(false, |model| {
|
||||
model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
|
||||
});
|
||||
if !is_using_zed_provider {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
if !self.is_using_zed_provider(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1213,24 +1278,8 @@ impl MessageEditor {
|
||||
Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
|
||||
})
|
||||
.unwrap_or(zed_llm_client::Plan::ZedFree);
|
||||
let usage = self.thread.read(cx).last_usage().or_else(|| {
|
||||
maybe!({
|
||||
let amount = user_store.model_request_usage_amount()?;
|
||||
let limit = user_store.model_request_usage_limit()?.variant?;
|
||||
|
||||
Some(RequestUsage {
|
||||
amount: amount as i32,
|
||||
limit: match limit {
|
||||
proto::usage_limit::Variant::Limited(limited) => {
|
||||
zed_llm_client::UsageLimit::Limited(limited.limit as i32)
|
||||
}
|
||||
proto::usage_limit::Variant::Unlimited(_) => {
|
||||
zed_llm_client::UsageLimit::Unlimited
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
})?;
|
||||
let usage = user_store.model_request_usage()?;
|
||||
|
||||
Some(
|
||||
div()
|
||||
@@ -1245,14 +1294,6 @@ impl MessageEditor {
|
||||
token_usage_ratio: TokenUsageRatio,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Div> {
|
||||
let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
|
||||
"Thread reached the token limit"
|
||||
} else {
|
||||
"Thread reaching the token limit soon"
|
||||
};
|
||||
|
||||
let message = "Start a new thread from a summary to continue the conversation.";
|
||||
|
||||
let icon = if token_usage_ratio == TokenUsageRatio::Exceeded {
|
||||
Icon::new(IconName::X)
|
||||
.color(Color::Error)
|
||||
@@ -1263,23 +1304,51 @@ impl MessageEditor {
|
||||
.size(IconSize::XSmall)
|
||||
};
|
||||
|
||||
Some(
|
||||
div()
|
||||
.child(ui::Callout::multi_line(
|
||||
title,
|
||||
message,
|
||||
icon,
|
||||
"Start New Thread",
|
||||
Box::new(cx.listener(|this, _, window, cx| {
|
||||
let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
|
||||
"Thread reached the token limit"
|
||||
} else {
|
||||
"Thread reaching the token limit soon"
|
||||
};
|
||||
|
||||
let description = if self.is_using_zed_provider(cx) {
|
||||
"To continue, start a new thread from a summary or turn burn mode on."
|
||||
} else {
|
||||
"To continue, start a new thread from a summary."
|
||||
};
|
||||
|
||||
let mut callout = Callout::new()
|
||||
.line_height(line_height)
|
||||
.icon(icon)
|
||||
.title(title)
|
||||
.description(description)
|
||||
.primary_action(
|
||||
Button::new("start-new-thread", "Start New Thread")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
let from_thread_id = Some(this.thread.read(cx).id().clone());
|
||||
window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
|
||||
})),
|
||||
))
|
||||
.line_height(line_height),
|
||||
);
|
||||
|
||||
if self.is_using_zed_provider(cx) {
|
||||
callout = callout.secondary_action(
|
||||
IconButton::new("burn-mode-callout", IconName::ZedBurnMode)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.toggle_burn_mode(&ToggleBurnMode, window, cx);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Some(
|
||||
div()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(callout),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn last_estimated_token_count(&self) -> Option<usize> {
|
||||
pub fn last_estimated_token_count(&self) -> Option<u64> {
|
||||
self.last_estimated_token_count
|
||||
}
|
||||
|
||||
@@ -1403,6 +1472,69 @@ impl MessageEditor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContextCreasesAddon {
|
||||
creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Addon for ContextCreasesAddon {
|
||||
fn to_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextCreasesAddon {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
creases: HashMap::default(),
|
||||
_subscription: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_creases(
|
||||
&mut self,
|
||||
context_store: &Entity<ContextStore>,
|
||||
key: AgentContextKey,
|
||||
creases: impl IntoIterator<Item = (CreaseId, SharedString)>,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.creases.entry(key).or_default().extend(creases);
|
||||
self._subscription = Some(cx.subscribe(
|
||||
&context_store,
|
||||
|editor, _, event, cx| match event {
|
||||
ContextStoreEvent::ContextRemoved(key) => {
|
||||
let Some(this) = editor.addon_mut::<Self>() else {
|
||||
return;
|
||||
};
|
||||
let (crease_ids, replacement_texts): (Vec<_>, Vec<_>) = this
|
||||
.creases
|
||||
.remove(key)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let ranges = editor
|
||||
.remove_creases(crease_ids, cx)
|
||||
.into_iter()
|
||||
.map(|(_, range)| range)
|
||||
.collect::<Vec<_>>();
|
||||
editor.unfold_ranges(&ranges, false, false, cx);
|
||||
editor.edit(ranges.into_iter().zip(replacement_texts), cx);
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>> {
|
||||
self.creases
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_message_creases(
|
||||
editor: &mut Editor,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
@@ -1441,8 +1573,9 @@ pub fn extract_message_creases(
|
||||
let context = contexts_by_crease_id.remove(&id);
|
||||
MessageCrease {
|
||||
range,
|
||||
metadata,
|
||||
context,
|
||||
label: metadata.label,
|
||||
icon_path: metadata.icon_path,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -1455,6 +1588,7 @@ impl EventEmitter<MessageEditorEvent> for MessageEditor {}
|
||||
pub enum MessageEditorEvent {
|
||||
EstimatedTokenCount,
|
||||
Changed,
|
||||
ScrollThreadToBottom,
|
||||
}
|
||||
|
||||
impl Focusable for MessageEditor {
|
||||
@@ -1472,6 +1606,8 @@ impl Render for MessageEditor {
|
||||
total_token_usage.ratio()
|
||||
});
|
||||
|
||||
let burn_mode_enabled = thread.completion_mode() == CompletionMode::Burn;
|
||||
|
||||
let action_log = self.thread.read(cx).action_log();
|
||||
let changed_buffers = action_log.read(cx).changed_buffers(cx);
|
||||
|
||||
@@ -1480,7 +1616,7 @@ impl Render for MessageEditor {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.when(changed_buffers.len() > 0, |parent| {
|
||||
parent.child(self.render_changed_buffers(&changed_buffers, window, cx))
|
||||
parent.child(self.render_edits_bar(&changed_buffers, window, cx))
|
||||
})
|
||||
.child(self.render_editor(window, cx))
|
||||
.children({
|
||||
@@ -1488,7 +1624,7 @@ impl Render for MessageEditor {
|
||||
|
||||
if usage_callout.is_some() {
|
||||
usage_callout
|
||||
} else if token_usage_ratio != TokenUsageRatio::Normal {
|
||||
} else if token_usage_ratio != TokenUsageRatio::Normal && !burn_mode_enabled {
|
||||
self.render_token_limit_callout(line_height, token_usage_ratio, cx)
|
||||
} else {
|
||||
None
|
||||
@@ -1511,8 +1647,8 @@ pub fn insert_message_creases(
|
||||
let start = buffer_snapshot.anchor_after(crease.range.start);
|
||||
let end = buffer_snapshot.anchor_before(crease.range.end);
|
||||
crease_for_mention(
|
||||
crease.metadata.label.clone(),
|
||||
crease.metadata.icon_path.clone(),
|
||||
crease.label.clone(),
|
||||
crease.icon_path.clone(),
|
||||
start..end,
|
||||
cx.weak_entity(),
|
||||
)
|
||||
@@ -1524,12 +1660,7 @@ pub fn insert_message_creases(
|
||||
for (crease, id) in message_creases.iter().zip(ids) {
|
||||
if let Some(context) = crease.context.as_ref() {
|
||||
let key = AgentContextKey(context.clone());
|
||||
addon.add_creases(
|
||||
context_store,
|
||||
key,
|
||||
vec![(id, crease.metadata.label.clone())],
|
||||
cx,
|
||||
);
|
||||
addon.add_creases(context_store, key, vec![(id, crease.label.clone())], cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{
|
||||
AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, GroupedAgentProfiles,
|
||||
builtin_profiles,
|
||||
use crate::{ManageProfiles, ToggleProfileSelector};
|
||||
use agent::{
|
||||
Thread,
|
||||
agent_profile::{AgentProfile, AvailableProfiles},
|
||||
};
|
||||
use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
|
||||
use fs::Fs;
|
||||
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
|
||||
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, prelude::*};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use settings::{Settings as _, SettingsStore, update_settings_file};
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
ContextMenu, ContextMenuEntry, DocumentationSide, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{ManageProfiles, Thread, ThreadStore, ToggleProfileSelector};
|
||||
|
||||
pub struct ProfileSelector {
|
||||
profiles: GroupedAgentProfiles,
|
||||
profiles: AvailableProfiles,
|
||||
fs: Arc<dyn Fs>,
|
||||
thread: Entity<Thread>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
focus_handle: FocusHandle,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
@@ -30,7 +27,6 @@ impl ProfileSelector {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
thread: Entity<Thread>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
focus_handle: FocusHandle,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -39,10 +35,9 @@ impl ProfileSelector {
|
||||
});
|
||||
|
||||
Self {
|
||||
profiles: GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx)),
|
||||
profiles: AgentProfile::available_profiles(cx),
|
||||
fs,
|
||||
thread,
|
||||
thread_store,
|
||||
menu_handle: PopoverMenuHandle::default(),
|
||||
focus_handle,
|
||||
_subscriptions: vec![settings_subscription],
|
||||
@@ -54,7 +49,7 @@ impl ProfileSelector {
|
||||
}
|
||||
|
||||
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
||||
self.profiles = GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx));
|
||||
self.profiles = AgentProfile::available_profiles(cx);
|
||||
}
|
||||
|
||||
fn build_context_menu(
|
||||
@@ -64,21 +59,30 @@ impl ProfileSelector {
|
||||
) -> Entity<ContextMenu> {
|
||||
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
for (profile_id, profile) in self.profiles.builtin.iter() {
|
||||
|
||||
let mut found_non_builtin = false;
|
||||
for (profile_id, profile_name) in self.profiles.iter() {
|
||||
if !builtin_profiles::is_builtin(profile_id) {
|
||||
found_non_builtin = true;
|
||||
continue;
|
||||
}
|
||||
menu = menu.item(self.menu_entry_for_profile(
|
||||
profile_id.clone(),
|
||||
profile,
|
||||
profile_name,
|
||||
settings,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
|
||||
if !self.profiles.custom.is_empty() {
|
||||
if found_non_builtin {
|
||||
menu = menu.separator().header("Custom Profiles");
|
||||
for (profile_id, profile) in self.profiles.custom.iter() {
|
||||
for (profile_id, profile_name) in self.profiles.iter() {
|
||||
if builtin_profiles::is_builtin(profile_id) {
|
||||
continue;
|
||||
}
|
||||
menu = menu.item(self.menu_entry_for_profile(
|
||||
profile_id.clone(),
|
||||
profile,
|
||||
profile_name,
|
||||
settings,
|
||||
cx,
|
||||
));
|
||||
@@ -99,19 +103,20 @@ impl ProfileSelector {
|
||||
fn menu_entry_for_profile(
|
||||
&self,
|
||||
profile_id: AgentProfileId,
|
||||
profile: &AgentProfile,
|
||||
profile_name: &SharedString,
|
||||
settings: &AgentSettings,
|
||||
_cx: &App,
|
||||
cx: &App,
|
||||
) -> ContextMenuEntry {
|
||||
let documentation = match profile.name.to_lowercase().as_str() {
|
||||
let documentation = match profile_name.to_lowercase().as_str() {
|
||||
builtin_profiles::WRITE => Some("Get help to write anything."),
|
||||
builtin_profiles::ASK => Some("Chat about your codebase."),
|
||||
builtin_profiles::MINIMAL => Some("Chat about anything with no tools."),
|
||||
_ => None,
|
||||
};
|
||||
let thread_profile_id = self.thread.read(cx).profile().id();
|
||||
|
||||
let entry = ContextMenuEntry::new(profile.name.clone())
|
||||
.toggleable(IconPosition::End, profile_id == settings.default_profile);
|
||||
let entry = ContextMenuEntry::new(profile_name.clone())
|
||||
.toggleable(IconPosition::End, &profile_id == thread_profile_id);
|
||||
|
||||
let entry = if let Some(doc_text) = documentation {
|
||||
entry.documentation_aside(documentation_side(settings.dock), move |_| {
|
||||
@@ -123,7 +128,7 @@ impl ProfileSelector {
|
||||
|
||||
entry.handler({
|
||||
let fs = self.fs.clone();
|
||||
let thread_store = self.thread_store.clone();
|
||||
let thread = self.thread.clone();
|
||||
let profile_id = profile_id.clone();
|
||||
move |_window, cx| {
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, {
|
||||
@@ -133,11 +138,9 @@ impl ProfileSelector {
|
||||
}
|
||||
});
|
||||
|
||||
thread_store
|
||||
.update(cx, |this, cx| {
|
||||
this.load_profile_by_id(profile_id.clone(), cx);
|
||||
})
|
||||
.log_err();
|
||||
thread.update(cx, |this, cx| {
|
||||
this.set_profile(profile_id.clone(), cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -146,7 +149,7 @@ impl ProfileSelector {
|
||||
impl Render for ProfileSelector {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
let profile_id = &settings.default_profile;
|
||||
let profile_id = self.thread.read(cx).profile().id();
|
||||
let profile = settings.profiles.get(profile_id);
|
||||
|
||||
let selected_profile = profile
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::context_editor::ContextEditor;
|
||||
use crate::text_thread_editor::TextThreadEditor;
|
||||
use anyhow::Result;
|
||||
pub use assistant_slash_command::SlashCommand;
|
||||
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
|
||||
@@ -10,9 +10,7 @@ use parking_lot::Mutex;
|
||||
use project::{CompletionIntent, CompletionSource, lsp_store::CompletionDocumentation};
|
||||
use rope::Point;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
@@ -23,14 +21,14 @@ use workspace::Workspace;
|
||||
pub struct SlashCommandCompletionProvider {
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakEntity<ContextEditor>>,
|
||||
editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
}
|
||||
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakEntity<ContextEditor>>,
|
||||
editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -64,6 +62,7 @@ impl SlashCommandCompletionProvider {
|
||||
&candidates,
|
||||
&command_name,
|
||||
true,
|
||||
true,
|
||||
usize::MAX,
|
||||
&Default::default(),
|
||||
cx.background_executor().clone(),
|
||||
@@ -240,13 +239,14 @@ impl SlashCommandCompletionProvider {
|
||||
|
||||
Ok(vec![project::CompletionResponse {
|
||||
completions,
|
||||
is_incomplete: false,
|
||||
// TODO: Could have slash commands indicate whether their completions are incomplete.
|
||||
is_incomplete: true,
|
||||
}])
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(vec![project::CompletionResponse {
|
||||
completions: Vec::new(),
|
||||
is_incomplete: false,
|
||||
is_incomplete: true,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
@@ -275,17 +275,17 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
position.row,
|
||||
call.arguments.last().map_or(call.name.end, |arg| arg.end) as u32,
|
||||
);
|
||||
let command_range = buffer.anchor_after(command_range_start)
|
||||
let command_range = buffer.anchor_before(command_range_start)
|
||||
..buffer.anchor_after(command_range_end);
|
||||
|
||||
let name = line[call.name.clone()].to_string();
|
||||
let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last()
|
||||
{
|
||||
let last_arg_start =
|
||||
buffer.anchor_after(Point::new(position.row, argument.start as u32));
|
||||
buffer.anchor_before(Point::new(position.row, argument.start as u32));
|
||||
let first_arg_start = call.arguments.first().expect("we have the last element");
|
||||
let first_arg_start =
|
||||
buffer.anchor_after(Point::new(position.row, first_arg_start.start as u32));
|
||||
let first_arg_start = buffer
|
||||
.anchor_before(Point::new(position.row, first_arg_start.start as u32));
|
||||
let arguments = call
|
||||
.arguments
|
||||
.into_iter()
|
||||
@@ -298,7 +298,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
)
|
||||
} else {
|
||||
let start =
|
||||
buffer.anchor_after(Point::new(position.row, call.name.start as u32));
|
||||
buffer.anchor_before(Point::new(position.row, call.name.start as u32));
|
||||
(None, start..buffer_position)
|
||||
};
|
||||
|
||||
@@ -326,16 +326,6 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
_: Entity<Buffer>,
|
||||
_: Vec<usize>,
|
||||
_: Rc<RefCell<Box<[project::Completion]>>>,
|
||||
_: &mut Context<Editor>,
|
||||
) -> Task<Result<bool>> {
|
||||
Task::ready(Ok(true))
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||