Compare commits
564 Commits
v0.93.3
...
v0.96.0-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4274ccee62 | ||
|
|
05cd06177c | ||
|
|
760fece112 | ||
|
|
64d134a0dc | ||
|
|
07dc82409b | ||
|
|
9c9ce15afc | ||
|
|
e3f9a01f6b | ||
|
|
f4413b0969 | ||
|
|
c754c1e9e2 | ||
|
|
aacc4bb8b0 | ||
|
|
8c855680e7 | ||
|
|
96ef6ab326 | ||
|
|
929a9f97b2 | ||
|
|
fd72f4526d | ||
|
|
d023189bda | ||
|
|
d26f76ba90 | ||
|
|
843e74689d | ||
|
|
98b8008bcc | ||
|
|
c528880155 | ||
|
|
3058a96dee | ||
|
|
c5e47f27f5 | ||
|
|
33921183dc | ||
|
|
6ed7820f7c | ||
|
|
10db05f87f | ||
|
|
6f7a6e57fc | ||
|
|
94358ffb16 | ||
|
|
82a9d53c8a | ||
|
|
6349d90cac | ||
|
|
6123c67de9 | ||
|
|
23f25562b5 | ||
|
|
f52722b6a4 | ||
|
|
75d900704e | ||
|
|
91ba80ae98 | ||
|
|
9aeb970f09 | ||
|
|
7cb5326ba0 | ||
|
|
e73f394604 | ||
|
|
018eb06091 | ||
|
|
b00703a149 | ||
|
|
bf2dcd4582 | ||
|
|
fab26267db | ||
|
|
192f747bd1 | ||
|
|
aee008440b | ||
|
|
137734cfcf | ||
|
|
009cf48b26 | ||
|
|
a884bd77e1 | ||
|
|
fa529d9590 | ||
|
|
7fde3614fe | ||
|
|
96abba2b7d | ||
|
|
9e44de90af | ||
|
|
9f650dfa52 | ||
|
|
1a8bfdfa21 | ||
|
|
ede86a686c | ||
|
|
4efcf492ee | ||
|
|
04625fe376 | ||
|
|
6793d4b6b8 | ||
|
|
c9bf407431 | ||
|
|
fef73ae921 | ||
|
|
3e136943c0 | ||
|
|
6770aeeb3c | ||
|
|
a4bf19c5bd | ||
|
|
4cc06748c9 | ||
|
|
f887a17ffe | ||
|
|
dd6b674e7e | ||
|
|
8642a1d074 | ||
|
|
ee9123a7da | ||
|
|
5b6582a7c2 | ||
|
|
6c7a6d43fc | ||
|
|
94796e943b | ||
|
|
965cc2efbc | ||
|
|
11173b2199 | ||
|
|
dc557e1647 | ||
|
|
f5eac82e81 | ||
|
|
eaa8224076 | ||
|
|
10a1df3faa | ||
|
|
419cbcbaf8 | ||
|
|
f24001c130 | ||
|
|
322ebc33d1 | ||
|
|
4d91409bbc | ||
|
|
c3e8ea304a | ||
|
|
dcc2cd8dff | ||
|
|
b9e0074793 | ||
|
|
c69d0d50cd | ||
|
|
031172d3f2 | ||
|
|
c0b2326053 | ||
|
|
c7669317ec | ||
|
|
369ccc725c | ||
|
|
cde5b3952d | ||
|
|
c6195e6176 | ||
|
|
0f5489397f | ||
|
|
c466711cd1 | ||
|
|
9c150252aa | ||
|
|
31720d8825 | ||
|
|
21e7e35e73 | ||
|
|
2f2ef7c165 | ||
|
|
2e2333107a | ||
|
|
b14cd5f56d | ||
|
|
ccc78000bd | ||
|
|
c130dd6b47 | ||
|
|
f710efca3b | ||
|
|
2053418f21 | ||
|
|
29cbeb39bd | ||
|
|
bf9dfa3b51 | ||
|
|
f1b034d4f8 | ||
|
|
ff8a89a075 | ||
|
|
1424a7a56a | ||
|
|
415b8f0147 | ||
|
|
77c4fc98bd | ||
|
|
b7ed467690 | ||
|
|
50623c018c | ||
|
|
9da8f609cf | ||
|
|
331fd896b5 | ||
|
|
5797282b98 | ||
|
|
00b04f1c85 | ||
|
|
d5f7ad08fa | ||
|
|
ef7aa66959 | ||
|
|
9a1a9813cb | ||
|
|
608c16342c | ||
|
|
c2ffd8975b | ||
|
|
8cce403c11 | ||
|
|
26b9be628e | ||
|
|
5385ca411b | ||
|
|
c9ba4c764a | ||
|
|
6da5008f32 | ||
|
|
488b41826b | ||
|
|
1e8ee5361d | ||
|
|
7cbcc28b1b | ||
|
|
d164034198 | ||
|
|
ad4f5e55cb | ||
|
|
0c7949bdee | ||
|
|
6297675055 | ||
|
|
0e600ad2a4 | ||
|
|
1cc8ecad12 | ||
|
|
af9506b21d | ||
|
|
c732aa1617 | ||
|
|
37568ccbf0 | ||
|
|
c141519dba | ||
|
|
dc09a11090 | ||
|
|
2cb7d8aa96 | ||
|
|
e69240cf13 | ||
|
|
001e848393 | ||
|
|
2ac485a6ec | ||
|
|
c12821f6c5 | ||
|
|
6260d977fb | ||
|
|
6d96c6ef51 | ||
|
|
3db1aac119 | ||
|
|
99c2395a86 | ||
|
|
78c8324698 | ||
|
|
10c62779d9 | ||
|
|
5086e37e73 | ||
|
|
b9f5cb0301 | ||
|
|
33e2b52a01 | ||
|
|
297fa029e3 | ||
|
|
b68cd58a3b | ||
|
|
4b3bb2c661 | ||
|
|
4a4dd39875 | ||
|
|
d244c0fcea | ||
|
|
badf94b097 | ||
|
|
08e24bbbae | ||
|
|
af7b2f17ae | ||
|
|
ef296e46cb | ||
|
|
2ca4b3f4cc | ||
|
|
debe6f107e | ||
|
|
02f523094b | ||
|
|
9165320390 | ||
|
|
550aa2d6bd | ||
|
|
be881369fa | ||
|
|
5483bd1404 | ||
|
|
4b4d049b0a | ||
|
|
dd0dbdc5bd | ||
|
|
1649cf81de | ||
|
|
5012d618e6 | ||
|
|
98a0113ac3 | ||
|
|
efe8b8b6d0 | ||
|
|
298c2213a0 | ||
|
|
8161438a85 | ||
|
|
748e7af5a2 | ||
|
|
f5fec55930 | ||
|
|
91832c8cd8 | ||
|
|
15010e94fd | ||
|
|
f164eb5289 | ||
|
|
1fbf09fe4c | ||
|
|
a1fe5abeaf | ||
|
|
3c1ab3d0b8 | ||
|
|
4125e7eccc | ||
|
|
e83afdc5ab | ||
|
|
4f60679861 | ||
|
|
dce72a1ce7 | ||
|
|
307d8d9c8d | ||
|
|
82079dd422 | ||
|
|
a6d713eb3d | ||
|
|
e00e73f608 | ||
|
|
6739c31594 | ||
|
|
a75a7e2b1d | ||
|
|
92a0a4e367 | ||
|
|
273b9e1636 | ||
|
|
9ffe220def | ||
|
|
4029481fd0 | ||
|
|
f0cddeb478 | ||
|
|
0189742497 | ||
|
|
3318896ad9 | ||
|
|
6c8cb6b2a9 | ||
|
|
6e24ded2bc | ||
|
|
52a497be21 | ||
|
|
b4b0f622de | ||
|
|
232d14a3ae | ||
|
|
dea728a7e5 | ||
|
|
6cf13c62d1 | ||
|
|
d70f415e8e | ||
|
|
dbec2ed1f1 | ||
|
|
96ce0bb783 | ||
|
|
2ffce24ef0 | ||
|
|
75fe77c11d | ||
|
|
20d8a2a1ec | ||
|
|
460bf93866 | ||
|
|
362023ccf2 | ||
|
|
da7dce79f6 | ||
|
|
3f5667b101 | ||
|
|
caa29d57c2 | ||
|
|
b70b76029e | ||
|
|
66bf56fc4f | ||
|
|
4a69c71167 | ||
|
|
cb24cb1ea5 | ||
|
|
d69b07bafd | ||
|
|
abf3b4a54e | ||
|
|
79ece8a86e | ||
|
|
318deed25b | ||
|
|
c03dda1a0c | ||
|
|
6f1e988cb9 | ||
|
|
7d634f66e2 | ||
|
|
4ab2b8b24b | ||
|
|
e6ec0af743 | ||
|
|
fff65968bf | ||
|
|
e57f6f21fe | ||
|
|
3ca0170264 | ||
|
|
a86b6c42c7 | ||
|
|
793eff1695 | ||
|
|
b4ed0347b4 | ||
|
|
2c7e5e0671 | ||
|
|
11ae99fbd6 | ||
|
|
708852aa00 | ||
|
|
348c93e8bb | ||
|
|
5408275c7a | ||
|
|
3e245fec90 | ||
|
|
5e7d9dc718 | ||
|
|
b66453e771 | ||
|
|
0b0a161626 | ||
|
|
492b849ea1 | ||
|
|
8ced7ab00a | ||
|
|
c298cf7527 | ||
|
|
1936bdebb3 | ||
|
|
dd6629416c | ||
|
|
f6c96ec892 | ||
|
|
801f41e68e | ||
|
|
8b8bafef22 | ||
|
|
594b6e8d64 | ||
|
|
6a15ae9c01 | ||
|
|
76873c508a | ||
|
|
b80281e556 | ||
|
|
1baa13561d | ||
|
|
afccf608f4 | ||
|
|
de01fa1794 | ||
|
|
0e0d78df84 | ||
|
|
ec47464bba | ||
|
|
85add260f6 | ||
|
|
eff0ee3b60 | ||
|
|
91a94d299e | ||
|
|
cc88bff1ff | ||
|
|
d7f6b5e1a0 | ||
|
|
6ba1c3071a | ||
|
|
8b3b1a6074 | ||
|
|
64b77bfa8d | ||
|
|
5505ebf4bc | ||
|
|
d5f0df94f7 | ||
|
|
1914037922 | ||
|
|
03a00df8b1 | ||
|
|
a8602b2a0c | ||
|
|
25564ea058 | ||
|
|
a7ce602bac | ||
|
|
31483db5d8 | ||
|
|
b6520a8f1d | ||
|
|
4c51ab8a25 | ||
|
|
76af424d79 | ||
|
|
e45d3a0a63 | ||
|
|
48371ab8b2 | ||
|
|
e9b34de7c8 | ||
|
|
0d18b72cf8 | ||
|
|
f461a70970 | ||
|
|
65dbb38926 | ||
|
|
c5a42c317a | ||
|
|
a732b2e043 | ||
|
|
c409059dc4 | ||
|
|
5a1476a1e5 | ||
|
|
0b4c5db5e2 | ||
|
|
8a5e7047f0 | ||
|
|
d5acfe8fc1 | ||
|
|
0733e8d50f | ||
|
|
f8316dd127 | ||
|
|
c700342a1c | ||
|
|
0e4c904091 | ||
|
|
d2127825e3 | ||
|
|
fe57e04016 | ||
|
|
b055f594b0 | ||
|
|
14eab4e94f | ||
|
|
6c01aeaf77 | ||
|
|
806268f0db | ||
|
|
85701c9b80 | ||
|
|
4eedc3e646 | ||
|
|
8efb66be67 | ||
|
|
43d4f04331 | ||
|
|
e36d5f41c8 | ||
|
|
026ad191eb | ||
|
|
525521eeb3 | ||
|
|
138de37cbf | ||
|
|
18a5a47f8a | ||
|
|
3408b98167 | ||
|
|
36907bb4dc | ||
|
|
e017d62e92 | ||
|
|
ae54e1d224 | ||
|
|
f83514cde4 | ||
|
|
92df76e632 | ||
|
|
7c2c1a279b | ||
|
|
cec884b5a5 | ||
|
|
a5d9a10d7b | ||
|
|
6042cf928c | ||
|
|
4a654f5252 | ||
|
|
0db0876289 | ||
|
|
a9c1395b9b | ||
|
|
ed75c31640 | ||
|
|
b699e5c142 | ||
|
|
e3ab54942e | ||
|
|
1d737e490b | ||
|
|
3be8977ee8 | ||
|
|
c1a6292152 | ||
|
|
081e340d26 | ||
|
|
818ddbc703 | ||
|
|
888d3b3fd6 | ||
|
|
d000ea9739 | ||
|
|
1eb0f3d091 | ||
|
|
98f71a7fa3 | ||
|
|
e57364ede6 | ||
|
|
aeafa6f6d6 | ||
|
|
c84f3b3bfc | ||
|
|
54fad5969f | ||
|
|
3027e4729a | ||
|
|
6747acbb84 | ||
|
|
ac6e9c88e9 | ||
|
|
d8d0bdc479 | ||
|
|
3d6e063a6d | ||
|
|
d22a576f5e | ||
|
|
abb58c41db | ||
|
|
9ee2707d43 | ||
|
|
a9a51ab3ad | ||
|
|
a6dabf7acf | ||
|
|
787412b545 | ||
|
|
cd670e340f | ||
|
|
33f5248d4f | ||
|
|
f6b64dc67a | ||
|
|
73b0f3b23d | ||
|
|
5366631173 | ||
|
|
530561e4eb | ||
|
|
77b120323b | ||
|
|
d6112e4a59 | ||
|
|
2678dfdc57 | ||
|
|
167dd1c5d2 | ||
|
|
b146762f68 | ||
|
|
652909cdba | ||
|
|
3445bc42b6 | ||
|
|
98edc0f885 | ||
|
|
083e4e76e2 | ||
|
|
943c93fda7 | ||
|
|
30e77aa388 | ||
|
|
429a9cddae | ||
|
|
bb9ade5b6f | ||
|
|
2b59f27c3b | ||
|
|
0972766d1d | ||
|
|
15e0feb91d | ||
|
|
667b70afde | ||
|
|
480d8c511b | ||
|
|
3312c9114b | ||
|
|
2c54d926ea | ||
|
|
143a020694 | ||
|
|
67214f0e55 | ||
|
|
096bad1f73 | ||
|
|
f77b680db9 | ||
|
|
976edfedf7 | ||
|
|
5c21ed4263 | ||
|
|
dfb30218ca | ||
|
|
acef5ff195 | ||
|
|
11fee4ce42 | ||
|
|
4d4544f680 | ||
|
|
2c7900e11b | ||
|
|
83b3a914bc | ||
|
|
890b164278 | ||
|
|
a68e68a0d9 | ||
|
|
ba3d1e4dba | ||
|
|
f25a09bfd8 | ||
|
|
48982c3036 | ||
|
|
316e19ce94 | ||
|
|
96a34ad0ee | ||
|
|
781fa0cff4 | ||
|
|
c61de29c11 | ||
|
|
cb4b92aa61 | ||
|
|
d59e91aff2 | ||
|
|
d6828583d8 | ||
|
|
1722d61190 | ||
|
|
7fddc223cd | ||
|
|
31f0f9f7b1 | ||
|
|
97e5d40579 | ||
|
|
8d982a6c2d | ||
|
|
4c78019317 | ||
|
|
2f1a27631e | ||
|
|
a31d3eca45 | ||
|
|
9698b51524 | ||
|
|
3b9a2e3261 | ||
|
|
70a45fc800 | ||
|
|
7ac1885449 | ||
|
|
58343563ba | ||
|
|
debdc3603e | ||
|
|
ddcbc73bf0 | ||
|
|
6368cf1a27 | ||
|
|
8c03e9e122 | ||
|
|
e82b4d8957 | ||
|
|
5322aa09b9 | ||
|
|
8f68688a64 | ||
|
|
49c00fd571 | ||
|
|
e217a95fcc | ||
|
|
76d35b7122 | ||
|
|
2b989a9f12 | ||
|
|
d4d88252c3 | ||
|
|
f2c510000b | ||
|
|
29bb6c67b0 | ||
|
|
9ae611fa89 | ||
|
|
05dc672c2a | ||
|
|
10765d69f4 | ||
|
|
e744fb8842 | ||
|
|
d2fef07782 | ||
|
|
8cdf1a0faf | ||
|
|
89137e2e83 | ||
|
|
34c6d66d04 | ||
|
|
bec9c26fa2 | ||
|
|
02e124cec4 | ||
|
|
b231fa47af | ||
|
|
c898298c5c | ||
|
|
1ed52276e0 | ||
|
|
ea837a183b | ||
|
|
8acc5cf8f4 | ||
|
|
f155f5ded7 | ||
|
|
b3aa75a363 | ||
|
|
e1f22c3684 | ||
|
|
7abaf22b93 | ||
|
|
6d1068d1e9 | ||
|
|
271cd25a1d | ||
|
|
addb62c1fc | ||
|
|
63074c5cd8 | ||
|
|
2b1b1225f5 | ||
|
|
7684a26daa | ||
|
|
c7fa8dbc70 | ||
|
|
df20a43704 | ||
|
|
8a64b07622 | ||
|
|
e280483c5f | ||
|
|
afa59eed01 | ||
|
|
f940104b6f | ||
|
|
dbd4b33568 | ||
|
|
9ce9b73879 | ||
|
|
f5f495831a | ||
|
|
2e730d8fa4 | ||
|
|
4d76162da8 | ||
|
|
2ba3262f29 | ||
|
|
ab7dd80423 | ||
|
|
568a67c4d7 | ||
|
|
daa2ebb57f | ||
|
|
5fadbf77d4 | ||
|
|
5ad85b44d6 | ||
|
|
3028767d12 | ||
|
|
b193d62a5d | ||
|
|
7397b8028c | ||
|
|
d506522eef | ||
|
|
b5233b3ad5 | ||
|
|
9287634548 | ||
|
|
78b3c9b88a | ||
|
|
83f4320b60 | ||
|
|
4c3c0eb796 | ||
|
|
2ead3de7de | ||
|
|
6e3d1b962a | ||
|
|
387415eb01 | ||
|
|
f83cfda9bc | ||
|
|
7a268b1cf6 | ||
|
|
79b97f9e75 | ||
|
|
8a3b515f56 | ||
|
|
39137fc19f | ||
|
|
1ae5261024 | ||
|
|
922d8f30d6 | ||
|
|
8609ccdcf7 | ||
|
|
ae9eb7c67a | ||
|
|
764968e7d0 | ||
|
|
ba80c53278 | ||
|
|
0a7245a583 | ||
|
|
c7c38c7da2 | ||
|
|
a08d60fc61 | ||
|
|
5211328234 | ||
|
|
d285d56fe3 | ||
|
|
8bff641cc4 | ||
|
|
a6f7e31bb9 | ||
|
|
97dc7b77f4 | ||
|
|
0627c198fd | ||
|
|
17f2fed3c8 | ||
|
|
ba17fae8d9 | ||
|
|
b015f506da | ||
|
|
14ff411907 | ||
|
|
6ffa6afd20 | ||
|
|
e0d618862c | ||
|
|
2e162f8af7 | ||
|
|
fd68a2afae | ||
|
|
bfdd0824e2 | ||
|
|
2ed0284d49 | ||
|
|
48bed2ee03 | ||
|
|
1177980172 | ||
|
|
db2b3e47bc | ||
|
|
e30ad9109c | ||
|
|
85e71415fe | ||
|
|
400d39740c | ||
|
|
3ca3de807c | ||
|
|
0b900f4faf | ||
|
|
1d4a922185 | ||
|
|
bf2c2fe242 | ||
|
|
cf8e0befc8 | ||
|
|
40ff7779bb | ||
|
|
4659c34d46 | ||
|
|
18dd3102bf | ||
|
|
6c53653831 | ||
|
|
9d19dea7dd | ||
|
|
d1bdfa0be6 | ||
|
|
4bfe3de1f2 | ||
|
|
b2de28ccfc | ||
|
|
953e928bdb | ||
|
|
74b693d6b9 | ||
|
|
2a8d1343d6 | ||
|
|
0f232e0ce2 | ||
|
|
7937a16002 | ||
|
|
5632f24d24 | ||
|
|
65bbb7c57b | ||
|
|
c4b83c86cc | ||
|
|
7caa096bd0 | ||
|
|
c071b271be | ||
|
|
374c1a3a3e | ||
|
|
3302e1133f | ||
|
|
a8acf28989 | ||
|
|
dd309070eb | ||
|
|
d4a4db42aa | ||
|
|
0abda54d3c | ||
|
|
e1cd6cebb9 | ||
|
|
9b63d6f832 | ||
|
|
80a894b829 | ||
|
|
e15be61ded | ||
|
|
f91e95f24a | ||
|
|
7e70e24bfc | ||
|
|
afa1434aa9 | ||
|
|
da2ee55013 | ||
|
|
abe5ecc5ec | ||
|
|
f81ccbd652 | ||
|
|
4d24eae901 | ||
|
|
bca625a197 | ||
|
|
ec0409a3d1 | ||
|
|
86247bf657 |
14
.github/workflows/ci.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
rustup update stable
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
@@ -54,12 +54,12 @@ jobs:
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
@@ -104,12 +104,12 @@ jobs:
|
||||
rustup target add wasm32-wasi
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
@@ -148,8 +148,8 @@ jobs:
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
|
||||
- name: Upload app bundle to workflow run if main branch or specifi label
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload app bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
|
||||
4
.github/workflows/randomized_tests.yml
vendored
@@ -29,12 +29,12 @@ jobs:
|
||||
rustup update stable
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
4
.github/workflows/release_actions.yml
vendored
@@ -16,8 +16,4 @@ jobs:
|
||||
|
||||
Restart your Zed or head to https://zed.dev/releases/stable/latest to grab it.
|
||||
|
||||
```md
|
||||
# Changelog
|
||||
|
||||
${{ github.event.release.body }}
|
||||
```
|
||||
|
||||
228
Cargo.lock
generated
@@ -118,7 +118,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"tiktoken-rs",
|
||||
"tiktoken-rs 0.4.2",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
@@ -161,7 +161,7 @@ dependencies = [
|
||||
"miow 0.3.7",
|
||||
"nix",
|
||||
"parking_lot 0.12.1",
|
||||
"regex-automata",
|
||||
"regex-automata 0.1.10",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"signal-hook",
|
||||
@@ -772,9 +772,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
@@ -969,13 +969,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.4.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
|
||||
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"regex-automata 0.3.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1053,6 +1052,10 @@ dependencies = [
|
||||
"media",
|
||||
"postage",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"util",
|
||||
]
|
||||
@@ -1401,7 +1404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tungstenite",
|
||||
@@ -1484,12 +1487,14 @@ dependencies = [
|
||||
"picker",
|
||||
"postage",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"settings",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"util",
|
||||
"vcs_menu",
|
||||
"workspace",
|
||||
"zed-actions",
|
||||
]
|
||||
@@ -1989,7 +1994,6 @@ checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libnghttp2-sys",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
@@ -2311,9 +2315,8 @@ dependencies = [
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"tree-sitter-html",
|
||||
"tree-sitter-javascript",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)",
|
||||
"tree-sitter-typescript",
|
||||
"unindent",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -2447,6 +2450,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.11.0"
|
||||
@@ -2691,6 +2700,7 @@ dependencies = [
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"tempfile",
|
||||
"time 0.3.21",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -3143,6 +3153,15 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.1"
|
||||
@@ -3784,15 +3803,16 @@ dependencies = [
|
||||
"text",
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"tree-sitter-elixir",
|
||||
"tree-sitter-embedded-template",
|
||||
"tree-sitter-heex",
|
||||
"tree-sitter-html",
|
||||
"tree-sitter-javascript",
|
||||
"tree-sitter-json 0.19.0",
|
||||
"tree-sitter-json 0.20.0",
|
||||
"tree-sitter-markdown",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-ruby",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tree-sitter-typescript",
|
||||
"unicase",
|
||||
"unindent",
|
||||
"util",
|
||||
@@ -3904,16 +3924,6 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
||||
|
||||
[[package]]
|
||||
name = "libnghttp2-sys"
|
||||
version = "0.1.7+1.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.24.2"
|
||||
@@ -4002,7 +4012,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"hmac 0.12.1",
|
||||
"jwt",
|
||||
"lazy_static",
|
||||
"live_kit_server",
|
||||
"log",
|
||||
"media",
|
||||
@@ -4126,7 +4135,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4141,6 +4150,16 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-owned"
|
||||
version = "0.3.4"
|
||||
@@ -4486,6 +4505,7 @@ dependencies = [
|
||||
"async-tar",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -5097,7 +5117,7 @@ version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.21.2",
|
||||
"indexmap 1.9.3",
|
||||
"line-wrap",
|
||||
"quick-xml",
|
||||
@@ -5344,6 +5364,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"collections",
|
||||
"context_menu",
|
||||
"db",
|
||||
"drag_and_drop",
|
||||
@@ -5353,6 +5374,7 @@ dependencies = [
|
||||
"language",
|
||||
"menu",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -5658,6 +5680,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.7.0"
|
||||
@@ -5701,6 +5729,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"db",
|
||||
"editor",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
@@ -5776,6 +5805,12 @@ dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
@@ -5824,7 +5859,7 @@ version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.21.2",
|
||||
"bytes 1.4.0",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@@ -6026,6 +6061,21 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink 0.7.0",
|
||||
"libsqlite3-sys",
|
||||
"memchr",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.6.1"
|
||||
@@ -6161,7 +6211,7 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.21.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6424,6 +6474,7 @@ name = "search"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"client",
|
||||
"collections",
|
||||
"editor",
|
||||
@@ -6957,7 +7008,7 @@ dependencies = [
|
||||
"futures-executor",
|
||||
"futures-intrusive",
|
||||
"futures-util",
|
||||
"hashlink",
|
||||
"hashlink 0.8.1",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac 0.12.1",
|
||||
@@ -7461,7 +7512,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ba161c549e2c0686f35f5d920e63fad5cafba2c28ad2caceaf07e5d9fa6e8c4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.0",
|
||||
"base64 0.21.2",
|
||||
"bstr",
|
||||
"fancy-regex",
|
||||
"lazy_static",
|
||||
"parking_lot 0.12.1",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiktoken-rs"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a99d843674a3468b4a9200a565bbe909a0152f95e82a52feae71e6bf2d4b49d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.2",
|
||||
"bstr",
|
||||
"fancy-regex",
|
||||
"lazy_static",
|
||||
@@ -7876,6 +7942,15 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-bash"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter-bash?rev=1b0321ee85701d5036c334a6f04761cdc672e64c#1b0321ee85701d5036c334a6f04761cdc672e64c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-c"
|
||||
version = "0.20.2"
|
||||
@@ -7952,16 +8027,6 @@ dependencies = [
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-javascript"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-json"
|
||||
version = "0.19.0"
|
||||
@@ -8000,6 +8065,15 @@ dependencies = [
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-php"
|
||||
version = "0.19.1"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter-php?rev=d43130fd1525301e9826f420c5393a4d169819fc#d43130fd1525301e9826f420c5393a4d169819fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.20.2"
|
||||
@@ -8049,19 +8123,18 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-toml"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter-toml?rev=342d9be207c2dba869b9967124c679b5e6fd0ebe#342d9be207c2dba869b9967124c679b5e6fd0ebe"
|
||||
name = "tree-sitter-svelte"
|
||||
version = "0.10.2"
|
||||
source = "git+https://github.com/Himujjal/tree-sitter-svelte?rev=697bb515471871e85ff799ea57a76298a71a9cca#697bb515471871e85ff799ea57a76298a71a9cca"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-typescript"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a"
|
||||
name = "tree-sitter-toml"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter-toml?rev=342d9be207c2dba869b9967124c679b5e6fd0ebe#342d9be207c2dba869b9967124c679b5e6fd0ebe"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
@@ -8373,6 +8446,54 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vcs_menu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"picker",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vector_store"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"editor",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"isahc",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"matrixmultiply",
|
||||
"picker",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"rpc",
|
||||
"rusqlite",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"tempdir",
|
||||
"theme",
|
||||
"tiktoken-rs 0.5.0",
|
||||
"tree-sitter",
|
||||
"tree-sitter-rust",
|
||||
"unindent",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
@@ -8393,7 +8514,6 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"nvim-rs",
|
||||
"parking_lot 0.11.2",
|
||||
@@ -9334,7 +9454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.93.3"
|
||||
version = "0.96.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
@@ -9418,6 +9538,7 @@ dependencies = [
|
||||
"tiny_http",
|
||||
"toml",
|
||||
"tree-sitter",
|
||||
"tree-sitter-bash",
|
||||
"tree-sitter-c",
|
||||
"tree-sitter-cpp",
|
||||
"tree-sitter-css",
|
||||
@@ -9429,19 +9550,22 @@ dependencies = [
|
||||
"tree-sitter-json 0.20.0",
|
||||
"tree-sitter-lua",
|
||||
"tree-sitter-markdown",
|
||||
"tree-sitter-php",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-racket",
|
||||
"tree-sitter-ruby",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-scheme",
|
||||
"tree-sitter-svelte",
|
||||
"tree-sitter-toml",
|
||||
"tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-yaml",
|
||||
"unindent",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid 1.3.2",
|
||||
"vector_store",
|
||||
"vim",
|
||||
"welcome",
|
||||
"workspace",
|
||||
|
||||
29
Cargo.toml
@@ -63,7 +63,9 @@ members = [
|
||||
"crates/theme",
|
||||
"crates/theme_selector",
|
||||
"crates/util",
|
||||
"crates/vector_store",
|
||||
"crates/vim",
|
||||
"crates/vcs_menu",
|
||||
"crates/workspace",
|
||||
"crates/welcome",
|
||||
"crates/xtask",
|
||||
@@ -81,7 +83,8 @@ env_logger = { version = "0.9" }
|
||||
futures = { version = "0.3" }
|
||||
globset = { version = "0.4" }
|
||||
indoc = "1"
|
||||
isahc = "1.7.2"
|
||||
# We explicitly disable a http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
|
||||
lazy_static = { version = "1.4.0" }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = { version = "2.1.1" }
|
||||
@@ -102,6 +105,30 @@ time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = { version = "0.5" }
|
||||
tree-sitter = "0.20"
|
||||
unindent = { version = "0.1.7" }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" }
|
||||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-cpp = "0.20.0"
|
||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-php = { git = "https://github.com/tree-sitter/tree-sitter-php", rev = "d43130fd1525301e9826f420c5393a4d169819fc" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-ruby = "0.20.0"
|
||||
tree-sitter-html = "0.19.0"
|
||||
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"}
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca"}
|
||||
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"}
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
|
||||
tree-sitter-lua = "0.0.14"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "49226023693107fba9a1191136a4f47f38cdca73" }
|
||||
|
||||
@@ -16,22 +16,25 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
|
||||
brew install foreman
|
||||
```
|
||||
|
||||
* Ensure the Zed.dev website is checked out in a sibling directory:
|
||||
* Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies:
|
||||
|
||||
```
|
||||
cd ..
|
||||
git clone https://github.com/zed-industries/zed.dev
|
||||
cd zed.dev && npm install
|
||||
npm install -g vercel
|
||||
```
|
||||
|
||||
* Initialize submodules
|
||||
* Return to Zed project directory and Initialize submodules
|
||||
|
||||
```
|
||||
cd zed
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
* Set up a local `zed` database and seed it with some initial users:
|
||||
|
||||
Create a personal GitHub token to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope).
|
||||
[Create a personal GitHub token](https://github.com/settings/tokens/new) to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope).
|
||||
Then delete that token.
|
||||
|
||||
```
|
||||
|
||||
5
assets/icons/file_icons/archive.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 7.63H8" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="2" y="2" width="10" height="3" rx="1" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M3 5H11L10.5663 11.0712C10.529 11.5946 10.0935 12 9.56888 12H4.43112C3.90648 12 3.47104 11.5946 3.43366 11.0712L3 5Z" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
4
assets/icons/file_icons/book.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2C7.44772 2 7 2.44772 7 3V13C7.5 12 8.5 11.375 10 11.375H11C11.5523 11.375 12 10.9273 12 10.375V3C12 2.44772 11.5523 2 11 2H8Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 2C6.55228 2 7 2.44772 7 3V13C6.5 12 5.5 11.375 4 11.375H3C2.44772 11.375 2 10.9273 2 10.375V3C2 2.44772 2.44772 2 3 2H6Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 545 B |
4
assets/icons/file_icons/camera.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10.1111C12 10.602 11.593 11 11.0909 11H2.90909C2.40701 11 2 10.602 2 10.1111V5.22222C2 4.7313 2.40701 4.38 2.90909 4.38H4.72727L5.5 3H8.5L9.27273 4.38H11.0909C11.593 4.38 12 4.7313 12 5.22222V10.1111Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.005 9C7.90246 9 8.63 8.27246 8.63 7.375C8.63 6.47754 7.90246 5.75 7.005 5.75C6.10753 5.75 5.38 6.47754 5.38 7.375C5.38 8.27246 6.10753 9 7.005 9Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
4
assets/icons/file_icons/code.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 2C3.5 2 2.25 3 3 4.5C3.5 5.5 3 6.5 2 6.99894C3 7.5 3.5 8.5 3 9.5C2.25 11 3 12 5 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 2C10.5 2 11.75 3 11 4.5C10.5 5.5 11 6.5 12 6.99894C11 7.5 10.5 8.5 11 9.5C11.75 11 11 12 9 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
5
assets/icons/file_icons/database.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="7" cy="4" rx="5" ry="2" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M12 4V10C12 11.1046 9.76142 12 7 12C4.23858 12 2 11.1046 2 10V4" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M12 7C12 8.10457 9.76142 9 7 9C4.23858 9 2 8.10457 2 7" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 390 B |
4
assets/icons/file_icons/eslint.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.3969 7.5625L11.8557 7.25L12.3969 7.5625C12.5979 7.21442 12.5979 6.78558 12.3969 6.4375L11.8557 6.75L12.3969 6.4375L10.1856 2.60737C9.98464 2.2593 9.61325 2.04487 9.21132 2.04487L4.78868 2.04487C4.38675 2.04487 4.01536 2.2593 3.8144 2.60737L1.60307 6.4375C1.40211 6.78558 1.40211 7.21442 1.60307 7.5625L3.8144 11.3926C4.01536 11.7407 4.38675 11.9551 4.78867 11.9551L9.21132 11.9551C9.61325 11.9551 9.98464 11.7407 10.1856 11.3926L12.3969 7.5625Z" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M6.75 4.14434C6.9047 4.05502 7.0953 4.05502 7.25 4.14434L9.34808 5.35566C9.50278 5.44498 9.59808 5.61004 9.59808 5.78868V8.21132C9.59808 8.38996 9.50278 8.55502 9.34808 8.64434L7.25 9.85566C7.0953 9.94498 6.9047 9.94498 6.75 9.85566L4.65192 8.64434C4.49722 8.55502 4.40192 8.38996 4.40192 8.21132L4.40192 5.78868C4.40192 5.61004 4.49722 5.44498 4.65192 5.35566L6.75 4.14434Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 999 B |
5
assets/icons/file_icons/file.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 4H10" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 7H12" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 10H8" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
149
assets/icons/file_icons/file_types.json
Normal file
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"suffixes": {
|
||||
"aac": "audio",
|
||||
"bash": "terminal",
|
||||
"bmp": "image",
|
||||
"c": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"cc": "code",
|
||||
"css": "code",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
"flac": "audio",
|
||||
"fish": "terminal",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gif": "image",
|
||||
"go": "code",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"svelte": "template",
|
||||
"hpp": "code",
|
||||
"ico": "image",
|
||||
"ini": "settings",
|
||||
"java": "code",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"lock": "lock",
|
||||
"log": "log",
|
||||
"md": "document",
|
||||
"mdx": "document",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"ods": "document",
|
||||
"odp": "document",
|
||||
"odt": "document",
|
||||
"ogg": "video",
|
||||
"pdf": "document",
|
||||
"php": "code",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierrc": "prettier",
|
||||
"prettierignore": "prettier",
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "code",
|
||||
"rb": "code",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"rtf": "document",
|
||||
"scm": "code",
|
||||
"sh": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
"bash_logout": "terminal",
|
||||
"profile": "terminal",
|
||||
"zshrc": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zsh_profile": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
"zsh_histfile": "terminal",
|
||||
"zlogin": "terminal",
|
||||
"sql": "code",
|
||||
"svg": "image",
|
||||
"swift": "code",
|
||||
"tiff": "image",
|
||||
"toml": "settings",
|
||||
"ts": "typescript",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"zsh": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"directory": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_directory": {
|
||||
"icon": "icons/file_icons/folder-open.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
assets/icons/file_icons/folder-open.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.08592 5.30382C4.22115 4.89402 4.60401 4.61719 5.03555 4.61719H11.9295C12.6108 4.61719 13.0926 5.28358 12.8791 5.93056L11.2266 10.9384C11.0914 11.3482 10.7085 11.625 10.2769 11.625H3.38303C2.70174 11.625 2.2199 10.9586 2.4334 10.3116L4.08592 5.30382Z" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M8 4.4024L7.27505 2.93264C7.10664 2.59119 6.75894 2.375 6.37821 2.375H3C2.44772 2.375 2 2.82272 2 3.375V4.4024V10.625C2 11.1773 2.44772 11.625 3 11.625H4.00781" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 634 B |
4
assets/icons/file_icons/folder.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 5.625C2 5.07272 2.44772 4.625 3 4.625H11C11.5523 4.625 12 5.07272 12 5.625V10.625C12 11.1773 11.5523 11.625 11 11.625H3C2.44772 11.625 2 11.1773 2 10.625V5.625Z" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M8 4.375L7.27639 2.92779C7.107 2.589 6.76074 2.375 6.38197 2.375H3C2.44772 2.375 2 2.82272 2 3.375V8" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 462 B |
6
assets/icons/file_icons/git.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="4" cy="10" r="2" stroke="black" stroke-width="1.25"/>
|
||||
<circle cx="10" cy="4" r="2" stroke="black" stroke-width="1.25"/>
|
||||
<line x1="3.625" y1="2.625" x2="3.625" y2="7.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M10 6V6C10 8.20914 8.20914 10 6 10V10" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 430 B |
6
assets/icons/file_icons/hash.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="10.2795" y1="2.63847" x2="7.74785" y2="11.0142" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<line x1="6.26624" y1="2.99597" x2="3.7346" y2="11.3717" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<line x1="3.15982" y1="5.3799" x2="11.9098" y2="5.3799" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<line x1="2.0983" y1="8.62407" x2="10.8483" y2="8.62407" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
5
assets/icons/file_icons/html.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.15732 3.17108L5.84268 10.8289" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M4 5L2 7L4 9" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 9L12 7L10 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 423 B |
6
assets/icons/file_icons/image.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 3C6.91421 3 7.25 2.66421 7.25 2.25C7.25 1.83579 6.91421 1.5 6.5 1.5C6.08579 1.5 5.75 1.83579 5.75 2.25C5.75 2.66421 6.08579 3 6.5 3Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.375 2H3C2.44772 2 2 2.44772 2 3V11C2 11.5523 2.44772 12 3 12H7.35938M9.64062 2H11C11.5523 2 12 2.44772 12 3V11C12 11.5523 11.5523 12 11 12H10.125" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 10L5 7L7 9" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8L9 5L12 8" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 763 B |
5
assets/icons/file_icons/info.svg
Normal file
|
After Width: | Height: | Size: 46 KiB |
6
assets/icons/file_icons/lock.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="5" width="8" height="7" rx="1" stroke="black" stroke-width="1.35"/>
|
||||
<path d="M4 4C4 2.89543 4.89543 2 6 2H8C9.10457 2 10 2.89543 10 4V5H4V4Z" stroke="black" stroke-width="1.35"/>
|
||||
<circle cx="7" cy="8" r="1" fill="black"/>
|
||||
<path d="M7 8V9.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
6
assets/icons/file_icons/notebook.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4 12H3.59997C2.71633 12 2 11.3367 2 10.5185V3.48148C2 2.66328 2.71633 2 3.59997 2H10.4C11.2837 2 12 2.66328 12 3.48148V10.5185C12 11.3367 11.2837 12 10.4 12Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 5L7.5 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 7H7.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 2V13" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 673 B |
3
assets/icons/file_icons/package.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.62679 3.88473L6.99984 6.78517M1.62679 3.88473L1.63138 9.90006L7.00444 12.8005M1.62679 3.88473L4.31118 2.54212M6.99984 6.78517L7.00444 12.8005M6.99984 6.78517L9.68415 5.33085M7.00444 12.8005L12.3731 9.89186L12.3685 3.87652M4.31118 2.54212L6.99558 1.1995L12.3685 3.87652M4.31118 2.54212L9.68415 5.33085M12.3685 3.87652L9.68415 5.33085" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
12
assets/icons/file_icons/prettier.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 2.86328H8.51563" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M11 2.86328L12 2.86328" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M9.64062 5.6263L12 5.6263" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M4.79688 5.6263L7.15625 5.6263" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 5.6263L2.35937 5.6263" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M7.15625 8.3737L12 8.3737" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 8.3737L4.64062 8.3737" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 11.1094H3.54687" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M5.97656 11.1094H8.35938" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M10.8203 11.1094L12 11.1094" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
4
assets/icons/file_icons/rust.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.27935 9.98207C4.32063 9.4038 3.9204 8.89049 3.35998 8.80276L2.60081 8.68387C2.37979 8.64945 2.20167 8.48001 2.15225 8.25614L2.01378 7.63511C1.96382 7.41235 2.05233 7.1807 2.23696 7.05125L2.8631 6.61242C3.33337 6.28297 3.47456 5.6369 3.18621 5.13364L2.79467 4.45092C2.68118 4.25261 2.69801 4.00374 2.83757 3.82321L3.22314 3.32436C3.3627 3.14438 3.59621 3.06994 3.81071 3.13772L4.57531 3.37769C5.11944 3.54879 5.70048 3.26159 5.90683 2.71886L6.1811 1.99782C6.26255 1.78395 6.46345 1.64285 6.68772 1.6423L7.31007 1.64063C7.53434 1.64007 7.73579 1.78006 7.81834 1.99337L8.09965 2.72275C8.30821 3.26214 8.88655 3.54712 9.42903 3.37714L10.1632 3.14716C10.3772 3.07994 10.6096 3.15382 10.7492 3.3327L11.1374 3.83099" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.76988 10.5933C7.76988 10.6595 7.8236 10.7133 7.88988 10.7133H7.97588C8.32602 10.7133 8.60988 10.9971 8.60988 11.3472V11.3472C8.60988 11.6974 8.32602 11.9812 7.97588 11.9812H6.05587C5.70573 11.9812 5.42188 11.6974 5.42188 11.3472V11.3472C5.42188 10.9971 5.70573 10.7133 6.05587 10.7133H6.14188C6.20815 10.7133 6.26188 10.6595 6.26188 10.5933V6.66925C6.26188 6.60298 6.20815 6.54925 6.14188 6.54925H6.05588C5.70573 6.54925 5.42188 6.2654 5.42188 5.91525V5.91525C5.42188 5.5651 5.70573 5.28125 6.05588 5.28125H8.89988C10.0518 5.28125 11.8619 5.71487 11.8619 7.15185C11.8619 7.67078 11.7284 8.10362 11.4642 8.45348C11.1981 8.79765 10.8458 9.05637 10.4056 9.22931V9.22931C10.3782 9.24007 10.3673 9.27304 10.3829 9.29801L11.2163 10.6342C11.247 10.6834 11.3008 10.7133 11.3588 10.7133H11.7319C12.082 10.7133 12.3659 10.9971 12.3659 11.3472V11.3472C12.3659 11.6974 12.082 11.9812 11.7319 11.9812H10.5637C10.4955 11.9812 10.432 11.9465 10.3952 11.889L8.96523 9.65406C8.92847 9.59661 8.86496 9.56185 8.79676 9.56185H7.96988C7.85942 9.56185 7.76988 9.65139 7.76988 9.76185V10.5933ZM8.61188 6.54925C9.02963 6.54925 10.125 6.54925 10.2339 7.18785C10.2975 7.56123 10.1181 7.86557 9.88118 8.07715C9.64227 8.29046 9.20527 8.38985 8.58788 8.38985H7.86988C7.81465 8.38985 7.76988 8.34508 7.76988 8.28985V6.64925C7.76988 6.59402 7.81465 6.54925 7.86988 6.54925H8.61188Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
4
assets/icons/file_icons/settings.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.60081 8.94324L3.35998 9.06214C3.9204 9.14987 4.32063 9.66318 4.27935 10.2415L4.22342 11.0252C4.20713 11.2536 4.32877 11.4686 4.53024 11.568L5.09174 11.8446C5.29321 11.9441 5.53379 11.9068 5.69834 11.7519L6.26255 11.2186C6.67855 10.8253 7.32041 10.8253 7.7369 11.2186L8.3011 11.7519C8.46565 11.9075 8.70572 11.9441 8.90772 11.8446L9.47028 11.5675C9.67124 11.4686 9.79234 11.2541 9.77607 11.0264L9.72007 10.2415C9.67883 9.66318 10.079 9.14987 10.6394 9.06214L11.3986 8.94324C11.6197 8.90883 11.7978 8.73938 11.8477 8.51607L11.9862 7.89504C12.0362 7.67173 11.9477 7.44007 11.763 7.31118L11.1293 6.86731C10.6617 6.53959 10.5189 5.89966 10.8013 5.39691L11.1841 4.71586C11.2954 4.51754 11.277 4.26923 11.1374 4.09036L10.7492 3.59207C10.6096 3.4132 10.3772 3.33932 10.1632 3.40653L9.42903 3.63651C8.88655 3.80649 8.30821 3.52152 8.09965 2.98213L7.81834 2.25275C7.73579 2.03944 7.53434 1.89945 7.31007 1.9L6.68772 1.90167C6.46345 1.90222 6.26255 2.04333 6.1811 2.2572L5.90683 2.97824C5.70048 3.52097 5.11944 3.80816 4.57531 3.63707L3.81071 3.39709C3.59621 3.32932 3.3627 3.40375 3.22314 3.58374L2.83757 4.08258C2.69801 4.26312 2.68118 4.51199 2.79467 4.7103L3.18621 5.39302C3.47456 5.89628 3.33337 6.54235 2.8631 6.8718L2.23696 7.31062C2.05233 7.44007 1.96382 7.67173 2.01378 7.89449L2.15225 8.51552C2.20167 8.73938 2.37979 8.90883 2.60081 8.94324Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.14913 5.85093L8.14909 5.85089C7.51453 5.21637 6.48549 5.21637 5.85092 5.85089L5.85089 5.85092C5.21637 6.48549 5.21637 7.51453 5.85089 8.14909L5.85093 8.14913C6.48549 8.78362 7.51452 8.78362 8.14908 8.14913L8.14913 8.14908C8.78362 7.51452 8.78362 6.48549 8.14913 5.85093Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
5
assets/icons/file_icons/terminal.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.65625" y="2" width="10.6875" height="10" rx="1" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M4.375 9L6.375 7L4.375 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.625 9L9.90625 9" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 407 B |
5
assets/icons/file_icons/typescript.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 4.375V3C12 2.44772 11.5523 2 11 2H3C2.44772 2 2 2.44772 2 3V11C2 11.5523 2.44772 12 3 12H3.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M10.6836 7.82805C10.7933 7.65392 10.9823 7.57377 11.174 7.57377C11.2904 7.57377 11.4019 7.59384 11.5091 7.62792C11.8324 7.73069 12.2148 7.63925 12.3392 7.32368L12.3773 7.22707C12.4703 6.99131 12.3823 6.71761 12.1522 6.61154C11.8328 6.46436 11.4984 6.375 11.1262 6.375C9.87707 6.375 8.91934 7.60671 9.4239 8.84869C9.54204 9.13951 9.74218 9.36166 9.95149 9.54337C10.1061 9.6776 10.2858 9.80516 10.4475 9.92002C10.4972 9.95529 10.5452 9.98936 10.5902 10.0221C11.0283 10.34 11.2526 10.5876 11.2526 10.9466C11.2526 11.1518 11.1622 11.3133 11.016 11.4128C10.8777 11.5071 10.7055 11.5357 10.5454 11.5222C10.393 11.5093 10.2529 11.4717 10.1214 11.4196C9.81632 11.2989 9.45533 11.4015 9.3364 11.7073L9.28139 11.8487C9.19162 12.0796 9.27489 12.3463 9.49799 12.4539C10.0893 12.7391 10.7377 12.8279 11.3915 12.5872C12.0569 12.3423 12.595 11.7708 12.595 10.9068C12.595 10.1301 12.1336 9.69583 11.6966 9.36109C11.606 9.29163 11.5259 9.23292 11.4493 9.17682C11.3259 9.08638 11.1964 8.99109 11.0734 8.88536C10.8937 8.73082 10.7518 8.57274 10.6594 8.38613C10.5746 8.21464 10.5815 7.99013 10.6836 7.82805Z" fill="black"/>
|
||||
<path d="M6.98644 7.70935H7.69396C7.98162 7.70935 8.21481 7.47616 8.21481 7.1885V7.02346C8.21481 6.7358 7.98162 6.50261 7.69396 6.50261H4.96848C4.68082 6.50261 4.44763 6.7358 4.44763 7.02346V7.1885C4.44763 7.47616 4.68082 7.70935 4.96848 7.70935H5.676V12.102C5.676 12.3896 5.90919 12.6228 6.19685 12.6228H6.46559C6.75325 12.6228 6.98644 12.3896 6.98644 12.102V7.70935Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
4
assets/icons/radix/maximize.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 1.5H13.5M13.5 1.5V5.5M13.5 1.5C12.1332 2.86683 10.3668 4.63317 9 6" stroke="white" stroke-linecap="round"/>
|
||||
<path d="M1.5 9.5V13.5M1.5 13.5L6 9M1.5 13.5H5.5" stroke="white" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
4
assets/icons/radix/minimize.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 6L9 6M9 6L9 2M9 6C10.3668 4.63316 12.1332 2.86683 13.5 1.5" stroke="white" stroke-linecap="round"/>
|
||||
<path d="M6 13L6 9M6 9L1.5 13.5M6 9L2 9" stroke="white" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 297 B |
@@ -9,6 +9,7 @@
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"alt-cmd-b": "editor::GoToDefinitionSplit",
|
||||
"cmd-<": "editor::ScrollCursorCenter",
|
||||
"cmd-g": [
|
||||
"editor::SelectNext",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"cmd-{": "pane::ActivatePrevItem",
|
||||
@@ -39,6 +40,7 @@
|
||||
"cmd-shift-n": "workspace::NewWindow",
|
||||
"cmd-o": "workspace::Open",
|
||||
"alt-cmd-o": "projects::OpenRecent",
|
||||
"alt-cmd-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||
"shift-escape": "workspace::ToggleZoom"
|
||||
@@ -193,8 +195,8 @@
|
||||
{
|
||||
"context": "Editor && mode == auto_height",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::Newline",
|
||||
"cmd-alt-enter": "editor::NewlineBelow"
|
||||
"shift-enter": "editor::Newline",
|
||||
"cmd-shift-enter": "editor::NewlineBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -220,7 +222,8 @@
|
||||
"escape": "buffer_search::Dismiss",
|
||||
"tab": "buffer_search::FocusEditor",
|
||||
"enter": "search::SelectNextMatch",
|
||||
"shift-enter": "search::SelectPrevMatch"
|
||||
"shift-enter": "search::SelectPrevMatch",
|
||||
"alt-enter": "search::SelectAllMatches"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -241,6 +244,7 @@
|
||||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-r": "search::ToggleRegex"
|
||||
@@ -295,7 +299,9 @@
|
||||
"shift-f8": "editor::GoToPrevDiagnostic",
|
||||
"f2": "editor::Rename",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"alt-f12": "editor::GoToDefinitionSplit",
|
||||
"cmd-f12": "editor::GoToTypeDefinition",
|
||||
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
@@ -404,6 +410,7 @@
|
||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||
"cmd-t": "project_symbols::Toggle",
|
||||
"cmd-ctrl-t": "semantic_search::Toggle",
|
||||
"cmd-p": "file_finder::Toggle",
|
||||
"cmd-shift-p": "command_palette::Toggle",
|
||||
"cmd-shift-m": "diagnostics::Deploy",
|
||||
|
||||
@@ -46,8 +46,9 @@
|
||||
"alt-f7": "editor::FindAllReferences",
|
||||
"cmd-alt-f7": "editor::FindAllReferences",
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"cmd-alt-b": "editor::GoToDefinition",
|
||||
"cmd-alt-b": "editor::GoToDefinitionSplit",
|
||||
"cmd-shift-b": "editor::GoToTypeDefinition",
|
||||
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-enter": "editor::ToggleCodeActions",
|
||||
"f2": "editor::GoToDiagnostic",
|
||||
"cmd-f2": "editor::GoToPrevDiagnostic",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
|
||||
"alt-shift-cmd-down": "editor::FindAllReferences",
|
||||
"ctrl-.": "editor::GoToHunk",
|
||||
"ctrl-,": "editor::GoToPrevHunk",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-shift-o": "projects::OpenRecent",
|
||||
"cmd-shift-b": "branches::OpenRecent",
|
||||
"cmd-alt-tab": "project_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
@@ -11,6 +12,7 @@
|
||||
"cmd-l": "go_to_line::Toggle",
|
||||
"ctrl-shift-d": "editor::DuplicateLine",
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"alt-cmd-b": "editor::GoToDefinition",
|
||||
"cmd-j": "editor::ScrollCursorCenter",
|
||||
"cmd-shift-l": "editor::SelectLine",
|
||||
"cmd-shift-t": "outline::Toggle",
|
||||
|
||||
@@ -35,8 +35,11 @@
|
||||
"l": "vim::Right",
|
||||
"right": "vim::Right",
|
||||
"$": "vim::EndOfLine",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"shift-g": "vim::EndOfDocument",
|
||||
"w": "vim::NextWordStart",
|
||||
"{": "vim::StartOfParagraph",
|
||||
"}": "vim::EndOfParagraph",
|
||||
"shift-w": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
@@ -57,6 +60,8 @@
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"n": "search::SelectNextMatch",
|
||||
"shift-n": "search::SelectPrevMatch",
|
||||
"%": "vim::Matching",
|
||||
"f": [
|
||||
"vim::PushOperator",
|
||||
@@ -92,7 +97,16 @@
|
||||
],
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"ctrl-]": "editor::GoToDefinition",
|
||||
"escape": "editor::Cancel",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl+[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
"1": [
|
||||
"vim::Number",
|
||||
@@ -165,7 +179,6 @@
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"x": "vim::DeleteRight",
|
||||
"shift-x": "vim::DeleteLeft",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"~": "vim::ChangeCase",
|
||||
@@ -188,10 +201,11 @@
|
||||
"p": "vim::Paste",
|
||||
"u": "editor::Undo",
|
||||
"ctrl-r": "editor::Redo",
|
||||
"/": [
|
||||
"buffer_search::Deploy",
|
||||
"/": "vim::Search",
|
||||
"?": [
|
||||
"vim::Search",
|
||||
{
|
||||
"focus": true
|
||||
"backwards": true,
|
||||
}
|
||||
],
|
||||
"ctrl-f": "vim::PageDown",
|
||||
@@ -229,11 +243,20 @@
|
||||
"h": "editor::Hover",
|
||||
"t": "pane::ActivateNextItem",
|
||||
"shift-t": "pane::ActivatePrevItem",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
"d": "editor::GoToDefinition",
|
||||
"shift-d": "editor::GoToTypeDefinition",
|
||||
"*": [
|
||||
"vim::MoveToNext",
|
||||
{
|
||||
"partialWord": true
|
||||
}
|
||||
],
|
||||
"d": "editor::GoToDefinition"
|
||||
"#": [
|
||||
"vim::MoveToPrev",
|
||||
{
|
||||
"partialWord": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -260,10 +283,6 @@
|
||||
"t": "editor::ScrollCursorTop",
|
||||
"z": "editor::ScrollCursorCenter",
|
||||
"b": "editor::ScrollCursorBottom",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -305,15 +324,20 @@
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
],
|
||||
"> >": "editor::Indent",
|
||||
"< <": "editor::Outdent"
|
||||
"ctrl-c": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
">": "editor::Indent",
|
||||
"<": "editor::Outdent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == insert",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore"
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -321,7 +345,21 @@
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": "editor::Cancel"
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl+[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -24,6 +24,17 @@
|
||||
},
|
||||
// The default font size for text in the editor
|
||||
"buffer_font_size": 15,
|
||||
// Set the buffer's line height.
|
||||
// May take 3 values:
|
||||
// 1. Use a line height that's comfortable for reading (1.618)
|
||||
// "line_height": "comfortable"
|
||||
// 2. Use a standard line height, (1.3)
|
||||
// "line_height": "standard",
|
||||
// 3. Use a custom line height
|
||||
// "line_height": {
|
||||
// "custom": 2
|
||||
// },
|
||||
"buffer_line_height": "comfortable",
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"active_pane_magnification": 1.0,
|
||||
@@ -55,6 +66,11 @@
|
||||
// 3. Draw all invisible symbols:
|
||||
// "all"
|
||||
"show_whitespaces": "selection",
|
||||
// Settings related to calls in Zed
|
||||
"calls": {
|
||||
// Join calls with the microphone muted by default
|
||||
"mute_on_join": true
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the editor.
|
||||
@@ -71,11 +87,25 @@
|
||||
// "never"
|
||||
"show": "auto",
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true
|
||||
"git_diff": true,
|
||||
// Whether to show selections in the scrollbar.
|
||||
"selections": true
|
||||
},
|
||||
// Inlay hint related settings
|
||||
"inlay_hints": {
|
||||
// Global switch to toggle hints on and off, switched off by default.
|
||||
"enabled": false,
|
||||
// Toggle certain types of hints on and off, all switched on by default.
|
||||
"show_type_hints": true,
|
||||
"show_parameter_hints": true,
|
||||
// Corresponds to null/None LSP hint type value.
|
||||
"show_other_hints": true
|
||||
},
|
||||
"project_panel": {
|
||||
// Whether to show the git status in the project panel.
|
||||
"git_status": true,
|
||||
// Whether to show file icons in the project panel.
|
||||
"file_icons": true,
|
||||
// Where to dock project panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the project panel.
|
||||
@@ -105,6 +135,13 @@
|
||||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Settings related to the editor's tabs
|
||||
"tabs": {
|
||||
// Show git status colors in the editor tabs.
|
||||
"git_status": false,
|
||||
// Position of the close button on the editor tabs.
|
||||
"close_position": "right"
|
||||
},
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
@@ -270,7 +307,6 @@
|
||||
// "line_height": {
|
||||
// "custom": 2
|
||||
// },
|
||||
//
|
||||
"line_height": "comfortable"
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
@@ -279,6 +315,11 @@
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono"
|
||||
},
|
||||
// Difference settings for vector_store
|
||||
"vector_store": {
|
||||
"enabled": false,
|
||||
"reindexing_delay_seconds": 600
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
|
||||
@@ -207,16 +207,11 @@ impl ActivityIndicator {
|
||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||
let mut failed = SmallVec::<[_; 3]>::new();
|
||||
for status in &self.statuses {
|
||||
let name = status.name.clone();
|
||||
match status.status {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate => {
|
||||
checking_for_update.push(status.name.clone());
|
||||
}
|
||||
LanguageServerBinaryStatus::Downloading => {
|
||||
downloading.push(status.name.clone());
|
||||
}
|
||||
LanguageServerBinaryStatus::Failed { .. } => {
|
||||
failed.push(status.name.clone());
|
||||
}
|
||||
LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
|
||||
LanguageServerBinaryStatus::Downloading => downloading.push(name),
|
||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
|
||||
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ffi::OsStr,
|
||||
fmt::{self, Display},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
@@ -80,6 +81,9 @@ impl SavedConversationMetadata {
|
||||
let mut conversations = Vec::<SavedConversationMetadata>::new();
|
||||
while let Some(path) = paths.next().await {
|
||||
let path = path?;
|
||||
if path.extension() != Some(OsStr::new("json")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pattern = r" - \d+.zed.json$";
|
||||
let re = Regex::new(pattern).unwrap();
|
||||
|
||||
@@ -147,8 +147,9 @@ impl AssistantPanel {
|
||||
.await
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.saved_conversations = saved_conversations
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.saved_conversations = saved_conversations;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -297,12 +298,22 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
|
||||
let mut propagate_action = true;
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) {
|
||||
return;
|
||||
}
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
if search_bar.show(cx) {
|
||||
search_bar.search_suggested(cx);
|
||||
if action.focus {
|
||||
search_bar.select_query(cx);
|
||||
cx.focus_self();
|
||||
}
|
||||
propagate_action = false
|
||||
}
|
||||
});
|
||||
}
|
||||
if propagate_action {
|
||||
cx.propagate_action();
|
||||
}
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
|
||||
@@ -319,13 +330,13 @@ impl AssistantPanel {
|
||||
|
||||
fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, cx));
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, cx));
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1911,7 +1922,7 @@ impl ConversationEditor {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let Some(editor) = workspace.active_item(cx).and_then(|item| item.downcast::<Editor>()) else {
|
||||
let Some(editor) = workspace.active_item(cx).and_then(|item| item.act_as::<Editor>(cx)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -2060,6 +2071,8 @@ impl ConversationEditor {
|
||||
let remaining_tokens = self.conversation.read(cx).remaining_tokens()?;
|
||||
let remaining_tokens_style = if remaining_tokens <= 0 {
|
||||
&style.no_remaining_tokens
|
||||
} else if remaining_tokens <= 500 {
|
||||
&style.low_remaining_tokens
|
||||
} else {
|
||||
&style.remaining_tokens
|
||||
};
|
||||
|
||||
@@ -36,6 +36,10 @@ anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
futures.workspace = true
|
||||
postage.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
pub mod call_settings;
|
||||
pub mod participant;
|
||||
pub mod room;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::{proto, Client, TypedEnvelope, User, UserStore};
|
||||
use call_settings::CallSettings;
|
||||
use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore};
|
||||
use collections::HashSet;
|
||||
use futures::{future::Shared, FutureExt};
|
||||
use postage::watch;
|
||||
@@ -19,6 +21,8 @@ pub use participant::ParticipantLocation;
|
||||
pub use room::Room;
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
||||
settings::register::<CallSettings>(cx);
|
||||
|
||||
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(active_call);
|
||||
}
|
||||
@@ -198,6 +202,7 @@ impl ActiveCall {
|
||||
let result = invite.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_invites.remove(&called_user_id);
|
||||
this.report_call_event("invite", cx);
|
||||
cx.notify();
|
||||
});
|
||||
result
|
||||
@@ -243,21 +248,26 @@ impl ActiveCall {
|
||||
};
|
||||
|
||||
let join = Room::join(&call, self.client.clone(), self.user_store.clone(), cx);
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("accept incoming", cx)
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decline_incoming(&mut self) -> Result<()> {
|
||||
pub fn decline_incoming(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
let call = self
|
||||
.incoming_call
|
||||
.0
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.ok_or_else(|| anyhow!("no incoming call"))?;
|
||||
Self::report_call_event_for_room("decline incoming", call.room_id, &self.client, cx);
|
||||
self.client.send(proto::DeclineCall {
|
||||
room_id: call.room_id,
|
||||
})?;
|
||||
@@ -266,6 +276,7 @@ impl ActiveCall {
|
||||
|
||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.report_call_event("hang up", cx);
|
||||
if let Some((room, _)) = self.room.take() {
|
||||
room.update(cx, |room, cx| room.leave(cx))
|
||||
} else {
|
||||
@@ -273,12 +284,28 @@ impl ActiveCall {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(&self, cx: &mut AppContext) {
|
||||
if let Some(room) = self.room().cloned() {
|
||||
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
self.report_call_event("disable screen share", cx);
|
||||
Task::ready(room.unshare_screen(cx))
|
||||
} else {
|
||||
self.report_call_event("enable screen share", cx);
|
||||
room.share_screen(cx)
|
||||
}
|
||||
});
|
||||
toggle_screen_sharing.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("share project", cx);
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
@@ -291,6 +318,7 @@ impl ActiveCall {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("unshare project", cx);
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
} else {
|
||||
Err(anyhow!("no active call"))
|
||||
@@ -349,7 +377,29 @@ impl ActiveCall {
|
||||
self.room.as_ref().map(|(room, _)| room)
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Arc<Client> {
|
||||
self.client.clone()
|
||||
}
|
||||
|
||||
pub fn pending_invites(&self) -> &HashSet<u64> {
|
||||
&self.pending_invites
|
||||
}
|
||||
|
||||
fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
|
||||
if let Some(room) = self.room() {
|
||||
Self::report_call_event_for_room(operation, room.read(cx).id(), &self.client, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_call_event_for_room(
|
||||
operation: &'static str,
|
||||
room_id: u64,
|
||||
client: &Arc<Client>,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
let telemetry = client.telemetry();
|
||||
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
|
||||
let event = ClickhouseEvent::Call { operation, room_id };
|
||||
telemetry.report_clickhouse_event(event, telemetry_settings);
|
||||
}
|
||||
}
|
||||
|
||||
27
crates/call/src/call_settings.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::Setting;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
pub mute_on_join: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct CallSettingsContent {
|
||||
pub mute_on_join: Option<bool>,
|
||||
}
|
||||
|
||||
impl Setting for CallSettings {
|
||||
const KEY: Option<&'static str> = Some("calls");
|
||||
|
||||
type FileContent = CallSettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &gpui::AppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
call_settings::CallSettings,
|
||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
|
||||
IncomingCall,
|
||||
};
|
||||
@@ -19,7 +20,7 @@ use live_kit_client::{
|
||||
};
|
||||
use postage::stream::Stream;
|
||||
use project::Project;
|
||||
use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration};
|
||||
use std::{future::Future, mem, panic::Location, pin::Pin, sync::Arc, time::Duration};
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
@@ -153,8 +154,10 @@ impl Room {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
connect.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))
|
||||
.await?;
|
||||
if !cx.read(|cx| settings::get::<CallSettings>(cx).mute_on_join) {
|
||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))
|
||||
.await?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -656,7 +659,7 @@ impl Room {
|
||||
peer_id,
|
||||
projects: participant.projects,
|
||||
location,
|
||||
muted: false,
|
||||
muted: true,
|
||||
speaking: false,
|
||||
video_tracks: Default::default(),
|
||||
audio_tracks: Default::default(),
|
||||
@@ -670,6 +673,10 @@ impl Room {
|
||||
live_kit.room.remote_video_tracks(&user.id.to_string());
|
||||
let audio_tracks =
|
||||
live_kit.room.remote_audio_tracks(&user.id.to_string());
|
||||
let publications = live_kit
|
||||
.room
|
||||
.remote_audio_track_publications(&user.id.to_string());
|
||||
|
||||
for track in video_tracks {
|
||||
this.remote_video_track_updated(
|
||||
RemoteVideoTrackUpdate::Subscribed(track),
|
||||
@@ -677,9 +684,15 @@ impl Room {
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
for track in audio_tracks {
|
||||
|
||||
for (track, publication) in
|
||||
audio_tracks.iter().zip(publications.iter())
|
||||
{
|
||||
this.remote_audio_track_updated(
|
||||
RemoteAudioTrackUpdate::Subscribed(track),
|
||||
RemoteAudioTrackUpdate::Subscribed(
|
||||
track.clone(),
|
||||
publication.clone(),
|
||||
),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
@@ -819,8 +832,8 @@ impl Room {
|
||||
cx.notify();
|
||||
}
|
||||
RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
|
||||
let mut found = false;
|
||||
for participant in &mut self.remote_participants.values_mut() {
|
||||
let mut found = false;
|
||||
for track in participant.audio_tracks.values() {
|
||||
if track.sid() == track_id {
|
||||
found = true;
|
||||
@@ -832,16 +845,20 @@ impl Room {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
RemoteAudioTrackUpdate::Subscribed(track) => {
|
||||
RemoteAudioTrackUpdate::Subscribed(track, publication) => {
|
||||
let user_id = track.publisher_id().parse()?;
|
||||
let track_id = track.sid().to_string();
|
||||
let participant = self
|
||||
.remote_participants
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||
|
||||
participant.audio_tracks.insert(track_id.clone(), track);
|
||||
participant.muted = publication.is_muted();
|
||||
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
@@ -1053,7 +1070,7 @@ impl Room {
|
||||
self.live_kit
|
||||
.as_ref()
|
||||
.and_then(|live_kit| match &live_kit.microphone_track {
|
||||
LocalTrack::None => None,
|
||||
LocalTrack::None => Some(true),
|
||||
LocalTrack::Pending { muted, .. } => Some(*muted),
|
||||
LocalTrack::Published { muted, .. } => Some(*muted),
|
||||
})
|
||||
@@ -1070,7 +1087,9 @@ impl Room {
|
||||
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
dbg!(Location::caller());
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
} else if self.is_sharing_mic() {
|
||||
@@ -1244,6 +1263,10 @@ impl Room {
|
||||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
|
||||
let should_mute = !self.is_muted();
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
if matches!(live_kit.microphone_track, LocalTrack::None) {
|
||||
return Ok(self.share_microphone(cx));
|
||||
}
|
||||
|
||||
let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?;
|
||||
live_kit.muted_by_user = should_mute;
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ impl Bundle {
|
||||
self.zed_version_string()
|
||||
);
|
||||
}
|
||||
|
||||
Self::LocalPath { executable, .. } => {
|
||||
let executable_parent = executable
|
||||
.parent()
|
||||
|
||||
@@ -40,6 +40,7 @@ lazy_static! {
|
||||
struct ClickhouseEventRequestBody {
|
||||
token: &'static str,
|
||||
installation_id: Option<Arc<str>>,
|
||||
is_staff: Option<bool>,
|
||||
app_version: Option<Arc<str>>,
|
||||
os_name: &'static str,
|
||||
os_version: Option<Arc<str>>,
|
||||
@@ -70,6 +71,10 @@ pub enum ClickhouseEvent {
|
||||
suggestion_accepted: bool,
|
||||
file_extension: Option<String>,
|
||||
},
|
||||
Call {
|
||||
operation: &'static str,
|
||||
room_id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -220,6 +225,7 @@ impl Telemetry {
|
||||
&ClickhouseEventRequestBody {
|
||||
token: ZED_SECRET_CLIENT_TOKEN,
|
||||
installation_id: state.installation_id.clone(),
|
||||
is_staff: state.is_staff.clone(),
|
||||
app_version: state.app_version.clone(),
|
||||
os_name: state.os_name,
|
||||
os_version: state.os_version.clone(),
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
@@ -14,7 +14,6 @@ name = "seed"
|
||||
required-features = ["seed-support"]
|
||||
|
||||
[dependencies]
|
||||
audio = { path = "../audio" }
|
||||
collections = { path = "../collections" }
|
||||
live_kit_server = { path = "../live_kit_server" }
|
||||
rpc = { path = "../rpc" }
|
||||
@@ -58,6 +57,7 @@ tracing-log = "0.1.3"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
|
||||
|
||||
[dev-dependencies]
|
||||
audio = { path = "../audio" }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
@@ -68,7 +68,7 @@ fs = { path = "../fs", features = ["test-support"] }
|
||||
git = { path = "../git", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
pretty_assertions.workspace = true
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
|
||||
@@ -3517,7 +3517,6 @@ pub use test::*;
|
||||
mod test {
|
||||
use super::*;
|
||||
use gpui::executor::Background;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use sea_orm::ConnectionTrait;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
@@ -3566,9 +3565,7 @@ mod test {
|
||||
}
|
||||
|
||||
pub fn postgres(background: Arc<Background>) -> Self {
|
||||
lazy_static! {
|
||||
static ref LOCK: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
static LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
let _guard = LOCK.lock();
|
||||
let mut rng = StdRng::from_entropy();
|
||||
|
||||
@@ -201,6 +201,7 @@ impl Server {
|
||||
.add_message_handler(update_language_server)
|
||||
.add_message_handler(update_diagnostic_summary)
|
||||
.add_message_handler(update_worktree_settings)
|
||||
.add_message_handler(refresh_inlay_hints)
|
||||
.add_request_handler(forward_project_request::<proto::GetHover>)
|
||||
.add_request_handler(forward_project_request::<proto::GetDefinition>)
|
||||
.add_request_handler(forward_project_request::<proto::GetTypeDefinition>)
|
||||
@@ -226,6 +227,7 @@ impl Server {
|
||||
.add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::ExpandProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
|
||||
.add_request_handler(forward_project_request::<proto::InlayHints>)
|
||||
.add_message_handler(create_buffer_for_peer)
|
||||
.add_request_handler(update_buffer)
|
||||
.add_message_handler(update_buffer_file)
|
||||
@@ -1574,6 +1576,10 @@ async fn update_worktree_settings(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn refresh_inlay_hints(request: proto::RefreshInlayHints, session: Session) -> Result<()> {
|
||||
broadcast_project_message(request.project_id, request, session).await
|
||||
}
|
||||
|
||||
async fn start_language_server(
|
||||
request: proto::StartLanguageServer,
|
||||
session: Session,
|
||||
@@ -1750,7 +1756,15 @@ async fn buffer_reloaded(request: proto::BufferReloaded, session: Session) -> Re
|
||||
}
|
||||
|
||||
async fn buffer_saved(request: proto::BufferSaved, session: Session) -> Result<()> {
|
||||
let project_id = ProjectId::from_proto(request.project_id);
|
||||
broadcast_project_message(request.project_id, request, session).await
|
||||
}
|
||||
|
||||
async fn broadcast_project_message<T: EnvelopedMessage>(
|
||||
project_id: u64,
|
||||
request: T,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let project_id = ProjectId::from_proto(project_id);
|
||||
let project_connection_ids = session
|
||||
.db()
|
||||
.await
|
||||
|
||||
@@ -18,7 +18,7 @@ use gpui::{
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, Formatter},
|
||||
language_settings::{AllLanguageSettings, Formatter, InlayHintSettings},
|
||||
tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||
LanguageConfig, OffsetRangeExt, Point, Rope,
|
||||
};
|
||||
@@ -34,7 +34,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
atomic::{AtomicBool, AtomicU32, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
@@ -157,7 +157,7 @@ async fn test_basic_calls(
|
||||
// User C receives the call, but declines it.
|
||||
let call_c = incoming_call_c.next().await.unwrap().unwrap();
|
||||
assert_eq!(call_c.calling_user.github_login, "user_b");
|
||||
active_call_c.update(cx_c, |call, _| call.decline_incoming().unwrap());
|
||||
active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
|
||||
assert!(incoming_call_c.next().await.unwrap().is_none());
|
||||
|
||||
deterministic.run_until_parked();
|
||||
@@ -1080,7 +1080,7 @@ async fn test_calls_on_multiple_connections(
|
||||
|
||||
// User B declines the call on one of the two connections, causing both connections
|
||||
// to stop ringing.
|
||||
active_call_b2.update(cx_b2, |call, _| call.decline_incoming().unwrap());
|
||||
active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
|
||||
deterministic.run_until_parked();
|
||||
assert!(incoming_call_b1.next().await.unwrap().is_none());
|
||||
assert!(incoming_call_b2.next().await.unwrap().is_none());
|
||||
@@ -5945,7 +5945,7 @@ async fn test_contacts(
|
||||
[("user_b".to_string(), "online", "busy")]
|
||||
);
|
||||
|
||||
active_call_b.update(cx_b, |call, _| call.decline_incoming().unwrap());
|
||||
active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
|
||||
deterministic.run_until_parked();
|
||||
assert_eq!(
|
||||
contacts(&client_a, cx_a),
|
||||
@@ -7217,7 +7217,7 @@ async fn test_peers_following_each_other(
|
||||
|
||||
// Clients A and B follow each other in split panes
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
});
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
@@ -7228,7 +7228,7 @@ async fn test_peers_following_each_other(
|
||||
.await
|
||||
.unwrap();
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
});
|
||||
workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
@@ -7455,7 +7455,7 @@ async fn test_auto_unfollowing(
|
||||
|
||||
// When client B activates a different pane, it continues following client A in the original pane.
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
|
||||
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||
@@ -7800,6 +7800,525 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
cx_a.update(|cx| {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: true,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: true,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
client_a.language_registry.add(Arc::clone(&language));
|
||||
client_b.language_registry.add(language);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let workspace_a = client_a.build_workspace(&project_a, cx_a);
|
||||
cx_a.foreground().start_waiting();
|
||||
|
||||
let _buffer_a = project_a
|
||||
.update(cx_a, |project, cx| {
|
||||
project.open_local_buffer("/a/main.rs", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
let next_call_id = Arc::new(AtomicU32::new(0));
|
||||
let editor_a = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
fake_language_server
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let task_next_call_id = Arc::clone(&next_call_id);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst);
|
||||
let mut new_hints = Vec::with_capacity(current_call_id as usize);
|
||||
loop {
|
||||
new_hints.push(lsp::InlayHint {
|
||||
position: lsp::Position::new(0, current_call_id),
|
||||
label: lsp::InlayHintLabel::String(current_call_id.to_string()),
|
||||
kind: None,
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
});
|
||||
if current_call_id == 0 {
|
||||
break;
|
||||
}
|
||||
current_call_id -= 1;
|
||||
}
|
||||
Ok(Some(new_hints))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx_a.foreground().finish_waiting();
|
||||
cx_a.foreground().run_until_parked();
|
||||
|
||||
let mut edits_made = 1;
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
assert_eq!(
|
||||
vec!["0".to_string()],
|
||||
extract_hint_labels(editor),
|
||||
"Host should get its first hints when opens an editor"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
"Host editor update the cache version after every cache/view change",
|
||||
);
|
||||
});
|
||||
let workspace_b = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
cx_b.foreground().run_until_parked();
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
vec!["0".to_string(), "1".to_string()],
|
||||
extract_hint_labels(editor),
|
||||
"Client should get its first hints when opens an editor"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
"Guest editor update the cache version after every cache/view change"
|
||||
);
|
||||
});
|
||||
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.handle_input(":", cx);
|
||||
cx.focus(&editor_b);
|
||||
edits_made += 1;
|
||||
});
|
||||
cx_a.foreground().run_until_parked();
|
||||
cx_b.foreground().run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
assert_eq!(
|
||||
vec!["0".to_string(), "1".to_string(), "2".to_string()],
|
||||
extract_hint_labels(editor),
|
||||
"Host should get hints from the 1st edit and 1st LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
vec![
|
||||
"0".to_string(),
|
||||
"1".to_string(),
|
||||
"2".to_string(),
|
||||
"3".to_string()
|
||||
],
|
||||
extract_hint_labels(editor),
|
||||
"Guest should get hints the 1st edit and 2nd LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
});
|
||||
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input("a change to increment both buffers' versions", cx);
|
||||
cx.focus(&editor_a);
|
||||
edits_made += 1;
|
||||
});
|
||||
cx_a.foreground().run_until_parked();
|
||||
cx_b.foreground().run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
assert_eq!(
|
||||
vec![
|
||||
"0".to_string(),
|
||||
"1".to_string(),
|
||||
"2".to_string(),
|
||||
"3".to_string(),
|
||||
"4".to_string()
|
||||
],
|
||||
extract_hint_labels(editor),
|
||||
"Host should get hints from 3rd edit, 5th LSP query: \
|
||||
4th query was made by guest (but not applied) due to cache invalidation logic"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
vec![
|
||||
"0".to_string(),
|
||||
"1".to_string(),
|
||||
"2".to_string(),
|
||||
"3".to_string(),
|
||||
"4".to_string(),
|
||||
"5".to_string(),
|
||||
],
|
||||
extract_hint_labels(editor),
|
||||
"Guest should get hints from 3rd edit, 6th LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
});
|
||||
|
||||
fake_language_server
|
||||
.request::<lsp::request::InlayHintRefreshRequest>(())
|
||||
.await
|
||||
.expect("inlay refresh request failed");
|
||||
edits_made += 1;
|
||||
cx_a.foreground().run_until_parked();
|
||||
cx_b.foreground().run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
assert_eq!(
|
||||
vec![
|
||||
"0".to_string(),
|
||||
"1".to_string(),
|
||||
"2".to_string(),
|
||||
"3".to_string(),
|
||||
"4".to_string(),
|
||||
"5".to_string(),
|
||||
"6".to_string(),
|
||||
],
|
||||
extract_hint_labels(editor),
|
||||
"Host should react to /refresh LSP request and get new hints from 7th LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
"Host should accepted all edits and bump its cache version every time"
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
vec![
|
||||
"0".to_string(),
|
||||
"1".to_string(),
|
||||
"2".to_string(),
|
||||
"3".to_string(),
|
||||
"4".to_string(),
|
||||
"5".to_string(),
|
||||
"6".to_string(),
|
||||
"7".to_string(),
|
||||
],
|
||||
extract_hint_labels(editor),
|
||||
"Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version,
|
||||
edits_made,
|
||||
"Guest should accepted all edits and bump its cache version every time"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_inlay_hint_refresh_is_forwarded(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
cx_a.update(|cx| {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: false,
|
||||
show_type_hints: false,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: true,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
client_a.language_registry.add(Arc::clone(&language));
|
||||
client_b.language_registry.add(language);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let workspace_a = client_a.build_workspace(&project_a, cx_a);
|
||||
let workspace_b = client_b.build_workspace(&project_b, cx_b);
|
||||
cx_a.foreground().start_waiting();
|
||||
cx_b.foreground().start_waiting();
|
||||
|
||||
let editor_a = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
let next_call_id = Arc::new(AtomicU32::new(0));
|
||||
fake_language_server
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let task_next_call_id = Arc::clone(&next_call_id);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst);
|
||||
let mut new_hints = Vec::with_capacity(current_call_id as usize);
|
||||
loop {
|
||||
new_hints.push(lsp::InlayHint {
|
||||
position: lsp::Position::new(0, current_call_id),
|
||||
label: lsp::InlayHintLabel::String(current_call_id.to_string()),
|
||||
kind: None,
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
});
|
||||
if current_call_id == 0 {
|
||||
break;
|
||||
}
|
||||
current_call_id -= 1;
|
||||
}
|
||||
Ok(Some(new_hints))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.foreground().finish_waiting();
|
||||
cx_b.foreground().finish_waiting();
|
||||
|
||||
cx_a.foreground().run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
assert!(
|
||||
extract_hint_labels(editor).is_empty(),
|
||||
"Host should get no hints due to them turned off"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 0,
|
||||
"Host should not increment its cache version due to no changes",
|
||||
);
|
||||
});
|
||||
|
||||
let mut edits_made = 1;
|
||||
cx_b.foreground().run_until_parked();
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
vec!["0".to_string()],
|
||||
extract_hint_labels(editor),
|
||||
"Client should get its first hints when opens an editor"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
"Guest editor update the cache version after every cache/view change"
|
||||
);
|
||||
});
|
||||
|
||||
fake_language_server
|
||||
.request::<lsp::request::InlayHintRefreshRequest>(())
|
||||
.await
|
||||
.expect("inlay refresh request failed");
|
||||
cx_a.foreground().run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
assert!(
|
||||
extract_hint_labels(editor).is_empty(),
|
||||
"Host should get nop hints due to them turned off, even after the /refresh"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 0,
|
||||
"Host should not increment its cache version due to no changes",
|
||||
);
|
||||
});
|
||||
|
||||
edits_made += 1;
|
||||
cx_b.foreground().run_until_parked();
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
assert_eq!(
|
||||
vec!["0".to_string(), "1".to_string(),],
|
||||
extract_hint_labels(editor),
|
||||
"Guest should get a /refresh LSP request propagated by host despite host hints are off"
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
"Guest should accepted all edits and bump its cache version every time"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct RoomParticipants {
|
||||
remote: Vec<String>,
|
||||
@@ -7823,3 +8342,17 @@ fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomP
|
||||
RoomParticipants { remote, pending }
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
let mut labels = Vec::new();
|
||||
for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
|
||||
let excerpt_hints = excerpt_hints.read();
|
||||
for (_, inlay) in excerpt_hints.hints.iter() {
|
||||
match &inlay.label {
|
||||
project::InlayHintLabel::String(s) => labels.push(s.to_string()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
labels
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ use util::ResultExt;
|
||||
lazy_static::lazy_static! {
|
||||
static ref PLAN_LOAD_PATH: Option<PathBuf> = path_env_var("LOAD_PLAN");
|
||||
static ref PLAN_SAVE_PATH: Option<PathBuf> = path_env_var("SAVE_PLAN");
|
||||
static ref LOADED_PLAN_JSON: Mutex<Option<Vec<u8>>> = Default::default();
|
||||
static ref PLAN: Mutex<Option<Arc<Mutex<TestPlan>>>> = Default::default();
|
||||
}
|
||||
static LOADED_PLAN_JSON: Mutex<Option<Vec<u8>>> = Mutex::new(None);
|
||||
static PLAN: Mutex<Option<Arc<Mutex<TestPlan>>>> = Mutex::new(None);
|
||||
|
||||
#[gpui::test(iterations = 100, on_failure = "on_failure")]
|
||||
async fn test_random_collaboration(
|
||||
@@ -365,7 +365,7 @@ async fn apply_client_operation(
|
||||
}
|
||||
|
||||
log::info!("{}: declining incoming call", client.username);
|
||||
active_call.update(cx, |call, _| call.decline_incoming())?;
|
||||
active_call.update(cx, |call, cx| call.decline_incoming(cx))?;
|
||||
}
|
||||
|
||||
ClientOperation::LeaveCall => {
|
||||
|
||||
@@ -35,13 +35,16 @@ gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
picker = { path = "../picker" }
|
||||
project = { path = "../project" }
|
||||
recent_projects = {path = "../recent_projects"}
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
theme_selector = { path = "../theme_selector" }
|
||||
vcs_menu = { path = "../vcs_menu" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
zed-actions = {path = "../zed-actions"}
|
||||
|
||||
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -18,19 +18,25 @@ use gpui::{
|
||||
AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use picker::PickerEvent;
|
||||
use project::{Project, RepositoryEntry};
|
||||
use recent_projects::{build_recent_projects, RecentProjects};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::{AvatarStyle, Theme};
|
||||
use util::ResultExt;
|
||||
use workspace::{FollowNextCollaborator, Workspace};
|
||||
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
|
||||
use workspace::{FollowNextCollaborator, Workspace, WORKSPACE_DB};
|
||||
|
||||
// const MAX_TITLE_LENGTH: usize = 75;
|
||||
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
||||
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
||||
|
||||
actions!(
|
||||
collab,
|
||||
[
|
||||
ToggleContactsMenu,
|
||||
ToggleUserMenu,
|
||||
ToggleProjectMenu,
|
||||
SwitchBranch,
|
||||
ShareProject,
|
||||
UnshareProject,
|
||||
]
|
||||
@@ -41,6 +47,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(CollabTitlebarItem::share_project);
|
||||
cx.add_action(CollabTitlebarItem::unshare_project);
|
||||
cx.add_action(CollabTitlebarItem::toggle_user_menu);
|
||||
cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
|
||||
cx.add_action(CollabTitlebarItem::toggle_project_menu);
|
||||
}
|
||||
|
||||
pub struct CollabTitlebarItem {
|
||||
@@ -49,6 +57,8 @@ pub struct CollabTitlebarItem {
|
||||
client: Arc<Client>,
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
contacts_popover: Option<ViewHandle<ContactsPopover>>,
|
||||
branch_popover: Option<ViewHandle<BranchList>>,
|
||||
project_popover: Option<ViewHandle<recent_projects::RecentProjects>>,
|
||||
user_menu: ViewHandle<ContextMenu>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -69,12 +79,11 @@ impl View for CollabTitlebarItem {
|
||||
return Empty::new().into_any();
|
||||
};
|
||||
|
||||
let project = self.project.read(cx);
|
||||
let theme = theme::current(cx).clone();
|
||||
let mut left_container = Flex::row();
|
||||
let mut right_container = Flex::row().align_children_center();
|
||||
|
||||
left_container.add_child(self.collect_title_root_names(&project, theme.clone(), cx));
|
||||
left_container.add_child(self.collect_title_root_names(theme.clone(), cx));
|
||||
|
||||
let user = self.user_store.read(cx).current_user();
|
||||
let peer_id = self.client.peer_id();
|
||||
@@ -182,52 +191,113 @@ impl CollabTitlebarItem {
|
||||
menu.set_position_mode(OverlayPositionMode::Local);
|
||||
menu
|
||||
}),
|
||||
branch_popover: None,
|
||||
project_popover: None,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_title_root_names(
|
||||
&self,
|
||||
project: &Project,
|
||||
theme: Arc<Theme>,
|
||||
cx: &ViewContext<Self>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> AnyElement<Self> {
|
||||
let mut names_and_branches = project.visible_worktrees(cx).map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
(worktree.root_name(), worktree.root_git_entry())
|
||||
});
|
||||
let project = self.project.read(cx);
|
||||
|
||||
let (name, entry) = names_and_branches.next().unwrap_or(("", None));
|
||||
let (name, entry) = {
|
||||
let mut names_and_branches = project.visible_worktrees(cx).map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
(worktree.root_name(), worktree.root_git_entry())
|
||||
});
|
||||
|
||||
names_and_branches.next().unwrap_or(("", None))
|
||||
};
|
||||
|
||||
let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
|
||||
let branch_prepended = entry
|
||||
.as_ref()
|
||||
.and_then(RepositoryEntry::branch)
|
||||
.map(|branch| format!("/{branch}"));
|
||||
let text_style = theme.titlebar.title.clone();
|
||||
.map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH));
|
||||
let project_style = theme.titlebar.project_menu_button.clone();
|
||||
let git_style = theme.titlebar.git_menu_button.clone();
|
||||
let divider_style = theme.titlebar.project_name_divider.clone();
|
||||
let item_spacing = theme.titlebar.item_spacing;
|
||||
|
||||
let mut highlight = text_style.clone();
|
||||
highlight.color = theme.titlebar.highlight_color;
|
||||
|
||||
let style = LabelStyle {
|
||||
text: text_style,
|
||||
highlight_text: Some(highlight),
|
||||
};
|
||||
let mut ret = Flex::row().with_child(
|
||||
Label::new(name.to_owned(), style.clone())
|
||||
.with_highlights((0..name.len()).into_iter().collect())
|
||||
.contained()
|
||||
.aligned()
|
||||
.left()
|
||||
.into_any_named("title-project-name"),
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleProjectMenu, Self>::new(0, cx, |mouse_state, cx| {
|
||||
let style = project_style
|
||||
.in_state(self.project_popover.is_some())
|
||||
.style_for(mouse_state);
|
||||
enum RecentProjectsTooltip {}
|
||||
Label::new(name, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.aligned()
|
||||
.left()
|
||||
.with_tooltip::<RecentProjectsTooltip>(
|
||||
0,
|
||||
"Recent projects".into(),
|
||||
Some(Box::new(recent_projects::OpenRecent)),
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.into_any_named("title-project-name")
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(MouseButton::Left, move |_, this, cx| {
|
||||
this.toggle_project_menu(&Default::default(), cx)
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, _, _| {}),
|
||||
)
|
||||
.with_children(self.render_project_popover_host(&theme.titlebar, cx)),
|
||||
);
|
||||
if let Some(git_branch) = branch_prepended {
|
||||
ret = ret.with_child(
|
||||
Label::new(git_branch, style)
|
||||
.contained()
|
||||
.with_margin_right(item_spacing)
|
||||
.aligned()
|
||||
.left()
|
||||
.into_any_named("title-project-branch"),
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new("/", divider_style.text)
|
||||
.contained()
|
||||
.with_style(divider_style.container)
|
||||
.aligned()
|
||||
.left(),
|
||||
)
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleVcsMenu, Self>::new(
|
||||
0,
|
||||
cx,
|
||||
|mouse_state, cx| {
|
||||
enum BranchPopoverTooltip {}
|
||||
let style = git_style
|
||||
.in_state(self.branch_popover.is_some())
|
||||
.style_for(mouse_state);
|
||||
Label::new(git_branch, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container.clone())
|
||||
.with_margin_right(item_spacing)
|
||||
.aligned()
|
||||
.left()
|
||||
.with_tooltip::<BranchPopoverTooltip>(
|
||||
0,
|
||||
"Recent branches".into(),
|
||||
Some(Box::new(ToggleVcsMenu)),
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.into_any_named("title-project-branch")
|
||||
},
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(MouseButton::Left, move |_, this, cx| {
|
||||
this.toggle_vcs_menu(&Default::default(), cx)
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, _, _| {}),
|
||||
)
|
||||
.with_children(self.render_branches_popover_host(&theme.titlebar, cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
ret.into_any()
|
||||
@@ -317,10 +387,138 @@ impl CollabTitlebarItem {
|
||||
),
|
||||
]
|
||||
};
|
||||
user_menu.show(Default::default(), AnchorCorner::TopRight, items, cx);
|
||||
user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
|
||||
});
|
||||
}
|
||||
fn render_branches_popover_host<'a>(
|
||||
&'a self,
|
||||
_theme: &'a theme::Titlebar,
|
||||
cx: &'a mut ViewContext<Self>,
|
||||
) -> Option<AnyElement<Self>> {
|
||||
self.branch_popover.as_ref().map(|child| {
|
||||
let theme = theme::current(cx).clone();
|
||||
let child = ChildView::new(child, cx);
|
||||
let child = MouseEventHandler::<BranchList, Self>::new(0, cx, |_, _| {
|
||||
child
|
||||
.flex(1., true)
|
||||
.contained()
|
||||
.constrained()
|
||||
.with_width(theme.contacts_popover.width)
|
||||
.with_height(theme.contacts_popover.height)
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, _, _| {})
|
||||
.on_down_out(MouseButton::Left, move |_, this, cx| {
|
||||
this.branch_popover.take();
|
||||
cx.emit(());
|
||||
cx.notify();
|
||||
})
|
||||
.contained()
|
||||
.into_any();
|
||||
|
||||
Overlay::new(child)
|
||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||
.with_anchor_corner(AnchorCorner::TopLeft)
|
||||
.with_z_index(999)
|
||||
.aligned()
|
||||
.bottom()
|
||||
.left()
|
||||
.into_any()
|
||||
})
|
||||
}
|
||||
fn render_project_popover_host<'a>(
|
||||
&'a self,
|
||||
_theme: &'a theme::Titlebar,
|
||||
cx: &'a mut ViewContext<Self>,
|
||||
) -> Option<AnyElement<Self>> {
|
||||
self.project_popover.as_ref().map(|child| {
|
||||
let theme = theme::current(cx).clone();
|
||||
let child = ChildView::new(child, cx);
|
||||
let child = MouseEventHandler::<RecentProjects, Self>::new(0, cx, |_, _| {
|
||||
child
|
||||
.flex(1., true)
|
||||
.contained()
|
||||
.constrained()
|
||||
.with_width(theme.contacts_popover.width)
|
||||
.with_height(theme.contacts_popover.height)
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, _, _| {})
|
||||
.on_down_out(MouseButton::Left, move |_, this, cx| {
|
||||
this.project_popover.take();
|
||||
cx.emit(());
|
||||
cx.notify();
|
||||
})
|
||||
.into_any();
|
||||
|
||||
Overlay::new(child)
|
||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||
.with_anchor_corner(AnchorCorner::TopLeft)
|
||||
.with_z_index(999)
|
||||
.aligned()
|
||||
.bottom()
|
||||
.left()
|
||||
.into_any()
|
||||
})
|
||||
}
|
||||
pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
|
||||
if self.branch_popover.take().is_none() {
|
||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
let view = cx.add_view(|cx| build_branch_list(workspace, cx));
|
||||
cx.subscribe(&view, |this, _, event, cx| {
|
||||
match event {
|
||||
PickerEvent::Dismiss => {
|
||||
this.branch_popover = None;
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
self.project_popover.take();
|
||||
cx.focus(&view);
|
||||
self.branch_popover = Some(view);
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
|
||||
let workspace = self.workspace.clone();
|
||||
if self.project_popover.take().is_none() {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let workspaces = WORKSPACE_DB
|
||||
.recent_workspaces_on_disk()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(_, location)| location)
|
||||
.collect();
|
||||
|
||||
let workspace = workspace.clone();
|
||||
this.update(&mut cx, move |this, cx| {
|
||||
let view = cx.add_view(|cx| build_recent_projects(workspace, workspaces, cx));
|
||||
|
||||
cx.subscribe(&view, |this, _, event, cx| {
|
||||
match event {
|
||||
PickerEvent::Dismiss => {
|
||||
this.project_popover = None;
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
cx.focus(&view);
|
||||
this.branch_popover.take();
|
||||
this.project_popover = Some(view);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
fn render_toggle_contacts_button(
|
||||
&self,
|
||||
theme: &Theme,
|
||||
@@ -454,10 +652,10 @@ impl CollabTitlebarItem {
|
||||
let is_muted = room.read(cx).is_muted();
|
||||
if is_muted {
|
||||
icon = "icons/radix/mic-mute.svg";
|
||||
tooltip = "Unmute microphone\nRight click for options";
|
||||
tooltip = "Unmute microphone";
|
||||
} else {
|
||||
icon = "icons/radix/mic.svg";
|
||||
tooltip = "Mute microphone\nRight click for options";
|
||||
tooltip = "Mute microphone";
|
||||
}
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
@@ -507,10 +705,10 @@ impl CollabTitlebarItem {
|
||||
let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
|
||||
if is_deafened {
|
||||
icon = "icons/radix/speaker-off.svg";
|
||||
tooltip = "Unmute speakers\nRight click for options";
|
||||
tooltip = "Unmute speakers";
|
||||
} else {
|
||||
icon = "icons/radix/speaker-loud.svg";
|
||||
tooltip = "Mute speakers\nRight click for options";
|
||||
tooltip = "Mute speakers";
|
||||
}
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
@@ -683,6 +881,9 @@ impl CollabTitlebarItem {
|
||||
.into_any()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(MouseButton::Left, move |_, this, cx| {
|
||||
this.user_menu.update(cx, |menu, _| menu.delay_cancel());
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||
this.toggle_user_menu(&Default::default(), cx)
|
||||
})
|
||||
@@ -730,7 +931,7 @@ impl CollabTitlebarItem {
|
||||
self.contacts_popover.as_ref().map(|popover| {
|
||||
Overlay::new(ChildView::new(popover, cx))
|
||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||
.with_anchor_corner(AnchorCorner::TopRight)
|
||||
.with_anchor_corner(AnchorCorner::TopLeft)
|
||||
.with_z_index(999)
|
||||
.aligned()
|
||||
.bottom()
|
||||
|
||||
@@ -18,16 +18,11 @@ use workspace::AppState;
|
||||
|
||||
actions!(
|
||||
collab,
|
||||
[
|
||||
ToggleScreenSharing,
|
||||
ToggleMute,
|
||||
ToggleDeafen,
|
||||
LeaveCall,
|
||||
ShareMicrophone
|
||||
]
|
||||
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||
);
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
vcs_menu::init(cx);
|
||||
collab_titlebar_item::init(cx);
|
||||
contact_list::init(cx);
|
||||
contact_finder::init(cx);
|
||||
@@ -39,15 +34,28 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
cx.add_global_action(toggle_screen_sharing);
|
||||
cx.add_global_action(toggle_mute);
|
||||
cx.add_global_action(toggle_deafen);
|
||||
cx.add_global_action(share_microphone);
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
ActiveCall::report_call_event_for_room(
|
||||
"disable screen share",
|
||||
room.id(),
|
||||
&client,
|
||||
cx,
|
||||
);
|
||||
Task::ready(room.unshare_screen(cx))
|
||||
} else {
|
||||
ActiveCall::report_call_event_for_room(
|
||||
"enable screen share",
|
||||
room.id(),
|
||||
&client,
|
||||
cx,
|
||||
);
|
||||
room.share_screen(cx)
|
||||
}
|
||||
});
|
||||
@@ -70,10 +78,3 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
room.update(cx, Room::share_microphone)
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(user) = self.potential_contacts.get(self.selected_index) {
|
||||
let user_store = self.user_store.read(cx);
|
||||
match user_store.contact_request_status(user) {
|
||||
|
||||
@@ -99,8 +99,8 @@ impl IncomingCallNotification {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
} else {
|
||||
active_call.update(cx, |active_call, _| {
|
||||
active_call.decline_incoming().log_err();
|
||||
active_call.update(cx, |active_call, cx| {
|
||||
active_call.decline_incoming(cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if !self.matches.is_empty() {
|
||||
let window_id = cx.window_id();
|
||||
let focused_view_id = self.focused_view_id;
|
||||
@@ -369,6 +369,7 @@ mod tests {
|
||||
editor::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
init(cx);
|
||||
Project::init_settings(cx);
|
||||
app_state
|
||||
})
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ pub struct ContextMenu {
|
||||
items: Vec<ContextMenuItem>,
|
||||
selected_index: Option<usize>,
|
||||
visible: bool,
|
||||
delay_cancel: bool,
|
||||
previously_focused_view_id: Option<usize>,
|
||||
parent_view_id: usize,
|
||||
_actions_observation: Subscription,
|
||||
@@ -178,6 +179,7 @@ impl ContextMenu {
|
||||
pub fn new(parent_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
show_count: 0,
|
||||
delay_cancel: false,
|
||||
anchor_position: Default::default(),
|
||||
anchor_corner: AnchorCorner::TopLeft,
|
||||
position_mode: OverlayPositionMode::Window,
|
||||
@@ -232,15 +234,22 @@ impl ContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delay_cancel(&mut self) {
|
||||
self.delay_cancel = true;
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
self.reset(cx);
|
||||
let show_count = self.show_count;
|
||||
cx.defer(move |this, cx| {
|
||||
if cx.handle().is_focused(cx) && this.show_count == show_count {
|
||||
let window_id = cx.window_id();
|
||||
(**cx).focus(window_id, this.previously_focused_view_id.take());
|
||||
}
|
||||
});
|
||||
if !self.delay_cancel {
|
||||
self.reset(cx);
|
||||
let show_count = self.show_count;
|
||||
cx.defer(move |this, cx| {
|
||||
if cx.handle().is_focused(cx) && this.show_count == show_count {
|
||||
(**cx).focus(this.previously_focused_view_id.take());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.delay_cancel = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self, cx: &mut ViewContext<Self>) {
|
||||
@@ -293,6 +302,34 @@ impl ContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(
|
||||
&mut self,
|
||||
anchor_position: Vector2F,
|
||||
anchor_corner: AnchorCorner,
|
||||
items: Vec<ContextMenuItem>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.visible() {
|
||||
self.cancel(&Cancel, cx);
|
||||
} else {
|
||||
let mut items = items.into_iter().peekable();
|
||||
if items.peek().is_some() {
|
||||
self.items = items.collect();
|
||||
self.anchor_position = anchor_position;
|
||||
self.anchor_corner = anchor_corner;
|
||||
self.visible = true;
|
||||
self.show_count += 1;
|
||||
if !cx.is_self_focused() {
|
||||
self.previously_focused_view_id = cx.focused_view_id();
|
||||
}
|
||||
cx.focus_self();
|
||||
} else {
|
||||
self.visible = false;
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn show(
|
||||
&mut self,
|
||||
anchor_position: Vector2F,
|
||||
|
||||
@@ -15,7 +15,7 @@ use language::{
|
||||
ToPointUtf16,
|
||||
};
|
||||
use log::{debug, error};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
use request::{LogMessage, StatusNotification};
|
||||
use settings::SettingsStore;
|
||||
@@ -340,7 +340,7 @@ impl Copilot {
|
||||
let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
let this = cx.add_model(|cx| Self {
|
||||
http: http.clone(),
|
||||
node_runtime: NodeRuntime::new(http, cx.background().clone()),
|
||||
node_runtime: NodeRuntime::instance(http, cx.background().clone()),
|
||||
server: CopilotServer::Running(RunningCopilotServer {
|
||||
lsp: Arc::new(server),
|
||||
sign_in_status: SignInStatus::Authorized,
|
||||
@@ -361,11 +361,14 @@ impl Copilot {
|
||||
let start_language_server = async {
|
||||
let server_path = get_copilot_lsp(http).await?;
|
||||
let node_path = node_runtime.binary_path().await?;
|
||||
let arguments: &[OsString] = &[server_path.into(), "--stdio".into()];
|
||||
let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
||||
let binary = LanguageServerBinary {
|
||||
path: node_path,
|
||||
arguments,
|
||||
};
|
||||
let server = LanguageServer::new(
|
||||
LanguageServerId(0),
|
||||
&node_path,
|
||||
arguments,
|
||||
binary,
|
||||
Path::new("/"),
|
||||
None,
|
||||
cx.clone(),
|
||||
|
||||
@@ -102,6 +102,9 @@ impl View for CopilotButton {
|
||||
}
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(MouseButton::Left, |_, this, cx| {
|
||||
this.popup_menu.update(cx, |menu, _| menu.delay_cancel());
|
||||
})
|
||||
.on_click(MouseButton::Left, {
|
||||
let status = status.clone();
|
||||
move |_, this, cx| match status {
|
||||
@@ -186,7 +189,7 @@ impl CopilotButton {
|
||||
}));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
menu.toggle(
|
||||
Default::default(),
|
||||
AnchorCorner::BottomRight,
|
||||
menu_options,
|
||||
@@ -266,7 +269,7 @@ impl CopilotButton {
|
||||
menu_options.push(ContextMenuItem::action("Sign Out", SignOut));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
menu.toggle(
|
||||
Default::default(),
|
||||
AnchorCorner::BottomRight,
|
||||
menu_options,
|
||||
|
||||
@@ -7,7 +7,6 @@ use anyhow::Context;
|
||||
use gpui::AppContext;
|
||||
pub use indoc::indoc;
|
||||
pub use lazy_static;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
pub use smol;
|
||||
pub use sqlez;
|
||||
pub use sqlez_macros;
|
||||
@@ -17,11 +16,9 @@ pub use util::paths::DB_DIR;
|
||||
use sqlez::domain::Migrator;
|
||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
use sqlez_macros::sql;
|
||||
use std::fs::create_dir_all;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use util::channel::ReleaseChannel;
|
||||
use util::{async_iife, ResultExt};
|
||||
|
||||
@@ -41,10 +38,7 @@ const FALLBACK_DB_NAME: &'static str = "FALLBACK_MEMORY_DB";
|
||||
const DB_FILE_NAME: &'static str = "db.sqlite";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
// !!!!!!! CHANGE BACK TO DEFAULT FALSE BEFORE SHIPPING
|
||||
static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
|
||||
static ref DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(());
|
||||
pub static ref BACKUP_DB_PATH: RwLock<Option<PathBuf>> = RwLock::new(None);
|
||||
pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
|
||||
pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
@@ -64,66 +58,14 @@ pub async fn open_db<M: Migrator + 'static>(
|
||||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
||||
|
||||
let connection = async_iife!({
|
||||
// Note: This still has a race condition where 1 set of migrations succeeds
|
||||
// (e.g. (Workspace, Editor)) and another fails (e.g. (Workspace, Terminal))
|
||||
// This will cause the first connection to have the database taken out
|
||||
// from under it. This *should* be fine though. The second dabatase failure will
|
||||
// cause errors in the log and so should be observed by developers while writing
|
||||
// soon-to-be good migrations. If user databases are corrupted, we toss them out
|
||||
// and try again from a blank. As long as running all migrations from start to end
|
||||
// on a blank database is ok, this race condition will never be triggered.
|
||||
//
|
||||
// Basically: Don't ever push invalid migrations to stable or everyone will have
|
||||
// a bad time.
|
||||
|
||||
// If no db folder, create one at 0-{channel}
|
||||
create_dir_all(&main_db_dir).context("Could not create db directory")?;
|
||||
smol::fs::create_dir_all(&main_db_dir)
|
||||
.await
|
||||
.context("Could not create db directory")
|
||||
.log_err()?;
|
||||
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
||||
|
||||
// Optimistically open databases in parallel
|
||||
if !DB_FILE_OPERATIONS.is_locked() {
|
||||
// Try building a connection
|
||||
if let Some(connection) = open_main_db(&db_path).await {
|
||||
return Ok(connection)
|
||||
};
|
||||
}
|
||||
|
||||
// Take a lock in the failure case so that we move the db once per process instead
|
||||
// of potentially multiple times from different threads. This shouldn't happen in the
|
||||
// normal path
|
||||
let _lock = DB_FILE_OPERATIONS.lock();
|
||||
if let Some(connection) = open_main_db(&db_path).await {
|
||||
return Ok(connection)
|
||||
};
|
||||
|
||||
let backup_timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("System clock is set before the unix timestamp, Zed does not support this region of spacetime")
|
||||
.as_millis();
|
||||
|
||||
// If failed, move 0-{channel} to {current unix timestamp}-{channel}
|
||||
let backup_db_dir = db_dir.join(Path::new(&format!(
|
||||
"{}-{}",
|
||||
backup_timestamp,
|
||||
release_channel_name,
|
||||
)));
|
||||
|
||||
std::fs::rename(&main_db_dir, &backup_db_dir)
|
||||
.context("Failed clean up corrupted database, panicking.")?;
|
||||
|
||||
// Set a static ref with the failed timestamp and error so we can notify the user
|
||||
{
|
||||
let mut guard = BACKUP_DB_PATH.write();
|
||||
*guard = Some(backup_db_dir);
|
||||
}
|
||||
|
||||
// Create a new 0-{channel}
|
||||
create_dir_all(&main_db_dir).context("Should be able to create the database directory")?;
|
||||
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
||||
|
||||
// Try again
|
||||
open_main_db(&db_path).await.context("Could not newly created db")
|
||||
}).await.log_err();
|
||||
open_main_db(&db_path).await
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(connection) = connection {
|
||||
return connection;
|
||||
@@ -250,13 +192,13 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs, thread};
|
||||
use std::thread;
|
||||
|
||||
use sqlez::{connection::Connection, domain::Domain};
|
||||
use sqlez::domain::Domain;
|
||||
use sqlez_macros::sql;
|
||||
use tempdir::TempDir;
|
||||
|
||||
use crate::{open_db, DB_FILE_NAME};
|
||||
use crate::open_db;
|
||||
|
||||
// Test bad migration panics
|
||||
#[gpui::test]
|
||||
@@ -322,31 +264,10 @@ mod tests {
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let mut corrupted_backup_dir = fs::read_dir(tempdir.path())
|
||||
.unwrap()
|
||||
.find(|entry| {
|
||||
!entry
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.starts_with("0")
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.path();
|
||||
corrupted_backup_dir.push(DB_FILE_NAME);
|
||||
|
||||
let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy());
|
||||
assert!(backup.select_row::<usize>("SELECT * FROM test").unwrap()()
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
|
||||
/// Test that DB exists but corrupted (causing recreate)
|
||||
#[gpui::test]
|
||||
#[gpui::test(iterations = 30)]
|
||||
async fn test_simultaneous_db_corruption() {
|
||||
enum CorruptedDB {}
|
||||
|
||||
|
||||
@@ -57,16 +57,16 @@ ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
rand = { workspace = true, optional = true }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
tree-sitter-rust = { version = "*", optional = true }
|
||||
tree-sitter-html = { version = "*", optional = true }
|
||||
tree-sitter-javascript = { version = "*", optional = true }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", optional = true }
|
||||
|
||||
rand = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-html = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
copilot = { path = "../copilot", features = ["test-support"] }
|
||||
@@ -84,7 +84,6 @@ env_logger.workspace = true
|
||||
rand.workspace = true
|
||||
unindent.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-rust = "0.20"
|
||||
tree-sitter-html = "0.19"
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-javascript = "0.20"
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
mod block_map;
|
||||
mod fold_map;
|
||||
mod suggestion_map;
|
||||
mod inlay_map;
|
||||
mod tab_map;
|
||||
mod wrap_map;
|
||||
|
||||
use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use crate::{Anchor, AnchorRangeExt, InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
pub use block_map::{BlockMap, BlockPoint};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fold_map::{FoldMap, FoldOffset};
|
||||
use fold_map::FoldMap;
|
||||
use gpui::{
|
||||
color::Color,
|
||||
fonts::{FontId, HighlightStyle},
|
||||
Entity, ModelContext, ModelHandle,
|
||||
};
|
||||
use inlay_map::InlayMap;
|
||||
use language::{
|
||||
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
|
||||
};
|
||||
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||
pub use suggestion_map::Suggestion;
|
||||
use suggestion_map::SuggestionMap;
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::TabMap;
|
||||
use wrap_map::WrapMap;
|
||||
@@ -28,6 +27,8 @@ pub use block_map::{
|
||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
||||
};
|
||||
|
||||
pub use self::inlay_map::Inlay;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FoldStatus {
|
||||
Folded,
|
||||
@@ -44,7 +45,7 @@ pub struct DisplayMap {
|
||||
buffer: ModelHandle<MultiBuffer>,
|
||||
buffer_subscription: BufferSubscription,
|
||||
fold_map: FoldMap,
|
||||
suggestion_map: SuggestionMap,
|
||||
inlay_map: InlayMap,
|
||||
tab_map: TabMap,
|
||||
wrap_map: ModelHandle<WrapMap>,
|
||||
block_map: BlockMap,
|
||||
@@ -69,8 +70,8 @@ impl DisplayMap {
|
||||
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
|
||||
let tab_size = Self::tab_size(&buffer, cx);
|
||||
let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
|
||||
let (suggestion_map, snapshot) = SuggestionMap::new(snapshot);
|
||||
let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
|
||||
let (fold_map, snapshot) = FoldMap::new(snapshot);
|
||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
|
||||
let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
|
||||
@@ -79,7 +80,7 @@ impl DisplayMap {
|
||||
buffer,
|
||||
buffer_subscription,
|
||||
fold_map,
|
||||
suggestion_map,
|
||||
inlay_map,
|
||||
tab_map,
|
||||
wrap_map,
|
||||
block_map,
|
||||
@@ -88,16 +89,13 @@ impl DisplayMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
|
||||
pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits);
|
||||
let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits);
|
||||
|
||||
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (tab_snapshot, edits) = self
|
||||
.tab_map
|
||||
.sync(suggestion_snapshot.clone(), edits, tab_size);
|
||||
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
|
||||
let (wrap_snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
|
||||
@@ -106,7 +104,7 @@ impl DisplayMap {
|
||||
DisplaySnapshot {
|
||||
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
|
||||
fold_snapshot,
|
||||
suggestion_snapshot,
|
||||
inlay_snapshot,
|
||||
tab_snapshot,
|
||||
wrap_snapshot,
|
||||
block_snapshot,
|
||||
@@ -132,15 +130,14 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.fold(ranges);
|
||||
let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
@@ -157,15 +154,14 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
|
||||
let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
@@ -181,8 +177,8 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
@@ -199,8 +195,8 @@ impl DisplayMap {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
@@ -231,32 +227,6 @@ impl DisplayMap {
|
||||
self.text_highlights.remove(&Some(type_id))
|
||||
}
|
||||
|
||||
pub fn has_suggestion(&self) -> bool {
|
||||
self.suggestion_map.has_suggestion()
|
||||
}
|
||||
|
||||
pub fn replace_suggestion<T>(
|
||||
&self,
|
||||
new_suggestion: Option<Suggestion<T>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Suggestion<FoldOffset>>
|
||||
where
|
||||
T: ToPoint,
|
||||
{
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits, old_suggestion) =
|
||||
self.suggestion_map.replace(new_suggestion, snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
old_suggestion
|
||||
}
|
||||
|
||||
pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
|
||||
self.wrap_map
|
||||
.update(cx, |map, cx| map.set_font(font_id, font_size, cx))
|
||||
@@ -271,6 +241,39 @@ impl DisplayMap {
|
||||
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
||||
}
|
||||
|
||||
pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
|
||||
self.inlay_map.current_inlays()
|
||||
}
|
||||
|
||||
pub fn splice_inlays(
|
||||
&mut self,
|
||||
to_remove: Vec<InlayId>,
|
||||
to_insert: Vec<Inlay>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if to_remove.is_empty() && to_insert.is_empty() {
|
||||
return;
|
||||
}
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
|
||||
let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
}
|
||||
|
||||
fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
|
||||
let language = buffer
|
||||
.read(cx)
|
||||
@@ -288,7 +291,7 @@ impl DisplayMap {
|
||||
pub struct DisplaySnapshot {
|
||||
pub buffer_snapshot: MultiBufferSnapshot,
|
||||
fold_snapshot: fold_map::FoldSnapshot,
|
||||
suggestion_snapshot: suggestion_map::SuggestionSnapshot,
|
||||
inlay_snapshot: inlay_map::InlaySnapshot,
|
||||
tab_snapshot: tab_map::TabSnapshot,
|
||||
wrap_snapshot: wrap_map::WrapSnapshot,
|
||||
block_snapshot: block_map::BlockSnapshot,
|
||||
@@ -316,9 +319,11 @@ impl DisplaySnapshot {
|
||||
|
||||
pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
||||
loop {
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Left);
|
||||
*fold_point.column_mut() = 0;
|
||||
point = fold_point.to_buffer_point(&self.fold_snapshot);
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
|
||||
fold_point.0.column = 0;
|
||||
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
point = self.inlay_snapshot.to_buffer_point(inlay_point);
|
||||
|
||||
let mut display_point = self.point_to_display_point(point, Bias::Left);
|
||||
*display_point.column_mut() = 0;
|
||||
@@ -332,9 +337,11 @@ impl DisplaySnapshot {
|
||||
|
||||
pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
||||
loop {
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Right);
|
||||
*fold_point.column_mut() = self.fold_snapshot.line_len(fold_point.row());
|
||||
point = fold_point.to_buffer_point(&self.fold_snapshot);
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
|
||||
fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
|
||||
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
point = self.inlay_snapshot.to_buffer_point(inlay_point);
|
||||
|
||||
let mut display_point = self.point_to_display_point(point, Bias::Right);
|
||||
*display_point.column_mut() = self.line_len(display_point.row());
|
||||
@@ -364,9 +371,9 @@ impl DisplaySnapshot {
|
||||
}
|
||||
|
||||
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
||||
let fold_point = self.fold_snapshot.to_fold_point(point, bias);
|
||||
let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(suggestion_point);
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
|
||||
let block_point = self.block_snapshot.to_block_point(wrap_point);
|
||||
DisplayPoint(block_point)
|
||||
@@ -376,9 +383,9 @@ impl DisplaySnapshot {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
|
||||
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
|
||||
let suggestion_point = self.tab_snapshot.to_suggestion_point(tab_point, bias).0;
|
||||
let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point);
|
||||
fold_point.to_buffer_point(&self.fold_snapshot)
|
||||
let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
self.inlay_snapshot.to_buffer_point(inlay_point)
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> DisplayPoint {
|
||||
@@ -388,7 +395,13 @@ impl DisplaySnapshot {
|
||||
/// Returns text chunks starting at the given display row until the end of the file
|
||||
pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
|
||||
self.block_snapshot
|
||||
.chunks(display_row..self.max_point().row() + 1, false, None, None)
|
||||
.chunks(
|
||||
display_row..self.max_point().row() + 1,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|h| h.text)
|
||||
}
|
||||
|
||||
@@ -396,7 +409,7 @@ impl DisplaySnapshot {
|
||||
pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
|
||||
(0..=display_row).into_iter().rev().flat_map(|row| {
|
||||
self.block_snapshot
|
||||
.chunks(row..row + 1, false, None, None)
|
||||
.chunks(row..row + 1, false, None, None, None)
|
||||
.map(|h| h.text)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
@@ -408,13 +421,15 @@ impl DisplaySnapshot {
|
||||
&self,
|
||||
display_rows: Range<u32>,
|
||||
language_aware: bool,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
hint_highlights: Option<HighlightStyle>,
|
||||
suggestion_highlights: Option<HighlightStyle>,
|
||||
) -> DisplayChunks<'_> {
|
||||
self.block_snapshot.chunks(
|
||||
display_rows,
|
||||
language_aware,
|
||||
Some(&self.text_highlights),
|
||||
suggestion_highlight,
|
||||
hint_highlights,
|
||||
suggestion_highlights,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -790,9 +805,10 @@ impl DisplayPoint {
|
||||
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
|
||||
let wrap_point = map.block_snapshot.to_wrap_point(self.0);
|
||||
let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
|
||||
let suggestion_point = map.tab_snapshot.to_suggestion_point(tab_point, bias).0;
|
||||
let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point);
|
||||
fold_point.to_buffer_offset(&map.fold_snapshot)
|
||||
let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
|
||||
map.inlay_snapshot
|
||||
.to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1706,7 +1722,7 @@ pub mod tests {
|
||||
) -> Vec<(String, Option<Color>, Option<Color>)> {
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
|
||||
for chunk in snapshot.chunks(rows, true, None) {
|
||||
for chunk in snapshot.chunks(rows, true, None, None) {
|
||||
let syntax_color = chunk
|
||||
.syntax_highlight_id
|
||||
.and_then(|id| id.style(theme)?.color);
|
||||
|
||||
@@ -573,9 +573,15 @@ impl<'a> BlockMapWriter<'a> {
|
||||
impl BlockSnapshot {
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(0..self.transforms.summary().output_rows, false, None, None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
self.chunks(
|
||||
0..self.transforms.summary().output_rows,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn chunks<'a>(
|
||||
@@ -583,7 +589,8 @@ impl BlockSnapshot {
|
||||
rows: Range<u32>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
hint_highlights: Option<HighlightStyle>,
|
||||
suggestion_highlights: Option<HighlightStyle>,
|
||||
) -> BlockChunks<'a> {
|
||||
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
||||
@@ -616,7 +623,8 @@ impl BlockSnapshot {
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
text_highlights,
|
||||
suggestion_highlight,
|
||||
hint_highlights,
|
||||
suggestion_highlights,
|
||||
),
|
||||
input_chunk: Default::default(),
|
||||
transforms: cursor,
|
||||
@@ -989,7 +997,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::suggestion_map::SuggestionMap;
|
||||
use crate::display_map::inlay_map::InlayMap;
|
||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
||||
use crate::multi_buffer::MultiBuffer;
|
||||
use gpui::{elements::Empty, Element};
|
||||
@@ -1030,9 +1038,9 @@ mod tests {
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx);
|
||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
|
||||
|
||||
@@ -1175,12 +1183,11 @@ mod tests {
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
let (fold_snapshot, fold_edits) =
|
||||
fold_map.read(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, 4.try_into().unwrap());
|
||||
tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
@@ -1205,9 +1212,9 @@ mod tests {
|
||||
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx);
|
||||
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
|
||||
|
||||
@@ -1277,9 +1284,9 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, tab_size);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) =
|
||||
WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx);
|
||||
let mut block_map = BlockMap::new(
|
||||
@@ -1332,12 +1339,11 @@ mod tests {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (fold_snapshot, fold_edits) =
|
||||
fold_map.read(buffer_snapshot.clone(), vec![]);
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size);
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
@@ -1357,12 +1363,11 @@ mod tests {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let (fold_snapshot, fold_edits) =
|
||||
fold_map.read(buffer_snapshot.clone(), vec![]);
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size);
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
@@ -1381,11 +1386,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
@@ -1499,6 +1503,7 @@ mod tests {
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect::<String>();
|
||||
|
||||
1787
crates/editor/src/display_map/inlay_map.rs
Normal file
@@ -1,871 +0,0 @@
|
||||
use super::{
|
||||
fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot},
|
||||
TextHighlights,
|
||||
};
|
||||
use crate::{MultiBufferSnapshot, ToPoint};
|
||||
use gpui::fonts::HighlightStyle;
|
||||
use language::{Bias, Chunk, Edit, Patch, Point, Rope, TextSummary};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cmp,
|
||||
ops::{Add, AddAssign, Range, Sub},
|
||||
};
|
||||
use util::post_inc;
|
||||
|
||||
pub type SuggestionEdit = Edit<SuggestionOffset>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct SuggestionOffset(pub usize);
|
||||
|
||||
impl Add for SuggestionOffset {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for SuggestionOffset {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for SuggestionOffset {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.0 += rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct SuggestionPoint(pub Point);
|
||||
|
||||
impl SuggestionPoint {
|
||||
pub fn new(row: u32, column: u32) -> Self {
|
||||
Self(Point::new(row, column))
|
||||
}
|
||||
|
||||
pub fn row(self) -> u32 {
|
||||
self.0.row
|
||||
}
|
||||
|
||||
pub fn column(self) -> u32 {
|
||||
self.0.column
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Suggestion<T> {
|
||||
pub position: T,
|
||||
pub text: Rope,
|
||||
}
|
||||
|
||||
pub struct SuggestionMap(Mutex<SuggestionSnapshot>);
|
||||
|
||||
impl SuggestionMap {
|
||||
pub fn new(fold_snapshot: FoldSnapshot) -> (Self, SuggestionSnapshot) {
|
||||
let snapshot = SuggestionSnapshot {
|
||||
fold_snapshot,
|
||||
suggestion: None,
|
||||
version: 0,
|
||||
};
|
||||
(Self(Mutex::new(snapshot.clone())), snapshot)
|
||||
}
|
||||
|
||||
pub fn replace<T>(
|
||||
&self,
|
||||
new_suggestion: Option<Suggestion<T>>,
|
||||
fold_snapshot: FoldSnapshot,
|
||||
fold_edits: Vec<FoldEdit>,
|
||||
) -> (
|
||||
SuggestionSnapshot,
|
||||
Vec<SuggestionEdit>,
|
||||
Option<Suggestion<FoldOffset>>,
|
||||
)
|
||||
where
|
||||
T: ToPoint,
|
||||
{
|
||||
let new_suggestion = new_suggestion.map(|new_suggestion| {
|
||||
let buffer_point = new_suggestion
|
||||
.position
|
||||
.to_point(fold_snapshot.buffer_snapshot());
|
||||
let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left);
|
||||
let fold_offset = fold_point.to_offset(&fold_snapshot);
|
||||
Suggestion {
|
||||
position: fold_offset,
|
||||
text: new_suggestion.text,
|
||||
}
|
||||
});
|
||||
|
||||
let (_, edits) = self.sync(fold_snapshot, fold_edits);
|
||||
let mut snapshot = self.0.lock();
|
||||
|
||||
let mut patch = Patch::new(edits);
|
||||
let old_suggestion = snapshot.suggestion.take();
|
||||
if let Some(suggestion) = &old_suggestion {
|
||||
patch = patch.compose([SuggestionEdit {
|
||||
old: SuggestionOffset(suggestion.position.0)
|
||||
..SuggestionOffset(suggestion.position.0 + suggestion.text.len()),
|
||||
new: SuggestionOffset(suggestion.position.0)
|
||||
..SuggestionOffset(suggestion.position.0),
|
||||
}]);
|
||||
}
|
||||
|
||||
if let Some(suggestion) = new_suggestion.as_ref() {
|
||||
patch = patch.compose([SuggestionEdit {
|
||||
old: SuggestionOffset(suggestion.position.0)
|
||||
..SuggestionOffset(suggestion.position.0),
|
||||
new: SuggestionOffset(suggestion.position.0)
|
||||
..SuggestionOffset(suggestion.position.0 + suggestion.text.len()),
|
||||
}]);
|
||||
}
|
||||
|
||||
snapshot.suggestion = new_suggestion;
|
||||
snapshot.version += 1;
|
||||
(snapshot.clone(), patch.into_inner(), old_suggestion)
|
||||
}
|
||||
|
||||
pub fn sync(
|
||||
&self,
|
||||
fold_snapshot: FoldSnapshot,
|
||||
fold_edits: Vec<FoldEdit>,
|
||||
) -> (SuggestionSnapshot, Vec<SuggestionEdit>) {
|
||||
let mut snapshot = self.0.lock();
|
||||
|
||||
if snapshot.fold_snapshot.version != fold_snapshot.version {
|
||||
snapshot.version += 1;
|
||||
}
|
||||
|
||||
let mut suggestion_edits = Vec::new();
|
||||
|
||||
let mut suggestion_old_len = 0;
|
||||
let mut suggestion_new_len = 0;
|
||||
for fold_edit in fold_edits {
|
||||
let start = fold_edit.new.start;
|
||||
let end = FoldOffset(start.0 + fold_edit.old_len().0);
|
||||
if let Some(suggestion) = snapshot.suggestion.as_mut() {
|
||||
if end <= suggestion.position {
|
||||
suggestion.position.0 += fold_edit.new_len().0;
|
||||
suggestion.position.0 -= fold_edit.old_len().0;
|
||||
} else if start > suggestion.position {
|
||||
suggestion_old_len = suggestion.text.len();
|
||||
suggestion_new_len = suggestion_old_len;
|
||||
} else {
|
||||
suggestion_old_len = suggestion.text.len();
|
||||
snapshot.suggestion.take();
|
||||
suggestion_edits.push(SuggestionEdit {
|
||||
old: SuggestionOffset(fold_edit.old.start.0)
|
||||
..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len),
|
||||
new: SuggestionOffset(fold_edit.new.start.0)
|
||||
..SuggestionOffset(fold_edit.new.end.0),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
suggestion_edits.push(SuggestionEdit {
|
||||
old: SuggestionOffset(fold_edit.old.start.0 + suggestion_old_len)
|
||||
..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len),
|
||||
new: SuggestionOffset(fold_edit.new.start.0 + suggestion_new_len)
|
||||
..SuggestionOffset(fold_edit.new.end.0 + suggestion_new_len),
|
||||
});
|
||||
}
|
||||
snapshot.fold_snapshot = fold_snapshot;
|
||||
|
||||
(snapshot.clone(), suggestion_edits)
|
||||
}
|
||||
|
||||
pub fn has_suggestion(&self) -> bool {
|
||||
let snapshot = self.0.lock();
|
||||
snapshot.suggestion.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SuggestionSnapshot {
|
||||
pub fold_snapshot: FoldSnapshot,
|
||||
pub suggestion: Option<Suggestion<FoldOffset>>,
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
impl SuggestionSnapshot {
|
||||
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
|
||||
self.fold_snapshot.buffer_snapshot()
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> SuggestionPoint {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_point = suggestion.position.to_point(&self.fold_snapshot);
|
||||
let mut max_point = suggestion_point.0;
|
||||
max_point += suggestion.text.max_point();
|
||||
max_point += self.fold_snapshot.max_point().0 - suggestion_point.0;
|
||||
SuggestionPoint(max_point)
|
||||
} else {
|
||||
SuggestionPoint(self.fold_snapshot.max_point().0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> SuggestionOffset {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let mut len = suggestion.position.0;
|
||||
len += suggestion.text.len();
|
||||
len += self.fold_snapshot.len().0 - suggestion.position.0;
|
||||
SuggestionOffset(len)
|
||||
} else {
|
||||
SuggestionOffset(self.fold_snapshot.len().0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
if let Some(suggestion) = &self.suggestion {
|
||||
let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
|
||||
if row < suggestion_start.row {
|
||||
self.fold_snapshot.line_len(row)
|
||||
} else if row > suggestion_end.row {
|
||||
self.fold_snapshot
|
||||
.line_len(suggestion_start.row + (row - suggestion_end.row))
|
||||
} else {
|
||||
let mut result = suggestion.text.line_len(row - suggestion_start.row);
|
||||
if row == suggestion_start.row {
|
||||
result += suggestion_start.column;
|
||||
}
|
||||
if row == suggestion_end.row {
|
||||
result +=
|
||||
self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column;
|
||||
}
|
||||
result
|
||||
}
|
||||
} else {
|
||||
self.fold_snapshot.line_len(row)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: SuggestionPoint, bias: Bias) -> SuggestionPoint {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
if point.0 <= suggestion_start {
|
||||
SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0)
|
||||
} else if point.0 > suggestion_end {
|
||||
let fold_point = self.fold_snapshot.clip_point(
|
||||
FoldPoint(suggestion_start + (point.0 - suggestion_end)),
|
||||
bias,
|
||||
);
|
||||
let suggestion_point = suggestion_end + (fold_point.0 - suggestion_start);
|
||||
if bias == Bias::Left && suggestion_point == suggestion_end {
|
||||
SuggestionPoint(suggestion_start)
|
||||
} else {
|
||||
SuggestionPoint(suggestion_point)
|
||||
}
|
||||
} else if bias == Bias::Left || suggestion_start == self.fold_snapshot.max_point().0 {
|
||||
SuggestionPoint(suggestion_start)
|
||||
} else {
|
||||
let fold_point = if self.fold_snapshot.line_len(suggestion_start.row)
|
||||
> suggestion_start.column
|
||||
{
|
||||
FoldPoint(suggestion_start + Point::new(0, 1))
|
||||
} else {
|
||||
FoldPoint(suggestion_start + Point::new(1, 0))
|
||||
};
|
||||
let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias);
|
||||
SuggestionPoint(suggestion_end + (clipped_fold_point.0 - suggestion_start))
|
||||
}
|
||||
} else {
|
||||
SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_offset(&self, point: SuggestionPoint) -> SuggestionOffset {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
|
||||
if point.0 <= suggestion_start {
|
||||
SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0)
|
||||
} else if point.0 > suggestion_end {
|
||||
let fold_offset = FoldPoint(suggestion_start + (point.0 - suggestion_end))
|
||||
.to_offset(&self.fold_snapshot);
|
||||
SuggestionOffset(fold_offset.0 + suggestion.text.len())
|
||||
} else {
|
||||
let offset_in_suggestion =
|
||||
suggestion.text.point_to_offset(point.0 - suggestion_start);
|
||||
SuggestionOffset(suggestion.position.0 + offset_in_suggestion)
|
||||
}
|
||||
} else {
|
||||
SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_point(&self, offset: SuggestionOffset) -> SuggestionPoint {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_point_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
if offset.0 <= suggestion.position.0 {
|
||||
SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0)
|
||||
} else if offset.0 > (suggestion.position.0 + suggestion.text.len()) {
|
||||
let fold_point = FoldOffset(offset.0 - suggestion.text.len())
|
||||
.to_point(&self.fold_snapshot)
|
||||
.0;
|
||||
|
||||
SuggestionPoint(
|
||||
suggestion_point_start
|
||||
+ suggestion.text.max_point()
|
||||
+ (fold_point - suggestion_point_start),
|
||||
)
|
||||
} else {
|
||||
let point_in_suggestion = suggestion
|
||||
.text
|
||||
.offset_to_point(offset.0 - suggestion.position.0);
|
||||
SuggestionPoint(suggestion_point_start + point_in_suggestion)
|
||||
}
|
||||
} else {
|
||||
SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, point: SuggestionPoint) -> FoldPoint {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
|
||||
if point.0 <= suggestion_start {
|
||||
FoldPoint(point.0)
|
||||
} else if point.0 > suggestion_end {
|
||||
FoldPoint(suggestion_start + (point.0 - suggestion_end))
|
||||
} else {
|
||||
FoldPoint(suggestion_start)
|
||||
}
|
||||
} else {
|
||||
FoldPoint(point.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_suggestion_point(&self, point: FoldPoint) -> SuggestionPoint {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
|
||||
if point.0 <= suggestion_start {
|
||||
SuggestionPoint(point.0)
|
||||
} else {
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
SuggestionPoint(suggestion_end + (point.0 - suggestion_start))
|
||||
}
|
||||
} else {
|
||||
SuggestionPoint(point.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range(&self, range: Range<SuggestionPoint>) -> TextSummary {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
let mut summary = TextSummary::default();
|
||||
|
||||
let prefix_range =
|
||||
cmp::min(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_start);
|
||||
if prefix_range.start < prefix_range.end {
|
||||
summary += self.fold_snapshot.text_summary_for_range(
|
||||
FoldPoint(prefix_range.start)..FoldPoint(prefix_range.end),
|
||||
);
|
||||
}
|
||||
|
||||
let suggestion_range =
|
||||
cmp::max(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_end);
|
||||
if suggestion_range.start < suggestion_range.end {
|
||||
let point_range = suggestion_range.start - suggestion_start
|
||||
..suggestion_range.end - suggestion_start;
|
||||
let offset_range = suggestion.text.point_to_offset(point_range.start)
|
||||
..suggestion.text.point_to_offset(point_range.end);
|
||||
summary += suggestion
|
||||
.text
|
||||
.cursor(offset_range.start)
|
||||
.summary::<TextSummary>(offset_range.end);
|
||||
}
|
||||
|
||||
let suffix_range = cmp::max(range.start.0, suggestion_end)..range.end.0;
|
||||
if suffix_range.start < suffix_range.end {
|
||||
let start = suggestion_start + (suffix_range.start - suggestion_end);
|
||||
let end = suggestion_start + (suffix_range.end - suggestion_end);
|
||||
summary += self
|
||||
.fold_snapshot
|
||||
.text_summary_for_range(FoldPoint(start)..FoldPoint(end));
|
||||
}
|
||||
|
||||
summary
|
||||
} else {
|
||||
self.fold_snapshot
|
||||
.text_summary_for_range(FoldPoint(range.start.0)..FoldPoint(range.end.0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chars_at(&self, start: SuggestionPoint) -> impl '_ + Iterator<Item = char> {
|
||||
let start = self.to_offset(start);
|
||||
self.chunks(start..self.len(), false, None, None)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
}
|
||||
|
||||
pub fn chunks<'a>(
|
||||
&'a self,
|
||||
range: Range<SuggestionOffset>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
) -> SuggestionChunks<'a> {
|
||||
if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let suggestion_range =
|
||||
suggestion.position.0..suggestion.position.0 + suggestion.text.len();
|
||||
|
||||
let prefix_chunks = if range.start.0 < suggestion_range.start {
|
||||
Some(self.fold_snapshot.chunks(
|
||||
FoldOffset(range.start.0)
|
||||
..cmp::min(FoldOffset(suggestion_range.start), FoldOffset(range.end.0)),
|
||||
language_aware,
|
||||
text_highlights,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let clipped_suggestion_range = cmp::max(range.start.0, suggestion_range.start)
|
||||
..cmp::min(range.end.0, suggestion_range.end);
|
||||
let suggestion_chunks = if clipped_suggestion_range.start < clipped_suggestion_range.end
|
||||
{
|
||||
let start = clipped_suggestion_range.start - suggestion_range.start;
|
||||
let end = clipped_suggestion_range.end - suggestion_range.start;
|
||||
Some(suggestion.text.chunks_in_range(start..end))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let suffix_chunks = if range.end.0 > suggestion_range.end {
|
||||
let start = cmp::max(suggestion_range.end, range.start.0) - suggestion_range.len();
|
||||
let end = range.end.0 - suggestion_range.len();
|
||||
Some(self.fold_snapshot.chunks(
|
||||
FoldOffset(start)..FoldOffset(end),
|
||||
language_aware,
|
||||
text_highlights,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
SuggestionChunks {
|
||||
prefix_chunks,
|
||||
suggestion_chunks,
|
||||
suffix_chunks,
|
||||
highlight_style: suggestion_highlight,
|
||||
}
|
||||
} else {
|
||||
SuggestionChunks {
|
||||
prefix_chunks: Some(self.fold_snapshot.chunks(
|
||||
FoldOffset(range.start.0)..FoldOffset(range.end.0),
|
||||
language_aware,
|
||||
text_highlights,
|
||||
)),
|
||||
suggestion_chunks: None,
|
||||
suffix_chunks: None,
|
||||
highlight_style: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_rows<'a>(&'a self, row: u32) -> SuggestionBufferRows<'a> {
|
||||
let suggestion_range = if let Some(suggestion) = self.suggestion.as_ref() {
|
||||
let start = suggestion.position.to_point(&self.fold_snapshot).0;
|
||||
let end = start + suggestion.text.max_point();
|
||||
start.row..end.row
|
||||
} else {
|
||||
u32::MAX..u32::MAX
|
||||
};
|
||||
|
||||
let fold_buffer_rows = if row <= suggestion_range.start {
|
||||
self.fold_snapshot.buffer_rows(row)
|
||||
} else if row > suggestion_range.end {
|
||||
self.fold_snapshot
|
||||
.buffer_rows(row - (suggestion_range.end - suggestion_range.start))
|
||||
} else {
|
||||
let mut rows = self.fold_snapshot.buffer_rows(suggestion_range.start);
|
||||
rows.next();
|
||||
rows
|
||||
};
|
||||
|
||||
SuggestionBufferRows {
|
||||
current_row: row,
|
||||
suggestion_row_start: suggestion_range.start,
|
||||
suggestion_row_end: suggestion_range.end,
|
||||
fold_buffer_rows,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(Default::default()..self.len(), false, None, None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SuggestionChunks<'a> {
|
||||
prefix_chunks: Option<FoldChunks<'a>>,
|
||||
suggestion_chunks: Option<text::Chunks<'a>>,
|
||||
suffix_chunks: Option<FoldChunks<'a>>,
|
||||
highlight_style: Option<HighlightStyle>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SuggestionChunks<'a> {
|
||||
type Item = Chunk<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(chunks) = self.prefix_chunks.as_mut() {
|
||||
if let Some(chunk) = chunks.next() {
|
||||
return Some(chunk);
|
||||
} else {
|
||||
self.prefix_chunks = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(chunks) = self.suggestion_chunks.as_mut() {
|
||||
if let Some(chunk) = chunks.next() {
|
||||
return Some(Chunk {
|
||||
text: chunk,
|
||||
highlight_style: self.highlight_style,
|
||||
..Default::default()
|
||||
});
|
||||
} else {
|
||||
self.suggestion_chunks = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(chunks) = self.suffix_chunks.as_mut() {
|
||||
if let Some(chunk) = chunks.next() {
|
||||
return Some(chunk);
|
||||
} else {
|
||||
self.suffix_chunks = None;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SuggestionBufferRows<'a> {
|
||||
current_row: u32,
|
||||
suggestion_row_start: u32,
|
||||
suggestion_row_end: u32,
|
||||
fold_buffer_rows: FoldBufferRows<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SuggestionBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let row = post_inc(&mut self.current_row);
|
||||
if row <= self.suggestion_row_start || row > self.suggestion_row_end {
|
||||
self.fold_buffer_rows.next()
|
||||
} else {
|
||||
Some(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{display_map::fold_map::FoldMap, MultiBuffer};
|
||||
use gpui::AppContext;
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
env,
|
||||
ops::{Bound, RangeBounds},
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_basic(cx: &mut AppContext) {
|
||||
let buffer = MultiBuffer::build_simple("abcdefghi", cx);
|
||||
let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
|
||||
let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
|
||||
assert_eq!(suggestion_snapshot.text(), "abcdefghi");
|
||||
|
||||
let (suggestion_snapshot, _, _) = suggestion_map.replace(
|
||||
Some(Suggestion {
|
||||
position: 3,
|
||||
text: "123\n456".into(),
|
||||
}),
|
||||
fold_snapshot,
|
||||
Default::default(),
|
||||
);
|
||||
assert_eq!(suggestion_snapshot.text(), "abc123\n456defghi");
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[(0..0, "ABC"), (3..3, "DEF"), (4..4, "GHI"), (9..9, "JKL")],
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
);
|
||||
let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
|
||||
assert_eq!(suggestion_snapshot.text(), "ABCabcDEF123\n456dGHIefghiJKL");
|
||||
|
||||
let (mut fold_map_writer, _, _) =
|
||||
fold_map.write(buffer.read(cx).snapshot(cx), Default::default());
|
||||
let (fold_snapshot, fold_edits) = fold_map_writer.fold([0..3]);
|
||||
let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
assert_eq!(suggestion_snapshot.text(), "⋯abcDEF123\n456dGHIefghiJKL");
|
||||
|
||||
let (mut fold_map_writer, _, _) =
|
||||
fold_map.write(buffer.read(cx).snapshot(cx), Default::default());
|
||||
let (fold_snapshot, fold_edits) = fold_map_writer.fold([6..10]);
|
||||
let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
assert_eq!(suggestion_snapshot.text(), "⋯abc⋯GHIefghiJKL");
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) {
|
||||
init_test(cx);
|
||||
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
let len = rng.gen_range(0..30);
|
||||
let buffer = if rng.gen() {
|
||||
let text = util::RandomCharIter::new(&mut rng)
|
||||
.take(len)
|
||||
.collect::<String>();
|
||||
MultiBuffer::build_simple(&text, cx)
|
||||
} else {
|
||||
MultiBuffer::build_random(&mut rng, cx)
|
||||
};
|
||||
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
log::info!("buffer text: {:?}", buffer_snapshot.text());
|
||||
|
||||
let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
|
||||
|
||||
for _ in 0..operations {
|
||||
let mut suggestion_edits = Patch::default();
|
||||
|
||||
let mut prev_suggestion_text = suggestion_snapshot.text();
|
||||
let mut buffer_edits = Vec::new();
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=29 => {
|
||||
let (_, edits) = suggestion_map.randomly_mutate(&mut rng);
|
||||
suggestion_edits = suggestion_edits.compose(edits);
|
||||
}
|
||||
30..=59 => {
|
||||
for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
|
||||
fold_snapshot = new_fold_snapshot;
|
||||
let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
|
||||
suggestion_edits = suggestion_edits.compose(edits);
|
||||
}
|
||||
}
|
||||
_ => buffer.update(cx, |buffer, cx| {
|
||||
let subscription = buffer.subscribe();
|
||||
let edit_count = rng.gen_range(1..=5);
|
||||
buffer.randomly_mutate(&mut rng, edit_count, cx);
|
||||
buffer_snapshot = buffer.snapshot(cx);
|
||||
let edits = subscription.consume().into_inner();
|
||||
log::info!("editing {:?}", edits);
|
||||
buffer_edits.extend(edits);
|
||||
}),
|
||||
};
|
||||
|
||||
let (new_fold_snapshot, fold_edits) =
|
||||
fold_map.read(buffer_snapshot.clone(), buffer_edits);
|
||||
fold_snapshot = new_fold_snapshot;
|
||||
let (new_suggestion_snapshot, edits) =
|
||||
suggestion_map.sync(fold_snapshot.clone(), fold_edits);
|
||||
suggestion_snapshot = new_suggestion_snapshot;
|
||||
suggestion_edits = suggestion_edits.compose(edits);
|
||||
|
||||
log::info!("buffer text: {:?}", buffer_snapshot.text());
|
||||
log::info!("folds text: {:?}", fold_snapshot.text());
|
||||
log::info!("suggestions text: {:?}", suggestion_snapshot.text());
|
||||
|
||||
let mut expected_text = Rope::from(fold_snapshot.text().as_str());
|
||||
let mut expected_buffer_rows = fold_snapshot.buffer_rows(0).collect::<Vec<_>>();
|
||||
if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() {
|
||||
expected_text.replace(
|
||||
suggestion.position.0..suggestion.position.0,
|
||||
&suggestion.text.to_string(),
|
||||
);
|
||||
let suggestion_start = suggestion.position.to_point(&fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
expected_buffer_rows.splice(
|
||||
(suggestion_start.row + 1) as usize..(suggestion_start.row + 1) as usize,
|
||||
(0..suggestion_end.row - suggestion_start.row).map(|_| None),
|
||||
);
|
||||
}
|
||||
assert_eq!(suggestion_snapshot.text(), expected_text.to_string());
|
||||
for row_start in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
suggestion_snapshot
|
||||
.buffer_rows(row_start as u32)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[row_start..],
|
||||
"incorrect buffer rows starting at {}",
|
||||
row_start
|
||||
);
|
||||
}
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut end = rng.gen_range(0..=suggestion_snapshot.len().0);
|
||||
end = expected_text.clip_offset(end, Bias::Right);
|
||||
let mut start = rng.gen_range(0..=end);
|
||||
start = expected_text.clip_offset(start, Bias::Right);
|
||||
|
||||
let actual_text = suggestion_snapshot
|
||||
.chunks(
|
||||
SuggestionOffset(start)..SuggestionOffset(end),
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect::<String>();
|
||||
assert_eq!(
|
||||
actual_text,
|
||||
expected_text.slice(start..end).to_string(),
|
||||
"incorrect text in range {:?}",
|
||||
start..end
|
||||
);
|
||||
|
||||
let start_point = SuggestionPoint(expected_text.offset_to_point(start));
|
||||
let end_point = SuggestionPoint(expected_text.offset_to_point(end));
|
||||
assert_eq!(
|
||||
suggestion_snapshot.text_summary_for_range(start_point..end_point),
|
||||
expected_text.slice(start..end).summary()
|
||||
);
|
||||
}
|
||||
|
||||
for edit in suggestion_edits.into_inner() {
|
||||
prev_suggestion_text.replace_range(
|
||||
edit.new.start.0..edit.new.start.0 + edit.old_len().0,
|
||||
&suggestion_snapshot.text()[edit.new.start.0..edit.new.end.0],
|
||||
);
|
||||
}
|
||||
assert_eq!(prev_suggestion_text, suggestion_snapshot.text());
|
||||
|
||||
assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0);
|
||||
assert_eq!(expected_text.len(), suggestion_snapshot.len().0);
|
||||
|
||||
let mut suggestion_point = SuggestionPoint::default();
|
||||
let mut suggestion_offset = SuggestionOffset::default();
|
||||
for ch in expected_text.chars() {
|
||||
assert_eq!(
|
||||
suggestion_snapshot.to_offset(suggestion_point),
|
||||
suggestion_offset,
|
||||
"invalid to_offset({:?})",
|
||||
suggestion_point
|
||||
);
|
||||
assert_eq!(
|
||||
suggestion_snapshot.to_point(suggestion_offset),
|
||||
suggestion_point,
|
||||
"invalid to_point({:?})",
|
||||
suggestion_offset
|
||||
);
|
||||
assert_eq!(
|
||||
suggestion_snapshot
|
||||
.to_suggestion_point(suggestion_snapshot.to_fold_point(suggestion_point)),
|
||||
suggestion_snapshot.clip_point(suggestion_point, Bias::Left),
|
||||
);
|
||||
|
||||
let mut bytes = [0; 4];
|
||||
for byte in ch.encode_utf8(&mut bytes).as_bytes() {
|
||||
suggestion_offset.0 += 1;
|
||||
if *byte == b'\n' {
|
||||
suggestion_point.0 += Point::new(1, 0);
|
||||
} else {
|
||||
suggestion_point.0 += Point::new(0, 1);
|
||||
}
|
||||
|
||||
let clipped_left_point =
|
||||
suggestion_snapshot.clip_point(suggestion_point, Bias::Left);
|
||||
let clipped_right_point =
|
||||
suggestion_snapshot.clip_point(suggestion_point, Bias::Right);
|
||||
assert!(
|
||||
clipped_left_point <= clipped_right_point,
|
||||
"clipped left point {:?} is greater than clipped right point {:?}",
|
||||
clipped_left_point,
|
||||
clipped_right_point
|
||||
);
|
||||
assert_eq!(
|
||||
clipped_left_point.0,
|
||||
expected_text.clip_point(clipped_left_point.0, Bias::Left)
|
||||
);
|
||||
assert_eq!(
|
||||
clipped_right_point.0,
|
||||
expected_text.clip_point(clipped_right_point.0, Bias::Right)
|
||||
);
|
||||
assert!(clipped_left_point <= suggestion_snapshot.max_point());
|
||||
assert!(clipped_right_point <= suggestion_snapshot.max_point());
|
||||
|
||||
if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() {
|
||||
let suggestion_start = suggestion.position.to_point(&fold_snapshot).0;
|
||||
let suggestion_end = suggestion_start + suggestion.text.max_point();
|
||||
let invalid_range = (
|
||||
Bound::Excluded(suggestion_start),
|
||||
Bound::Included(suggestion_end),
|
||||
);
|
||||
assert!(
|
||||
!invalid_range.contains(&clipped_left_point.0),
|
||||
"clipped left point {:?} is inside invalid suggestion range {:?}",
|
||||
clipped_left_point,
|
||||
invalid_range
|
||||
);
|
||||
assert!(
|
||||
!invalid_range.contains(&clipped_right_point.0),
|
||||
"clipped right point {:?} is inside invalid suggestion range {:?}",
|
||||
clipped_right_point,
|
||||
invalid_range
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut AppContext) {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
}
|
||||
|
||||
impl SuggestionMap {
|
||||
pub fn randomly_mutate(
|
||||
&self,
|
||||
rng: &mut impl Rng,
|
||||
) -> (SuggestionSnapshot, Vec<SuggestionEdit>) {
|
||||
let fold_snapshot = self.0.lock().fold_snapshot.clone();
|
||||
let new_suggestion = if rng.gen_bool(0.3) {
|
||||
None
|
||||
} else {
|
||||
let index = rng.gen_range(0..=fold_snapshot.buffer_snapshot().len());
|
||||
let len = rng.gen_range(0..30);
|
||||
Some(Suggestion {
|
||||
position: index,
|
||||
text: util::RandomCharIter::new(rng)
|
||||
.take(len)
|
||||
.filter(|ch| *ch != '\r')
|
||||
.collect::<String>()
|
||||
.as_str()
|
||||
.into(),
|
||||
})
|
||||
};
|
||||
|
||||
log::info!("replacing suggestion with {:?}", new_suggestion);
|
||||
let (snapshot, edits, _) =
|
||||
self.replace(new_suggestion, fold_snapshot, Default::default());
|
||||
(snapshot, edits)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,76 @@
|
||||
use super::{
|
||||
suggestion_map::{self, SuggestionChunks, SuggestionEdit, SuggestionPoint, SuggestionSnapshot},
|
||||
fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
|
||||
TextHighlights,
|
||||
};
|
||||
use crate::MultiBufferSnapshot;
|
||||
use gpui::fonts::HighlightStyle;
|
||||
use language::{Chunk, Point};
|
||||
use parking_lot::Mutex;
|
||||
use std::{cmp, mem, num::NonZeroU32, ops::Range};
|
||||
use sum_tree::Bias;
|
||||
|
||||
const MAX_EXPANSION_COLUMN: u32 = 256;
|
||||
|
||||
pub struct TabMap(Mutex<TabSnapshot>);
|
||||
pub struct TabMap(TabSnapshot);
|
||||
|
||||
impl TabMap {
|
||||
pub fn new(input: SuggestionSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
|
||||
pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
|
||||
let snapshot = TabSnapshot {
|
||||
suggestion_snapshot: input,
|
||||
fold_snapshot,
|
||||
tab_size,
|
||||
max_expansion_column: MAX_EXPANSION_COLUMN,
|
||||
version: 0,
|
||||
};
|
||||
(Self(Mutex::new(snapshot.clone())), snapshot)
|
||||
(Self(snapshot.clone()), snapshot)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_max_expansion_column(&self, column: u32) -> TabSnapshot {
|
||||
self.0.lock().max_expansion_column = column;
|
||||
self.0.lock().clone()
|
||||
pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot {
|
||||
self.0.max_expansion_column = column;
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
pub fn sync(
|
||||
&self,
|
||||
suggestion_snapshot: SuggestionSnapshot,
|
||||
mut suggestion_edits: Vec<SuggestionEdit>,
|
||||
&mut self,
|
||||
fold_snapshot: FoldSnapshot,
|
||||
mut fold_edits: Vec<FoldEdit>,
|
||||
tab_size: NonZeroU32,
|
||||
) -> (TabSnapshot, Vec<TabEdit>) {
|
||||
let mut old_snapshot = self.0.lock();
|
||||
let old_snapshot = &mut self.0;
|
||||
let mut new_snapshot = TabSnapshot {
|
||||
suggestion_snapshot,
|
||||
fold_snapshot,
|
||||
tab_size,
|
||||
max_expansion_column: old_snapshot.max_expansion_column,
|
||||
version: old_snapshot.version,
|
||||
};
|
||||
|
||||
if old_snapshot.suggestion_snapshot.version != new_snapshot.suggestion_snapshot.version {
|
||||
if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version {
|
||||
new_snapshot.version += 1;
|
||||
}
|
||||
|
||||
let mut tab_edits = Vec::with_capacity(suggestion_edits.len());
|
||||
let mut tab_edits = Vec::with_capacity(fold_edits.len());
|
||||
|
||||
if old_snapshot.tab_size == new_snapshot.tab_size {
|
||||
// Expand each edit to include the next tab on the same line as the edit,
|
||||
// and any subsequent tabs on that line that moved across the tab expansion
|
||||
// boundary.
|
||||
for suggestion_edit in &mut suggestion_edits {
|
||||
let old_end = old_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_point(suggestion_edit.old.end);
|
||||
let old_end_row_successor_offset =
|
||||
old_snapshot.suggestion_snapshot.to_offset(cmp::min(
|
||||
SuggestionPoint::new(old_end.row() + 1, 0),
|
||||
old_snapshot.suggestion_snapshot.max_point(),
|
||||
));
|
||||
let new_end = new_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_point(suggestion_edit.new.end);
|
||||
for fold_edit in &mut fold_edits {
|
||||
let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
|
||||
let old_end_row_successor_offset = cmp::min(
|
||||
FoldPoint::new(old_end.row() + 1, 0),
|
||||
old_snapshot.fold_snapshot.max_point(),
|
||||
)
|
||||
.to_offset(&old_snapshot.fold_snapshot);
|
||||
let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
|
||||
|
||||
let mut offset_from_edit = 0;
|
||||
let mut first_tab_offset = None;
|
||||
let mut last_tab_with_changed_expansion_offset = None;
|
||||
'outer: for chunk in old_snapshot.suggestion_snapshot.chunks(
|
||||
suggestion_edit.old.end..old_end_row_successor_offset,
|
||||
'outer: for chunk in old_snapshot.fold_snapshot.chunks(
|
||||
fold_edit.old.end..old_end_row_successor_offset,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
for (ix, _) in chunk.text.match_indices('\t') {
|
||||
let offset_from_edit = offset_from_edit + (ix as u32);
|
||||
@@ -102,39 +98,31 @@ impl TabMap {
|
||||
}
|
||||
|
||||
if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) {
|
||||
suggestion_edit.old.end.0 += offset as usize + 1;
|
||||
suggestion_edit.new.end.0 += offset as usize + 1;
|
||||
fold_edit.old.end.0 += offset as usize + 1;
|
||||
fold_edit.new.end.0 += offset as usize + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Combine any edits that overlap due to the expansion.
|
||||
let mut ix = 1;
|
||||
while ix < suggestion_edits.len() {
|
||||
let (prev_edits, next_edits) = suggestion_edits.split_at_mut(ix);
|
||||
while ix < fold_edits.len() {
|
||||
let (prev_edits, next_edits) = fold_edits.split_at_mut(ix);
|
||||
let prev_edit = prev_edits.last_mut().unwrap();
|
||||
let edit = &next_edits[0];
|
||||
if prev_edit.old.end >= edit.old.start {
|
||||
prev_edit.old.end = edit.old.end;
|
||||
prev_edit.new.end = edit.new.end;
|
||||
suggestion_edits.remove(ix);
|
||||
fold_edits.remove(ix);
|
||||
} else {
|
||||
ix += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for suggestion_edit in suggestion_edits {
|
||||
let old_start = old_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_point(suggestion_edit.old.start);
|
||||
let old_end = old_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_point(suggestion_edit.old.end);
|
||||
let new_start = new_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_point(suggestion_edit.new.start);
|
||||
let new_end = new_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_point(suggestion_edit.new.end);
|
||||
for fold_edit in fold_edits {
|
||||
let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot);
|
||||
let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
|
||||
let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
|
||||
let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
|
||||
tab_edits.push(TabEdit {
|
||||
old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end),
|
||||
new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end),
|
||||
@@ -155,7 +143,7 @@ impl TabMap {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TabSnapshot {
|
||||
pub suggestion_snapshot: SuggestionSnapshot,
|
||||
pub fold_snapshot: FoldSnapshot,
|
||||
pub tab_size: NonZeroU32,
|
||||
pub max_expansion_column: u32,
|
||||
pub version: usize,
|
||||
@@ -163,18 +151,15 @@ pub struct TabSnapshot {
|
||||
|
||||
impl TabSnapshot {
|
||||
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
|
||||
self.suggestion_snapshot.buffer_snapshot()
|
||||
&self.fold_snapshot.inlay_snapshot.buffer
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
let max_point = self.max_point();
|
||||
if row < max_point.row() {
|
||||
self.to_tab_point(SuggestionPoint::new(
|
||||
row,
|
||||
self.suggestion_snapshot.line_len(row),
|
||||
))
|
||||
.0
|
||||
.column
|
||||
self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
|
||||
.0
|
||||
.column
|
||||
} else {
|
||||
max_point.column()
|
||||
}
|
||||
@@ -185,10 +170,10 @@ impl TabSnapshot {
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
|
||||
let input_start = self.to_suggestion_point(range.start, Bias::Left).0;
|
||||
let input_end = self.to_suggestion_point(range.end, Bias::Right).0;
|
||||
let input_start = self.to_fold_point(range.start, Bias::Left).0;
|
||||
let input_end = self.to_fold_point(range.end, Bias::Right).0;
|
||||
let input_summary = self
|
||||
.suggestion_snapshot
|
||||
.fold_snapshot
|
||||
.text_summary_for_range(input_start..input_end);
|
||||
|
||||
let mut first_line_chars = 0;
|
||||
@@ -198,7 +183,7 @@ impl TabSnapshot {
|
||||
self.max_point()
|
||||
};
|
||||
for c in self
|
||||
.chunks(range.start..line_end, false, None, None)
|
||||
.chunks(range.start..line_end, false, None, None, None)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
{
|
||||
if c == '\n' {
|
||||
@@ -217,6 +202,7 @@ impl TabSnapshot {
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
{
|
||||
@@ -238,15 +224,17 @@ impl TabSnapshot {
|
||||
range: Range<TabPoint>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
hint_highlights: Option<HighlightStyle>,
|
||||
suggestion_highlights: Option<HighlightStyle>,
|
||||
) -> TabChunks<'a> {
|
||||
let (input_start, expanded_char_column, to_next_stop) =
|
||||
self.to_suggestion_point(range.start, Bias::Left);
|
||||
self.to_fold_point(range.start, Bias::Left);
|
||||
let input_column = input_start.column();
|
||||
let input_start = self.suggestion_snapshot.to_offset(input_start);
|
||||
let input_start = input_start.to_offset(&self.fold_snapshot);
|
||||
let input_end = self
|
||||
.suggestion_snapshot
|
||||
.to_offset(self.to_suggestion_point(range.end, Bias::Right).0);
|
||||
.to_fold_point(range.end, Bias::Right)
|
||||
.0
|
||||
.to_offset(&self.fold_snapshot);
|
||||
let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
|
||||
range.end.column() - range.start.column()
|
||||
} else {
|
||||
@@ -254,11 +242,12 @@ impl TabSnapshot {
|
||||
};
|
||||
|
||||
TabChunks {
|
||||
suggestion_chunks: self.suggestion_snapshot.chunks(
|
||||
fold_chunks: self.fold_snapshot.chunks(
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
text_highlights,
|
||||
suggestion_highlight,
|
||||
hint_highlights,
|
||||
suggestion_highlights,
|
||||
),
|
||||
input_column,
|
||||
column: expanded_char_column,
|
||||
@@ -275,63 +264,58 @@ impl TabSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, row: u32) -> suggestion_map::SuggestionBufferRows {
|
||||
self.suggestion_snapshot.buffer_rows(row)
|
||||
pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
|
||||
self.fold_snapshot.buffer_rows(row)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(TabPoint::zero()..self.max_point(), false, None, None)
|
||||
self.chunks(TabPoint::zero()..self.max_point(), false, None, None, None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> TabPoint {
|
||||
self.to_tab_point(self.suggestion_snapshot.max_point())
|
||||
self.to_tab_point(self.fold_snapshot.max_point())
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
|
||||
self.to_tab_point(
|
||||
self.suggestion_snapshot
|
||||
.clip_point(self.to_suggestion_point(point, bias).0, bias),
|
||||
self.fold_snapshot
|
||||
.clip_point(self.to_fold_point(point, bias).0, bias),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_tab_point(&self, input: SuggestionPoint) -> TabPoint {
|
||||
let chars = self
|
||||
.suggestion_snapshot
|
||||
.chars_at(SuggestionPoint::new(input.row(), 0));
|
||||
pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
|
||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0));
|
||||
let expanded = self.expand_tabs(chars, input.column());
|
||||
TabPoint::new(input.row(), expanded)
|
||||
}
|
||||
|
||||
pub fn to_suggestion_point(&self, output: TabPoint, bias: Bias) -> (SuggestionPoint, u32, u32) {
|
||||
let chars = self
|
||||
.suggestion_snapshot
|
||||
.chars_at(SuggestionPoint::new(output.row(), 0));
|
||||
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
|
||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
|
||||
let expanded = output.column();
|
||||
let (collapsed, expanded_char_column, to_next_stop) =
|
||||
self.collapse_tabs(chars, expanded, bias);
|
||||
(
|
||||
SuggestionPoint::new(output.row(), collapsed as u32),
|
||||
FoldPoint::new(output.row(), collapsed as u32),
|
||||
expanded_char_column,
|
||||
to_next_stop,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
let fold_point = self
|
||||
.suggestion_snapshot
|
||||
.fold_snapshot
|
||||
.to_fold_point(point, bias);
|
||||
let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point);
|
||||
self.to_tab_point(suggestion_point)
|
||||
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
self.to_tab_point(fold_point)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
||||
let suggestion_point = self.to_suggestion_point(point, bias).0;
|
||||
let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point);
|
||||
fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot)
|
||||
let fold_point = self.to_fold_point(point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
self.fold_snapshot
|
||||
.inlay_snapshot
|
||||
.to_buffer_point(inlay_point)
|
||||
}
|
||||
|
||||
fn expand_tabs(&self, chars: impl Iterator<Item = char>, column: u32) -> u32 {
|
||||
@@ -490,7 +474,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
|
||||
const SPACES: &str = " ";
|
||||
|
||||
pub struct TabChunks<'a> {
|
||||
suggestion_chunks: SuggestionChunks<'a>,
|
||||
fold_chunks: FoldChunks<'a>,
|
||||
chunk: Chunk<'a>,
|
||||
column: u32,
|
||||
max_expansion_column: u32,
|
||||
@@ -506,7 +490,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.chunk.text.is_empty() {
|
||||
if let Some(chunk) = self.suggestion_chunks.next() {
|
||||
if let Some(chunk) = self.fold_chunks.next() {
|
||||
self.chunk = chunk;
|
||||
if self.inside_leading_tab {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
@@ -574,7 +558,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
@@ -583,9 +567,9 @@ mod tests {
|
||||
fn test_expand_tabs(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0);
|
||||
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4);
|
||||
@@ -600,9 +584,9 @@ mod tests {
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
tab_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(tab_snapshot.text(), output);
|
||||
@@ -615,6 +599,7 @@ mod tests {
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>(),
|
||||
@@ -626,16 +611,16 @@ mod tests {
|
||||
let input_point = Point::new(0, ix as u32);
|
||||
let output_point = Point::new(0, output.find(c).unwrap() as u32);
|
||||
assert_eq!(
|
||||
tab_snapshot.to_tab_point(SuggestionPoint(input_point)),
|
||||
tab_snapshot.to_tab_point(FoldPoint(input_point)),
|
||||
TabPoint(output_point),
|
||||
"to_tab_point({input_point:?})"
|
||||
);
|
||||
assert_eq!(
|
||||
tab_snapshot
|
||||
.to_suggestion_point(TabPoint(output_point), Bias::Left)
|
||||
.to_fold_point(TabPoint(output_point), Bias::Left)
|
||||
.0,
|
||||
SuggestionPoint(input_point),
|
||||
"to_suggestion_point({output_point:?})"
|
||||
FoldPoint(input_point),
|
||||
"to_fold_point({output_point:?})"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -648,9 +633,9 @@ mod tests {
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
tab_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(tab_snapshot.text(), input);
|
||||
@@ -662,9 +647,9 @@ mod tests {
|
||||
|
||||
let buffer = MultiBuffer::build_simple(&input, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
assert_eq!(
|
||||
chunks(&tab_snapshot, TabPoint::zero()),
|
||||
@@ -689,7 +674,7 @@ mod tests {
|
||||
let mut chunks = Vec::new();
|
||||
let mut was_tab = false;
|
||||
let mut text = String::new();
|
||||
for chunk in snapshot.chunks(start..snapshot.max_point(), false, None, None) {
|
||||
for chunk in snapshot.chunks(start..snapshot.max_point(), false, None, None, None) {
|
||||
if chunk.is_tab != was_tab {
|
||||
if !text.is_empty() {
|
||||
chunks.push((mem::take(&mut text), was_tab));
|
||||
@@ -721,15 +706,16 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
|
||||
let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
|
||||
fold_map.randomly_mutate(&mut rng);
|
||||
let (fold_snapshot, _) = fold_map.read(buffer_snapshot, vec![]);
|
||||
let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (suggestion_map, _) = SuggestionMap::new(fold_snapshot);
|
||||
let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng);
|
||||
log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text());
|
||||
let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
|
||||
let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size);
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
|
||||
let text = text::Rope::from(tabs_snapshot.text().as_str());
|
||||
@@ -757,7 +743,7 @@ mod tests {
|
||||
let expected_summary = TextSummary::from(expected_text.as_str());
|
||||
assert_eq!(
|
||||
tabs_snapshot
|
||||
.chunks(start..end, false, None, None)
|
||||
.chunks(start..end, false, None, None, None)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>(),
|
||||
expected_text,
|
||||
@@ -767,7 +753,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
|
||||
if tab_size.get() > 1 && suggestion_snapshot.text().contains('\t') {
|
||||
if tab_size.get() > 1 && inlay_snapshot.text().contains('\t') {
|
||||
actual_summary.longest_row = expected_summary.longest_row;
|
||||
actual_summary.longest_row_chars = expected_summary.longest_row_chars;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
suggestion_map::SuggestionBufferRows,
|
||||
fold_map::FoldBufferRows,
|
||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||
TextHighlights,
|
||||
};
|
||||
@@ -65,7 +65,7 @@ pub struct WrapChunks<'a> {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WrapBufferRows<'a> {
|
||||
input_buffer_rows: SuggestionBufferRows<'a>,
|
||||
input_buffer_rows: FoldBufferRows<'a>,
|
||||
input_buffer_row: Option<u32>,
|
||||
output_row: u32,
|
||||
soft_wrapped: bool,
|
||||
@@ -446,6 +446,7 @@ impl WrapSnapshot {
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let mut edit_transforms = Vec::<Transform>::new();
|
||||
for _ in edit.new_rows.start..edit.new_rows.end {
|
||||
@@ -575,7 +576,8 @@ impl WrapSnapshot {
|
||||
rows: Range<u32>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
hint_highlights: Option<HighlightStyle>,
|
||||
suggestion_highlights: Option<HighlightStyle>,
|
||||
) -> WrapChunks<'a> {
|
||||
let output_start = WrapPoint::new(rows.start, 0);
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
@@ -593,7 +595,8 @@ impl WrapSnapshot {
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
text_highlights,
|
||||
suggestion_highlight,
|
||||
hint_highlights,
|
||||
suggestion_highlights,
|
||||
),
|
||||
input_chunk: Default::default(),
|
||||
output_position: output_start,
|
||||
@@ -757,28 +760,18 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
let text = language::Rope::from(self.text().as_str());
|
||||
let input_buffer_rows = self.buffer_snapshot().buffer_rows(0).collect::<Vec<_>>();
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut prev_fold_row = 0;
|
||||
let mut prev_tab_row = 0;
|
||||
for display_row in 0..=self.max_point().row() {
|
||||
let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
|
||||
let suggestion_point = self
|
||||
.tab_snapshot
|
||||
.to_suggestion_point(tab_point, Bias::Left)
|
||||
.0;
|
||||
let fold_point = self
|
||||
.tab_snapshot
|
||||
.suggestion_snapshot
|
||||
.to_fold_point(suggestion_point);
|
||||
if fold_point.row() == prev_fold_row && display_row != 0 {
|
||||
if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
expected_buffer_rows.push(None);
|
||||
} else {
|
||||
let buffer_point = fold_point
|
||||
.to_buffer_point(&self.tab_snapshot.suggestion_snapshot.fold_snapshot);
|
||||
expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]);
|
||||
prev_fold_row = fold_point.row();
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
}
|
||||
|
||||
prev_tab_row = tab_point.row();
|
||||
assert_eq!(self.line_len(display_row), text.line_len(display_row));
|
||||
}
|
||||
|
||||
@@ -1038,7 +1031,7 @@ fn consolidate_wrap_edits(edits: &mut Vec<WrapEdit>) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap, tab_map::TabMap},
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use gpui::test::observe;
|
||||
@@ -1089,11 +1082,11 @@ mod tests {
|
||||
});
|
||||
let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
|
||||
log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text());
|
||||
let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size);
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
@@ -1122,6 +1115,7 @@ mod tests {
|
||||
);
|
||||
log::info!("Wrapped text: {:?}", actual_text);
|
||||
|
||||
let mut next_inlay_id = 0;
|
||||
let mut edits = Vec::new();
|
||||
for _i in 0..operations {
|
||||
log::info!("{} ==============================================", _i);
|
||||
@@ -1139,10 +1133,8 @@ mod tests {
|
||||
}
|
||||
20..=39 => {
|
||||
for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size);
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
@@ -1151,10 +1143,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
40..=59 => {
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.randomly_mutate(&mut rng);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size);
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
@@ -1173,13 +1166,12 @@ mod tests {
|
||||
}
|
||||
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (suggestion_snapshot, suggestion_edits) =
|
||||
suggestion_map.sync(fold_snapshot, fold_edits);
|
||||
log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text());
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size);
|
||||
let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
let unwrapped_text = tabs_snapshot.text();
|
||||
@@ -1227,7 +1219,7 @@ mod tests {
|
||||
if tab_size.get() == 1
|
||||
|| !wrapped_snapshot
|
||||
.tab_snapshot
|
||||
.suggestion_snapshot
|
||||
.fold_snapshot
|
||||
.text()
|
||||
.contains('\t')
|
||||
{
|
||||
@@ -1328,8 +1320,14 @@ mod tests {
|
||||
}
|
||||
|
||||
pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
|
||||
self.chunks(wrap_row..self.max_point().row() + 1, false, None, None)
|
||||
.map(|h| h.text)
|
||||
self.chunks(
|
||||
wrap_row..self.max_point().row() + 1,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|h| h.text)
|
||||
}
|
||||
|
||||
fn verify_chunks(&mut self, rng: &mut impl Rng) {
|
||||
@@ -1352,7 +1350,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let actual_text = self
|
||||
.chunks(start_row..end_row, true, None, None)
|
||||
.chunks(start_row..end_row, true, None, None, None)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>();
|
||||
assert_eq!(
|
||||
|
||||
@@ -2,6 +2,7 @@ mod blink_manager;
|
||||
pub mod display_map;
|
||||
mod editor_settings;
|
||||
mod element;
|
||||
mod inlay_hint_cache;
|
||||
|
||||
mod git;
|
||||
mod highlight_matching_bracket;
|
||||
@@ -25,7 +26,7 @@ use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Result};
|
||||
use blink_manager::BlinkManager;
|
||||
use client::{ClickhouseEvent, TelemetrySettings};
|
||||
use clock::ReplicaId;
|
||||
use clock::{Global, ReplicaId};
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||
use copilot::Copilot;
|
||||
pub use display_map::DisplayPoint;
|
||||
@@ -52,11 +53,12 @@ use gpui::{
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
pub use language::{char_kind, CharKind};
|
||||
use language::{
|
||||
language_settings::{self, all_language_settings},
|
||||
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
||||
Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt,
|
||||
OffsetUtf16, Point, Selection, SelectionGoal, TransactionId,
|
||||
@@ -64,11 +66,12 @@ use language::{
|
||||
use link_go_to_definition::{
|
||||
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
||||
};
|
||||
use log::error;
|
||||
use multi_buffer::ToOffsetUtf16;
|
||||
pub use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
|
||||
ToPoint,
|
||||
};
|
||||
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||
use scroll::{
|
||||
@@ -85,12 +88,13 @@ use std::{
|
||||
cmp::{self, Ordering, Reverse},
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
ops::{ControlFlow, Deref, DerefMut, Range},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use sum_tree::Bias;
|
||||
use text::Rope;
|
||||
use theme::{DiagnosticStyle, Theme, ThemeSettings};
|
||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::{ItemNavHistory, ViewId, Workspace};
|
||||
@@ -180,6 +184,21 @@ pub struct GutterHover {
|
||||
pub hovered: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum InlayId {
|
||||
Suggestion(usize),
|
||||
Hint(usize),
|
||||
}
|
||||
|
||||
impl InlayId {
|
||||
fn id(&self) -> usize {
|
||||
match self {
|
||||
Self::Suggestion(id) => *id,
|
||||
Self::Hint(id) => *id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actions!(
|
||||
editor,
|
||||
[
|
||||
@@ -252,7 +271,9 @@ actions!(
|
||||
SelectLargerSyntaxNode,
|
||||
SelectSmallerSyntaxNode,
|
||||
GoToDefinition,
|
||||
GoToDefinitionSplit,
|
||||
GoToTypeDefinition,
|
||||
GoToTypeDefinitionSplit,
|
||||
MoveToEnclosingBracket,
|
||||
UndoSelection,
|
||||
RedoSelection,
|
||||
@@ -388,7 +409,9 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Editor::go_to_hunk);
|
||||
cx.add_action(Editor::go_to_prev_hunk);
|
||||
cx.add_action(Editor::go_to_definition);
|
||||
cx.add_action(Editor::go_to_definition_split);
|
||||
cx.add_action(Editor::go_to_type_definition);
|
||||
cx.add_action(Editor::go_to_type_definition_split);
|
||||
cx.add_action(Editor::fold);
|
||||
cx.add_action(Editor::fold_at);
|
||||
cx.add_action(Editor::unfold_lines);
|
||||
@@ -475,6 +498,7 @@ pub enum SoftWrap {
|
||||
#[derive(Clone)]
|
||||
pub struct EditorStyle {
|
||||
pub text: TextStyle,
|
||||
pub line_height_scalar: f32,
|
||||
pub placeholder_text: Option<TextStyle>,
|
||||
pub theme: theme::Editor,
|
||||
pub theme_id: usize,
|
||||
@@ -525,6 +549,7 @@ pub struct Editor {
|
||||
pending_rename: Option<RenameState>,
|
||||
searchable: bool,
|
||||
cursor_shape: CursorShape,
|
||||
collapse_matches: bool,
|
||||
workspace: Option<(WeakViewHandle<Workspace>, i64)>,
|
||||
keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
|
||||
input_enabled: bool,
|
||||
@@ -535,6 +560,8 @@ pub struct Editor {
|
||||
gutter_hovered: bool,
|
||||
link_go_to_definition_state: LinkGoToDefinitionState,
|
||||
copilot_state: CopilotState,
|
||||
inlay_hint_cache: InlayHintCache,
|
||||
next_inlay_id: usize,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -1056,6 +1083,7 @@ pub struct CopilotState {
|
||||
cycled: bool,
|
||||
completions: Vec<copilot::Completion>,
|
||||
active_completion_index: usize,
|
||||
suggestion: Option<Inlay>,
|
||||
}
|
||||
|
||||
impl Default for CopilotState {
|
||||
@@ -1067,6 +1095,7 @@ impl Default for CopilotState {
|
||||
completions: Default::default(),
|
||||
active_completion_index: 0,
|
||||
cycled: false,
|
||||
suggestion: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1181,6 +1210,14 @@ enum GotoDefinitionKind {
|
||||
Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum InlayRefreshReason {
|
||||
SettingsChange(InlayHintSettings),
|
||||
NewLinesShown,
|
||||
BufferEdited(HashSet<Arc<Language>>),
|
||||
RefreshRequested,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn single_line(
|
||||
field_editor_style: Option<Arc<GetFieldEditorTheme>>,
|
||||
@@ -1282,15 +1319,28 @@ impl Editor {
|
||||
let soft_wrap_mode_override =
|
||||
(mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
|
||||
|
||||
let mut project_subscription = None;
|
||||
if mode == EditorMode::Full && buffer.read(cx).is_singleton() {
|
||||
let mut project_subscriptions = Vec::new();
|
||||
if mode == EditorMode::Full {
|
||||
if let Some(project) = project.as_ref() {
|
||||
project_subscription = Some(cx.observe(project, |_, _, cx| {
|
||||
cx.emit(Event::TitleChanged);
|
||||
}))
|
||||
if buffer.read(cx).is_singleton() {
|
||||
project_subscriptions.push(cx.observe(project, |_, _, cx| {
|
||||
cx.emit(Event::TitleChanged);
|
||||
}));
|
||||
}
|
||||
project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
|
||||
if let project::Event::RefreshInlays = event {
|
||||
editor.refresh_inlays(InlayRefreshReason::RefreshRequested, cx);
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let inlay_hint_settings = inlay_hint_settings(
|
||||
selections.newest_anchor().head(),
|
||||
&buffer.read(cx).snapshot(cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
let mut this = Self {
|
||||
handle: cx.weak_handle(),
|
||||
buffer: buffer.clone(),
|
||||
@@ -1324,6 +1374,7 @@ impl Editor {
|
||||
.add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
|
||||
completion_tasks: Default::default(),
|
||||
next_completion_id: 0,
|
||||
next_inlay_id: 0,
|
||||
available_code_actions: Default::default(),
|
||||
code_actions_task: Default::default(),
|
||||
document_highlights_task: Default::default(),
|
||||
@@ -1331,6 +1382,7 @@ impl Editor {
|
||||
searchable: true,
|
||||
override_text_style: None,
|
||||
cursor_shape: Default::default(),
|
||||
collapse_matches: false,
|
||||
workspace: None,
|
||||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
@@ -1340,6 +1392,7 @@ impl Editor {
|
||||
hover_state: Default::default(),
|
||||
link_go_to_definition_state: Default::default(),
|
||||
copilot_state: Default::default(),
|
||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||
gutter_hovered: false,
|
||||
_subscriptions: vec![
|
||||
cx.observe(&buffer, Self::on_buffer_changed),
|
||||
@@ -1350,9 +1403,7 @@ impl Editor {
|
||||
],
|
||||
};
|
||||
|
||||
if let Some(project_subscription) = project_subscription {
|
||||
this._subscriptions.push(project_subscription);
|
||||
}
|
||||
this._subscriptions.extend(project_subscriptions);
|
||||
|
||||
this.end_selection(cx);
|
||||
this.scroll_manager.show_scrollbar(cx);
|
||||
@@ -1471,6 +1522,17 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
|
||||
self.collapse_matches = collapse_matches;
|
||||
}
|
||||
|
||||
fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
|
||||
if self.collapse_matches {
|
||||
return range.start..range.start;
|
||||
}
|
||||
range.clone()
|
||||
}
|
||||
|
||||
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
||||
if self.display_map.read(cx).clip_at_line_ends != clip {
|
||||
self.display_map
|
||||
@@ -1873,7 +1935,7 @@ impl Editor {
|
||||
s.set_pending(pending, mode);
|
||||
});
|
||||
} else {
|
||||
log::error!("update_selection dispatched with no pending selection");
|
||||
error!("update_selection dispatched with no pending selection");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1991,6 +2053,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
let mut brace_inserted = false;
|
||||
let mut edits = Vec::new();
|
||||
let mut new_selections = Vec::with_capacity(selections.len());
|
||||
let mut new_autoclose_regions = Vec::new();
|
||||
@@ -2049,6 +2112,7 @@ impl Editor {
|
||||
selection.range(),
|
||||
format!("{}{}", text, bracket_pair.end).into(),
|
||||
));
|
||||
brace_inserted = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2075,6 +2139,7 @@ impl Editor {
|
||||
selection.end..selection.end,
|
||||
bracket_pair.end.as_str().into(),
|
||||
));
|
||||
brace_inserted = true;
|
||||
new_selections.push((
|
||||
Selection {
|
||||
id: selection.id,
|
||||
@@ -2142,8 +2207,7 @@ impl Editor {
|
||||
let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||
|
||||
// When buffer contents is updated and caret is moved, try triggering on type formatting.
|
||||
if settings::get::<EditorSettings>(cx).use_on_type_format {
|
||||
if !brace_inserted && settings::get::<EditorSettings>(cx).use_on_type_format {
|
||||
if let Some(on_type_format_task) =
|
||||
this.trigger_on_type_formatting(text.to_string(), cx)
|
||||
{
|
||||
@@ -2577,6 +2641,113 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext<Self>) {
|
||||
if self.project.is_none() || self.mode != EditorMode::Full {
|
||||
return;
|
||||
}
|
||||
|
||||
let (invalidate_cache, required_languages) = match reason {
|
||||
InlayRefreshReason::SettingsChange(new_settings) => {
|
||||
match self.inlay_hint_cache.update_settings(
|
||||
&self.buffer,
|
||||
new_settings,
|
||||
self.visible_inlay_hints(cx),
|
||||
cx,
|
||||
) {
|
||||
ControlFlow::Break(Some(InlaySplice {
|
||||
to_remove,
|
||||
to_insert,
|
||||
})) => {
|
||||
self.splice_inlay_hints(to_remove, to_insert, cx);
|
||||
return;
|
||||
}
|
||||
ControlFlow::Break(None) => return,
|
||||
ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
|
||||
}
|
||||
}
|
||||
InlayRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
|
||||
InlayRefreshReason::BufferEdited(buffer_languages) => {
|
||||
(InvalidationStrategy::BufferEdited, Some(buffer_languages))
|
||||
}
|
||||
InlayRefreshReason::RefreshRequested => (InvalidationStrategy::RefreshRequested, None),
|
||||
};
|
||||
|
||||
if let Some(InlaySplice {
|
||||
to_remove,
|
||||
to_insert,
|
||||
}) = self.inlay_hint_cache.spawn_hint_refresh(
|
||||
self.excerpt_visible_offsets(required_languages.as_ref(), cx),
|
||||
invalidate_cache,
|
||||
cx,
|
||||
) {
|
||||
self.splice_inlay_hints(to_remove, to_insert, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec<Inlay> {
|
||||
self.display_map
|
||||
.read(cx)
|
||||
.current_inlays()
|
||||
.filter(move |inlay| {
|
||||
Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id)
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn excerpt_visible_offsets(
|
||||
&self,
|
||||
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
|
||||
cx: &mut ViewContext<'_, '_, Editor>,
|
||||
) -> HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)> {
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let multi_buffer_visible_start = self
|
||||
.scroll_manager
|
||||
.anchor()
|
||||
.anchor
|
||||
.to_point(&multi_buffer_snapshot);
|
||||
let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
|
||||
multi_buffer_visible_start
|
||||
+ Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
|
||||
Bias::Left,
|
||||
);
|
||||
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
|
||||
multi_buffer
|
||||
.range_to_buffer_ranges(multi_buffer_visible_range, cx)
|
||||
.into_iter()
|
||||
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
|
||||
.filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let language = buffer.language()?;
|
||||
if let Some(restrict_to_languages) = restrict_to_languages {
|
||||
if !restrict_to_languages.contains(language) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some((
|
||||
excerpt_id,
|
||||
(
|
||||
buffer_handle,
|
||||
buffer.version().clone(),
|
||||
excerpt_visible_range,
|
||||
),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn splice_inlay_hints(
|
||||
&self,
|
||||
to_remove: Vec<InlayId>,
|
||||
to_insert: Vec<Inlay>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.splice_inlays(to_remove, to_insert, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn trigger_on_type_formatting(
|
||||
&self,
|
||||
input: String,
|
||||
@@ -3227,10 +3398,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if let Some(suggestion) = self
|
||||
.display_map
|
||||
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx))
|
||||
{
|
||||
if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
|
||||
if let Some((copilot, completion)) =
|
||||
Copilot::global(cx).zip(self.copilot_state.active_completion())
|
||||
{
|
||||
@@ -3249,7 +3417,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if self.has_active_copilot_suggestion(cx) {
|
||||
if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
copilot
|
||||
.update(cx, |copilot, cx| {
|
||||
@@ -3260,8 +3428,9 @@ impl Editor {
|
||||
self.report_copilot_event(None, false, cx)
|
||||
}
|
||||
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.splice_inlays(vec![suggestion.id], Vec::new(), cx)
|
||||
});
|
||||
cx.notify();
|
||||
true
|
||||
} else {
|
||||
@@ -3282,7 +3451,26 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
|
||||
self.display_map.read(cx).has_suggestion()
|
||||
if let Some(suggestion) = self.copilot_state.suggestion.as_ref() {
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
suggestion.position.is_valid(&buffer)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
|
||||
let suggestion = self.copilot_state.suggestion.take()?;
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.splice_inlays(vec![suggestion.id], Default::default(), cx);
|
||||
});
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
|
||||
if suggestion.position.is_valid(&buffer) {
|
||||
Some(suggestion)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
|
||||
@@ -3299,14 +3487,17 @@ impl Editor {
|
||||
.copilot_state
|
||||
.text_for_active_completion(cursor, &snapshot)
|
||||
{
|
||||
let text = Rope::from(text);
|
||||
let mut to_remove = Vec::new();
|
||||
if let Some(suggestion) = self.copilot_state.suggestion.take() {
|
||||
to_remove.push(suggestion.id);
|
||||
}
|
||||
|
||||
let suggestion_inlay =
|
||||
Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
|
||||
self.copilot_state.suggestion = Some(suggestion_inlay.clone());
|
||||
self.display_map.update(cx, move |map, cx| {
|
||||
map.replace_suggestion(
|
||||
Some(Suggestion {
|
||||
position: cursor,
|
||||
text: text.trim_end().into(),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
map.splice_inlays(to_remove, vec![suggestion_inlay], cx)
|
||||
});
|
||||
cx.notify();
|
||||
} else {
|
||||
@@ -4955,7 +5146,7 @@ impl Editor {
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
selection.collapse_to(
|
||||
movement::start_of_paragraph(map, selection.head()),
|
||||
movement::start_of_paragraph(map, selection.head(), 1),
|
||||
SelectionGoal::None,
|
||||
)
|
||||
});
|
||||
@@ -4975,7 +5166,7 @@ impl Editor {
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
selection.collapse_to(
|
||||
movement::end_of_paragraph(map, selection.head()),
|
||||
movement::end_of_paragraph(map, selection.head(), 1),
|
||||
SelectionGoal::None,
|
||||
)
|
||||
});
|
||||
@@ -4994,7 +5185,10 @@ impl Editor {
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_heads_with(|map, head, _| {
|
||||
(movement::start_of_paragraph(map, head), SelectionGoal::None)
|
||||
(
|
||||
movement::start_of_paragraph(map, head, 1),
|
||||
SelectionGoal::None,
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -5011,7 +5205,10 @@ impl Editor {
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_heads_with(|map, head, _| {
|
||||
(movement::end_of_paragraph(map, head), SelectionGoal::None)
|
||||
(
|
||||
movement::end_of_paragraph(map, head, 1),
|
||||
SelectionGoal::None,
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -6010,14 +6207,31 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, cx);
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, cx);
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
|
||||
}
|
||||
|
||||
fn go_to_definition_of_kind(&mut self, kind: GotoDefinitionKind, cx: &mut ViewContext<Self>) {
|
||||
pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_type_definition_split(
|
||||
&mut self,
|
||||
_: &GoToTypeDefinitionSplit,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx);
|
||||
}
|
||||
|
||||
fn go_to_definition_of_kind(
|
||||
&mut self,
|
||||
kind: GotoDefinitionKind,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(cx) else { return };
|
||||
let buffer = self.buffer.read(cx);
|
||||
let head = self.selections.newest::<usize>(cx).head();
|
||||
@@ -6036,7 +6250,7 @@ impl Editor {
|
||||
cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move {
|
||||
let definitions = definitions.await?;
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.navigate_to_definitions(definitions, cx);
|
||||
editor.navigate_to_definitions(definitions, split, cx);
|
||||
})?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
})
|
||||
@@ -6046,6 +6260,7 @@ impl Editor {
|
||||
pub fn navigate_to_definitions(
|
||||
&mut self,
|
||||
mut definitions: Vec<LocationLink>,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(cx) else { return };
|
||||
@@ -6059,18 +6274,24 @@ impl Editor {
|
||||
.to_offset(definition.target.buffer.read(cx));
|
||||
|
||||
if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() {
|
||||
let range = self.range_for_match(&range);
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
} else {
|
||||
cx.window_context().defer(move |cx| {
|
||||
let target_editor: ViewHandle<Self> = workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_project_item(definition.target.buffer.clone(), cx)
|
||||
if split {
|
||||
workspace.split_project_item(definition.target.buffer.clone(), cx)
|
||||
} else {
|
||||
workspace.open_project_item(definition.target.buffer.clone(), cx)
|
||||
}
|
||||
});
|
||||
target_editor.update(cx, |target_editor, cx| {
|
||||
// When selecting a definition in a different buffer, disable the nav history
|
||||
// to avoid creating a history entry at the previous cursor location.
|
||||
pane.update(cx, |pane, _| pane.disable_history());
|
||||
let range = target_editor.range_for_match(&range);
|
||||
target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
@@ -6101,7 +6322,9 @@ impl Editor {
|
||||
.map(|definition| definition.target)
|
||||
.collect();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx)
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace, locations, replica_id, title, split, cx,
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -6146,7 +6369,7 @@ impl Editor {
|
||||
})
|
||||
.unwrap();
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace, locations, replica_id, title, cx,
|
||||
workspace, locations, replica_id, title, false, cx,
|
||||
);
|
||||
})?;
|
||||
|
||||
@@ -6161,6 +6384,7 @@ impl Editor {
|
||||
mut locations: Vec<Location>,
|
||||
replica_id: ReplicaId,
|
||||
title: String,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
// If there are multiple definitions, open them in a multibuffer
|
||||
@@ -6207,7 +6431,11 @@ impl Editor {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
workspace.add_item(Box::new(editor), cx);
|
||||
if split {
|
||||
workspace.split_item(Box::new(editor), cx);
|
||||
} else {
|
||||
workspace.add_item(Box::new(editor), cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||
@@ -6641,7 +6869,7 @@ impl Editor {
|
||||
if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
|
||||
*end_selections = Some(self.selections.disjoint_anchors());
|
||||
} else {
|
||||
log::error!("unexpectedly ended a transaction that wasn't started by this editor");
|
||||
error!("unexpectedly ended a transaction that wasn't started by this editor");
|
||||
}
|
||||
|
||||
cx.emit(Event::Edited);
|
||||
@@ -7048,6 +7276,47 @@ impl Editor {
|
||||
}
|
||||
results
|
||||
}
|
||||
pub fn background_highlights_in_range_for<T: 'static>(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
theme: &Theme,
|
||||
) -> Vec<(Range<DisplayPoint>, Color)> {
|
||||
let mut results = Vec::new();
|
||||
let buffer = &display_snapshot.buffer_snapshot;
|
||||
let Some((color_fetcher, ranges)) = self.background_highlights
|
||||
.get(&TypeId::of::<T>()) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let color = color_fetcher(theme);
|
||||
let start_ix = match ranges.binary_search_by(|probe| {
|
||||
let cmp = probe.end.cmp(&search_range.start, buffer);
|
||||
if cmp.is_gt() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
}) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
for range in &ranges[start_ix..] {
|
||||
if range.start.cmp(&search_range.end, buffer).is_ge() {
|
||||
break;
|
||||
}
|
||||
let start = range
|
||||
.start
|
||||
.to_point(buffer)
|
||||
.to_display_point(display_snapshot);
|
||||
let end = range
|
||||
.end
|
||||
.to_point(buffer)
|
||||
.to_display_point(display_snapshot);
|
||||
results.push((start..end, color))
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
pub fn highlight_text<T: 'static>(
|
||||
&mut self,
|
||||
@@ -7091,7 +7360,7 @@ impl Editor {
|
||||
|
||||
fn on_buffer_event(
|
||||
&mut self,
|
||||
_: ModelHandle<MultiBuffer>,
|
||||
multibuffer: ModelHandle<MultiBuffer>,
|
||||
event: &multi_buffer::Event,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
@@ -7103,6 +7372,33 @@ impl Editor {
|
||||
self.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
cx.emit(Event::BufferEdited);
|
||||
|
||||
if let Some(project) = &self.project {
|
||||
let project = project.read(cx);
|
||||
let languages_affected = multibuffer
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let language = buffer.language()?;
|
||||
if project.is_local()
|
||||
&& project.language_servers_for_buffer(buffer, cx).count() == 0
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(language)
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
if !languages_affected.is_empty() {
|
||||
self.refresh_inlays(
|
||||
InlayRefreshReason::BufferEdited(languages_affected),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
multi_buffer::Event::ExcerptsAdded {
|
||||
buffer,
|
||||
@@ -7127,7 +7423,7 @@ impl Editor {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn on_display_map_changed(&mut self, _: ModelHandle<DisplayMap>, cx: &mut ViewContext<Self>) {
|
||||
@@ -7136,6 +7432,14 @@ impl Editor {
|
||||
|
||||
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.refresh_copilot_suggestions(true, cx);
|
||||
self.refresh_inlays(
|
||||
InlayRefreshReason::SettingsChange(inlay_hint_settings(
|
||||
self.selections.newest_anchor().head(),
|
||||
&self.buffer.read(cx).snapshot(cx),
|
||||
cx,
|
||||
)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_searchable(&mut self, searchable: bool) {
|
||||
@@ -7315,7 +7619,7 @@ impl Editor {
|
||||
|
||||
fn report_editor_event(
|
||||
&self,
|
||||
name: &'static str,
|
||||
operation: &'static str,
|
||||
file_extension: Option<String>,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
@@ -7352,7 +7656,7 @@ impl Editor {
|
||||
let event = ClickhouseEvent::Editor {
|
||||
file_extension,
|
||||
vim_mode,
|
||||
operation: name,
|
||||
operation,
|
||||
copilot_enabled,
|
||||
copilot_enabled_for_language,
|
||||
};
|
||||
@@ -7425,6 +7729,23 @@ impl Editor {
|
||||
let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { return; };
|
||||
cx.write_to_clipboard(ClipboardItem::new(lines));
|
||||
}
|
||||
|
||||
pub fn inlay_hint_cache(&self) -> &InlayHintCache {
|
||||
&self.inlay_hint_cache
|
||||
}
|
||||
}
|
||||
|
||||
fn inlay_hint_settings(
|
||||
location: Anchor,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
cx: &mut ViewContext<'_, '_, Editor>,
|
||||
) -> InlayHintSettings {
|
||||
let file = snapshot.file_at(location);
|
||||
let language = snapshot.language_at(location);
|
||||
let settings = all_language_settings(file, cx);
|
||||
settings
|
||||
.language(language.map(|l| l.name()).as_deref())
|
||||
.inlay_hints
|
||||
}
|
||||
|
||||
fn consume_contiguous_rows(
|
||||
@@ -7834,7 +8155,7 @@ fn build_style(
|
||||
cx: &AppContext,
|
||||
) -> EditorStyle {
|
||||
let font_cache = cx.font_cache();
|
||||
|
||||
let line_height_scalar = settings.line_height();
|
||||
let theme_id = settings.theme.meta.id;
|
||||
let mut theme = settings.theme.editor.clone();
|
||||
let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme {
|
||||
@@ -7848,6 +8169,7 @@ fn build_style(
|
||||
EditorStyle {
|
||||
text: field_editor_theme.text,
|
||||
placeholder_text: field_editor_theme.placeholder_text,
|
||||
line_height_scalar,
|
||||
theme,
|
||||
theme_id,
|
||||
}
|
||||
@@ -7870,6 +8192,7 @@ fn build_style(
|
||||
underline: Default::default(),
|
||||
},
|
||||
placeholder_text: None,
|
||||
line_height_scalar,
|
||||
theme,
|
||||
theme_id,
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct EditorSettings {
|
||||
pub struct Scrollbar {
|
||||
pub show: ShowScrollbar,
|
||||
pub git_diff: bool,
|
||||
pub selections: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
@@ -39,6 +40,7 @@ pub struct EditorSettingsContent {
|
||||
pub struct ScrollbarContent {
|
||||
pub show: Option<ShowScrollbar>,
|
||||
pub git_diff: Option<bool>,
|
||||
pub selections: Option<bool>,
|
||||
}
|
||||
|
||||
impl Setting for EditorSettings {
|
||||
|
||||
@@ -22,7 +22,10 @@ use language::{
|
||||
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use project::project_settings::{LspSettings, ProjectSettings};
|
||||
use project::FakeFs;
|
||||
use std::sync::atomic;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
@@ -1796,7 +1799,7 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
|
||||
"});
|
||||
}
|
||||
// Ensure that comment continuations can be disabled.
|
||||
update_test_settings(cx, |settings| {
|
||||
update_test_language_settings(cx, |settings| {
|
||||
settings.defaults.extend_comment_on_newline = Some(false);
|
||||
});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
@@ -3833,7 +3836,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
|
||||
autoclose_before: "})]>".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
));
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
@@ -4546,7 +4549,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
assert!(!cx.read(|cx| editor.is_dirty(cx)));
|
||||
|
||||
// Set rust language override and assert overridden tabsize is sent to language server
|
||||
update_test_settings(cx, |settings| {
|
||||
update_test_language_settings(cx, |settings| {
|
||||
settings.languages.insert(
|
||||
"Rust".into(),
|
||||
LanguageSettingsContent {
|
||||
@@ -4660,7 +4663,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
assert!(!cx.read(|cx| editor.is_dirty(cx)));
|
||||
|
||||
// Set rust language override and assert overridden tabsize is sent to language server
|
||||
update_test_settings(cx, |settings| {
|
||||
update_test_language_settings(cx, |settings| {
|
||||
settings.languages.insert(
|
||||
"Rust".into(),
|
||||
LanguageSettingsContent {
|
||||
@@ -5380,7 +5383,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
line_comment: Some("// ".into()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
));
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
@@ -6979,6 +6982,338 @@ async fn test_copilot_disabled_globs(
|
||||
assert!(copilot_requests.try_next().is_ok());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
brackets: BracketPairConfig {
|
||||
pairs: vec![BracketPair {
|
||||
start: "{".to_string(),
|
||||
end: "}".to_string(),
|
||||
close: true,
|
||||
newline: true,
|
||||
}],
|
||||
disabled_scopes_by_bracket_ix: Vec::new(),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: "{".to_string(),
|
||||
more_trigger_character: None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { let a = 5; }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
})
|
||||
});
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/a/main.rs", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.foreground().run_until_parked();
|
||||
cx.foreground().start_waiting();
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let editor_handle = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 21),
|
||||
);
|
||||
|
||||
Ok(Some(vec![lsp::TextEdit {
|
||||
new_text: "]".to_string(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
|
||||
}]))
|
||||
});
|
||||
|
||||
editor_handle.update(cx, |editor, cx| {
|
||||
cx.focus(&editor_handle);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
|
||||
});
|
||||
editor.handle_input("{", cx);
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"fn main() { let a = {5}; }",
|
||||
"No extra braces from on type formatting should appear in the buffer"
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language_name: Arc<str> = "Rust".into();
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::clone(&language_name),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
|
||||
let server_restarts = Arc::new(AtomicUsize::new(0));
|
||||
let closure_restarts = Arc::clone(&server_restarts);
|
||||
let language_server_name = "test language server";
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: language_server_name,
|
||||
initialization_options: Some(json!({
|
||||
"testOptionValue": true
|
||||
})),
|
||||
initializer: Some(Box::new(move |fake_server| {
|
||||
let task_restarts = Arc::clone(&closure_restarts);
|
||||
fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
|
||||
task_restarts.fetch_add(1, atomic::Ordering::Release);
|
||||
futures::future::ready(Ok(()))
|
||||
});
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { let a = 5; }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let _buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/a/main.rs", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let _fake_server = fake_servers.next().await.unwrap();
|
||||
update_test_language_settings(cx, |language_settings| {
|
||||
language_settings.languages.insert(
|
||||
Arc::clone(&language_name),
|
||||
LanguageSettingsContent {
|
||||
tab_size: NonZeroU32::new(8),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
server_restarts.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Should not restart LSP server on an unrelated change"
|
||||
);
|
||||
|
||||
update_test_project_settings(cx, |project_settings| {
|
||||
project_settings.lsp.insert(
|
||||
"Some other server name".into(),
|
||||
LspSettings {
|
||||
initialization_options: Some(json!({
|
||||
"some other init value": false
|
||||
})),
|
||||
},
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
server_restarts.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Should not restart LSP server on an unrelated LSP settings change"
|
||||
);
|
||||
|
||||
update_test_project_settings(cx, |project_settings| {
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
},
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
server_restarts.load(atomic::Ordering::Acquire),
|
||||
1,
|
||||
"Should restart LSP server on a related LSP settings change"
|
||||
);
|
||||
|
||||
update_test_project_settings(cx, |project_settings| {
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
},
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
server_restarts.load(atomic::Ordering::Acquire),
|
||||
1,
|
||||
"Should not restart LSP server on a related LSP settings change that is the same"
|
||||
);
|
||||
|
||||
update_test_project_settings(cx, |project_settings| {
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
initialization_options: None,
|
||||
},
|
||||
);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
server_restarts.load(atomic::Ordering::Acquire),
|
||||
2,
|
||||
"Should restart LSP server on another related LSP settings change"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
|
||||
cx.simulate_keystroke(".");
|
||||
let completion_item = lsp::CompletionItem {
|
||||
label: "some".into(),
|
||||
kind: Some(lsp::CompletionItemKind::SNIPPET),
|
||||
detail: Some("Wrap the expression in an `Option::Some`".to_string()),
|
||||
documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
|
||||
kind: lsp::MarkupKind::Markdown,
|
||||
value: "```rust\nSome(2)\n```".to_string(),
|
||||
})),
|
||||
deprecated: Some(false),
|
||||
sort_text: Some("fffffff2".to_string()),
|
||||
filter_text: Some("some".to_string()),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 22,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 22,
|
||||
},
|
||||
},
|
||||
new_text: "Some(2)".to_string(),
|
||||
})),
|
||||
additional_text_edits: Some(vec![lsp::TextEdit {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 20,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 22,
|
||||
},
|
||||
},
|
||||
new_text: "".to_string(),
|
||||
}]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let closure_completion_item = completion_item.clone();
|
||||
let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
|
||||
let task_completion_item = closure_completion_item.clone();
|
||||
async move {
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
task_completion_item,
|
||||
])))
|
||||
}
|
||||
});
|
||||
|
||||
request.next().await;
|
||||
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
let apply_additional_edits = cx.update_editor(|editor, cx| {
|
||||
editor
|
||||
.confirm_completion(&ConfirmCompletion::default(), cx)
|
||||
.unwrap()
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
|
||||
|
||||
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
|
||||
let task_completion_item = completion_item.clone();
|
||||
async move { Ok(task_completion_item) }
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
apply_additional_edits.await.unwrap();
|
||||
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(row as u32, column as u32);
|
||||
point..point
|
||||
@@ -7098,7 +7433,7 @@ fn handle_copilot_completion_request(
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn update_test_settings(
|
||||
pub(crate) fn update_test_language_settings(
|
||||
cx: &mut TestAppContext,
|
||||
f: impl Fn(&mut AllLanguageSettingsContent),
|
||||
) {
|
||||
@@ -7109,6 +7444,17 @@ pub(crate) fn update_test_settings(
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn update_test_project_settings(
|
||||
cx: &mut TestAppContext,
|
||||
f: impl Fn(&mut ProjectSettings),
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||
store.update_user_settings::<ProjectSettings>(cx, f);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
|
||||
cx.foreground().forbid_parking();
|
||||
|
||||
@@ -7122,5 +7468,5 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
||||
crate::init(cx);
|
||||
});
|
||||
|
||||
update_test_settings(cx, f);
|
||||
update_test_language_settings(cx, f);
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@ impl EditorElement {
|
||||
event.position,
|
||||
event.cmd,
|
||||
event.shift,
|
||||
event.alt,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
@@ -308,6 +309,7 @@ impl EditorElement {
|
||||
position: Vector2F,
|
||||
cmd: bool,
|
||||
shift: bool,
|
||||
alt: bool,
|
||||
position_map: &PositionMap,
|
||||
text_bounds: RectF,
|
||||
cx: &mut EventContext<Editor>,
|
||||
@@ -324,9 +326,9 @@ impl EditorElement {
|
||||
|
||||
if point == target_point {
|
||||
if shift {
|
||||
go_to_fetched_type_definition(editor, point, cx);
|
||||
go_to_fetched_type_definition(editor, point, alt, cx);
|
||||
} else {
|
||||
go_to_fetched_definition(editor, point, cx);
|
||||
go_to_fetched_definition(editor, point, alt, cx);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1008,6 +1010,7 @@ impl EditorElement {
|
||||
bounds: RectF,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
editor: &Editor,
|
||||
) {
|
||||
enum ScrollbarMouseHandlers {}
|
||||
if layout.mode != EditorMode::Full {
|
||||
@@ -1050,9 +1053,76 @@ impl EditorElement {
|
||||
background: style.track.background_color,
|
||||
..Default::default()
|
||||
});
|
||||
let scrollbar_settings = settings::get::<EditorSettings>(cx).scrollbar;
|
||||
let theme = theme::current(cx);
|
||||
let scrollbar_theme = &theme.editor.scrollbar;
|
||||
if layout.is_singleton && scrollbar_settings.selections {
|
||||
let start_anchor = Anchor::min();
|
||||
let end_anchor = Anchor::max();
|
||||
let mut start_row = None;
|
||||
let mut end_row = None;
|
||||
let color = scrollbar_theme.selections;
|
||||
let border = Border {
|
||||
width: 1.,
|
||||
color: style.thumb.border.color,
|
||||
overlay: false,
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: true,
|
||||
};
|
||||
let mut push_region = |start, end| {
|
||||
if let (Some(start_display), Some(end_display)) = (start, end) {
|
||||
let start_y = y_for_row(start_display as f32);
|
||||
let mut end_y = y_for_row(end_display as f32);
|
||||
if end_y - start_y < 1. {
|
||||
end_y = start_y + 1.;
|
||||
}
|
||||
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
||||
|
||||
if layout.is_singleton && settings::get::<EditorSettings>(cx).scrollbar.git_diff {
|
||||
let diff_style = theme::current(cx).editor.scrollbar.git.clone();
|
||||
scene.push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
border,
|
||||
corner_radius: style.thumb.corner_radius,
|
||||
})
|
||||
}
|
||||
};
|
||||
for (row, _) in &editor
|
||||
.background_highlights_in_range_for::<crate::items::BufferSearchHighlights>(
|
||||
start_anchor..end_anchor,
|
||||
&layout.position_map.snapshot,
|
||||
&theme,
|
||||
)
|
||||
{
|
||||
let start_display = row.start;
|
||||
let end_display = row.end;
|
||||
|
||||
if start_row.is_none() {
|
||||
assert_eq!(end_row, None);
|
||||
start_row = Some(start_display.row());
|
||||
end_row = Some(end_display.row());
|
||||
continue;
|
||||
}
|
||||
if let Some(current_end) = end_row.as_mut() {
|
||||
if start_display.row() > *current_end + 1 {
|
||||
push_region(start_row, end_row);
|
||||
start_row = Some(start_display.row());
|
||||
end_row = Some(end_display.row());
|
||||
} else {
|
||||
// Merge two hunks.
|
||||
*current_end = end_display.row();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
|
||||
push_region(start_row, end_row);
|
||||
}
|
||||
|
||||
if layout.is_singleton && scrollbar_settings.git_diff {
|
||||
let diff_style = scrollbar_theme.git.clone();
|
||||
for hunk in layout
|
||||
.position_map
|
||||
.snapshot
|
||||
@@ -1114,8 +1184,10 @@ impl EditorElement {
|
||||
});
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
|
||||
.on_move(move |_, editor: &mut Editor, cx| {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
.on_move(move |event, editor: &mut Editor, cx| {
|
||||
if event.pressed_button.is_none() {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
})
|
||||
.on_down(MouseButton::Left, {
|
||||
let row_range = row_range.clone();
|
||||
@@ -1239,7 +1311,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
|
||||
let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
|
||||
let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
|
||||
let style = &self.style;
|
||||
|
||||
cx.text_layout_cache()
|
||||
@@ -1392,7 +1464,12 @@ impl EditorElement {
|
||||
} else {
|
||||
let style = &self.style;
|
||||
let chunks = snapshot
|
||||
.chunks(rows.clone(), true, Some(style.theme.suggestion))
|
||||
.chunks(
|
||||
rows.clone(),
|
||||
true,
|
||||
Some(style.theme.hint),
|
||||
Some(style.theme.suggestion),
|
||||
)
|
||||
.map(|chunk| {
|
||||
let mut highlight_style = chunk
|
||||
.syntax_highlight_id
|
||||
@@ -1900,7 +1977,7 @@ impl Element<Editor> for EditorElement {
|
||||
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let style = self.style.clone();
|
||||
let line_height = style.text.line_height(cx.font_cache());
|
||||
let line_height = (style.text.font_size * style.line_height_scalar).round();
|
||||
|
||||
let gutter_padding;
|
||||
let gutter_width;
|
||||
@@ -1921,7 +1998,7 @@ impl Element<Editor> for EditorElement {
|
||||
let em_advance = style.text.em_advance(cx.font_cache());
|
||||
let overscroll = vec2f(em_width, 0.);
|
||||
let snapshot = {
|
||||
editor.set_visible_line_count(size.y() / line_height);
|
||||
editor.set_visible_line_count(size.y() / line_height, cx);
|
||||
|
||||
let editor_width = text_width - gutter_margin - overscroll.x() - em_width;
|
||||
let wrap_width = match editor.soft_wrap_mode(cx) {
|
||||
@@ -2078,6 +2155,9 @@ impl Element<Editor> for EditorElement {
|
||||
ShowScrollbar::Auto => {
|
||||
// Git
|
||||
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
|
||||
||
|
||||
// Selections
|
||||
(is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
|
||||
// Scrollmanager
|
||||
|| editor.scroll_manager.scrollbars_visible()
|
||||
}
|
||||
@@ -2363,7 +2443,7 @@ impl Element<Editor> for EditorElement {
|
||||
if !layout.blocks.is_empty() {
|
||||
self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx);
|
||||
}
|
||||
self.paint_scrollbar(scene, bounds, layout, cx);
|
||||
self.paint_scrollbar(scene, bounds, layout, cx, &editor);
|
||||
scene.pop_layer();
|
||||
|
||||
scene.pop_layer();
|
||||
@@ -2840,7 +2920,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{BlockDisposition, BlockProperties},
|
||||
editor_tests::{init_test, update_test_settings},
|
||||
editor_tests::{init_test, update_test_language_settings},
|
||||
Editor, MultiBuffer,
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
@@ -3037,7 +3117,7 @@ mod tests {
|
||||
let resize_step = 10.0;
|
||||
let mut editor_width = 200.0;
|
||||
while editor_width <= 1000.0 {
|
||||
update_test_settings(cx, |s| {
|
||||
update_test_language_settings(cx, |s| {
|
||||
s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.preferred_line_length = Some(editor_width as u32);
|
||||
|
||||
@@ -198,7 +198,7 @@ fn show_hover(
|
||||
|
||||
// Construct new hover popover from hover request
|
||||
let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
|
||||
if hover_result.contents.is_empty() {
|
||||
if hover_result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ fn render_blocks(
|
||||
|
||||
RenderedInfo {
|
||||
theme_id,
|
||||
text,
|
||||
text: text.trim().to_string(),
|
||||
highlights,
|
||||
region_ranges,
|
||||
regions,
|
||||
@@ -816,6 +816,118 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Hover with keyboard has no delay
|
||||
cx.set_state(indoc! {"
|
||||
fˇn test() { println!(); }
|
||||
"});
|
||||
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
«fn» test() { println!(); }
|
||||
"});
|
||||
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
|
||||
Ok(Some(lsp::Hover {
|
||||
contents: lsp::HoverContents::Array(vec![
|
||||
lsp::MarkedString::String("regular text for hover to show".to_string()),
|
||||
lsp::MarkedString::String("".to_string()),
|
||||
lsp::MarkedString::LanguageString(lsp::LanguageString {
|
||||
language: "Rust".to_string(),
|
||||
value: "".to_string(),
|
||||
}),
|
||||
]),
|
||||
range: Some(symbol_range),
|
||||
}))
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||
cx.editor(|editor, _| {
|
||||
assert_eq!(
|
||||
editor.hover_state.info_popover.clone().unwrap().blocks,
|
||||
vec![HoverBlock {
|
||||
text: "regular text for hover to show".to_string(),
|
||||
kind: HoverBlockKind::Markdown,
|
||||
}],
|
||||
"No empty string hovers should be shown"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Hover with keyboard has no delay
|
||||
cx.set_state(indoc! {"
|
||||
fˇn test() { println!(); }
|
||||
"});
|
||||
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
«fn» test() { println!(); }
|
||||
"});
|
||||
|
||||
let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
|
||||
let markdown_string = format!("\n```rust\n{code_str}```");
|
||||
|
||||
let closure_markdown_string = markdown_string.clone();
|
||||
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
|
||||
let future_markdown_string = closure_markdown_string.clone();
|
||||
async move {
|
||||
Ok(Some(lsp::Hover {
|
||||
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
|
||||
kind: lsp::MarkupKind::Markdown,
|
||||
value: future_markdown_string,
|
||||
}),
|
||||
range: Some(symbol_range),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||
cx.editor(|editor, cx| {
|
||||
let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
|
||||
assert_eq!(
|
||||
blocks,
|
||||
vec![HoverBlock {
|
||||
text: markdown_string,
|
||||
kind: HoverBlockKind::Markdown,
|
||||
}],
|
||||
);
|
||||
|
||||
let style = editor.style(cx);
|
||||
let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
|
||||
assert_eq!(
|
||||
rendered.text,
|
||||
code_str.trim(),
|
||||
"Should not have extra line breaks at end of rendered hover"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
2618
crates/editor/src/inlay_hint_cache.rs
Normal file
@@ -883,14 +883,24 @@ impl ProjectItem for Editor {
|
||||
}
|
||||
}
|
||||
|
||||
enum BufferSearchHighlights {}
|
||||
pub(crate) enum BufferSearchHighlights {}
|
||||
impl SearchableItem for Editor {
|
||||
type Match = Range<Anchor>;
|
||||
|
||||
fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
|
||||
fn to_search_event(
|
||||
&mut self,
|
||||
event: &Self::Event,
|
||||
_: &mut ViewContext<Self>,
|
||||
) -> Option<SearchEvent> {
|
||||
match event {
|
||||
Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
|
||||
Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged),
|
||||
Event::SelectionsChanged { .. } => {
|
||||
if self.selections.disjoint_anchors().len() == 1 {
|
||||
Some(SearchEvent::ActiveMatchChanged)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -936,46 +946,68 @@ impl SearchableItem for Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.unfold_ranges([matches[index].clone()], false, true, cx);
|
||||
let range = self.range_for_match(&matches[index]);
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([matches[index].clone()])
|
||||
});
|
||||
s.select_ranges([range]);
|
||||
})
|
||||
}
|
||||
|
||||
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
|
||||
self.unfold_ranges(matches.clone(), false, false, cx);
|
||||
let mut ranges = Vec::new();
|
||||
for m in &matches {
|
||||
ranges.push(self.range_for_match(&m))
|
||||
}
|
||||
self.change_selections(None, cx, |s| s.select_ranges(ranges));
|
||||
}
|
||||
|
||||
fn match_index_for_direction(
|
||||
&mut self,
|
||||
matches: &Vec<Range<Anchor>>,
|
||||
mut current_index: usize,
|
||||
current_index: usize,
|
||||
direction: Direction,
|
||||
count: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> usize {
|
||||
let buffer = self.buffer().read(cx).snapshot(cx);
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
if matches[current_index].start.cmp(&cursor, &buffer).is_gt() {
|
||||
if direction == Direction::Prev {
|
||||
if current_index == 0 {
|
||||
current_index = matches.len() - 1;
|
||||
let current_index_position = if self.selections.disjoint_anchors().len() == 1 {
|
||||
self.selections.newest_anchor().head()
|
||||
} else {
|
||||
matches[current_index].start
|
||||
};
|
||||
|
||||
let mut count = count % matches.len();
|
||||
if count == 0 {
|
||||
return current_index;
|
||||
}
|
||||
match direction {
|
||||
Direction::Next => {
|
||||
if matches[current_index]
|
||||
.start
|
||||
.cmp(¤t_index_position, &buffer)
|
||||
.is_gt()
|
||||
{
|
||||
count = count - 1
|
||||
}
|
||||
|
||||
(current_index + count) % matches.len()
|
||||
}
|
||||
Direction::Prev => {
|
||||
if matches[current_index]
|
||||
.end
|
||||
.cmp(¤t_index_position, &buffer)
|
||||
.is_lt()
|
||||
{
|
||||
count = count - 1;
|
||||
}
|
||||
|
||||
if current_index >= count {
|
||||
current_index - count
|
||||
} else {
|
||||
current_index -= 1;
|
||||
matches.len() - (count - current_index)
|
||||
}
|
||||
}
|
||||
} else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() {
|
||||
if direction == Direction::Next {
|
||||
current_index = 0;
|
||||
}
|
||||
} else if direction == Direction::Prev {
|
||||
if current_index == 0 {
|
||||
current_index = matches.len() - 1;
|
||||
} else {
|
||||
current_index -= 1;
|
||||
}
|
||||
} else if direction == Direction::Next {
|
||||
if current_index == matches.len() - 1 {
|
||||
current_index = 0
|
||||
} else {
|
||||
current_index += 1;
|
||||
}
|
||||
};
|
||||
current_index
|
||||
}
|
||||
}
|
||||
|
||||
fn find_matches(
|
||||
|
||||
@@ -246,23 +246,26 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
pub fn go_to_fetched_definition(
|
||||
editor: &mut Editor,
|
||||
point: DisplayPoint,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, cx);
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_fetched_type_definition(
|
||||
editor: &mut Editor,
|
||||
point: DisplayPoint,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, cx);
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx);
|
||||
}
|
||||
|
||||
fn go_to_fetched_definition_of_kind(
|
||||
kind: LinkDefinitionKind,
|
||||
editor: &mut Editor,
|
||||
point: DisplayPoint,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let cached_definitions = editor.link_go_to_definition_state.definitions.clone();
|
||||
@@ -275,7 +278,7 @@ fn go_to_fetched_definition_of_kind(
|
||||
cx.focus_self();
|
||||
}
|
||||
|
||||
editor.navigate_to_definitions(cached_definitions, cx);
|
||||
editor.navigate_to_definitions(cached_definitions, split, cx);
|
||||
} else {
|
||||
editor.select(
|
||||
SelectPhase::Begin {
|
||||
@@ -403,7 +406,7 @@ mod tests {
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
go_to_fetched_type_definition(editor, hover_point, cx);
|
||||
go_to_fetched_type_definition(editor, hover_point, false, cx);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
@@ -614,7 +617,7 @@ mod tests {
|
||||
|
||||
// Cmd click with existing definition doesn't re-request and dismisses highlight
|
||||
cx.update_editor(|editor, cx| {
|
||||
go_to_fetched_definition(editor, hover_point, cx);
|
||||
go_to_fetched_definition(editor, hover_point, false, cx);
|
||||
});
|
||||
// Assert selection moved to to definition
|
||||
cx.lsp
|
||||
@@ -655,7 +658,7 @@ mod tests {
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
go_to_fetched_definition(editor, hover_point, cx);
|
||||
go_to_fetched_definition(editor, hover_point, false, cx);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
@@ -193,7 +193,11 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||
pub fn start_of_paragraph(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
mut count: usize,
|
||||
) -> DisplayPoint {
|
||||
let point = display_point.to_point(map);
|
||||
if point.row == 0 {
|
||||
return map.max_point();
|
||||
@@ -203,7 +207,11 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) ->
|
||||
for row in (0..point.row + 1).rev() {
|
||||
let blank = map.buffer_snapshot.is_line_blank(row);
|
||||
if found_non_blank_line && blank {
|
||||
return Point::new(row, 0).to_display_point(map);
|
||||
if count <= 1 {
|
||||
return Point::new(row, 0).to_display_point(map);
|
||||
}
|
||||
count -= 1;
|
||||
found_non_blank_line = false;
|
||||
}
|
||||
|
||||
found_non_blank_line |= !blank;
|
||||
@@ -212,7 +220,11 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) ->
|
||||
DisplayPoint::zero()
|
||||
}
|
||||
|
||||
pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||
pub fn end_of_paragraph(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
mut count: usize,
|
||||
) -> DisplayPoint {
|
||||
let point = display_point.to_point(map);
|
||||
if point.row == map.max_buffer_row() {
|
||||
return DisplayPoint::zero();
|
||||
@@ -222,7 +234,11 @@ pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> D
|
||||
for row in point.row..map.max_buffer_row() + 1 {
|
||||
let blank = map.buffer_snapshot.is_line_blank(row);
|
||||
if found_non_blank_line && blank {
|
||||
return Point::new(row, 0).to_display_point(map);
|
||||
if count <= 1 {
|
||||
return Point::new(row, 0).to_display_point(map);
|
||||
}
|
||||
count -= 1;
|
||||
found_non_blank_line = false;
|
||||
}
|
||||
|
||||
found_non_blank_line |= !blank;
|
||||
@@ -263,13 +279,13 @@ pub fn find_preceding_boundary(
|
||||
|
||||
if let Some((prev_ch, prev_point)) = prev {
|
||||
if is_boundary(ch, prev_ch) {
|
||||
return prev_point;
|
||||
return map.clip_point(prev_point, Bias::Left);
|
||||
}
|
||||
}
|
||||
|
||||
prev = Some((ch, point));
|
||||
}
|
||||
DisplayPoint::zero()
|
||||
map.clip_point(DisplayPoint::zero(), Bias::Left)
|
||||
}
|
||||
|
||||
/// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the
|
||||
@@ -292,7 +308,7 @@ pub fn find_preceding_boundary_in_line(
|
||||
for (ch, point) in map.reverse_chars_at(from) {
|
||||
if let Some((prev_ch, prev_point)) = prev {
|
||||
if is_boundary(ch, prev_ch) {
|
||||
return prev_point;
|
||||
return map.clip_point(prev_point, Bias::Left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +319,7 @@ pub fn find_preceding_boundary_in_line(
|
||||
prev = Some((ch, point));
|
||||
}
|
||||
|
||||
prev.map(|(_, point)| point).unwrap_or(from)
|
||||
map.clip_point(prev.map(|(_, point)| point).unwrap_or(from), Bias::Left)
|
||||
}
|
||||
|
||||
/// Scans for a boundary following the given start point until a boundary is found, indicated by the
|
||||
@@ -406,8 +422,12 @@ pub fn split_display_range_by_lines(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
|
||||
use crate::{
|
||||
display_map::Inlay, test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange,
|
||||
InlayId, MultiBuffer,
|
||||
};
|
||||
use settings::SettingsStore;
|
||||
use util::post_inc;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_previous_word_start(cx: &mut gpui::AppContext) {
|
||||
@@ -505,6 +525,80 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let input_text = "abcdefghijklmnopqrstuvwxys";
|
||||
let family_id = cx
|
||||
.font_cache()
|
||||
.load_family(&["Helvetica"], &Default::default())
|
||||
.unwrap();
|
||||
let font_id = cx
|
||||
.font_cache()
|
||||
.select_font(family_id, &Default::default())
|
||||
.unwrap();
|
||||
let font_size = 14.0;
|
||||
let buffer = MultiBuffer::build_simple(input_text, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let display_map =
|
||||
cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
|
||||
|
||||
// add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
|
||||
let mut id = 0;
|
||||
let inlays = (0..buffer_snapshot.len())
|
||||
.map(|offset| {
|
||||
[
|
||||
Inlay {
|
||||
id: InlayId::Suggestion(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Suggestion(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
let snapshot = display_map.update(cx, |map, cx| {
|
||||
map.splice_inlays(Vec::new(), inlays, cx);
|
||||
map.snapshot(cx)
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
find_preceding_boundary(
|
||||
&snapshot,
|
||||
buffer_snapshot.len().to_display_point(&snapshot),
|
||||
|left, _| left == 'a',
|
||||
),
|
||||
0.to_display_point(&snapshot),
|
||||
"Should not stop at inlays when looking for boundaries"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_preceding_boundary_in_line(
|
||||
&snapshot,
|
||||
buffer_snapshot.len().to_display_point(&snapshot),
|
||||
|left, _| left == 'a',
|
||||
),
|
||||
0.to_display_point(&snapshot),
|
||||
"Should not stop at inlays when looking for boundaries in line"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_next_word_end(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -49,6 +49,10 @@ impl Anchor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bias(&self) -> Bias {
|
||||
self.text_anchor.bias
|
||||
}
|
||||
|
||||
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.text_anchor.bias != Bias::Left {
|
||||
if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
@@ -81,6 +85,19 @@ impl Anchor {
|
||||
{
|
||||
snapshot.summary_for_anchor(self)
|
||||
}
|
||||
|
||||
pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
|
||||
if *self == Anchor::min() || *self == Anchor::max() {
|
||||
true
|
||||
} else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
excerpt.contains(self)
|
||||
&& (self.text_anchor == excerpt.range.context.start
|
||||
|| self.text_anchor == excerpt.range.context.end
|
||||
|| self.text_anchor.is_valid(&excerpt.buffer))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for Anchor {
|
||||
|
||||
@@ -13,13 +13,14 @@ use gpui::{
|
||||
};
|
||||
use language::{Bias, Point};
|
||||
use util::ResultExt;
|
||||
use workspace::WorkspaceId;
|
||||
use workspace::{item::Item, WorkspaceId};
|
||||
|
||||
use crate::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
hover_popover::hide_hover,
|
||||
persistence::DB,
|
||||
Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
|
||||
Anchor, DisplayPoint, Editor, EditorMode, Event, InlayRefreshReason, MultiBufferSnapshot,
|
||||
ToPoint,
|
||||
};
|
||||
|
||||
use self::{
|
||||
@@ -293,8 +294,19 @@ impl Editor {
|
||||
self.scroll_manager.visible_line_count
|
||||
}
|
||||
|
||||
pub(crate) fn set_visible_line_count(&mut self, lines: f32) {
|
||||
self.scroll_manager.visible_line_count = Some(lines)
|
||||
pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
|
||||
let opened_first_time = self.scroll_manager.visible_line_count.is_none();
|
||||
self.scroll_manager.visible_line_count = Some(lines);
|
||||
if opened_first_time {
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
editor.refresh_inlays(InlayRefreshReason::NewLinesShown, cx)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
|
||||
@@ -320,6 +332,10 @@ impl Editor {
|
||||
workspace_id,
|
||||
cx,
|
||||
);
|
||||
|
||||
if !self.is_singleton(cx) {
|
||||
self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
||||
|
||||
@@ -16,13 +16,13 @@ use crate::{
|
||||
Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingSelection {
|
||||
pub selection: Selection<Anchor>,
|
||||
pub mode: SelectMode,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SelectionsCollection {
|
||||
display_map: ModelHandle<DisplayMap>,
|
||||
buffer: ModelHandle<MultiBuffer>,
|
||||
|
||||
@@ -210,6 +210,10 @@ impl<'a> EditorTestContext<'a> {
|
||||
self.assert_selections(expected_selections, marked_text.to_string())
|
||||
}
|
||||
|
||||
pub fn editor_state(&mut self) -> String {
|
||||
generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
|
||||
let expected_ranges = self.ranges(marked_text);
|
||||
@@ -248,14 +252,8 @@ impl<'a> EditorTestContext<'a> {
|
||||
self.assert_selections(expected_selections, expected_marked_text)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_selections(
|
||||
&mut self,
|
||||
expected_selections: Vec<Range<usize>>,
|
||||
expected_marked_text: String,
|
||||
) {
|
||||
let actual_selections = self
|
||||
.editor
|
||||
fn editor_selections(&self) -> Vec<Range<usize>> {
|
||||
self.editor
|
||||
.read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
@@ -265,12 +263,22 @@ impl<'a> EditorTestContext<'a> {
|
||||
s.start..s.end
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_selections(
|
||||
&mut self,
|
||||
expected_selections: Vec<Range<usize>>,
|
||||
expected_marked_text: String,
|
||||
) {
|
||||
let actual_selections = self.editor_selections();
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
if expected_selections != actual_selections {
|
||||
panic!(
|
||||
indoc! {"
|
||||
|
||||
{}Editor has unexpected selections.
|
||||
|
||||
Expected selections:
|
||||
|
||||
@@ -60,6 +60,7 @@ pub(crate) struct FeedbackEditor {
|
||||
system_specs: SystemSpecs,
|
||||
editor: ViewHandle<Editor>,
|
||||
project: ModelHandle<Project>,
|
||||
pub allow_submission: bool,
|
||||
}
|
||||
|
||||
impl FeedbackEditor {
|
||||
@@ -82,10 +83,15 @@ impl FeedbackEditor {
|
||||
system_specs: system_specs.clone(),
|
||||
editor,
|
||||
project,
|
||||
allow_submission: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submit(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
|
||||
if !self.allow_submission {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let feedback_text = self.editor.read(cx).text(cx);
|
||||
let feedback_char_count = feedback_text.chars().count();
|
||||
let feedback_text = feedback_text.trim().to_string();
|
||||
@@ -122,19 +128,26 @@ impl FeedbackEditor {
|
||||
let answer = answer.recv().await;
|
||||
|
||||
if answer == Some(0) {
|
||||
this.update(&mut cx, |feedback_editor, cx| {
|
||||
feedback_editor.set_allow_submission(false, cx);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
|
||||
Ok(_) => {
|
||||
this.update(&mut cx, |_, cx| cx.emit(editor::Event::Closed))
|
||||
.log_err();
|
||||
}
|
||||
|
||||
Err(error) => {
|
||||
log::error!("{}", error);
|
||||
this.update(&mut cx, |_, cx| {
|
||||
this.update(&mut cx, |feedback_editor, cx| {
|
||||
cx.prompt(
|
||||
PromptLevel::Critical,
|
||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||
&["OK"],
|
||||
);
|
||||
feedback_editor.set_allow_submission(true, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
@@ -146,6 +159,11 @@ impl FeedbackEditor {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn set_allow_submission(&mut self, allow_submission: bool, cx: &mut ViewContext<Self>) {
|
||||
self.allow_submission = allow_submission;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
async fn submit_feedback(
|
||||
feedback_text: &str,
|
||||
zed_client: Arc<Client>,
|
||||
@@ -362,8 +380,13 @@ impl Item for FeedbackEditor {
|
||||
impl SearchableItem for FeedbackEditor {
|
||||
type Match = Range<Anchor>;
|
||||
|
||||
fn to_search_event(event: &Self::Event) -> Option<workspace::searchable::SearchEvent> {
|
||||
Editor::to_search_event(event)
|
||||
fn to_search_event(
|
||||
&mut self,
|
||||
event: &Self::Event,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<workspace::searchable::SearchEvent> {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.to_search_event(event, cx))
|
||||
}
|
||||
|
||||
fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
|
||||
@@ -391,6 +414,11 @@ impl SearchableItem for FeedbackEditor {
|
||||
.update(cx, |editor, cx| editor.activate_match(index, matches, cx))
|
||||
}
|
||||
|
||||
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
|
||||
self.editor
|
||||
.update(cx, |e, cx| e.select_matches(matches, cx))
|
||||
}
|
||||
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
|
||||
@@ -46,10 +46,28 @@ impl View for SubmitFeedbackButton {
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = theme::current(cx).clone();
|
||||
let allow_submission = self
|
||||
.active_item
|
||||
.as_ref()
|
||||
.map_or(true, |i| i.read(cx).allow_submission);
|
||||
|
||||
enum SubmitFeedbackButton {}
|
||||
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.feedback.submit_button.style_for(state);
|
||||
Label::new("Submit as Markdown", style.text.clone())
|
||||
let text;
|
||||
let style = if allow_submission {
|
||||
text = "Submit as Markdown";
|
||||
theme.feedback.submit_button.style_for(state)
|
||||
} else {
|
||||
text = "Submitting...";
|
||||
theme
|
||||
.feedback
|
||||
.submit_button
|
||||
.disabled
|
||||
.as_ref()
|
||||
.unwrap_or(&theme.feedback.submit_button.default)
|
||||
};
|
||||
|
||||
Label::new(text, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
})
|
||||
|
||||
@@ -442,53 +442,71 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) {
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<FileFinder>) {
|
||||
if let Some(m) = self.matches.get(self.selected_index()) {
|
||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
let open_task = workspace.update(cx, |workspace, cx| match m {
|
||||
Match::History(history_match) => {
|
||||
let worktree_id = history_match.project.worktree_id;
|
||||
if workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some()
|
||||
{
|
||||
workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&history_match.project.path),
|
||||
},
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
let open_task = workspace.update(cx, move |workspace, cx| {
|
||||
let split_or_open = |workspace: &mut Workspace, project_path, cx| {
|
||||
if secondary {
|
||||
workspace.split_path(project_path, cx)
|
||||
} else {
|
||||
match history_match.absolute.as_ref() {
|
||||
Some(abs_path) => {
|
||||
workspace.open_abs_path(abs_path.to_path_buf(), false, cx)
|
||||
}
|
||||
None => workspace.open_path(
|
||||
workspace.open_path(project_path, None, true, cx)
|
||||
}
|
||||
};
|
||||
match m {
|
||||
Match::History(history_match) => {
|
||||
let worktree_id = history_match.project.worktree_id;
|
||||
if workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some()
|
||||
{
|
||||
split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&history_match.project.path),
|
||||
},
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
match history_match.absolute.as_ref() {
|
||||
Some(abs_path) => {
|
||||
if secondary {
|
||||
workspace.split_abs_path(
|
||||
abs_path.to_path_buf(),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
workspace.open_abs_path(
|
||||
abs_path.to_path_buf(),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
None => split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&history_match.project.path),
|
||||
},
|
||||
cx,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Match::Search(m) => split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(m.worktree_id),
|
||||
path: m.path.clone(),
|
||||
},
|
||||
cx,
|
||||
),
|
||||
}
|
||||
Match::Search(m) => workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(m.worktree_id),
|
||||
path: m.path.clone(),
|
||||
},
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
),
|
||||
});
|
||||
|
||||
let row = self
|
||||
|
||||
@@ -31,6 +31,7 @@ serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
log.workspace = true
|
||||
libc = "0.2"
|
||||
time.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
|
||||
@@ -279,6 +279,9 @@ impl Fs for RealFs {
|
||||
|
||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||
let buffer_size = text.summary().len.min(10 * 1024);
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
let file = smol::fs::File::create(path).await?;
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
|
||||
for chunk in chunks(text, line_ending) {
|
||||
@@ -1077,6 +1080,9 @@ impl Fs for FakeFs {
|
||||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path);
|
||||
let content = chunks(text, line_ending).collect();
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
self.write_file_internal(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git2::ErrorCode;
|
||||
use git2::{BranchType, ErrorCode};
|
||||
use parking_lot::Mutex;
|
||||
use rpc::proto;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
@@ -16,6 +16,12 @@ use util::ResultExt;
|
||||
|
||||
pub use git2::Repository as LibGitRepository;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct Branch {
|
||||
pub name: Box<str>,
|
||||
/// Timestamp of most recent commit, normalized to Unix Epoch format.
|
||||
pub unix_timestamp: Option<i64>,
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
pub trait GitRepository: Send {
|
||||
fn reload_index(&self);
|
||||
@@ -27,6 +33,16 @@ pub trait GitRepository: Send {
|
||||
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
|
||||
|
||||
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn change_branch(&self, _: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn create_branch(&self, _: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn GitRepository {
|
||||
@@ -106,6 +122,46 @@ impl GitRepository for LibGitRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
let local_branches = self.branches(Some(BranchType::Local))?;
|
||||
let valid_branches = local_branches
|
||||
.filter_map(|branch| {
|
||||
branch.ok().and_then(|(branch, _)| {
|
||||
let name = branch.name().ok().flatten().map(Box::from)?;
|
||||
let timestamp = branch.get().peel_to_commit().ok()?.time();
|
||||
let unix_timestamp = timestamp.seconds();
|
||||
let timezone_offset = timestamp.offset_minutes();
|
||||
let utc_offset =
|
||||
time::UtcOffset::from_whole_seconds(timezone_offset * 60).ok()?;
|
||||
let unix_timestamp =
|
||||
time::OffsetDateTime::from_unix_timestamp(unix_timestamp).ok()?;
|
||||
Some(Branch {
|
||||
name,
|
||||
unix_timestamp: Some(unix_timestamp.to_offset(utc_offset).unix_timestamp()),
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Ok(valid_branches)
|
||||
}
|
||||
fn change_branch(&self, name: &str) -> Result<()> {
|
||||
let revision = self.find_branch(name, BranchType::Local)?;
|
||||
let revision = revision.get();
|
||||
let as_tree = revision.peel_to_tree()?;
|
||||
self.checkout_tree(as_tree.as_object(), None)?;
|
||||
self.set_head(
|
||||
revision
|
||||
.name()
|
||||
.ok_or_else(|| anyhow::anyhow!("Branch name could not be retrieved"))?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
fn create_branch(&self, name: &str) -> Result<()> {
|
||||
let current_commit = self.head()?.peel_to_commit()?;
|
||||
self.branch(name, ¤t_commit, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_status(status: git2::Status) -> Option<GitFileStatus> {
|
||||
|
||||
@@ -24,6 +24,7 @@ pub struct GoToLine {
|
||||
prev_scroll_position: Option<Vector2F>,
|
||||
cursor_point: Point,
|
||||
max_point: Point,
|
||||
has_focus: bool,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
@@ -57,6 +58,7 @@ impl GoToLine {
|
||||
prev_scroll_position: scroll_position,
|
||||
cursor_point,
|
||||
max_point,
|
||||
has_focus: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,11 +180,20 @@ impl View for GoToLine {
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
self.has_focus = true;
|
||||
cx.focus(&self.line_editor);
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
|
||||
self.has_focus = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Modal for GoToLine {
|
||||
fn has_focus(&self) -> bool {
|
||||
self.has_focus
|
||||
}
|
||||
|
||||
fn dismiss_on_event(event: &Self::Event) -> bool {
|
||||
matches!(event, Event::Dismissed)
|
||||
}
|
||||
|
||||
@@ -1073,7 +1073,7 @@ impl AppContext {
|
||||
|
||||
pub fn is_action_available(&self, action: &dyn Action) -> bool {
|
||||
let mut available_in_window = false;
|
||||
let action_type = action.as_any().type_id();
|
||||
let action_id = action.id();
|
||||
if let Some(window_id) = self.platform.main_window_id() {
|
||||
available_in_window = self
|
||||
.read_window(window_id, |cx| {
|
||||
@@ -1083,7 +1083,7 @@ impl AppContext {
|
||||
cx.views_metadata.get(&(window_id, view_id))
|
||||
{
|
||||
if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
|
||||
if actions.contains_key(&action_type) {
|
||||
if actions.contains_key(&action_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1094,7 +1094,7 @@ impl AppContext {
|
||||
})
|
||||
.unwrap_or(false);
|
||||
}
|
||||
available_in_window || self.global_actions.contains_key(&action_type)
|
||||
available_in_window || self.global_actions.contains_key(&action_id)
|
||||
}
|
||||
|
||||
fn actions_mut(
|
||||
@@ -2971,14 +2971,12 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, handle: &AnyViewHandle) {
|
||||
self.window_context
|
||||
.focus(handle.window_id, Some(handle.view_id));
|
||||
self.window_context.focus(Some(handle.view_id));
|
||||
}
|
||||
|
||||
pub fn focus_self(&mut self) {
|
||||
let window_id = self.window_id;
|
||||
let view_id = self.view_id;
|
||||
self.window_context.focus(window_id, Some(view_id));
|
||||
self.window_context.focus(Some(view_id));
|
||||
}
|
||||
|
||||
pub fn is_self_focused(&self) -> bool {
|
||||
@@ -2997,8 +2995,7 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
||||
}
|
||||
|
||||
pub fn blur(&mut self) {
|
||||
let window_id = self.window_id;
|
||||
self.window_context.focus(window_id, None);
|
||||
self.window_context.focus(None);
|
||||
}
|
||||
|
||||
pub fn on_window_should_close<F>(&mut self, mut callback: F)
|
||||
@@ -3304,11 +3301,15 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
||||
let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
|
||||
MouseState {
|
||||
hovered: self.window.hovered_region_ids.contains(®ion_id),
|
||||
clicked: self
|
||||
.window
|
||||
.clicked_region_ids
|
||||
.get(®ion_id)
|
||||
.and_then(|_| self.window.clicked_button),
|
||||
clicked: if let Some((clicked_region_id, button)) = self.window.clicked_region {
|
||||
if region_id == clicked_region_id {
|
||||
Some(button)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
accessed_hovered: false,
|
||||
accessed_clicked: false,
|
||||
}
|
||||
@@ -3398,7 +3399,7 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
||||
for (i, view_id) in self.ancestors(view_id).enumerate() {
|
||||
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
|
||||
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||
if actions.contains_key(&action.as_any().type_id()) {
|
||||
if actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(i);
|
||||
}
|
||||
}
|
||||
@@ -3406,12 +3407,12 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.global_actions.contains_key(&action.as_any().type_id()) {
|
||||
if self.global_actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(contexts.len())
|
||||
}
|
||||
|
||||
self.keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.bindings_for_action(action.id())
|
||||
.find_map(|b| {
|
||||
let highest_handler = handler_depth?;
|
||||
if action.eq(b.action())
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::{
|
||||
MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
|
||||
},
|
||||
scene::{
|
||||
CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
|
||||
MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
|
||||
CursorRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseEvent,
|
||||
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
|
||||
},
|
||||
text_layout::TextLayoutCache,
|
||||
util::post_inc,
|
||||
@@ -53,7 +53,7 @@ pub struct Window {
|
||||
last_mouse_moved_event: Option<Event>,
|
||||
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
|
||||
pub(crate) clicked_region_ids: HashSet<MouseRegionId>,
|
||||
pub(crate) clicked_button: Option<MouseButton>,
|
||||
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
|
||||
mouse_position: Vector2F,
|
||||
text_layout_cache: TextLayoutCache,
|
||||
}
|
||||
@@ -86,7 +86,7 @@ impl Window {
|
||||
last_mouse_moved_event: None,
|
||||
hovered_region_ids: Default::default(),
|
||||
clicked_region_ids: Default::default(),
|
||||
clicked_button: None,
|
||||
clicked_region: None,
|
||||
mouse_position: vec2f(0., 0.),
|
||||
titlebar_height,
|
||||
appearance,
|
||||
@@ -363,17 +363,13 @@ impl<'a> WindowContext<'a> {
|
||||
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
|
||||
let window_id = self.window_id;
|
||||
let mut contexts = Vec::new();
|
||||
let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
|
||||
let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
|
||||
for (depth, view_id) in self.ancestors(view_id).enumerate() {
|
||||
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
|
||||
contexts.push(view_metadata.keymap_context.clone());
|
||||
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||
handler_depths_by_action_type.extend(
|
||||
actions
|
||||
.keys()
|
||||
.copied()
|
||||
.map(|action_type| (action_type, depth)),
|
||||
);
|
||||
handler_depths_by_action_id
|
||||
.extend(actions.keys().copied().map(|action_id| (action_id, depth)));
|
||||
}
|
||||
} else {
|
||||
log::error!(
|
||||
@@ -383,21 +379,21 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
handler_depths_by_action_type.extend(
|
||||
handler_depths_by_action_id.extend(
|
||||
self.global_actions
|
||||
.keys()
|
||||
.copied()
|
||||
.map(|action_type| (action_type, contexts.len())),
|
||||
.map(|action_id| (action_id, contexts.len())),
|
||||
);
|
||||
|
||||
self.action_deserializers
|
||||
.iter()
|
||||
.filter_map(move |(name, (type_id, deserialize))| {
|
||||
if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
|
||||
.filter_map(move |(name, (action_id, deserialize))| {
|
||||
if let Some(action_depth) = handler_depths_by_action_id.get(action_id).copied() {
|
||||
let action = deserialize(serde_json::Value::Object(Default::default())).ok()?;
|
||||
let bindings = self
|
||||
.keystroke_matcher
|
||||
.bindings_for_action_type(*type_id)
|
||||
.bindings_for_action(*action_id)
|
||||
.filter(|b| {
|
||||
action.eq(b.action())
|
||||
&& (0..=action_depth)
|
||||
@@ -480,8 +476,8 @@ impl<'a> WindowContext<'a> {
|
||||
// specific ancestor element that contained both [positions]'
|
||||
// So we need to store the overlapping regions on mouse down.
|
||||
|
||||
// If there is already clicked_button stored, don't replace it.
|
||||
if self.window.clicked_button.is_none() {
|
||||
// If there is already region being clicked, don't replace it.
|
||||
if self.window.clicked_region.is_none() {
|
||||
self.window.clicked_region_ids = self
|
||||
.window
|
||||
.mouse_regions
|
||||
@@ -495,7 +491,17 @@ impl<'a> WindowContext<'a> {
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.window.clicked_button = Some(e.button);
|
||||
let mut highest_z_index = 0;
|
||||
let mut clicked_region_id = None;
|
||||
for (region, z_index) in self.window.mouse_regions.iter() {
|
||||
if region.bounds.contains_point(e.position) && *z_index >= highest_z_index {
|
||||
highest_z_index = *z_index;
|
||||
clicked_region_id = Some(region.id());
|
||||
}
|
||||
}
|
||||
|
||||
self.window.clicked_region =
|
||||
clicked_region_id.map(|region_id| (region_id, e.button));
|
||||
}
|
||||
|
||||
mouse_events.push(MouseEvent::Down(MouseDown {
|
||||
@@ -524,6 +530,10 @@ impl<'a> WindowContext<'a> {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
}));
|
||||
mouse_events.push(MouseEvent::ClickOut(MouseClickOut {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
Event::MouseMoved(
|
||||
@@ -556,7 +566,7 @@ impl<'a> WindowContext<'a> {
|
||||
prev_mouse_position: self.window.mouse_position,
|
||||
platform_event: e.clone(),
|
||||
}));
|
||||
} else if let Some(clicked_button) = self.window.clicked_button {
|
||||
} else if let Some((_, clicked_button)) = self.window.clicked_region {
|
||||
// Mouse up event happened outside the current window. Simulate mouse up button event
|
||||
let button_event = e.to_button_event(clicked_button);
|
||||
mouse_events.push(MouseEvent::Up(MouseUp {
|
||||
@@ -679,8 +689,8 @@ impl<'a> WindowContext<'a> {
|
||||
// Only raise click events if the released button is the same as the one stored
|
||||
if self
|
||||
.window
|
||||
.clicked_button
|
||||
.map(|clicked_button| clicked_button == e.button)
|
||||
.clicked_region
|
||||
.map(|(_, clicked_button)| clicked_button == e.button)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// Clear clicked regions and clicked button
|
||||
@@ -688,7 +698,7 @@ impl<'a> WindowContext<'a> {
|
||||
&mut self.window.clicked_region_ids,
|
||||
Default::default(),
|
||||
);
|
||||
self.window.clicked_button = None;
|
||||
self.window.clicked_region = None;
|
||||
|
||||
// Find regions which still overlap with the mouse since the last MouseDown happened
|
||||
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
|
||||
@@ -712,7 +722,10 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => {
|
||||
MouseEvent::MoveOut(_)
|
||||
| MouseEvent::UpOut(_)
|
||||
| MouseEvent::DownOut(_)
|
||||
| MouseEvent::ClickOut(_) => {
|
||||
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
|
||||
// NOT contains
|
||||
if !mouse_region
|
||||
@@ -860,18 +873,10 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
for view_id in &invalidation.updated {
|
||||
let titlebar_height = self.window.titlebar_height;
|
||||
let hovered_region_ids = self.window.hovered_region_ids.clone();
|
||||
let clicked_region_ids = self
|
||||
.window
|
||||
.clicked_button
|
||||
.map(|button| (self.window.clicked_region_ids.clone(), button));
|
||||
|
||||
let element = self
|
||||
.render_view(RenderParams {
|
||||
view_id: *view_id,
|
||||
titlebar_height,
|
||||
hovered_region_ids,
|
||||
clicked_region_ids,
|
||||
refreshing: false,
|
||||
appearance,
|
||||
})
|
||||
@@ -1085,6 +1090,10 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.focused_view_id
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, view_id: Option<usize>) {
|
||||
self.app_context.focus(self.window_id, view_id);
|
||||
}
|
||||
|
||||
pub fn window_bounds(&self) -> WindowBounds {
|
||||
self.window.platform_window.bounds()
|
||||
}
|
||||
@@ -1176,8 +1185,6 @@ impl<'a> WindowContext<'a> {
|
||||
pub struct RenderParams {
|
||||
pub view_id: usize,
|
||||
pub titlebar_height: f32,
|
||||
pub hovered_region_ids: HashSet<MouseRegionId>,
|
||||
pub clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
|
||||
pub refreshing: bool,
|
||||
pub appearance: Appearance,
|
||||
}
|
||||
@@ -1253,6 +1260,19 @@ impl Vector2FExt for Vector2F {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RectFExt {
|
||||
fn length_along(self, axis: Axis) -> f32;
|
||||
}
|
||||
|
||||
impl RectFExt for RectF {
|
||||
fn length_along(self, axis: Axis) -> f32 {
|
||||
match axis {
|
||||
Axis::Horizontal => self.width(),
|
||||
Axis::Vertical => self.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SizeConstraint {
|
||||
pub min: Vector2F,
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::{
|
||||
platform::CursorStyle,
|
||||
platform::MouseButton,
|
||||
scene::{
|
||||
CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
|
||||
MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
|
||||
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
|
||||
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
|
||||
},
|
||||
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
|
||||
SizeConstraint, View, ViewContext,
|
||||
@@ -136,6 +136,15 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseClickOut, &mut V, &mut EventContext<V>) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
|
||||
@@ -27,7 +27,9 @@ pub mod json;
|
||||
pub mod keymap_matcher;
|
||||
pub mod platform;
|
||||
pub use gpui_macros::{test, Element};
|
||||
pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
|
||||
pub use window::{Axis, RectFExt, SizeConstraint, Vector2FExt, WindowContext};
|
||||
|
||||
pub use anyhow;
|
||||
pub use serde_json;
|
||||
|
||||
actions!(zed, [NoAction]);
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{any::TypeId, fmt::Debug};
|
||||
use collections::HashMap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::Action;
|
||||
use crate::{Action, NoAction};
|
||||
|
||||
pub use binding::{Binding, BindingMatchResult};
|
||||
pub use keymap::Keymap;
|
||||
@@ -47,8 +47,8 @@ impl KeymapMatcher {
|
||||
self.keymap.clear();
|
||||
}
|
||||
|
||||
pub fn bindings_for_action_type(&self, action_type: TypeId) -> impl Iterator<Item = &Binding> {
|
||||
self.keymap.bindings_for_action_type(action_type)
|
||||
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
|
||||
self.keymap.bindings_for_action(action_id)
|
||||
}
|
||||
|
||||
pub fn clear_pending(&mut self) {
|
||||
@@ -81,6 +81,7 @@ impl KeymapMatcher {
|
||||
// The key is the reverse position of the binding in the bindings list so that later bindings
|
||||
// match before earlier ones in the user's config
|
||||
let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
|
||||
let no_action_id = (NoAction {}).id();
|
||||
|
||||
let first_keystroke = self.pending_keystrokes.is_empty();
|
||||
self.pending_keystrokes.push(keystroke.clone());
|
||||
@@ -108,7 +109,9 @@ impl KeymapMatcher {
|
||||
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
|
||||
{
|
||||
BindingMatchResult::Complete(action) => {
|
||||
matched_bindings.push((*view_id, action));
|
||||
if action.id() != no_action_id {
|
||||
matched_bindings.push((*view_id, action));
|
||||
}
|
||||
}
|
||||
BindingMatchResult::Partial => {
|
||||
self.pending_views
|
||||
|
||||
@@ -7,8 +7,8 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke};
|
||||
|
||||
pub struct Binding {
|
||||
action: Box<dyn Action>,
|
||||
keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
context_predicate: Option<KeymapContextPredicate>,
|
||||
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
pub(super) context_predicate: Option<KeymapContextPredicate>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Binding {
|
||||
|
||||
@@ -1,61 +1,388 @@
|
||||
use collections::HashSet;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
};
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
||||
use super::Binding;
|
||||
use crate::{Action, NoAction};
|
||||
|
||||
use super::{Binding, KeymapContextPredicate, Keystroke};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Keymap {
|
||||
bindings: Vec<Binding>,
|
||||
binding_indices_by_action_type: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||
disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn new(bindings: Vec<Binding>) -> Self {
|
||||
let mut binding_indices_by_action_type = HashMap::new();
|
||||
for (ix, binding) in bindings.iter().enumerate() {
|
||||
binding_indices_by_action_type
|
||||
.entry(binding.action().type_id())
|
||||
.or_insert_with(SmallVec::new)
|
||||
.push(ix);
|
||||
}
|
||||
|
||||
Self {
|
||||
binding_indices_by_action_type,
|
||||
bindings,
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(super) fn new(bindings: Vec<Binding>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.add_bindings(bindings);
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_for_action_type(
|
||||
pub(crate) fn bindings_for_action(
|
||||
&self,
|
||||
action_type: TypeId,
|
||||
action_id: TypeId,
|
||||
) -> impl Iterator<Item = &'_ Binding> {
|
||||
self.binding_indices_by_action_type
|
||||
.get(&action_type)
|
||||
self.binding_indices_by_action_id
|
||||
.get(&action_id)
|
||||
.map(SmallVec::as_slice)
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|ix| &self.bindings[*ix])
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
}
|
||||
|
||||
pub(crate) fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
|
||||
let no_action_id = (NoAction {}).id();
|
||||
let mut new_bindings = Vec::new();
|
||||
let mut has_new_disabled_keystrokes = false;
|
||||
for binding in bindings {
|
||||
self.binding_indices_by_action_type
|
||||
.entry(binding.action().as_any().type_id())
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(binding);
|
||||
if binding.action().id() == no_action_id {
|
||||
has_new_disabled_keystrokes |= self
|
||||
.disabled_keystrokes
|
||||
.entry(binding.keystrokes)
|
||||
.or_default()
|
||||
.insert(binding.context_predicate);
|
||||
} else {
|
||||
new_bindings.push(binding);
|
||||
}
|
||||
}
|
||||
|
||||
if has_new_disabled_keystrokes {
|
||||
self.binding_indices_by_action_id.retain(|_, indices| {
|
||||
indices.retain(|ix| {
|
||||
let binding = &self.bindings[*ix];
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => {
|
||||
!disabled_predicates.contains(&binding.context_predicate)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
});
|
||||
!indices.is_empty()
|
||||
});
|
||||
}
|
||||
|
||||
for new_binding in new_bindings {
|
||||
if !self.binding_disabled(&new_binding) {
|
||||
self.binding_indices_by_action_id
|
||||
.entry(new_binding.action().id())
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(new_binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_type.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
self.disabled_keystrokes.clear();
|
||||
}
|
||||
|
||||
pub fn bindings(&self) -> &Vec<Binding> {
|
||||
&self.bindings
|
||||
pub fn bindings(&self) -> Vec<&Binding> {
|
||||
self.bindings
|
||||
.iter()
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn binding_disabled(&self, binding: &Binding) -> bool {
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::actions;
|
||||
|
||||
use super::*;
|
||||
|
||||
actions!(
|
||||
keymap_test,
|
||||
[Present1, Present2, Present3, Duplicate, Missing]
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn regular_keymap() {
|
||||
let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let missing = Binding::new("ctrl-r", Missing {}, None);
|
||||
let all_bindings = [
|
||||
&present_1,
|
||||
&present_2,
|
||||
&present_3,
|
||||
&keystroke_duplicate_to_1,
|
||||
&full_duplicate_to_2,
|
||||
&missing,
|
||||
];
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
assert_absent(&keymap, &all_bindings);
|
||||
assert!(keymap.bindings().is_empty());
|
||||
|
||||
keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
|
||||
assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
|
||||
assert_present(
|
||||
&keymap,
|
||||
&[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
|
||||
);
|
||||
|
||||
keymap.add_bindings([
|
||||
keystroke_duplicate_to_1.clone(),
|
||||
full_duplicate_to_2.clone(),
|
||||
]);
|
||||
assert_absent(&keymap, &[&missing]);
|
||||
assert!(
|
||||
!keymap.binding_disabled(&keystroke_duplicate_to_1),
|
||||
"Duplicate binding 1 was added and should not be disabled"
|
||||
);
|
||||
assert!(
|
||||
!keymap.binding_disabled(&full_duplicate_to_2),
|
||||
"Duplicate binding 2 was added and should not be disabled"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
keymap
|
||||
.bindings_for_action(keystroke_duplicate_to_1.action().id())
|
||||
.map(|binding| &binding.keystrokes)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "q".to_string()
|
||||
}],
|
||||
"{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap
|
||||
.bindings_for_action(full_duplicate_to_2.action().id())
|
||||
.map(|binding| &binding.keystrokes)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "w".to_string()
|
||||
},
|
||||
&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "w".to_string()
|
||||
}
|
||||
],
|
||||
"{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
|
||||
);
|
||||
|
||||
let updated_bindings = keymap.bindings();
|
||||
let expected_updated_bindings = vec![
|
||||
&present_1,
|
||||
&present_2,
|
||||
&present_3,
|
||||
&keystroke_duplicate_to_1,
|
||||
&full_duplicate_to_2,
|
||||
];
|
||||
assert_eq!(
|
||||
updated_bindings.len(),
|
||||
expected_updated_bindings.len(),
|
||||
"Unexpected updated keymap bindings {updated_bindings:?}"
|
||||
);
|
||||
for (i, expected) in expected_updated_bindings.iter().enumerate() {
|
||||
let keymap_binding = &updated_bindings[i];
|
||||
assert_eq!(
|
||||
keymap_binding.context_predicate, expected.context_predicate,
|
||||
"Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap_binding.keystrokes, expected.keystrokes,
|
||||
"Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
}
|
||||
|
||||
keymap.clear();
|
||||
assert_absent(&keymap, &all_bindings);
|
||||
assert!(keymap.bindings().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keymap_with_ignored() {
|
||||
let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
|
||||
let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
|
||||
let ignored_3_with_other_context =
|
||||
Binding::new("ctrl-e", NoAction {}, Some("other_context"));
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
|
||||
keymap.add_bindings([
|
||||
ignored_1.clone(),
|
||||
ignored_2.clone(),
|
||||
ignored_3_with_other_context.clone(),
|
||||
]);
|
||||
assert_absent(&keymap, &[&present_3]);
|
||||
assert_disabled(
|
||||
&keymap,
|
||||
&[
|
||||
&present_1,
|
||||
&present_2,
|
||||
&ignored_1,
|
||||
&ignored_2,
|
||||
&ignored_3_with_other_context,
|
||||
],
|
||||
);
|
||||
assert!(keymap.bindings().is_empty());
|
||||
keymap.clear();
|
||||
|
||||
keymap.add_bindings([
|
||||
present_1.clone(),
|
||||
present_2.clone(),
|
||||
present_3.clone(),
|
||||
ignored_1.clone(),
|
||||
ignored_2.clone(),
|
||||
ignored_3_with_other_context.clone(),
|
||||
]);
|
||||
assert_present(&keymap, &[(&present_3, "e")]);
|
||||
assert_disabled(
|
||||
&keymap,
|
||||
&[
|
||||
&present_1,
|
||||
&present_2,
|
||||
&ignored_1,
|
||||
&ignored_2,
|
||||
&ignored_3_with_other_context,
|
||||
],
|
||||
);
|
||||
keymap.clear();
|
||||
|
||||
keymap.add_bindings([
|
||||
present_1.clone(),
|
||||
present_2.clone(),
|
||||
present_3.clone(),
|
||||
ignored_1.clone(),
|
||||
]);
|
||||
assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
|
||||
assert_disabled(&keymap, &[&present_1, &ignored_1]);
|
||||
assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
|
||||
keymap.clear();
|
||||
|
||||
keymap.add_bindings([
|
||||
present_1.clone(),
|
||||
present_2.clone(),
|
||||
present_3.clone(),
|
||||
keystroke_duplicate_to_1.clone(),
|
||||
full_duplicate_to_2.clone(),
|
||||
ignored_1.clone(),
|
||||
ignored_2.clone(),
|
||||
ignored_3_with_other_context.clone(),
|
||||
]);
|
||||
assert_present(&keymap, &[(&present_3, "e")]);
|
||||
assert_disabled(
|
||||
&keymap,
|
||||
&[
|
||||
&present_1,
|
||||
&present_2,
|
||||
&keystroke_duplicate_to_1,
|
||||
&full_duplicate_to_2,
|
||||
&ignored_1,
|
||||
&ignored_2,
|
||||
&ignored_3_with_other_context,
|
||||
],
|
||||
);
|
||||
keymap.clear();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
|
||||
let keymap_bindings = keymap.bindings();
|
||||
assert_eq!(
|
||||
expected_bindings.len(),
|
||||
keymap_bindings.len(),
|
||||
"Unexpected keymap bindings {keymap_bindings:?}"
|
||||
);
|
||||
for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
|
||||
assert!(
|
||||
!keymap.binding_disabled(expected),
|
||||
"{expected:?} should not be disabled as it was added into keymap for element {i}"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap
|
||||
.bindings_for_action(expected.action().id())
|
||||
.map(|binding| &binding.keystrokes)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: expected_key.to_string()
|
||||
}],
|
||||
"{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
|
||||
);
|
||||
|
||||
let keymap_binding = &keymap_bindings[i];
|
||||
assert_eq!(
|
||||
keymap_binding.context_predicate, expected.context_predicate,
|
||||
"Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap_binding.keystrokes, expected.keystrokes,
|
||||
"Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
for binding in bindings.iter() {
|
||||
assert!(
|
||||
!keymap.binding_disabled(binding),
|
||||
"{binding:?} should not be disabled in the keymap where was not added"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap.bindings_for_action(binding.action().id()).count(),
|
||||
0,
|
||||
"{binding:?} should have no actions in the keymap where was not added"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
for binding in bindings.iter() {
|
||||
assert!(
|
||||
keymap.binding_disabled(binding),
|
||||
"{binding:?} should be disabled in the keymap"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap.bindings_for_action(binding.action().id()).count(),
|
||||
0,
|
||||
"{binding:?} should have no actions in the keymap where it was disabled"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl KeymapContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum KeymapContextPredicate {
|
||||
Identifier(String),
|
||||
Equal(String, String),
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt::Write;
|
||||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Keystroke {
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
|
||||
@@ -4,7 +4,7 @@ use pathfinder_geometry::vector::vec2f;
|
||||
|
||||
use crate::{geometry::vector::Vector2F, keymap_matcher::Keystroke};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct KeyDownEvent {
|
||||
pub keystroke: Keystroke,
|
||||
pub is_held: bool,
|
||||
|
||||