Compare commits
377 Commits
v0.80.6
...
v0.83.1-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aee54c96e3 | ||
|
|
d290b1c83f | ||
|
|
f6b953c884 | ||
|
|
65f7228fed | ||
|
|
b414d43ee3 | ||
|
|
ad71020990 | ||
|
|
21bb13d309 | ||
|
|
32c57bcd22 | ||
|
|
960a2bc589 | ||
|
|
0ebe44bfd5 | ||
|
|
4dd917c123 | ||
|
|
c5f86bc6af | ||
|
|
9e2949e7ba | ||
|
|
c59204c5e6 | ||
|
|
26abc824a9 | ||
|
|
df94aee758 | ||
|
|
6156dbced0 | ||
|
|
bb4de47b15 | ||
|
|
2a5c0fa5f8 | ||
|
|
6e68ff5a50 | ||
|
|
ba7233f265 | ||
|
|
c1daf0fc36 | ||
|
|
ad8162fc9c | ||
|
|
f5bbb41cc2 | ||
|
|
5c8b41dd54 | ||
|
|
21e39e7523 | ||
|
|
370875b1d4 | ||
|
|
eca93c124a | ||
|
|
bed76462e2 | ||
|
|
df71a9cfae | ||
|
|
4151bd39da | ||
|
|
4d207981ae | ||
|
|
5d57167302 | ||
|
|
4c3d6c854a | ||
|
|
b9a7b70e52 | ||
|
|
34bcf6f072 | ||
|
|
672cf6b8c7 | ||
|
|
ce8442a3d8 | ||
|
|
dd73233973 | ||
|
|
26ab774b7f | ||
|
|
f16b96cafc | ||
|
|
9b8a3e4de5 | ||
|
|
2882e0fa5b | ||
|
|
745e5e3a09 | ||
|
|
70ff4ca48f | ||
|
|
ea1c3fa7a0 | ||
|
|
8610f3acf3 | ||
|
|
0326a45a91 | ||
|
|
54a78d7024 | ||
|
|
4a9989fe38 | ||
|
|
1fd07b6fcf | ||
|
|
699b2060b3 | ||
|
|
b3b8f8532d | ||
|
|
f9c60b98c0 | ||
|
|
27a6bacab8 | ||
|
|
c5e56a5e45 | ||
|
|
5934e882b8 | ||
|
|
ad9fe79cf2 | ||
|
|
7cc868bc8c | ||
|
|
44d26b69ae | ||
|
|
bd7d50f339 | ||
|
|
a8b3826955 | ||
|
|
4c086a4836 | ||
|
|
721baf5746 | ||
|
|
957ab65422 | ||
|
|
614a9c8977 | ||
|
|
ae0647c3a9 | ||
|
|
304eddbbe4 | ||
|
|
9afd804062 | ||
|
|
1b477c9e38 | ||
|
|
d26d0ac56f | ||
|
|
75d6b6360f | ||
|
|
8f25b98e6f | ||
|
|
695973d117 | ||
|
|
516964280b | ||
|
|
485c56e3bd | ||
|
|
837866f962 | ||
|
|
4adc92b8e5 | ||
|
|
14ef0edd7f | ||
|
|
233cd80f63 | ||
|
|
5d73e646d8 | ||
|
|
1f284408a9 | ||
|
|
f5a2534c1b | ||
|
|
61f4f8aaeb | ||
|
|
1dcd4717b1 | ||
|
|
ebe57254e0 | ||
|
|
3569c61784 | ||
|
|
5c3da91e15 | ||
|
|
c329546570 | ||
|
|
253411bfd0 | ||
|
|
e655a6c767 | ||
|
|
5ea49b3ae3 | ||
|
|
bb1cfd51b8 | ||
|
|
debb694d97 | ||
|
|
c13914bda1 | ||
|
|
6a75e884c0 | ||
|
|
5f0bf5929f | ||
|
|
f4daeb4778 | ||
|
|
495c7acadf | ||
|
|
5ca603dbeb | ||
|
|
3d14bfd90c | ||
|
|
2d97387f49 | ||
|
|
a85c2d71ad | ||
|
|
afbd275f4f | ||
|
|
ee238a300e | ||
|
|
b39b3541e9 | ||
|
|
6d8635fa29 | ||
|
|
12a286ac50 | ||
|
|
0b52308c99 | ||
|
|
c39764487c | ||
|
|
61d048cb25 | ||
|
|
ae930bde87 | ||
|
|
727afae4ff | ||
|
|
209e9a59c5 | ||
|
|
7ba094e10e | ||
|
|
20ec9f6daf | ||
|
|
65c2fb1cc6 | ||
|
|
de60657d53 | ||
|
|
975f5d5fa8 | ||
|
|
527f0310e2 | ||
|
|
42b10044fc | ||
|
|
5eb1719ab8 | ||
|
|
172441ab72 | ||
|
|
5e37c893c2 | ||
|
|
5898600239 | ||
|
|
651a83977e | ||
|
|
ac532cb6fa | ||
|
|
9e6d865882 | ||
|
|
6ba5e06247 | ||
|
|
643381ce0c | ||
|
|
67cb046298 | ||
|
|
abfbba68f0 | ||
|
|
25e3c4e586 | ||
|
|
e853e77d59 | ||
|
|
0e4de87e0e | ||
|
|
189784f5fd | ||
|
|
e79815622c | ||
|
|
9761febf82 | ||
|
|
3a82c04248 | ||
|
|
016343e65d | ||
|
|
7f73ebdab5 | ||
|
|
116d4f20fa | ||
|
|
372e31d54f | ||
|
|
acbf9b55d7 | ||
|
|
e50c48852a | ||
|
|
f519f32ec2 | ||
|
|
035189a2a1 | ||
|
|
676dbdc57b | ||
|
|
2615a11f7c | ||
|
|
f450692e77 | ||
|
|
9cf3481fc7 | ||
|
|
c58601ab8d | ||
|
|
5e55eb279c | ||
|
|
dad403f483 | ||
|
|
95eb918b4c | ||
|
|
8136d8a8a6 | ||
|
|
de9bf6dfbd | ||
|
|
aa7918c4b5 | ||
|
|
22a6a243bc | ||
|
|
dd00966cc6 | ||
|
|
ef04dc14cc | ||
|
|
4b39df4a6b | ||
|
|
8020ea783f | ||
|
|
4a61e2dfa4 | ||
|
|
f995d07542 | ||
|
|
d7f56d6126 | ||
|
|
4883ec2201 | ||
|
|
bf3b8adf35 | ||
|
|
1064b14779 | ||
|
|
2d63ed3ca4 | ||
|
|
e5861d6c35 | ||
|
|
bda7086220 | ||
|
|
8e68c7f808 | ||
|
|
43a94cda5f | ||
|
|
661fba8640 | ||
|
|
781d66f628 | ||
|
|
43b6e7a944 | ||
|
|
6e3bf7fed4 | ||
|
|
e4bb5c296c | ||
|
|
3b6c975658 | ||
|
|
5becdbdea8 | ||
|
|
af826f5efd | ||
|
|
77bb52f72c | ||
|
|
64428bac15 | ||
|
|
78742d5615 | ||
|
|
b585470518 | ||
|
|
e2c690cece | ||
|
|
0d0f6b5a05 | ||
|
|
7ba46a0630 | ||
|
|
15b26e10d4 | ||
|
|
781d065d0b | ||
|
|
03a4c9d6d5 | ||
|
|
dd416cdfd2 | ||
|
|
f920e02d96 | ||
|
|
908a7cf47e | ||
|
|
dcd8bdfc88 | ||
|
|
6e821eea4b | ||
|
|
661be7ba51 | ||
|
|
7a7dc95611 | ||
|
|
399451b676 | ||
|
|
1159f5517b | ||
|
|
1ccf174388 | ||
|
|
808ddc26e7 | ||
|
|
65fd605b82 | ||
|
|
e7150d0b66 | ||
|
|
7ff97e50a7 | ||
|
|
b58ac815a8 | ||
|
|
971c88db80 | ||
|
|
a2aad31cf1 | ||
|
|
e970d84184 | ||
|
|
5df5973262 | ||
|
|
60955fb8ee | ||
|
|
79d5bb45dc | ||
|
|
abf158a9e4 | ||
|
|
bcf9b2f10d | ||
|
|
91d85987b9 | ||
|
|
fa32a20c42 | ||
|
|
a1abe8d33c | ||
|
|
ae6a671fea | ||
|
|
47de4dcd32 | ||
|
|
6bfecd7f66 | ||
|
|
b251e249a7 | ||
|
|
7b0a6c0dfa | ||
|
|
5ecc9606af | ||
|
|
55297c1a26 | ||
|
|
f95732e981 | ||
|
|
1627cf7eae | ||
|
|
bab0e88b6f | ||
|
|
543301f949 | ||
|
|
2e33f8b228 | ||
|
|
48d9c30b0e | ||
|
|
0250898a2b | ||
|
|
c960277349 | ||
|
|
2c0a645f19 | ||
|
|
588b2da40a | ||
|
|
69557655ee | ||
|
|
5a397726d2 | ||
|
|
cdfe873802 | ||
|
|
0cb73b6579 | ||
|
|
b88aa9af1d | ||
|
|
ec5309b543 | ||
|
|
b6a9d90609 | ||
|
|
0b9e609e2a | ||
|
|
b26d4f4f62 | ||
|
|
75ecf92ae4 | ||
|
|
138aa0492a | ||
|
|
71cf6ec5ae | ||
|
|
27258a0889 | ||
|
|
aed8df96ff | ||
|
|
82a713fd1d | ||
|
|
59fb4b3d29 | ||
|
|
8c0db17634 | ||
|
|
c19e6f8f0e | ||
|
|
297fd5f985 | ||
|
|
dcaa355ad2 | ||
|
|
0ae298a86f | ||
|
|
0f4f3bfbde | ||
|
|
57b5c1ee20 | ||
|
|
f616e8bac9 | ||
|
|
b6882f0cbf | ||
|
|
e5a6b11d8f | ||
|
|
c1445fe015 | ||
|
|
bdce96b19a | ||
|
|
516e77906e | ||
|
|
0199ffed5f | ||
|
|
f561e5ef2c | ||
|
|
4865ea2efc | ||
|
|
54582fd77f | ||
|
|
b208d1a489 | ||
|
|
5f579a4287 | ||
|
|
b588ba1435 | ||
|
|
6e43e77c3f | ||
|
|
9b16277cf8 | ||
|
|
713f5f604f | ||
|
|
e38f52d595 | ||
|
|
c3188be4c1 | ||
|
|
81411b9114 | ||
|
|
c28d2c490b | ||
|
|
afc9b832c8 | ||
|
|
f5d4bcd934 | ||
|
|
3cfe61a4f4 | ||
|
|
cdde523ea4 | ||
|
|
f235d9f411 | ||
|
|
655897b182 | ||
|
|
58b453ad6e | ||
|
|
b7461c32dd | ||
|
|
e46cd2def3 | ||
|
|
007aa92581 | ||
|
|
dc51735112 | ||
|
|
adc5ef911f | ||
|
|
18c6c7ebb7 | ||
|
|
9ef3e45bcd | ||
|
|
e3a311fda9 | ||
|
|
af4c4c7cf0 | ||
|
|
79346b0706 | ||
|
|
5b3b74345d | ||
|
|
a8033b266d | ||
|
|
cc7c5b416c | ||
|
|
8fac32e1eb | ||
|
|
ec5ffe9922 | ||
|
|
76efab005f | ||
|
|
b5f762ab25 | ||
|
|
e3822a5b5c | ||
|
|
ae3b3ea458 | ||
|
|
ebd06b43f5 | ||
|
|
7601e7902a | ||
|
|
0c07a373a8 | ||
|
|
e558d3f903 | ||
|
|
737e2e1b3c | ||
|
|
f812796283 | ||
|
|
49447128a9 | ||
|
|
6e2a9297ff | ||
|
|
b86f8188d1 | ||
|
|
b15632bd45 | ||
|
|
b4593cd90b | ||
|
|
69989d0463 | ||
|
|
d5f53111e8 | ||
|
|
a128439699 | ||
|
|
87c1b190a8 | ||
|
|
719d0f0abf | ||
|
|
813f722925 | ||
|
|
d60ef03d66 | ||
|
|
0b0c7e4ce9 | ||
|
|
aea8475d30 | ||
|
|
a5cec18775 | ||
|
|
12370f120e | ||
|
|
ce9774be53 | ||
|
|
9d8d2bb8f4 | ||
|
|
5c1f82ae3d | ||
|
|
08e93e9321 | ||
|
|
c23feeab3a | ||
|
|
941da24f73 | ||
|
|
0ef9cefe0f | ||
|
|
6ff09865eb | ||
|
|
da81ff3295 | ||
|
|
034bc75467 | ||
|
|
d236d9e8c9 | ||
|
|
ef6c28326d | ||
|
|
2fede1c01f | ||
|
|
1162615043 | ||
|
|
6715e5247c | ||
|
|
093e0a30e9 | ||
|
|
2f95510a2e | ||
|
|
b16e2169ce | ||
|
|
9713d1bb31 | ||
|
|
99cca59c84 | ||
|
|
19cc86a2d4 | ||
|
|
15e29d44b9 | ||
|
|
b57d5174aa | ||
|
|
591e246450 | ||
|
|
155594c8b8 | ||
|
|
180371929b | ||
|
|
8ba9e63ab8 | ||
|
|
59d9277a74 | ||
|
|
797bb7d780 | ||
|
|
5471217089 | ||
|
|
9a99eaee96 | ||
|
|
455cdc8b37 | ||
|
|
368a6405a1 | ||
|
|
51cea1b1fb | ||
|
|
9c25d37dfc | ||
|
|
2c84b74126 | ||
|
|
1a9ff2420e | ||
|
|
e04d0be853 | ||
|
|
00e8625037 | ||
|
|
a3c7416218 | ||
|
|
576a9bb92c | ||
|
|
5a4fa4b11e | ||
|
|
3e3a703b60 | ||
|
|
c503ba00b6 | ||
|
|
2351f2bd0c | ||
|
|
99390a7237 | ||
|
|
210286da48 | ||
|
|
f1b3692a35 | ||
|
|
f243633f3e | ||
|
|
ce8dd5a286 | ||
|
|
a74c5073a4 |
9
.github/pull_request_template.md
vendored
@@ -1,9 +0,0 @@
|
||||
## Description of feature or change
|
||||
|
||||
## Link to related issues from zed or community
|
||||
|
||||
## Before Merging
|
||||
|
||||
- [ ] Does this have tests or have existing tests been updated to cover this change?
|
||||
- [ ] Have you added the necessary settings to configure this feature?
|
||||
- [ ] Has documentation been created or updated (including above changes to settings)?
|
||||
4
.github/workflows/ci.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
43
.github/workflows/randomized_tests.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Randomized Tests
|
||||
|
||||
concurrency: randomized-tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- randomized-tests-runner
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
ZED_SERVER_URL: https://zed.dev
|
||||
ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }}
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Run randomized tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- randomized-tests
|
||||
steps:
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup update stable
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Run randomized tests
|
||||
run: script/randomized-test-ci
|
||||
97
Cargo.lock
generated
@@ -785,6 +785,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"itertools",
|
||||
"language",
|
||||
"outline",
|
||||
"project",
|
||||
"search",
|
||||
"settings",
|
||||
@@ -1113,7 +1114,6 @@ dependencies = [
|
||||
"futures 0.3.25",
|
||||
"gpui",
|
||||
"image",
|
||||
"isahc",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
@@ -1124,6 +1124,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"settings",
|
||||
"smol",
|
||||
"staff_mode",
|
||||
"sum_tree",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
@@ -1191,7 +1192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tungstenite",
|
||||
@@ -1212,6 +1213,7 @@ dependencies = [
|
||||
"git",
|
||||
"gpui",
|
||||
"hyper",
|
||||
"indoc",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"lipsum",
|
||||
@@ -1332,6 +1334,50 @@ dependencies = [
|
||||
"theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "copilot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-tar",
|
||||
"clock",
|
||||
"collections",
|
||||
"context_menu",
|
||||
"fs",
|
||||
"futures 0.3.25",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "copilot_button"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"context_menu",
|
||||
"copilot",
|
||||
"editor",
|
||||
"futures 0.3.25",
|
||||
"gpui",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@@ -1788,6 +1834,7 @@ dependencies = [
|
||||
"editor",
|
||||
"gpui",
|
||||
"language",
|
||||
"lsp",
|
||||
"postage",
|
||||
"project",
|
||||
"serde_json",
|
||||
@@ -1932,6 +1979,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"context_menu",
|
||||
"copilot",
|
||||
"ctor",
|
||||
"db",
|
||||
"drag_and_drop",
|
||||
@@ -3910,6 +3958,23 @@ dependencies = [
|
||||
"memoffset 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "node_runtime"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-tar",
|
||||
"futures 0.3.25",
|
||||
"gpui",
|
||||
"parking_lot 0.11.2",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
@@ -4529,14 +4594,15 @@ checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
|
||||
|
||||
[[package]]
|
||||
name = "postage"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a63d25391d04a097954b76aba742b6b5b74f213dfe3dbaeeb36e8ddc1c657f0b"
|
||||
checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"crossbeam-queue",
|
||||
"futures 0.3.25",
|
||||
"log",
|
||||
"parking_lot 0.12.1",
|
||||
"pin-project",
|
||||
"pollster",
|
||||
"static_assertions",
|
||||
@@ -4625,6 +4691,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"copilot",
|
||||
"ctor",
|
||||
"db",
|
||||
"env_logger",
|
||||
@@ -5014,6 +5081,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"text",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
@@ -5882,12 +5950,14 @@ dependencies = [
|
||||
"gpui",
|
||||
"json_comments",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sqlez",
|
||||
"staff_mode",
|
||||
"theme",
|
||||
"toml",
|
||||
"tree-sitter",
|
||||
@@ -6298,6 +6368,14 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "staff_mode"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -6616,6 +6694,7 @@ dependencies = [
|
||||
"postage",
|
||||
"settings",
|
||||
"smol",
|
||||
"staff_mode",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -7500,11 +7579,15 @@ dependencies = [
|
||||
"dirs 3.0.2",
|
||||
"futures 0.3.25",
|
||||
"git2",
|
||||
"isahc",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"tempdir",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8439,7 +8522,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.80.0"
|
||||
version = "0.83.1"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -8460,6 +8543,8 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"context_menu",
|
||||
"copilot",
|
||||
"copilot_button",
|
||||
"ctor",
|
||||
"db",
|
||||
"diagnostics",
|
||||
@@ -8486,6 +8571,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
"num_cpus",
|
||||
"outline",
|
||||
"parking_lot 0.11.2",
|
||||
@@ -8509,6 +8595,7 @@ dependencies = [
|
||||
"simplelog",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"staff_mode",
|
||||
"sum_tree",
|
||||
"tempdir",
|
||||
"terminal_view",
|
||||
|
||||
@@ -13,6 +13,8 @@ members = [
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/context_menu",
|
||||
"crates/copilot",
|
||||
"crates/copilot_button",
|
||||
"crates/db",
|
||||
"crates/diagnostics",
|
||||
"crates/drag_and_drop",
|
||||
@@ -35,6 +37,7 @@ members = [
|
||||
"crates/lsp",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/node_runtime",
|
||||
"crates/outline",
|
||||
"crates/picker",
|
||||
"crates/plugin",
|
||||
@@ -51,6 +54,7 @@ members = [
|
||||
"crates/snippet",
|
||||
"crates/sqlez",
|
||||
"crates/sqlez_macros",
|
||||
"crates/staff_mode",
|
||||
"crates/sum_tree",
|
||||
"crates/terminal",
|
||||
"crates/text",
|
||||
@@ -71,7 +75,8 @@ serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
rand = { version = "0.8" }
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
|
||||
|
||||
12
assets/icons/copilot_16.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.2926 3.48996C3.79162 3.79616 3.44871 4.26316 3.44871 4.93872C3.44871 5.75753 3.65302 6.19648 3.88658 6.43349C4.11948 6.66983 4.47018 6.79529 4.95638 6.79529C5.64158 6.79529 6.23176 6.65786 6.64548 6.37099C7.03216 6.10286 7.32149 5.66636 7.35698 4.91278C7.38386 4.34213 7.36863 3.96084 7.21748 3.68905C7.09721 3.47279 6.81682 3.2089 5.96976 3.11109C5.4731 3.05374 4.81346 3.17162 4.2926 3.48996ZM3.72539 2.5525C4.46348 2.10138 5.36842 1.93724 6.09436 2.02107C7.1336 2.14107 7.8142 2.51324 8.17039 3.15373C8.49569 3.73867 8.47238 4.43479 8.44743 4.96466C8.39736 6.02772 7.95809 6.7938 7.26541 7.27411C6.59976 7.73566 5.75982 7.89249 4.95638 7.89249C4.2936 7.89249 3.61755 7.71967 3.11095 7.20558C2.605 6.69216 2.35705 5.92853 2.35705 4.93872C2.35705 3.80566 2.96744 3.01576 3.72539 2.5525Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.69546 8.97734C7.02432 8.97734 7.29091 9.24528 7.29091 9.57581V10.8725C7.29091 11.203 7.02432 11.471 6.69546 11.471C6.3666 11.471 6.1 11.203 6.1 10.8725V9.57581C6.1 9.24528 6.3666 8.97734 6.69546 8.97734Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.45301 7.32072C2.56382 6.90477 2.81104 6.35118 3.40175 6.17048L3.74851 7.31556C3.74509 7.31822 3.73425 7.32798 3.71842 7.35038C3.68409 7.39897 3.64151 7.48723 3.6034 7.6303C3.52629 7.91973 3.49839 8.31081 3.4984 8.73318V10.8761C3.5122 10.9688 3.52011 11.0083 3.53501 11.0478C3.5474 11.0807 3.57295 11.1339 3.6523 11.2153C3.83266 11.4004 4.24428 11.6866 5.21016 12.1174C5.99398 12.467 6.35125 12.6243 6.68361 12.7078C6.99799 12.7869 7.30564 12.8031 7.99999 12.8031V14C7.31311 14 6.86876 13.9882 6.3946 13.869C5.95125 13.7575 5.49691 13.5549 4.78914 13.2391C4.76868 13.23 4.74801 13.2208 4.72712 13.2115C3.73729 12.77 3.14865 12.4092 2.80139 12.0527C2.61692 11.8634 2.49682 11.6721 2.42136 11.4719C2.35507 11.2961 2.33141 11.1302 2.31663 11.0266C2.31561 11.0194 2.31463 11.0126 2.31369 11.0061L2.30749 10.9632V8.73321C2.30748 8.28334 2.33457 7.76532 2.45301 7.32072Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.83439 7.54965C2.14812 7.21281 2.52306 6.88008 2.81315 6.70729L3.42031 7.737C3.27036 7.82631 2.98468 8.06607 2.7038 8.36764C2.43565 8.65553 2.2592 8.90729 2.19783 9.04784C2.18425 9.16608 2.18871 9.38528 2.22654 9.6452C2.26959 9.94104 2.33715 10.1608 2.37974 10.2387L2.42237 10.3167L2.44057 10.4038C2.46806 10.5353 2.60072 10.7284 2.96139 10.9852C3.24332 11.1859 3.57562 11.3661 3.93098 11.5588C4.00968 11.6015 4.0895 11.6448 4.17017 11.689C4.56251 11.8768 5.17152 12.1512 5.7408 12.3785C6.02948 12.4938 6.30016 12.5938 6.5233 12.664C6.63493 12.6991 6.72826 12.7247 6.802 12.7411C6.87402 12.7571 6.90715 12.7597 6.91166 12.76L6.91213 13.957C6.68654 13.957 6.40667 13.8815 6.16744 13.8062C5.90465 13.7235 5.60351 13.6116 5.30115 13.4909C4.69547 13.2491 4.05361 12.9594 3.64268 12.7623L3.62786 12.7552L3.61345 12.7473C3.54294 12.7085 3.46868 12.6683 3.39187 12.6268C3.03384 12.433 2.62042 12.2092 2.27302 11.9619C1.88328 11.6844 1.44476 11.2894 1.29544 10.735C1.17095 10.4701 1.09192 10.1191 1.04817 9.81844C0.999401 9.48332 0.975841 9.08189 1.03513 8.7778L1.04265 8.73927L1.05511 8.70206C1.18745 8.30673 1.53258 7.87368 1.83439 7.54965Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.59243 10.4347V8.41995H2.78334V10.4347H1.59243Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7074 3.48996C12.2084 3.79616 12.5513 4.26316 12.5513 4.93872C12.5513 5.75753 12.347 6.19648 12.1134 6.43349C11.8805 6.66983 11.5298 6.79529 11.0436 6.79529C10.3584 6.79529 9.76824 6.65786 9.35452 6.37099C8.96784 6.10286 8.67851 5.66636 8.64302 4.91278C8.61614 4.34213 8.63137 3.96084 8.78252 3.68905C8.90279 3.47279 9.18318 3.2089 10.0302 3.11109C10.5269 3.05374 11.1865 3.17162 11.7074 3.48996ZM12.2746 2.5525C11.5365 2.10138 10.6316 1.93724 9.90564 2.02107C8.8664 2.14107 8.1858 2.51324 7.82961 3.15373C7.50431 3.73867 7.52762 4.43479 7.55258 4.96466C7.60264 6.02772 8.04191 6.7938 8.73459 7.27411C9.40024 7.73566 10.2402 7.89249 11.0436 7.89249C11.7064 7.89249 12.3824 7.71967 12.889 7.20558C13.395 6.69216 13.643 5.92853 13.643 4.93872C13.643 3.80566 13.0326 3.01576 12.2746 2.5525Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.30454 8.97734C8.97568 8.97734 8.70909 9.24528 8.70909 9.57581V10.8725C8.70909 11.203 8.97568 11.471 9.30454 11.471C9.6334 11.471 9.9 11.203 9.9 10.8725V9.57581C9.9 9.24528 9.6334 8.97734 9.30454 8.97734Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.547 7.32072C13.4362 6.90477 13.189 6.35118 12.5982 6.17048L12.2515 7.31556C12.2549 7.31822 12.2658 7.32798 12.2816 7.35038C12.3159 7.39897 12.3585 7.48723 12.3966 7.6303C12.4737 7.91973 12.5016 8.31081 12.5016 8.73318V10.8761C12.4878 10.9688 12.4799 11.0083 12.465 11.0478C12.4526 11.0807 12.427 11.1339 12.3477 11.2153C12.1673 11.4004 11.7557 11.6866 10.7898 12.1174C10.006 12.467 9.64875 12.6243 9.31639 12.7078C9.00201 12.7869 8.69433 12.8031 7.99999 12.8031V14C8.68686 14 9.13124 13.9882 9.6054 13.869C10.0488 13.7575 10.5031 13.5549 11.2109 13.2391C11.2313 13.23 11.252 13.2208 11.2729 13.2115C12.2627 12.77 12.8513 12.4092 13.1986 12.0527C13.3831 11.8634 13.5032 11.6721 13.5786 11.4719C13.6449 11.2961 13.6686 11.1302 13.6834 11.0266C13.6844 11.0194 13.6854 11.0126 13.6863 11.0061L13.6925 10.9632V8.73321C13.6925 8.28334 13.6654 7.76532 13.547 7.32072Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1656 7.54965C13.8519 7.21281 13.4769 6.88008 13.1868 6.70729L12.5797 7.737C12.7296 7.82631 13.0153 8.06607 13.2962 8.36764C13.5643 8.65553 13.7408 8.90729 13.8022 9.04784C13.8158 9.16608 13.8113 9.38528 13.7735 9.6452C13.7304 9.94104 13.6628 10.1608 13.6203 10.2387L13.5776 10.3167L13.5594 10.4038C13.5319 10.5353 13.3993 10.7284 13.0386 10.9852C12.7567 11.1859 12.4244 11.3661 12.069 11.5588C11.9903 11.6015 11.9105 11.6448 11.8298 11.689C11.4375 11.8768 10.8285 12.1512 10.2592 12.3785C9.97052 12.4938 9.69984 12.5938 9.4767 12.664C9.36507 12.6991 9.27174 12.7247 9.198 12.7411C9.12598 12.7571 9.09285 12.7597 9.08834 12.76L9.08787 13.957C9.31345 13.957 9.59333 13.8815 9.83256 13.8062C10.0953 13.7235 10.3965 13.6116 10.6989 13.4909C11.3045 13.2491 11.9464 12.9594 12.3573 12.7623L12.3721 12.7552L12.3865 12.7473C12.4571 12.7085 12.5313 12.6683 12.6081 12.6268C12.9662 12.433 13.3796 12.2092 13.727 11.9619C14.1167 11.6844 14.5552 11.2894 14.7046 10.735C14.829 10.4701 14.9081 10.1191 14.9518 9.81844C15.0006 9.48332 15.0242 9.08189 14.9649 8.7778L14.9574 8.73927L14.9449 8.70206C14.8126 8.30673 14.4674 7.87368 14.1656 7.54965Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4076 10.4347V8.41995H13.2167V10.4347H14.4076Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
9
assets/icons/copilot_disabled_16.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.5">
|
||||
<path d="M2.38084 5.44737C2.42754 5.92437 2.54311 6.33779 2.72903 6.68417C2.70043 6.72678 2.67414 6.77043 2.64995 6.81463C2.39071 6.9994 2.09192 7.27314 1.83439 7.54965C1.53258 7.87368 1.18745 8.30673 1.05511 8.70206L1.04265 8.73927L1.03513 8.7778C0.975841 9.08189 0.999401 9.48332 1.04817 9.81844C1.09192 10.1191 1.17095 10.4701 1.29544 10.735C1.44476 11.2894 1.88328 11.6844 2.27302 11.9619C2.6204 12.2092 3.03378 12.4329 3.39179 12.6267C3.4686 12.6683 3.54294 12.7085 3.61345 12.7473L3.62786 12.7552L3.64268 12.7623C4.05361 12.9594 4.69547 13.2491 5.30115 13.4909C5.60351 13.6116 5.90465 13.7235 6.16744 13.8062C6.39236 13.877 6.6532 13.948 6.87108 13.9562C7.19351 13.9948 7.54309 14 7.99999 14C8.45688 14 8.80648 13.9948 9.12892 13.9562C9.34679 13.948 9.60764 13.877 9.83256 13.8062C10.0953 13.7235 10.3965 13.6116 10.6989 13.4909C11.0041 13.369 11.3186 13.235 11.6081 13.1067L10.5467 12.2257C9.92791 12.5006 9.61228 12.6334 9.31639 12.7078C9.00201 12.7869 8.69433 12.8031 7.99999 12.8031C7.30564 12.8031 6.99799 12.7869 6.68361 12.7078C6.35125 12.6243 5.99398 12.467 5.21016 12.1174C4.24428 11.6866 3.83266 11.4004 3.6523 11.2153C3.57295 11.1339 3.5474 11.0807 3.53501 11.0478C3.52011 11.0083 3.5122 10.9688 3.4984 10.8761V8.73318C3.49839 8.31081 3.52629 7.91973 3.6034 7.6303C3.60757 7.61463 3.6118 7.59961 3.61607 7.58523C4.02831 7.80894 4.49555 7.89249 4.95638 7.89249C5.07488 7.89249 5.19417 7.88908 5.31358 7.88178L2.38084 5.44737Z" fill="white"/>
|
||||
<path d="M6.63684 8.9802C6.3355 9.00979 6.1 9.26516 6.1 9.57581V10.8725C6.1 11.203 6.3666 11.471 6.69546 11.471C7.02432 11.471 7.29091 11.203 7.29091 10.8725V9.57581C7.29091 9.55736 7.29008 9.53911 7.28846 9.52109L6.63684 8.9802Z" fill="white"/>
|
||||
<path d="M8.70909 10.7003V10.8725C8.70909 11.203 8.97568 11.471 9.30454 11.471C9.39795 11.471 9.48633 11.4493 9.56501 11.4108L8.70909 10.7003Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.09436 2.02107C5.38898 1.93962 4.51459 2.09228 3.78849 2.51486L4.71538 3.28426C5.1409 3.1227 5.6004 3.06844 5.96976 3.11109C6.81682 3.2089 7.09721 3.4728 7.21748 3.68905C7.36863 3.96084 7.38386 4.34213 7.35698 4.91278C7.34816 5.1 7.32368 5.26765 7.28592 5.41801L10.1885 7.82737C10.4719 7.87295 10.76 7.89249 11.0436 7.89249C11.5044 7.89249 11.9717 7.80894 12.3839 7.58523C12.3882 7.59961 12.3924 7.61463 12.3966 7.6303C12.4737 7.91973 12.5016 8.31081 12.5016 8.73318V9.74745L14.4065 11.3287C14.5379 11.1547 14.6446 10.9577 14.7046 10.735C14.829 10.4701 14.9081 10.1191 14.9518 9.81844C15.0006 9.48332 15.0242 9.08189 14.9649 8.7778L14.9574 8.73927L14.9449 8.70206C14.8126 8.30673 14.4674 7.87368 14.1656 7.54965C13.9081 7.27314 13.6093 6.9994 13.35 6.81463C13.3259 6.77043 13.2996 6.72678 13.271 6.68417C13.5201 6.21998 13.643 5.63537 13.643 4.93872C13.643 3.80567 13.0326 3.01576 12.2746 2.5525C11.5365 2.10138 10.6316 1.93724 9.90564 2.02107C9.01315 2.12413 8.38516 2.41316 8 2.89841C7.61484 2.41316 6.98685 2.12413 6.09436 2.02107ZM11.7074 3.48996C12.2084 3.79616 12.5513 4.26316 12.5513 4.93872C12.5513 5.75753 12.347 6.19648 12.1134 6.43349C11.8805 6.66983 11.5298 6.79529 11.0436 6.79529C10.3584 6.79529 9.76824 6.65786 9.35452 6.37099C8.96784 6.10286 8.67851 5.66636 8.64302 4.91278C8.61614 4.34213 8.63137 3.96084 8.78252 3.68905C8.90279 3.4728 9.18318 3.2089 10.0302 3.11109C10.5269 3.05374 11.1865 3.17162 11.7074 3.48996Z" fill="white"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1225 13.809C14.0341 13.9146 13.877 13.9289 13.7711 13.8409L1.1931 3.40021C1.08658 3.31178 1.0722 3.15362 1.16103 3.04743L1.87751 2.19101C1.96587 2.0854 2.12299 2.07112 2.22894 2.15906L14.8069 12.5998C14.9134 12.6882 14.9278 12.8464 14.839 12.9526L14.1225 13.809Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
7
assets/icons/copilot_error_16.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.5">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.09436 2.02107C5.36842 1.93724 4.46348 2.10138 3.72539 2.5525C2.96744 3.01576 2.35705 3.80567 2.35705 4.93872C2.35705 5.63537 2.47988 6.21998 2.72903 6.68417C2.70043 6.72678 2.67414 6.77043 2.64995 6.81463C2.39071 6.9994 2.09192 7.27314 1.83439 7.54965C1.53258 7.87368 1.18745 8.30673 1.05511 8.70206L1.04265 8.73927L1.03513 8.7778C0.975841 9.08189 0.999401 9.48332 1.04817 9.81844C1.09192 10.1191 1.17095 10.4701 1.29544 10.735C1.44476 11.2894 1.88328 11.6844 2.27302 11.9619C2.6204 12.2092 3.03378 12.4329 3.39179 12.6267C3.4686 12.6683 3.54294 12.7085 3.61345 12.7473L3.62786 12.7552L3.64268 12.7623C4.05361 12.9594 4.69547 13.2491 5.30115 13.4909C5.60351 13.6116 5.90465 13.7235 6.16744 13.8062C6.39236 13.877 6.6532 13.948 6.87108 13.9562C7.19351 13.9948 7.54309 14 7.99999 14C8.01293 14 8.02579 14 8.03857 14C7.97904 13.903 7.99191 13.7743 8.07655 13.6911L9.0197 12.7639C8.77857 12.7952 8.48273 12.8031 7.99999 12.8031C7.30564 12.8031 6.99799 12.7869 6.68361 12.7078C6.35125 12.6243 5.99398 12.467 5.21016 12.1174C4.24428 11.6866 3.83266 11.4004 3.6523 11.2153C3.57295 11.1339 3.5474 11.0807 3.53501 11.0478C3.52011 11.0083 3.5122 10.9688 3.4984 10.8761V8.73318C3.49839 8.31081 3.52629 7.91973 3.6034 7.6303C3.60757 7.61463 3.6118 7.59961 3.61607 7.58523C4.02831 7.80894 4.49555 7.89249 4.95638 7.89249C5.75982 7.89249 6.59976 7.73566 7.26541 7.27411C7.55937 7.07027 7.8077 6.81497 8 6.50734C8.1923 6.81497 8.44063 7.07027 8.73459 7.27411C9.40024 7.73566 10.2402 7.89249 11.0436 7.89249C11.5044 7.89249 11.9717 7.80894 12.3839 7.58523C12.3882 7.59961 12.3924 7.61463 12.3966 7.6303C12.4737 7.91973 12.5016 8.31081 12.5016 8.73318V9.34082L14.1266 7.7433C14.169 7.70159 14.2225 7.67811 14.2775 7.67276C14.2398 7.63028 14.2024 7.58915 14.1656 7.54965C13.9081 7.27314 13.6093 6.9994 13.35 6.81463C13.3259 6.77043 13.2996 6.72678 13.271 6.68417C13.5201 6.21998 13.643 5.63537 13.643 4.93872C13.643 3.80567 13.0326 3.01576 12.2746 2.5525C11.5365 2.10138 10.6316 1.93724 9.90564 2.02107C9.01315 2.12413 8.38516 2.41316 8 2.89841C7.61484 2.41316 6.98685 2.12413 6.09436 2.02107ZM3.44871 4.93872C3.44871 4.26316 3.79162 3.79616 4.2926 3.48996C4.81346 3.17162 5.4731 3.05374 5.96976 3.11109C6.81682 3.2089 7.09721 3.4728 7.21748 3.68905C7.36863 3.96084 7.38386 4.34213 7.35698 4.91278C7.32149 5.66636 7.03216 6.10286 6.64548 6.37099C6.23176 6.65786 5.64158 6.79529 4.95638 6.79529C4.47018 6.79529 4.11948 6.66983 3.88658 6.43349C3.65302 6.19648 3.44871 5.75753 3.44871 4.93872ZM12.5513 4.93872C12.5513 4.26316 12.2084 3.79616 11.7074 3.48996C11.1865 3.17162 10.5269 3.05374 10.0302 3.11109C9.18318 3.2089 8.90279 3.4728 8.78252 3.68905C8.63137 3.96084 8.61614 4.34213 8.64302 4.91278C8.67851 5.66636 8.96784 6.10286 9.35452 6.37099C9.76824 6.65786 10.3584 6.79529 11.0436 6.79529C11.5298 6.79529 11.8805 6.66983 12.1134 6.43349C12.347 6.19648 12.5513 5.75753 12.5513 4.93872Z" fill="white"/>
|
||||
<path d="M7.29091 9.57581C7.29091 9.24528 7.02432 8.97734 6.69546 8.97734C6.3666 8.97734 6.1 9.24528 6.1 9.57581V10.8725C6.1 11.203 6.3666 11.471 6.69546 11.471C7.02432 11.471 7.29091 11.203 7.29091 10.8725V9.57581Z" fill="white"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6668 14.9102C13.7644 15.0078 13.9227 15.0078 14.0203 14.9102L14.908 14.0224C15.0056 13.9248 15.0056 13.7665 14.908 13.6688L13.2229 11.9836L14.908 10.2983C15.0057 10.2007 15.0057 10.0424 14.908 9.94474L14.0203 9.05695C13.9227 8.95931 13.7644 8.95931 13.6668 9.05695L11.9817 10.7422L10.2966 9.05693C10.199 8.95929 10.0407 8.95929 9.94306 9.05693L9.05535 9.94473C8.95773 10.0424 8.95773 10.2007 9.05535 10.2983L10.7405 11.9836L9.05537 13.6688C8.95775 13.7665 8.95775 13.9248 9.05537 14.0224L9.94308 14.9102C10.0407 15.0079 10.199 15.0079 10.2966 14.9102L11.9817 13.225L13.6668 14.9102Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
4
assets/icons/copilot_init_16.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M3.44872 4.93872C3.44872 4.26316 3.79163 3.79616 4.29261 3.48996C4.81346 3.17162 5.47311 3.05374 5.96976 3.11109C6.81683 3.2089 7.09722 3.4728 7.21749 3.68905C7.36864 3.96084 7.38387 4.34213 7.35699 4.91278C7.3215 5.66636 7.03217 6.10286 6.64549 6.37099C6.23177 6.65786 5.64159 6.79529 4.95639 6.79529C4.47019 6.79529 4.11949 6.66983 3.88658 6.43349C3.65303 6.19648 3.44872 5.75753 3.44872 4.93872ZM6.09437 2.02107C5.36843 1.93724 4.46349 2.10138 3.7254 2.5525C2.96745 3.01576 2.35706 3.80567 2.35706 4.93872C2.35706 5.63537 2.47988 6.21998 2.72904 6.68417C2.70044 6.72678 2.67415 6.77043 2.64996 6.81463C2.39072 6.9994 2.09193 7.27314 1.83439 7.54965C1.53259 7.87368 1.18745 8.30673 1.05511 8.70206L1.04266 8.73927L1.03514 8.7778C0.975849 9.08189 0.999409 9.48332 1.04818 9.81844C1.09193 10.1191 1.17096 10.4701 1.29545 10.735C1.44476 11.2894 1.88329 11.6844 2.27303 11.9619C2.6204 12.2092 3.03379 12.4329 3.3918 12.6267L3.39183 12.6267L3.39185 12.6267L3.39188 12.6268C3.46869 12.6683 3.54295 12.7085 3.61346 12.7473L3.62787 12.7552L3.64269 12.7623C4.05362 12.9594 4.69548 13.2491 5.30115 13.4909C5.60352 13.6116 5.90466 13.7235 6.16745 13.8062C6.39237 13.877 6.65321 13.948 6.87108 13.9562C7.19351 13.9948 7.5431 14 8 14C8.45052 14 8.79672 13.9949 9.11543 13.9578C9.04001 13.6509 9.00001 13.3301 9.00001 13C9.00001 12.9213 9.00229 12.8431 9.00677 12.7656C8.76798 12.7955 8.47414 12.8031 8 12.8031C7.30565 12.8031 6.998 12.7869 6.68362 12.7078C6.35125 12.6243 5.99399 12.467 5.21017 12.1174C4.24429 11.6866 3.83267 11.4004 3.6523 11.2153C3.57296 11.1339 3.54741 11.0807 3.53502 11.0478C3.52011 11.0083 3.51221 10.9688 3.4984 10.8761V8.73318C3.49839 8.31081 3.5263 7.91973 3.6034 7.6303C3.60758 7.61463 3.61181 7.59961 3.61608 7.58523C4.02832 7.80894 4.49556 7.89249 4.95639 7.89249C5.75982 7.89249 6.59977 7.73566 7.26541 7.27411C7.55938 7.07027 7.8077 6.81497 8.00001 6.50734C8.19231 6.81497 8.44064 7.07027 8.7346 7.27411C9.40025 7.73566 10.2402 7.89249 11.0436 7.89249C11.5045 7.89249 11.9717 7.80894 12.3839 7.58523C12.3882 7.59961 12.3924 7.61463 12.3966 7.6303C12.4737 7.91973 12.5016 8.31081 12.5016 8.73318V9.03072C12.6649 9.01043 12.8312 8.99997 13 8.99997C13.7226 8.99997 14.4004 9.19157 14.9855 9.52673C15.0073 9.26739 15.0077 8.99725 14.9649 8.7778L14.9574 8.73927L14.9449 8.70206C14.8126 8.30673 14.4674 7.87368 14.1656 7.54965C13.9081 7.27314 13.6093 6.9994 13.3501 6.81463C13.3259 6.77043 13.2996 6.72678 13.271 6.68417C13.5201 6.21998 13.643 5.63537 13.643 4.93872C13.643 3.80567 13.0326 3.01576 12.2746 2.5525C11.5365 2.10138 10.6316 1.93724 9.90565 2.02107C9.01315 2.12413 8.38517 2.41316 8.00001 2.89841C7.61485 2.41316 6.98686 2.12413 6.09437 2.02107ZM9.9 10.4719V9.57581C9.9 9.24528 9.63341 8.97734 9.30455 8.97734C8.97569 8.97734 8.7091 9.24528 8.7091 9.57581V10.8725C8.7091 11.2024 8.97466 11.4699 9.30265 11.471C9.45294 11.1079 9.65515 10.7718 9.9 10.4719ZM7.29092 9.57581C7.29092 9.24528 7.02433 8.97734 6.69547 8.97734C6.36661 8.97734 6.10001 9.24528 6.10001 9.57581V10.8725C6.10001 11.203 6.36661 11.471 6.69547 11.471C7.02433 11.471 7.29092 11.203 7.29092 10.8725V9.57581ZM12.5513 4.93872C12.5513 4.26316 12.2084 3.79616 11.7074 3.48996C11.1866 3.17162 10.5269 3.05374 10.0303 3.11109C9.18318 3.2089 8.90279 3.4728 8.78253 3.68905C8.63138 3.96084 8.61615 4.34213 8.64303 4.91278C8.67852 5.66636 8.96785 6.10286 9.35453 6.37099C9.76825 6.65786 10.3584 6.79529 11.0436 6.79529C11.5298 6.79529 11.8805 6.66983 12.1134 6.43349C12.347 6.19648 12.5513 5.75753 12.5513 4.93872Z" fill="white"/>
|
||||
<circle cx="13" cy="13" r="3" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
1
assets/icons/github-copilot-dummy.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.24em" height="1em" viewBox="0 0 256 208"><path d="M205.28 31.36c14.096 14.88 20.016 35.2 22.512 63.68c6.626 0 12.805 1.47 16.976 7.152l7.792 10.56A17.548 17.548 0 0 1 256 123.2v28.688c-.008 3.704-1.843 7.315-4.832 9.504C215.885 187.222 172.35 208 128 208c-49.066 0-98.19-28.273-123.168-46.608c-2.989-2.189-4.825-5.8-4.832-9.504V123.2c0-3.776 1.2-7.424 3.424-10.464l7.792-10.544c4.173-5.657 10.38-7.152 16.992-7.152c2.496-28.48 8.4-48.8 22.512-63.68C77.331 3.165 112.567.06 127.552 0H128c14.72 0 50.4 2.88 77.28 31.36Zm-77.264 47.376c-3.04 0-6.544.176-10.272.544c-1.312 4.896-3.248 9.312-6.08 12.128c-11.2 11.2-24.704 12.928-31.936 12.928c-6.802 0-13.927-1.42-19.744-5.088c-5.502 1.808-10.786 4.415-11.136 10.912c-.586 12.28-.637 24.55-.688 36.824c-.026 6.16-.05 12.322-.144 18.488c.024 3.579 2.182 6.903 5.44 8.384C79.936 185.92 104.976 192 128.016 192c23.008 0 48.048-6.08 74.512-18.144c3.258-1.48 5.415-4.805 5.44-8.384c.317-18.418.062-36.912-.816-55.312h.016c-.342-6.534-5.648-9.098-11.168-10.912c-5.82 3.652-12.927 5.088-19.728 5.088c-7.232 0-20.72-1.728-31.936-12.928c-2.832-2.816-4.768-7.232-6.08-12.128a106.26 106.26 0 0 0-10.24-.544Zm-26.941 43.93c5.748 0 10.408 4.66 10.408 10.409v19.183c0 5.749-4.66 10.409-10.408 10.409c-5.748 0-10.408-4.66-10.408-10.409v-19.183c0-5.748 4.66-10.408 10.408-10.408Zm53.333 0c5.749 0 10.409 4.66 10.409 10.409v19.183c0 5.749-4.66 10.409-10.409 10.409c-5.748 0-10.408-4.66-10.408-10.409v-19.183c0-5.748 4.66-10.408 10.408-10.408ZM81.44 28.32c-11.2 1.12-20.64 4.8-25.44 9.92c-10.4 11.36-8.16 40.16-2.24 46.24c4.32 4.32 12.48 7.2 21.28 7.2c6.72 0 19.52-1.44 30.08-12.16c4.64-4.48 7.52-15.68 7.2-27.04c-.32-9.12-2.88-16.64-6.72-19.84c-4.16-3.68-13.6-5.28-24.16-4.32Zm68.96 4.32c-3.84 3.2-6.4 10.72-6.72 19.84c-.32 11.36 2.56 22.56 7.2 27.04c10.56 10.72 23.36 12.16 30.08 12.16c8.8 0 16.96-2.88 21.28-7.2c5.92-6.08 8.16-34.88-2.24-46.24c-4.8-5.12-14.24-8.8-25.44-9.92c-10.56-.96-20 .64-24.16 4.32ZM128 56c-2.56 0-5.6.16-8.96.48c.32 1.76.48 3.68.64 5.76c0 1.44 0 2.88-.16 4.48c3.2-.32 5.92-.32 8.48-.32c2.56 0 5.28 0 8.48.32c-.16-1.6-.16-3.04-.16-4.48c.16-2.08.32-4 .64-5.76c-3.36-.32-6.4-.48-8.96-.48Z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
5
assets/icons/link_out_12.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.5 1H7.5H8.75C8.88807 1 9 1.11193 9 1.25V4.5" stroke="#838994" stroke-linecap="round"/>
|
||||
<path d="M3.64645 5.64645C3.45118 5.84171 3.45118 6.15829 3.64645 6.35355C3.84171 6.54882 4.15829 6.54882 4.35355 6.35355L3.64645 5.64645ZM8.64645 0.646447L3.64645 5.64645L4.35355 6.35355L9.35355 1.35355L8.64645 0.646447Z" fill="#838994"/>
|
||||
<path d="M7.5 6.5V9C7.5 9.27614 7.27614 9.5 7 9.5H1C0.723858 9.5 0.5 9.27614 0.5 9V3C0.5 2.72386 0.723858 2.5 1 2.5H3.5" stroke="#838994" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 605 B |
14
assets/icons/zed_plus_copilot_32.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="93" height="32" viewBox="0 0 93 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.03996 7.04962C8.00936 7.67635 7.30396 8.63219 7.30396 10.0149C7.30396 11.6908 7.72425 12.5893 8.2047 13.0744C8.68381 13.5581 9.40526 13.8149 10.4054 13.8149C11.815 13.8149 13.0291 13.5336 13.8802 12.9464C14.6756 12.3977 15.2708 11.5042 15.3438 9.96182C15.3991 8.79382 15.3678 8.01341 15.0568 7.45711C14.8094 7.01449 14.2326 6.47436 12.4901 6.27416C11.4684 6.15678 10.1114 6.39804 9.03996 7.04962ZM7.87312 5.13084C9.39147 4.2075 11.2531 3.87155 12.7464 4.04312C14.8843 4.28874 16.2844 5.05049 17.0171 6.36142C17.6863 7.55867 17.6384 8.98348 17.587 10.068C17.484 12.2439 16.5804 13.8118 15.1554 14.7949C13.7861 15.7396 12.0582 16.0606 10.4054 16.0606C9.04201 16.0606 7.65128 15.7069 6.60913 14.6547C5.56832 13.6038 5.05825 12.0408 5.05825 10.0149C5.05825 7.6958 6.3139 6.07903 7.87312 5.13084Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.983 18.2811C14.6595 18.2811 15.2079 18.8295 15.2079 19.506V22.16C15.2079 22.8365 14.6595 23.385 13.983 23.385C13.3065 23.385 12.758 22.8365 12.758 22.16V19.506C12.758 18.8295 13.3065 18.2811 13.983 18.2811Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25566 14.8903C5.48361 14.039 5.99218 12.9059 7.20734 12.5361L7.92068 14.8798C7.92068 14.8798 7.91996 14.8801 7.92068 14.8798L7.92375 14.8785C7.92411 14.8783 7.92375 14.8785 7.92375 14.8785C7.92374 14.8785 7.92322 14.8778 7.92068 14.8798C7.91364 14.8852 7.89133 14.9052 7.85878 14.951C7.78816 15.0505 7.70057 15.2311 7.62216 15.524C7.46354 16.1164 7.40614 16.9168 7.40616 17.7813V22.1675C7.43456 22.3571 7.45082 22.438 7.48148 22.5189C7.50697 22.5862 7.55954 22.695 7.72276 22.8617C8.09379 23.2406 8.94055 23.8264 10.9275 24.7081C12.5399 25.4236 13.2749 25.7456 13.9586 25.9166C14.6053 26.0784 15.2382 26.1115 16.6666 26.1115V28.5613C15.2536 28.5613 14.3395 28.5372 13.3641 28.2932C12.452 28.0651 11.5174 27.6502 10.0614 27.004C10.0193 26.9853 9.97679 26.9664 9.93382 26.9474C7.8976 26.0438 6.68669 25.3053 5.97232 24.5757C5.59284 24.1882 5.34578 23.7967 5.19055 23.387C5.05418 23.0271 5.0055 22.6875 4.97509 22.4754C4.973 22.4608 4.97099 22.4468 4.96905 22.4335L4.95629 22.3458V17.7814C4.95629 17.7814 4.95629 17.7814 4.95629 17.7814C4.95627 16.8606 5.012 15.8003 5.25566 14.8903Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.98306 15.3589C4.62844 14.6695 5.39976 13.9884 5.99652 13.6348L7.24552 15.7424C6.93706 15.9252 6.34938 16.4159 5.77155 17.0332C5.21994 17.6224 4.85695 18.1377 4.73071 18.4254C4.70277 18.6674 4.71195 19.116 4.78977 19.648C4.87834 20.2536 5.01731 20.7033 5.10492 20.8628L5.19261 21.0224L5.23006 21.2007C5.28661 21.4698 5.55952 21.8651 6.30146 22.3907C6.88143 22.8015 7.56502 23.1703 8.29605 23.5648C8.45794 23.6521 8.62215 23.7407 8.78809 23.8313C9.5952 24.2156 10.848 24.7773 12.0191 25.2425C12.613 25.4784 13.1698 25.6831 13.6288 25.8268C13.8584 25.8987 14.0505 25.9512 14.2021 25.9847C14.3503 26.0175 14.4185 26.0227 14.4277 26.0234C14.4288 26.0234 14.4281 26.0234 14.4277 26.0234L14.4287 28.4733C13.9646 28.4733 13.3889 28.3188 12.8968 28.1648C12.3562 27.9955 11.7367 27.7664 11.1147 27.5193C9.86871 27.0244 8.54832 26.4314 7.70298 26.028L7.67249 26.0135L7.64284 25.9973C7.49779 25.918 7.34502 25.8357 7.18702 25.7506C6.4505 25.354 5.60004 24.896 4.88539 24.3898C4.08363 23.8219 3.18153 23.0135 2.87437 21.8785C2.61828 21.3365 2.4557 20.618 2.3657 20.0026C2.26537 19.3167 2.2169 18.4951 2.33888 17.8727L2.35434 17.7938L2.37996 17.7176C2.6522 16.9085 3.36219 16.0221 3.98306 15.3589Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.48531 21.264V17.1402H5.93518V21.264H3.48531Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.2932 7.04962C25.3238 7.67635 26.0292 8.63219 26.0292 10.0149C26.0292 11.6908 25.609 12.5893 25.1285 13.0744C24.6494 13.5581 23.9279 13.8149 22.9278 13.8149C21.5182 13.8149 20.3041 13.5336 19.453 12.9464C18.6576 12.3977 18.0624 11.5042 17.9894 9.96182C17.9341 8.79382 17.9654 8.01341 18.2764 7.45711C18.5238 7.01449 19.1006 6.47436 20.8431 6.27416C21.8648 6.15678 23.2218 6.39804 24.2932 7.04962ZM25.4601 5.13084C23.9417 4.2075 22.0801 3.87155 20.5868 4.04312C18.4489 4.28874 17.0488 5.05049 16.3161 6.36142C15.6469 7.55867 15.6948 8.98348 15.7462 10.068C15.8492 12.2439 16.7528 13.8118 18.1778 14.7949C19.5471 15.7396 21.275 16.0606 22.9278 16.0606C24.2912 16.0606 25.6819 15.7069 26.7241 14.6547C27.7649 13.6038 28.275 12.0408 28.275 10.0149C28.275 7.6958 27.0193 6.07903 25.4601 5.13084Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3502 18.2811C18.6737 18.2811 18.1253 18.8295 18.1253 19.506V22.16C18.1253 22.8365 18.6737 23.385 19.3502 23.385C20.0267 23.385 20.5752 22.8365 20.5752 22.16V19.506C20.5752 18.8295 20.0267 18.2811 19.3502 18.2811Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.0775 14.8903C27.8496 14.039 27.341 12.9059 26.1259 12.5361L25.4125 14.8798C25.4125 14.8798 25.4132 14.8801 25.4125 14.8798L25.4095 14.8785C25.4091 14.8783 25.4095 14.8785 25.4095 14.8785C25.4095 14.8785 25.41 14.8778 25.4125 14.8798C25.4196 14.8852 25.4419 14.9052 25.4744 14.951C25.545 15.0505 25.6326 15.2311 25.711 15.524C25.8697 16.1164 25.9271 16.9168 25.927 17.7813V22.1675C25.8986 22.3571 25.8824 22.438 25.8517 22.5189C25.8262 22.5862 25.7737 22.695 25.6104 22.8617C25.2394 23.2406 24.3927 23.8264 22.4057 24.7081C20.7933 25.4236 20.0583 25.7456 19.3746 25.9166C18.7279 26.0784 18.0949 26.1115 16.6666 26.1115V28.5613C18.0796 28.5613 18.9937 28.5372 19.9691 28.2932C20.8812 28.0651 21.8158 27.6502 23.2718 27.004C23.3139 26.9853 23.3564 26.9664 23.3994 26.9474C25.4356 26.0438 26.6465 25.3053 27.3609 24.5757C27.7404 24.1882 27.9874 23.7967 28.1427 23.387C28.279 23.0271 28.3277 22.6875 28.3581 22.4754C28.3602 22.4608 28.3622 22.4468 28.3642 22.4335L28.3769 22.3458V17.7814C28.3769 17.7814 28.3769 17.7814 28.3769 17.7814C28.3769 16.8606 28.3212 15.8003 28.0775 14.8903Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.3501 15.3589C28.7048 14.6695 27.9334 13.9884 27.3367 13.6348L26.0877 15.7424C26.3961 15.9252 26.9838 16.4159 27.5616 17.0332C28.1133 17.6224 28.4763 18.1377 28.6025 18.4254C28.6304 18.6674 28.6213 19.116 28.5434 19.648C28.4549 20.2536 28.3159 20.7033 28.2283 20.8628L28.1406 21.0224L28.1031 21.2007C28.0466 21.4698 27.7737 21.8651 27.0317 22.3907C26.4518 22.8015 25.7682 23.1703 25.0372 23.5648C24.8753 23.6521 24.711 23.7407 24.5451 23.8313C23.738 24.2156 22.4852 24.7773 21.3141 25.2425C20.7202 25.4784 20.1634 25.6831 19.7044 25.8268C19.4748 25.8987 19.2827 25.9512 19.1311 25.9847C18.9829 26.0175 18.9147 26.0227 18.9055 26.0234C18.9044 26.0234 18.9051 26.0234 18.9055 26.0234L18.9045 28.4733C19.3686 28.4733 19.9443 28.3188 20.4364 28.1648C20.977 27.9955 21.5965 27.7664 22.2185 27.5193C23.4645 27.0244 24.7849 26.4314 25.6302 26.028L25.6607 26.0135L25.6904 25.9973C25.8354 25.918 25.9882 25.8357 26.1462 25.7506C26.8827 25.354 27.7332 24.896 28.4478 24.3898C29.2496 23.8219 30.1517 23.0135 30.4588 21.8785C30.7149 21.3365 30.8775 20.618 30.9675 20.0026C31.0678 19.3167 31.1163 18.4951 30.9943 17.8727L30.9789 17.7938L30.9532 17.7176C30.681 16.9085 29.971 16.0221 29.3501 15.3589Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8479 21.264V17.1402H27.398V21.264H29.8479Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.6666 11C49.2189 11 49.6666 11.4477 49.6666 12V15H52.6666C53.2189 15 53.6666 15.4477 53.6666 16C53.6666 16.5523 53.2189 17 52.6666 17H49.6666V20C49.6666 20.5523 49.2189 21 48.6666 21C48.1143 21 47.6666 20.5523 47.6666 20V17H44.6666C44.1143 17 43.6666 16.5523 43.6666 16C43.6666 15.4477 44.1143 15 44.6666 15H47.6666V12C47.6666 11.4477 48.1143 11 48.6666 11Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.1666 4.33329C66.7064 4.33329 66.3333 4.70639 66.3333 5.16663V23.5H64.6666V5.16663C64.6666 3.78591 65.7859 2.66663 67.1666 2.66663H89.494C90.6077 2.66663 91.1654 4.01306 90.3779 4.80051L76.6264 18.552H80.5V16.8333H82.1666V18.9687C82.1666 19.6591 81.607 20.2187 80.9166 20.2187H74.9597L72.0951 23.0833H85.0833V12.6666H86.75V23.0833C86.75 24.0038 86.0038 24.75 85.0833 24.75H70.4285L67.5118 27.6666H88.8333C89.2935 27.6666 89.6666 27.2935 89.6666 26.8333V8.49996H91.3333V26.8333C91.3333 28.214 90.214 29.3333 88.8333 29.3333H66.5059C65.3922 29.3333 64.8345 27.9869 65.622 27.1994L79.3214 13.5H75.5V15.1666H73.8333V13.0833C73.8333 12.3929 74.3929 11.8333 75.0833 11.8333H80.9881L83.9048 8.91663H70.9166V19.3333H69.25V8.91663C69.25 7.99615 69.9962 7.24996 70.9166 7.24996H85.5714L88.4881 4.33329H67.1666Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
@@ -1,325 +1,325 @@
|
||||
[
|
||||
{
|
||||
"context": "Editor && VimControl && !VimWaiting",
|
||||
"bindings": {
|
||||
"g": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Namespace": "G"
|
||||
}
|
||||
],
|
||||
"i": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Object": {
|
||||
"around": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"a": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Object": {
|
||||
"around": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"h": "vim::Left",
|
||||
"backspace": "vim::Backspace",
|
||||
"j": "vim::Down",
|
||||
"enter": "vim::NextLineStart",
|
||||
"k": "vim::Up",
|
||||
"l": "vim::Right",
|
||||
"$": "vim::EndOfLine",
|
||||
"shift-g": "vim::EndOfDocument",
|
||||
"w": "vim::NextWordStart",
|
||||
"shift-w": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"e": "vim::NextWordEnd",
|
||||
"shift-e": [
|
||||
"vim::NextWordEnd",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"b": "vim::PreviousWordStart",
|
||||
"shift-b": [
|
||||
"vim::PreviousWordStart",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"%": "vim::Matching",
|
||||
"ctrl-y": [
|
||||
"vim::Scroll",
|
||||
"LineUp"
|
||||
],
|
||||
"f": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindForward": {
|
||||
"before": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"t": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindForward": {
|
||||
"before": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"shift-f": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindBackward": {
|
||||
"after": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"shift-t": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindBackward": {
|
||||
"after": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"escape": "editor::Cancel",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
"1": [
|
||||
"vim::Number",
|
||||
1
|
||||
],
|
||||
"2": [
|
||||
"vim::Number",
|
||||
2
|
||||
],
|
||||
"3": [
|
||||
"vim::Number",
|
||||
3
|
||||
],
|
||||
"4": [
|
||||
"vim::Number",
|
||||
4
|
||||
],
|
||||
"5": [
|
||||
"vim::Number",
|
||||
5
|
||||
],
|
||||
"6": [
|
||||
"vim::Number",
|
||||
6
|
||||
],
|
||||
"7": [
|
||||
"vim::Number",
|
||||
7
|
||||
],
|
||||
"8": [
|
||||
"vim::Number",
|
||||
8
|
||||
],
|
||||
"9": [
|
||||
"vim::Number",
|
||||
9
|
||||
]
|
||||
{
|
||||
"context": "Editor && VimControl && !VimWaiting",
|
||||
"bindings": {
|
||||
"g": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Namespace": "G"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
||||
"bindings": {
|
||||
"c": [
|
||||
"vim::PushOperator",
|
||||
"Change"
|
||||
],
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": [
|
||||
"vim::PushOperator",
|
||||
"Delete"
|
||||
],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"y": [
|
||||
"vim::PushOperator",
|
||||
"Yank"
|
||||
],
|
||||
"z": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Namespace": "Z"
|
||||
}
|
||||
],
|
||||
"i": [
|
||||
"vim::SwitchMode",
|
||||
"Insert"
|
||||
],
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"x": "vim::DeleteRight",
|
||||
"shift-x": "vim::DeleteLeft",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"v": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Visual": {
|
||||
"line": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"shift-v": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Visual": {
|
||||
"line": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"p": "vim::Paste",
|
||||
"u": "editor::Undo",
|
||||
"ctrl-r": "editor::Redo",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"/": [
|
||||
"buffer_search::Deploy",
|
||||
{
|
||||
"focus": true
|
||||
}
|
||||
],
|
||||
"ctrl-f": [
|
||||
"vim::Scroll",
|
||||
"PageDown"
|
||||
],
|
||||
"ctrl-b": [
|
||||
"vim::Scroll",
|
||||
"PageUp"
|
||||
],
|
||||
"ctrl-d": [
|
||||
"vim::Scroll",
|
||||
"HalfPageDown"
|
||||
],
|
||||
"ctrl-u": [
|
||||
"vim::Scroll",
|
||||
"HalfPageUp"
|
||||
],
|
||||
"ctrl-e": [
|
||||
"vim::Scroll",
|
||||
"LineDown"
|
||||
],
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
]
|
||||
],
|
||||
"i": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Object": {
|
||||
"around": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == n",
|
||||
"bindings": {
|
||||
"0": [
|
||||
"vim::Number",
|
||||
0
|
||||
]
|
||||
],
|
||||
"a": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Object": {
|
||||
"around": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == g",
|
||||
"bindings": {
|
||||
"g": "vim::StartOfDocument",
|
||||
"h": "editor::Hover",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"d": "editor::GoToDefinition"
|
||||
],
|
||||
"h": "vim::Left",
|
||||
"backspace": "vim::Backspace",
|
||||
"j": "vim::Down",
|
||||
"enter": "vim::NextLineStart",
|
||||
"k": "vim::Up",
|
||||
"l": "vim::Right",
|
||||
"$": "vim::EndOfLine",
|
||||
"shift-g": "vim::EndOfDocument",
|
||||
"w": "vim::NextWordStart",
|
||||
"shift-w": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == c",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine"
|
||||
],
|
||||
"e": "vim::NextWordEnd",
|
||||
"shift-e": [
|
||||
"vim::NextWordEnd",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine"
|
||||
],
|
||||
"b": "vim::PreviousWordStart",
|
||||
"shift-b": [
|
||||
"vim::PreviousWordStart",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == y",
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine"
|
||||
],
|
||||
"%": "vim::Matching",
|
||||
"ctrl-y": [
|
||||
"vim::Scroll",
|
||||
"LineUp"
|
||||
],
|
||||
"f": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindForward": {
|
||||
"before": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == z",
|
||||
"bindings": {
|
||||
"t": "editor::ScrollCursorTop",
|
||||
"z": "editor::ScrollCursorCenter",
|
||||
"b": "editor::ScrollCursorBottom",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
],
|
||||
"t": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindForward": {
|
||||
"before": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimObject",
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": [
|
||||
"vim::Word",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"s": "vim::Sentence",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
"\"": "vim::DoubleQuotes",
|
||||
"(": "vim::Parentheses",
|
||||
")": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets"
|
||||
],
|
||||
"shift-f": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindBackward": {
|
||||
"after": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == visual && !VimWaiting",
|
||||
"bindings": {
|
||||
"u": "editor::Undo",
|
||||
"c": "vim::VisualChange",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
"y": "vim::VisualYank",
|
||||
"p": "vim::VisualPaste",
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == insert",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimWaiting",
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": "editor::Cancel"
|
||||
],
|
||||
"shift-t": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"FindBackward": {
|
||||
"after": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"escape": "editor::Cancel",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
"1": [
|
||||
"vim::Number",
|
||||
1
|
||||
],
|
||||
"2": [
|
||||
"vim::Number",
|
||||
2
|
||||
],
|
||||
"3": [
|
||||
"vim::Number",
|
||||
3
|
||||
],
|
||||
"4": [
|
||||
"vim::Number",
|
||||
4
|
||||
],
|
||||
"5": [
|
||||
"vim::Number",
|
||||
5
|
||||
],
|
||||
"6": [
|
||||
"vim::Number",
|
||||
6
|
||||
],
|
||||
"7": [
|
||||
"vim::Number",
|
||||
7
|
||||
],
|
||||
"8": [
|
||||
"vim::Number",
|
||||
8
|
||||
],
|
||||
"9": [
|
||||
"vim::Number",
|
||||
9
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
||||
"bindings": {
|
||||
"c": [
|
||||
"vim::PushOperator",
|
||||
"Change"
|
||||
],
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": [
|
||||
"vim::PushOperator",
|
||||
"Delete"
|
||||
],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"y": [
|
||||
"vim::PushOperator",
|
||||
"Yank"
|
||||
],
|
||||
"z": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Namespace": "Z"
|
||||
}
|
||||
],
|
||||
"i": [
|
||||
"vim::SwitchMode",
|
||||
"Insert"
|
||||
],
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"x": "vim::DeleteRight",
|
||||
"shift-x": "vim::DeleteLeft",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"v": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Visual": {
|
||||
"line": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"shift-v": [
|
||||
"vim::SwitchMode",
|
||||
{
|
||||
"Visual": {
|
||||
"line": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"p": "vim::Paste",
|
||||
"u": "editor::Undo",
|
||||
"ctrl-r": "editor::Redo",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"/": [
|
||||
"buffer_search::Deploy",
|
||||
{
|
||||
"focus": true
|
||||
}
|
||||
],
|
||||
"ctrl-f": [
|
||||
"vim::Scroll",
|
||||
"PageDown"
|
||||
],
|
||||
"ctrl-b": [
|
||||
"vim::Scroll",
|
||||
"PageUp"
|
||||
],
|
||||
"ctrl-d": [
|
||||
"vim::Scroll",
|
||||
"HalfPageDown"
|
||||
],
|
||||
"ctrl-u": [
|
||||
"vim::Scroll",
|
||||
"HalfPageUp"
|
||||
],
|
||||
"ctrl-e": [
|
||||
"vim::Scroll",
|
||||
"LineDown"
|
||||
],
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == n",
|
||||
"bindings": {
|
||||
"0": [
|
||||
"vim::Number",
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == g",
|
||||
"bindings": {
|
||||
"g": "vim::StartOfDocument",
|
||||
"h": "editor::Hover",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"d": "editor::GoToDefinition"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == c",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == y",
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == z",
|
||||
"bindings": {
|
||||
"t": "editor::ScrollCursorTop",
|
||||
"z": "editor::ScrollCursorCenter",
|
||||
"b": "editor::ScrollCursorBottom",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimObject",
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": [
|
||||
"vim::Word",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"s": "vim::Sentence",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
"\"": "vim::DoubleQuotes",
|
||||
"(": "vim::Parentheses",
|
||||
")": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == visual && !VimWaiting",
|
||||
"bindings": {
|
||||
"u": "editor::Undo",
|
||||
"c": "vim::VisualChange",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
"y": "vim::VisualYank",
|
||||
"p": "vim::VisualPaste",
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == insert",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimWaiting",
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": "editor::Cancel"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,246 +1,257 @@
|
||||
{
|
||||
// The name of the Zed theme to use for the UI
|
||||
"theme": "One Dark",
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Mono",
|
||||
// The OpenType features to enable for text in the editor.
|
||||
"buffer_font_features": {
|
||||
// Disable ligatures:
|
||||
// "calt": false
|
||||
},
|
||||
// The default font size for text in the editor
|
||||
"buffer_font_size": 15,
|
||||
// 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,
|
||||
// Whether to enable vim modes and key bindings
|
||||
"vim_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
"hover_popover_enabled": true,
|
||||
// Whether to confirm before quitting Zed.
|
||||
"confirm_quit": false,
|
||||
// Whether the cursor blinks in the editor.
|
||||
"cursor_blink": true,
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
"enable_language_server": true,
|
||||
// When to automatically save edited buffers. This setting can
|
||||
// take four values.
|
||||
//
|
||||
// 1. Never automatically save:
|
||||
// "autosave": "off",
|
||||
// 2. Save when changing focus away from the Zed window:
|
||||
// "autosave": "on_window_change",
|
||||
// 3. Save when changing focus away from a specific buffer:
|
||||
// "autosave": "on_focus_change",
|
||||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Where to place the dock by default. This setting can take three
|
||||
// values:
|
||||
//
|
||||
// 1. Position the dock attached to the bottom of the workspace
|
||||
// "default_dock_anchor": "bottom"
|
||||
// 2. Position the dock to the right of the workspace like a side panel
|
||||
// "default_dock_anchor": "right"
|
||||
// 3. Position the dock full screen over the entire workspace"
|
||||
// "default_dock_anchor": "expanded"
|
||||
"default_dock_anchor": "bottom",
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
// Whether or not to ensure there's a single newline at the end of a buffer
|
||||
// when saving it.
|
||||
"ensure_final_newline_on_save": true,
|
||||
// Whether or not to perform a buffer format before saving
|
||||
"format_on_save": "on",
|
||||
// How to perform a buffer format. This setting can take two values:
|
||||
//
|
||||
// 1. Format code using the current language server:
|
||||
// "format_on_save": "language_server"
|
||||
// 2. Format code using an external command:
|
||||
// "format_on_save": {
|
||||
// "external": {
|
||||
// "command": "prettier",
|
||||
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
||||
// }
|
||||
// The name of the Zed theme to use for the UI
|
||||
"theme": "One Dark",
|
||||
// Features that can be globally enabled or disabled
|
||||
"features": {
|
||||
// Show Copilot icon in status bar
|
||||
"copilot": true
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Mono",
|
||||
// The OpenType features to enable for text in the editor.
|
||||
"buffer_font_features": {
|
||||
// Disable ligatures:
|
||||
// "calt": false
|
||||
},
|
||||
// The default font size for text in the editor
|
||||
"buffer_font_size": 15,
|
||||
// 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,
|
||||
// Whether to enable vim modes and key bindings
|
||||
"vim_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
"hover_popover_enabled": true,
|
||||
// Whether to confirm before quitting Zed.
|
||||
"confirm_quit": false,
|
||||
// Whether the cursor blinks in the editor.
|
||||
"cursor_blink": true,
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Controls whether copilot provides suggestion immediately
|
||||
// or waits for a `copilot::Toggle`
|
||||
"show_copilot_suggestions": true,
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
"enable_language_server": true,
|
||||
// When to automatically save edited buffers. This setting can
|
||||
// take four values.
|
||||
//
|
||||
// 1. Never automatically save:
|
||||
// "autosave": "off",
|
||||
// 2. Save when changing focus away from the Zed window:
|
||||
// "autosave": "on_window_change",
|
||||
// 3. Save when changing focus away from a specific buffer:
|
||||
// "autosave": "on_focus_change",
|
||||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Where to place the dock by default. This setting can take three
|
||||
// values:
|
||||
//
|
||||
// 1. Position the dock attached to the bottom of the workspace
|
||||
// "default_dock_anchor": "bottom"
|
||||
// 2. Position the dock to the right of the workspace like a side panel
|
||||
// "default_dock_anchor": "right"
|
||||
// 3. Position the dock full screen over the entire workspace"
|
||||
// "default_dock_anchor": "expanded"
|
||||
"default_dock_anchor": "bottom",
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
// Whether or not to ensure there's a single newline at the end of a buffer
|
||||
// when saving it.
|
||||
"ensure_final_newline_on_save": true,
|
||||
// Whether or not to perform a buffer format before saving
|
||||
"format_on_save": "on",
|
||||
// How to perform a buffer format. This setting can take two values:
|
||||
//
|
||||
// 1. Format code using the current language server:
|
||||
// "format_on_save": "language_server"
|
||||
// 2. Format code using an external command:
|
||||
// "format_on_save": {
|
||||
// "external": {
|
||||
// "command": "prettier",
|
||||
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
||||
// }
|
||||
// }
|
||||
"formatter": "language_server",
|
||||
// How to soft-wrap long lines of text. This setting can take
|
||||
// three values:
|
||||
//
|
||||
// 1. Do not soft wrap.
|
||||
// "soft_wrap": "none",
|
||||
// 2. Soft wrap lines that overflow the editor:
|
||||
// "soft_wrap": "editor_width",
|
||||
// 3. Soft wrap lines at the preferred line length
|
||||
// "soft_wrap": "preferred_line_length",
|
||||
"soft_wrap": "none",
|
||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
// is enabled.
|
||||
"preferred_line_length": 80,
|
||||
// Whether to indent lines using tab characters, as opposed to multiple
|
||||
// spaces.
|
||||
"hard_tabs": false,
|
||||
// How many columns a tab should occupy.
|
||||
"tab_size": 4,
|
||||
// Control what info is collected by Zed.
|
||||
"telemetry": {
|
||||
// Send debug info like crash reports.
|
||||
"diagnostics": true,
|
||||
// Send anonymized usage data like what languages you're using Zed with.
|
||||
"metrics": true
|
||||
},
|
||||
// Automatically update Zed
|
||||
"auto_update": true,
|
||||
// Git gutter behavior configuration.
|
||||
"git": {
|
||||
// Control whether the git gutter is shown. May take 2 values:
|
||||
// 1. Show the gutter
|
||||
// "git_gutter": "tracked_files"
|
||||
// 2. Hide the gutter
|
||||
// "git_gutter": "hide"
|
||||
"git_gutter": "tracked_files"
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
// The path of the directory where journal entries are stored
|
||||
"path": "~",
|
||||
// What format to display the hours in
|
||||
// May take 2 values:
|
||||
// 1. hour12
|
||||
// 2. hour24
|
||||
"hour_format": "hour12"
|
||||
},
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
// What shell to use when opening a terminal. May take 3 values:
|
||||
// 1. Use the system's default terminal configuration in /etc/passwd
|
||||
// "shell": "system"
|
||||
// 2. A program:
|
||||
// "shell": {
|
||||
// "program": "sh"
|
||||
// }
|
||||
// 3. A program with arguments:
|
||||
// "shell": {
|
||||
// "with_arguments": {
|
||||
// "program": "/bin/bash",
|
||||
// "arguments": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"formatter": "language_server",
|
||||
// How to soft-wrap long lines of text. This setting can take
|
||||
// three values:
|
||||
"shell": "system",
|
||||
// What working directory to use when launching the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Use the current file's project directory. Will Fallback to the
|
||||
// first project directory strategy if unsuccessful
|
||||
// "working_directory": "current_project_directory"
|
||||
// 2. Use the first project in this workspace's directory
|
||||
// "working_directory": "first_project_directory"
|
||||
// 3. Always use this platform's home directory (if we can find it)
|
||||
// "working_directory": "always_home"
|
||||
// 4. Always use a specific directory. This value will be shell expanded.
|
||||
// If this path is not a valid directory the terminal will default to
|
||||
// this platform's home directory (if we can find it)
|
||||
// "working_directory": {
|
||||
// "always": {
|
||||
// "directory": "~/zed/projects/"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 1. Do not soft wrap.
|
||||
// "soft_wrap": "none",
|
||||
// 2. Soft wrap lines that overflow the editor:
|
||||
// "soft_wrap": "editor_width",
|
||||
// 3. Soft wrap lines at the preferred line length
|
||||
// "soft_wrap": "preferred_line_length",
|
||||
"soft_wrap": "none",
|
||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
// is enabled.
|
||||
"preferred_line_length": 80,
|
||||
// Whether to indent lines using tab characters, as opposed to multiple
|
||||
// spaces.
|
||||
"hard_tabs": false,
|
||||
// How many columns a tab should occupy.
|
||||
"tab_size": 4,
|
||||
// Control what info is collected by Zed.
|
||||
"telemetry": {
|
||||
// Send debug info like crash reports.
|
||||
"diagnostics": true,
|
||||
// Send anonymized usage data like what languages you're using Zed with.
|
||||
"metrics": true
|
||||
},
|
||||
// Automatically update Zed
|
||||
"auto_update": true,
|
||||
// Git gutter behavior configuration.
|
||||
"git": {
|
||||
// Control whether the git gutter is shown. May take 2 values:
|
||||
// 1. Show the gutter
|
||||
// "git_gutter": "tracked_files"
|
||||
// 2. Hide the gutter
|
||||
// "git_gutter": "hide"
|
||||
"git_gutter": "tracked_files"
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
// The path of the directory where journal entries are stored
|
||||
"path": "~",
|
||||
// What format to display the hours in
|
||||
// May take 2 values:
|
||||
// 1. hour12
|
||||
// 2. hour24
|
||||
"hour_format": "hour12"
|
||||
},
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
// What shell to use when opening a terminal. May take 3 values:
|
||||
// 1. Use the system's default terminal configuration (e.g. $TERM).
|
||||
// "shell": "system"
|
||||
// 2. A program:
|
||||
// "shell": {
|
||||
// "program": "sh"
|
||||
// }
|
||||
// 3. A program with arguments:
|
||||
// "shell": {
|
||||
// "with_arguments": {
|
||||
// "program": "/bin/bash",
|
||||
// "arguments": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system",
|
||||
// What working directory to use when launching the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Use the current file's project directory. Will Fallback to the
|
||||
// first project directory strategy if unsuccessful
|
||||
// "working_directory": "current_project_directory"
|
||||
// 2. Use the first project in this workspace's directory
|
||||
// "working_directory": "first_project_directory"
|
||||
// 3. Always use this platform's home directory (if we can find it)
|
||||
// "working_directory": "always_home"
|
||||
// 4. Always use a specific directory. This value will be shell expanded.
|
||||
// If this path is not a valid directory the terminal will default to
|
||||
// this platform's home directory (if we can find it)
|
||||
// "working_directory": {
|
||||
// "always": {
|
||||
// "directory": "~/zed/projects/"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
"working_directory": "current_project_directory",
|
||||
// Set the cursor blinking behavior in the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Never blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "off",
|
||||
// 2. Default the cursor blink to off, but allow the terminal to
|
||||
// set blinking
|
||||
// "blinking": "terminal_controlled",
|
||||
// 3. Always blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "on",
|
||||
"blinking": "terminal_controlled",
|
||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
// presses when in the alternate screen (e.g. when running applications
|
||||
// like vim or less). The terminal can still set and unset this mode.
|
||||
// May take 2 values:
|
||||
// 1. Default alternate scroll mode to on
|
||||
// "alternate_scroll": "on",
|
||||
// 2. Default alternate scroll mode to off
|
||||
// "alternate_scroll": "off",
|
||||
"alternate_scroll": "off",
|
||||
// Set whether the option key behaves as the meta key.
|
||||
// May take 2 values:
|
||||
// 1. Rely on default platform handling of option key, on macOS
|
||||
// this means generating certain unicode characters
|
||||
// "option_to_meta": false,
|
||||
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
|
||||
// "option_to_meta": true,
|
||||
"option_as_meta": false,
|
||||
// Whether or not selecting text in the terminal will automatically
|
||||
// copy to the system clipboard.
|
||||
"copy_on_select": false,
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
// enviroment. Use `:` to seperate multiple values.
|
||||
"env": {
|
||||
// "KEY": "value1:value2"
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": "15"
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"Elixir": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Go": {
|
||||
"tab_size": 4,
|
||||
"hard_tabs": true
|
||||
},
|
||||
"Markdown": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"JavaScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TypeScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TSX": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"YAML": {
|
||||
"tab_size": 2
|
||||
}
|
||||
},
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// As of 8/10/22, supported LSPs are:
|
||||
// pyright
|
||||
// gopls
|
||||
// rust-analyzer
|
||||
// typescript-language-server
|
||||
// vscode-json-languageserver
|
||||
// "rust-analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
// "checkOnSave": {
|
||||
// "command": "clippy"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
"working_directory": "current_project_directory",
|
||||
// Set the cursor blinking behavior in the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Never blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "off",
|
||||
// 2. Default the cursor blink to off, but allow the terminal to
|
||||
// set blinking
|
||||
// "blinking": "terminal_controlled",
|
||||
// 3. Always blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "on",
|
||||
"blinking": "terminal_controlled",
|
||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
// presses when in the alternate screen (e.g. when running applications
|
||||
// like vim or less). The terminal can still set and unset this mode.
|
||||
// May take 2 values:
|
||||
// 1. Default alternate scroll mode to on
|
||||
// "alternate_scroll": "on",
|
||||
// 2. Default alternate scroll mode to off
|
||||
// "alternate_scroll": "off",
|
||||
"alternate_scroll": "off",
|
||||
// Set whether the option key behaves as the meta key.
|
||||
// May take 2 values:
|
||||
// 1. Rely on default platform handling of option key, on macOS
|
||||
// this means generating certain unicode characters
|
||||
// "option_to_meta": false,
|
||||
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
|
||||
// "option_to_meta": true,
|
||||
"option_as_meta": false,
|
||||
// Whether or not selecting text in the terminal will automatically
|
||||
// copy to the system clipboard.
|
||||
"copy_on_select": false,
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
// enviroment. Use `:` to seperate multiple values.
|
||||
"env": {
|
||||
// "KEY": "value1:value2"
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": "15"
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"Elixir": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Go": {
|
||||
"tab_size": 4,
|
||||
"hard_tabs": true
|
||||
},
|
||||
"Markdown": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"JavaScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TypeScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TSX": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"YAML": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"JSON": {
|
||||
"tab_size": 2
|
||||
}
|
||||
},
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// As of 8/10/22, supported LSPs are:
|
||||
// pyright
|
||||
// gopls
|
||||
// rust-analyzer
|
||||
// typescript-language-server
|
||||
// vscode-json-languageserver
|
||||
// "rust-analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
// "checkOnSave": {
|
||||
// "command": "clippy"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
// custom settings, run the `open default settings` command
|
||||
// from the command palette or from `Zed` application menu.
|
||||
{
|
||||
"buffer_font_size": 15
|
||||
"buffer_font_size": 15
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@ settings = { path = "../settings" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
futures = "0.3"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smallvec = { workspace = true }
|
||||
|
||||
@@ -2,8 +2,10 @@ use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
|
||||
use editor::Editor;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, elements::*, platform::CursorStyle, Action, AppContext, Entity, ModelHandle,
|
||||
MouseButton, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
||||
actions,
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AppContext, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
@@ -46,7 +48,7 @@ struct Content {
|
||||
action: Option<Box<dyn Action>>,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ActivityIndicator::show_error_message);
|
||||
cx.add_action(ActivityIndicator::dismiss_error_message);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ anyhow = "1.0.38"
|
||||
isahc = "1.7"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
smol = "1.2.5"
|
||||
tempdir = "0.3.7"
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
mod update_notification;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN};
|
||||
use client::{ZED_APP_PATH, ZED_APP_VERSION};
|
||||
use client::{ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||
MutableAppContext, Task, WeakViewHandle,
|
||||
Task, WeakViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
@@ -14,6 +13,7 @@ use smol::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use std::{ffi::OsString, sync::Arc, time::Duration};
|
||||
use update_notification::UpdateNotification;
|
||||
use util::channel::ReleaseChannel;
|
||||
use util::http::HttpClient;
|
||||
use workspace::Workspace;
|
||||
|
||||
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
|
||||
@@ -49,7 +49,7 @@ impl Entity for AutoUpdater {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut MutableAppContext) {
|
||||
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
|
||||
if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) {
|
||||
let server_url = server_url;
|
||||
let auto_updater = cx.add_model(|cx| {
|
||||
@@ -63,10 +63,10 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut Mutab
|
||||
cx.observe_global::<Settings, _>(move |updater, cx| {
|
||||
if cx.global::<Settings>().auto_update {
|
||||
if update_subscription.is_none() {
|
||||
*(&mut update_subscription) = Some(updater.start_polling(cx))
|
||||
update_subscription = Some(updater.start_polling(cx))
|
||||
}
|
||||
} else {
|
||||
(&mut update_subscription).take();
|
||||
update_subscription.take();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -95,7 +95,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut Mutab
|
||||
|
||||
pub fn notify_of_any_new_update(
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<()> {
|
||||
let updater = AutoUpdater::get(cx)?;
|
||||
let version = updater.read(cx).current_version;
|
||||
@@ -124,7 +124,7 @@ pub fn notify_of_any_new_update(
|
||||
}
|
||||
|
||||
impl AutoUpdater {
|
||||
pub fn get(cx: &mut MutableAppContext) -> Option<ModelHandle<Self>> {
|
||||
pub fn get(cx: &mut AppContext) -> Option<ModelHandle<Self>> {
|
||||
cx.default_global::<Option<ModelHandle<Self>>>().clone()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::ViewReleaseNotes;
|
||||
use gpui::{
|
||||
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
|
||||
platform::{AppVersion, CursorStyle},
|
||||
Element, Entity, MouseButton, View, ViewContext,
|
||||
platform::{AppVersion, CursorStyle, MouseButton},
|
||||
Element, Entity, View, ViewContext,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use settings::Settings;
|
||||
|
||||
@@ -18,6 +18,7 @@ search = { path = "../search" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
outline = { path = "../outline" }
|
||||
itertools = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use gpui::{
|
||||
elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
elements::*, platform::MouseButton, AppContext, Entity, RenderContext, Subscription, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use search::ProjectSearchView;
|
||||
@@ -14,6 +15,7 @@ pub enum Event {
|
||||
}
|
||||
|
||||
pub struct Breadcrumbs {
|
||||
pane_focused: bool,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
project_search: Option<ViewHandle<ProjectSearchView>>,
|
||||
subscription: Option<Subscription>,
|
||||
@@ -22,6 +24,7 @@ pub struct Breadcrumbs {
|
||||
impl Breadcrumbs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pane_focused: false,
|
||||
active_item: Default::default(),
|
||||
subscription: Default::default(),
|
||||
project_search: Default::default(),
|
||||
@@ -39,24 +42,53 @@ impl View for Breadcrumbs {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let active_item = match &self.active_item {
|
||||
Some(active_item) => active_item,
|
||||
None => return Empty::new().boxed(),
|
||||
};
|
||||
let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
||||
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
if let Some(breadcrumbs) = self
|
||||
.active_item
|
||||
.as_ref()
|
||||
.and_then(|item| item.breadcrumbs(&theme, cx))
|
||||
{
|
||||
Flex::row()
|
||||
.with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
|
||||
Label::new(" 〉 ", theme.breadcrumbs.text.clone()).boxed()
|
||||
}))
|
||||
.contained()
|
||||
.with_style(theme.breadcrumbs.container)
|
||||
let style = &theme.workspace.breadcrumbs;
|
||||
|
||||
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
|
||||
Some(breadcrumbs) => breadcrumbs,
|
||||
None => return Empty::new().boxed(),
|
||||
};
|
||||
|
||||
let crumbs = Flex::row()
|
||||
.with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
|
||||
Label::new(" 〉 ", style.default.text.clone()).boxed()
|
||||
}))
|
||||
.constrained()
|
||||
.with_height(theme.workspace.breadcrumb_height)
|
||||
.contained();
|
||||
|
||||
if not_editor || !self.pane_focused {
|
||||
return crumbs
|
||||
.with_style(style.default.container)
|
||||
.aligned()
|
||||
.left()
|
||||
.boxed()
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
.boxed();
|
||||
}
|
||||
|
||||
MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| {
|
||||
let style = style.style_for(state, false);
|
||||
crumbs.with_style(style.container).boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, cx| {
|
||||
cx.dispatch_action(outline::Toggle);
|
||||
})
|
||||
.with_tooltip::<Breadcrumbs, _>(
|
||||
0,
|
||||
"Show symbol outline".to_owned(),
|
||||
Some(Box::new(outline::Toggle)),
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.aligned()
|
||||
.left()
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,4 +135,8 @@ impl ToolbarItemView for Breadcrumbs {
|
||||
current_location
|
||||
}
|
||||
}
|
||||
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::AppContext) {
|
||||
self.pane_focused = pane_focused;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ use futures::{future::Shared, FutureExt};
|
||||
use postage::watch;
|
||||
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||
Subscription, Task, WeakModelHandle,
|
||||
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Subscription, Task,
|
||||
WeakModelHandle,
|
||||
};
|
||||
use project::Project;
|
||||
|
||||
pub use participant::ParticipantLocation;
|
||||
pub use room::Room;
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut MutableAppContext) {
|
||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
||||
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(active_call);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ use client::{
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakModelHandle,
|
||||
};
|
||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
|
||||
use language::LanguageRegistry;
|
||||
use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate};
|
||||
use postage::stream::Stream;
|
||||
@@ -64,16 +62,13 @@ pub struct Room {
|
||||
impl Entity for Room {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, cx: &mut MutableAppContext) {
|
||||
fn release(&mut self, cx: &mut AppContext) {
|
||||
if self.status.is_online() {
|
||||
self.leave_internal(cx).detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn app_will_quit(
|
||||
&mut self,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
|
||||
fn app_will_quit(&mut self, cx: &mut AppContext) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
|
||||
if self.status.is_online() {
|
||||
let leave = self.leave_internal(cx);
|
||||
Some(
|
||||
@@ -176,7 +171,7 @@ impl Room {
|
||||
initial_project: Option<ModelHandle<Project>>,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<ModelHandle<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let response = client.request(proto::CreateRoom {}).await?;
|
||||
@@ -219,7 +214,7 @@ impl Room {
|
||||
call: &IncomingCall,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<ModelHandle<Self>>> {
|
||||
let room_id = call.room_id;
|
||||
cx.spawn(|mut cx| async move {
|
||||
@@ -257,7 +252,7 @@ impl Room {
|
||||
self.leave_internal(cx)
|
||||
}
|
||||
|
||||
fn leave_internal(&mut self, cx: &mut MutableAppContext) -> Task<Result<()>> {
|
||||
fn leave_internal(&mut self, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
@@ -424,7 +419,7 @@ impl Room {
|
||||
false
|
||||
});
|
||||
|
||||
let response = self.client.request(proto::RejoinRoom {
|
||||
let response = self.client.request_envelope(proto::RejoinRoom {
|
||||
id: self.id,
|
||||
reshared_projects,
|
||||
rejoined_projects,
|
||||
@@ -432,6 +427,8 @@ impl Room {
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = response.await?;
|
||||
let message_id = response.message_id;
|
||||
let response = response.payload;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = RoomStatus::Online;
|
||||
@@ -448,7 +445,7 @@ impl Room {
|
||||
for rejoined_project in response.rejoined_projects {
|
||||
if let Some(project) = projects.get(&rejoined_project.id) {
|
||||
project.update(cx, |project, cx| {
|
||||
project.rejoined(rejoined_project, cx).log_err();
|
||||
project.rejoined(rejoined_project, message_id, cx).log_err();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ anyhow = "1.0"
|
||||
clap = { version = "3.1", features = ["derive"] }
|
||||
dirs = "3.0"
|
||||
ipc-channel = "0.16"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
|
||||
@@ -17,13 +17,13 @@ db = { path = "../db" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
rpc = { path = "../rpc" }
|
||||
staff_mode = { path = "../staff_mode" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
anyhow = "1.0.38"
|
||||
async-recursion = "0.3"
|
||||
async-tungstenite = { version = "0.16", features = ["async-tls"] }
|
||||
futures = "0.3"
|
||||
image = "0.23"
|
||||
isahc = "1.7"
|
||||
lazy_static = "1.4.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
parking_lot = "0.11.1"
|
||||
@@ -35,8 +35,8 @@ time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
tiny_http = "0.8"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
url = "2.2"
|
||||
serde = { version = "*", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
settings = { path = "../settings" }
|
||||
tempfile = "3"
|
||||
|
||||
@@ -45,3 +45,4 @@ collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
|
||||
pub mod http;
|
||||
pub mod telemetry;
|
||||
pub mod user;
|
||||
|
||||
@@ -11,14 +10,17 @@ use async_tungstenite::tungstenite::{
|
||||
error::Error as WebsocketError,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||
use futures::{
|
||||
future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
|
||||
TryStreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions,
|
||||
platform::AppVersion,
|
||||
serde_json::{self, Value},
|
||||
AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AppVersion,
|
||||
AsyncAppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle,
|
||||
AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
|
||||
AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use http::HttpClient;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
@@ -41,6 +43,7 @@ use telemetry::Telemetry;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use util::channel::ReleaseChannel;
|
||||
use util::http::HttpClient;
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub use rpc::*;
|
||||
@@ -68,7 +71,7 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
actions!(client, [SignIn, SignOut]);
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
cx.add_global_action({
|
||||
let client = client.clone();
|
||||
move |_: &SignIn, cx| {
|
||||
@@ -130,7 +133,7 @@ pub enum EstablishConnectionError {
|
||||
#[error("{0}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
#[error("{0}")]
|
||||
Http(#[from] http::Error),
|
||||
Http(#[from] util::http::Error),
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
@@ -294,7 +297,7 @@ impl<T: Entity> PendingEntitySubscription<T> {
|
||||
|
||||
state
|
||||
.entities_by_type_and_remote_id
|
||||
.insert(id, WeakSubscriber::Model(model.downgrade().into()));
|
||||
.insert(id, WeakSubscriber::Model(model.downgrade().into_any()));
|
||||
drop(state);
|
||||
for message in messages {
|
||||
self.client.handle_message(message, cx);
|
||||
@@ -461,7 +464,7 @@ impl Client {
|
||||
self.state
|
||||
.write()
|
||||
.entities_by_type_and_remote_id
|
||||
.insert(id, WeakSubscriber::View(cx.weak_handle().into()));
|
||||
.insert(id, WeakSubscriber::View(cx.weak_handle().into_any()));
|
||||
Subscription::Entity {
|
||||
client: Arc::downgrade(self),
|
||||
id,
|
||||
@@ -471,18 +474,22 @@ impl Client {
|
||||
pub fn subscribe_to_entity<T: Entity>(
|
||||
self: &Arc<Self>,
|
||||
remote_id: u64,
|
||||
) -> PendingEntitySubscription<T> {
|
||||
) -> Result<PendingEntitySubscription<T>> {
|
||||
let id = (TypeId::of::<T>(), remote_id);
|
||||
self.state
|
||||
.write()
|
||||
.entities_by_type_and_remote_id
|
||||
.insert(id, WeakSubscriber::Pending(Default::default()));
|
||||
|
||||
PendingEntitySubscription {
|
||||
client: self.clone(),
|
||||
remote_id,
|
||||
consumed: false,
|
||||
_entity_type: PhantomData,
|
||||
let mut state = self.state.write();
|
||||
if state.entities_by_type_and_remote_id.contains_key(&id) {
|
||||
return Err(anyhow!("already subscribed to entity"));
|
||||
} else {
|
||||
state
|
||||
.entities_by_type_and_remote_id
|
||||
.insert(id, WeakSubscriber::Pending(Default::default()));
|
||||
Ok(PendingEntitySubscription {
|
||||
client: self.clone(),
|
||||
remote_id,
|
||||
consumed: false,
|
||||
_entity_type: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +512,7 @@ impl Client {
|
||||
let mut state = self.state.write();
|
||||
state
|
||||
.models_by_message_type
|
||||
.insert(message_type_id, model.downgrade().into());
|
||||
.insert(message_type_id, model.downgrade().into_any());
|
||||
|
||||
let prev_handler = state.message_handlers.insert(
|
||||
message_type_id,
|
||||
@@ -1188,6 +1195,14 @@ impl Client {
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = Result<T::Response>> {
|
||||
self.request_envelope(request)
|
||||
.map_ok(|envelope| envelope.payload)
|
||||
}
|
||||
|
||||
pub fn request_envelope<T: RequestMessage>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
|
||||
let client_id = self.id;
|
||||
log::debug!(
|
||||
"rpc request start. client_id:{}. name:{}",
|
||||
@@ -1196,7 +1211,7 @@ impl Client {
|
||||
);
|
||||
let response = self
|
||||
.connection_id()
|
||||
.map(|conn_id| self.peer.request(conn_id, request));
|
||||
.map(|conn_id| self.peer.request_envelope(conn_id, request));
|
||||
async move {
|
||||
let response = response?.await;
|
||||
log::debug!(
|
||||
@@ -1396,10 +1411,11 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::{FakeHttpClient, FakeServer};
|
||||
use crate::test::FakeServer;
|
||||
use gpui::{executor::Deterministic, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use std::future;
|
||||
use util::http::FakeHttpClient;
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_reconnection(cx: &mut TestAppContext) {
|
||||
@@ -1594,14 +1610,17 @@ mod tests {
|
||||
|
||||
let _subscription1 = client
|
||||
.subscribe_to_entity(1)
|
||||
.unwrap()
|
||||
.set_model(&model1, &mut cx.to_async());
|
||||
let _subscription2 = client
|
||||
.subscribe_to_entity(2)
|
||||
.unwrap()
|
||||
.set_model(&model2, &mut cx.to_async());
|
||||
// Ensure dropping a subscription for the same entity type still allows receiving of
|
||||
// messages for other entity IDs of the same type.
|
||||
let subscription3 = client
|
||||
.subscribe_to_entity(3)
|
||||
.unwrap()
|
||||
.set_model(&model3, &mut cx.to_async());
|
||||
drop(subscription3);
|
||||
|
||||
@@ -1630,11 +1649,13 @@ mod tests {
|
||||
},
|
||||
);
|
||||
drop(subscription1);
|
||||
let _subscription2 =
|
||||
client.add_message_handler(model, move |_, _: TypedEnvelope<proto::Ping>, _, _| {
|
||||
let _subscription2 = client.add_message_handler(
|
||||
model.clone(),
|
||||
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
|
||||
done_tx2.try_send(()).unwrap();
|
||||
async { Ok(()) }
|
||||
});
|
||||
},
|
||||
);
|
||||
server.send(proto::Ping {});
|
||||
done_rx2.next().await.unwrap();
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
pub use anyhow::{anyhow, Result};
|
||||
use futures::future::BoxFuture;
|
||||
use isahc::{
|
||||
config::{Configurable, RedirectPolicy},
|
||||
AsyncBody,
|
||||
};
|
||||
pub use isahc::{
|
||||
http::{Method, Uri},
|
||||
Error,
|
||||
};
|
||||
use smol::future::FutureExt;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
pub use url::Url;
|
||||
|
||||
pub type Request = isahc::Request<AsyncBody>;
|
||||
pub type Response = isahc::Response<AsyncBody>;
|
||||
|
||||
pub trait HttpClient: Send + Sync {
|
||||
fn send(&self, req: Request) -> BoxFuture<Result<Response, Error>>;
|
||||
|
||||
fn get<'a>(
|
||||
&'a self,
|
||||
uri: &str,
|
||||
body: AsyncBody,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'a, Result<Response, Error>> {
|
||||
let request = isahc::Request::builder()
|
||||
.redirect_policy(if follow_redirects {
|
||||
RedirectPolicy::Follow
|
||||
} else {
|
||||
RedirectPolicy::None
|
||||
})
|
||||
.method(Method::GET)
|
||||
.uri(uri)
|
||||
.body(body);
|
||||
match request {
|
||||
Ok(request) => self.send(request),
|
||||
Err(error) => async move { Err(error.into()) }.boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client() -> Arc<dyn HttpClient> {
|
||||
Arc::new(
|
||||
isahc::HttpClient::builder()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.low_speed_timeout(100, Duration::from_secs(5))
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
impl HttpClient for isahc::HttpClient {
|
||||
fn send(&self, req: Request) -> BoxFuture<Result<Response, Error>> {
|
||||
Box::pin(async move { self.send_async(req).await })
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::http::HttpClient;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
executor::Background,
|
||||
serde_json::{self, value::Map, Value},
|
||||
AppContext, Task,
|
||||
};
|
||||
use isahc::Request;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
@@ -19,6 +17,7 @@ use std::{
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::http::HttpClient;
|
||||
use util::{channel::ReleaseChannel, post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -220,10 +219,10 @@ impl Telemetry {
|
||||
"App": true
|
||||
}),
|
||||
}])?;
|
||||
let request = Request::post(MIXPANEL_ENGAGE_URL)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(json_bytes.into())?;
|
||||
this.http_client.send(request).await?;
|
||||
|
||||
this.http_client
|
||||
.post_json(MIXPANEL_ENGAGE_URL, json_bytes.into())
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
@@ -316,10 +315,9 @@ impl Telemetry {
|
||||
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, &events)?;
|
||||
let request = Request::post(MIXPANEL_EVENTS_URL)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(json_bytes.into())?;
|
||||
this.http_client.send(request).await?;
|
||||
this.http_client
|
||||
.post_json(MIXPANEL_EVENTS_URL, json_bytes.into())
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use crate::{
|
||||
http::{self, HttpClient, Request, Response},
|
||||
Client, Connection, Credentials, EstablishConnectionError, UserStore,
|
||||
};
|
||||
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::{future::BoxFuture, stream::BoxStream, Future, StreamExt};
|
||||
use futures::{stream::BoxStream, StreamExt};
|
||||
use gpui::{executor, ModelHandle, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use rpc::{
|
||||
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
|
||||
ConnectionId, Peer, Receipt, TypedEnvelope,
|
||||
};
|
||||
use std::{fmt, rc::Rc, sync::Arc};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use util::http::FakeHttpClient;
|
||||
|
||||
pub struct FakeServer {
|
||||
peer: Arc<Peer>,
|
||||
@@ -219,46 +217,3 @@ impl Drop for FakeServer {
|
||||
self.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FakeHttpClient {
|
||||
handler: Box<
|
||||
dyn 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Fn(Request) -> BoxFuture<'static, Result<Response, http::Error>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl FakeHttpClient {
|
||||
pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>
|
||||
where
|
||||
Fut: 'static + Send + Future<Output = Result<Response, http::Error>>,
|
||||
F: 'static + Send + Sync + Fn(Request) -> Fut,
|
||||
{
|
||||
Arc::new(Self {
|
||||
handler: Box::new(move |req| Box::pin(handler(req))),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_404_response() -> Arc<dyn HttpClient> {
|
||||
Self::create(|_| async move {
|
||||
Ok(isahc::Response::builder()
|
||||
.status(404)
|
||||
.body(Default::default())
|
||||
.unwrap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FakeHttpClient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FakeHttpClient").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for FakeHttpClient {
|
||||
fn send(&self, req: Request) -> BoxFuture<Result<Response, crate::http::Error>> {
|
||||
let future = (self.handler)(req);
|
||||
Box::pin(async move { future.await.map(Into::into) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{http::HttpClient, proto, Client, Status, TypedEnvelope};
|
||||
use super::{proto, Client, Status, TypedEnvelope};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
|
||||
@@ -6,8 +6,10 @@ use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
|
||||
use postage::{sink::Sink, watch};
|
||||
use rpc::proto::{RequestMessage, UsersResponse};
|
||||
use settings::Settings;
|
||||
use staff_mode::StaffMode;
|
||||
use std::sync::{Arc, Weak};
|
||||
use util::{StaffMode, TryFutureExt as _};
|
||||
use util::http::HttpClient;
|
||||
use util::TryFutureExt as _;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct User {
|
||||
|
||||
@@ -9,4 +9,4 @@ path = "src/clock.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smallvec = { workspace = true }
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
@@ -41,9 +41,9 @@ scrypt = "0.7"
|
||||
# Remove fork dependency when a version with https://github.com/SeaQL/sea-orm/pull/1283 is released.
|
||||
sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls"] }
|
||||
sea-query = "0.27"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = "1.0"
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha-1 = "0.9"
|
||||
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
@@ -55,6 +55,7 @@ toml = "0.5.8"
|
||||
tracing = "0.1.34"
|
||||
tracing-log = "0.1.3"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
|
||||
indoc = "1.0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
@@ -79,7 +80,7 @@ env_logger = "0.9"
|
||||
util = { path = "../util" }
|
||||
lazy_static = "1.4"
|
||||
sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-sqlite"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde_json = { workspace = true }
|
||||
sqlx = { version = "0.6", features = ["sqlite"] }
|
||||
unindent = "0.1"
|
||||
|
||||
|
||||
@@ -175,25 +175,39 @@ impl Database {
|
||||
.map(|participant| participant.user_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Delete participants who failed to reconnect.
|
||||
// Delete participants who failed to reconnect and cancel their calls.
|
||||
let mut canceled_calls_to_user_ids = Vec::new();
|
||||
room_participant::Entity::delete_many()
|
||||
.filter(stale_participant_filter)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
let called_participants = room_participant::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(
|
||||
room_participant::Column::CallingUserId
|
||||
.is_in(stale_participant_user_ids.iter().copied()),
|
||||
)
|
||||
.add(room_participant::Column::AnsweringConnectionId.is_null()),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
room_participant::Entity::delete_many()
|
||||
.filter(
|
||||
room_participant::Column::Id
|
||||
.is_in(called_participants.iter().map(|participant| participant.id)),
|
||||
)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
canceled_calls_to_user_ids.extend(
|
||||
called_participants
|
||||
.into_iter()
|
||||
.map(|participant| participant.user_id),
|
||||
);
|
||||
|
||||
let room = self.get_room(room_id, &tx).await?;
|
||||
let mut canceled_calls_to_user_ids = Vec::new();
|
||||
// Delete the room if it becomes empty and cancel pending calls.
|
||||
// Delete the room if it becomes empty.
|
||||
if room.participants.is_empty() {
|
||||
canceled_calls_to_user_ids.extend(
|
||||
room.pending_participants
|
||||
.iter()
|
||||
.map(|pending_participant| UserId::from_proto(pending_participant.user_id)),
|
||||
);
|
||||
room_participant::Entity::delete_many()
|
||||
.filter(room_participant::Column::RoomId.eq(room_id))
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
project::Entity::delete_many()
|
||||
.filter(project::Column::RoomId.eq(room_id))
|
||||
.exec(&*tx)
|
||||
|
||||
@@ -228,7 +228,7 @@ impl Server {
|
||||
.add_message_handler(update_buffer_file)
|
||||
.add_message_handler(buffer_reloaded)
|
||||
.add_message_handler(buffer_saved)
|
||||
.add_request_handler(save_buffer)
|
||||
.add_request_handler(forward_project_request::<proto::SaveBuffer>)
|
||||
.add_request_handler(get_users)
|
||||
.add_request_handler(fuzzy_search_users)
|
||||
.add_request_handler(request_contact)
|
||||
@@ -1591,51 +1591,6 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_buffer(
|
||||
request: proto::SaveBuffer,
|
||||
response: Response<proto::SaveBuffer>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let project_id = ProjectId::from_proto(request.project_id);
|
||||
let host_connection_id = {
|
||||
let collaborators = session
|
||||
.db()
|
||||
.await
|
||||
.project_collaborators(project_id, session.connection_id)
|
||||
.await?;
|
||||
collaborators
|
||||
.iter()
|
||||
.find(|collaborator| collaborator.is_host)
|
||||
.ok_or_else(|| anyhow!("host not found"))?
|
||||
.connection_id
|
||||
};
|
||||
let response_payload = session
|
||||
.peer
|
||||
.forward_request(session.connection_id, host_connection_id, request.clone())
|
||||
.await?;
|
||||
|
||||
let mut collaborators = session
|
||||
.db()
|
||||
.await
|
||||
.project_collaborators(project_id, session.connection_id)
|
||||
.await?;
|
||||
collaborators.retain(|collaborator| collaborator.connection_id != session.connection_id);
|
||||
let project_connection_ids = collaborators
|
||||
.iter()
|
||||
.map(|collaborator| collaborator.connection_id);
|
||||
broadcast(
|
||||
Some(host_connection_id),
|
||||
project_connection_ids,
|
||||
|conn_id| {
|
||||
session
|
||||
.peer
|
||||
.forward_send(host_connection_id, conn_id, response_payload.clone())
|
||||
},
|
||||
);
|
||||
response.send(response_payload)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_buffer_for_peer(
|
||||
request: proto::CreateBufferForPeer,
|
||||
session: Session,
|
||||
@@ -1655,23 +1610,42 @@ async fn update_buffer(
|
||||
) -> Result<()> {
|
||||
session.executor.record_backtrace();
|
||||
let project_id = ProjectId::from_proto(request.project_id);
|
||||
let project_connection_ids = session
|
||||
.db()
|
||||
.await
|
||||
.project_connection_ids(project_id, session.connection_id)
|
||||
.await?;
|
||||
let mut guest_connection_ids;
|
||||
let mut host_connection_id = None;
|
||||
{
|
||||
let collaborators = session
|
||||
.db()
|
||||
.await
|
||||
.project_collaborators(project_id, session.connection_id)
|
||||
.await?;
|
||||
guest_connection_ids = Vec::with_capacity(collaborators.len() - 1);
|
||||
for collaborator in collaborators.iter() {
|
||||
if collaborator.is_host {
|
||||
host_connection_id = Some(collaborator.connection_id);
|
||||
} else {
|
||||
guest_connection_ids.push(collaborator.connection_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
let host_connection_id = host_connection_id.ok_or_else(|| anyhow!("host not found"))?;
|
||||
|
||||
session.executor.record_backtrace();
|
||||
|
||||
broadcast(
|
||||
Some(session.connection_id),
|
||||
project_connection_ids.iter().copied(),
|
||||
guest_connection_ids,
|
||||
|connection_id| {
|
||||
session
|
||||
.peer
|
||||
.forward_send(session.connection_id, connection_id, request.clone())
|
||||
},
|
||||
);
|
||||
if host_connection_id != session.connection_id {
|
||||
session
|
||||
.peer
|
||||
.forward_request(session.connection_id, host_connection_id, request.clone())
|
||||
.await?;
|
||||
}
|
||||
|
||||
response.send(proto::Ack {})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ use crate::{
|
||||
use anyhow::anyhow;
|
||||
use call::ActiveCall;
|
||||
use client::{
|
||||
self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials,
|
||||
EstablishConnectionError, UserStore,
|
||||
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
|
||||
};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
@@ -19,15 +18,17 @@ use parking_lot::Mutex;
|
||||
use project::{Project, WorktreeId};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
env,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
ops::{Deref, DerefMut},
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use theme::ThemeRegistry;
|
||||
use util::http::FakeHttpClient;
|
||||
use workspace::Workspace;
|
||||
|
||||
mod integration_tests;
|
||||
@@ -209,13 +210,10 @@ impl TestServer {
|
||||
let client = TestClient {
|
||||
client,
|
||||
username: name.to_string(),
|
||||
local_projects: Default::default(),
|
||||
remote_projects: Default::default(),
|
||||
next_root_dir_id: 0,
|
||||
state: Default::default(),
|
||||
user_store,
|
||||
fs,
|
||||
language_registry: Arc::new(LanguageRegistry::test()),
|
||||
buffers: Default::default(),
|
||||
};
|
||||
client.wait_for_current_user(cx).await;
|
||||
client
|
||||
@@ -314,12 +312,16 @@ impl Drop for TestServer {
|
||||
struct TestClient {
|
||||
client: Arc<Client>,
|
||||
username: String,
|
||||
local_projects: Vec<ModelHandle<Project>>,
|
||||
remote_projects: Vec<ModelHandle<Project>>,
|
||||
next_root_dir_id: usize,
|
||||
state: RefCell<TestClientState>,
|
||||
pub user_store: ModelHandle<UserStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<FakeFs>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestClientState {
|
||||
local_projects: Vec<ModelHandle<Project>>,
|
||||
remote_projects: Vec<ModelHandle<Project>>,
|
||||
buffers: HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>,
|
||||
}
|
||||
|
||||
@@ -358,6 +360,38 @@ impl TestClient {
|
||||
.await;
|
||||
}
|
||||
|
||||
fn local_projects<'a>(&'a self) -> impl Deref<Target = Vec<ModelHandle<Project>>> + 'a {
|
||||
Ref::map(self.state.borrow(), |state| &state.local_projects)
|
||||
}
|
||||
|
||||
fn remote_projects<'a>(&'a self) -> impl Deref<Target = Vec<ModelHandle<Project>>> + 'a {
|
||||
Ref::map(self.state.borrow(), |state| &state.remote_projects)
|
||||
}
|
||||
|
||||
fn local_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<ModelHandle<Project>>> + 'a {
|
||||
RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
|
||||
}
|
||||
|
||||
fn remote_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<ModelHandle<Project>>> + 'a {
|
||||
RefMut::map(self.state.borrow_mut(), |state| &mut state.remote_projects)
|
||||
}
|
||||
|
||||
fn buffers_for_project<'a>(
|
||||
&'a self,
|
||||
project: &ModelHandle<Project>,
|
||||
) -> impl DerefMut<Target = HashSet<ModelHandle<language::Buffer>>> + 'a {
|
||||
RefMut::map(self.state.borrow_mut(), |state| {
|
||||
state.buffers.entry(project.clone()).or_default()
|
||||
})
|
||||
}
|
||||
|
||||
fn buffers<'a>(
|
||||
&'a self,
|
||||
) -> impl DerefMut<Target = HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>> + 'a
|
||||
{
|
||||
RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
|
||||
}
|
||||
|
||||
fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
|
||||
self.user_store.read_with(cx, |store, _| ContactsSummary {
|
||||
current: store
|
||||
@@ -431,15 +465,6 @@ impl TestClient {
|
||||
let (_, root_view) = cx.add_window(|_| EmptyView);
|
||||
cx.add_view(&root_view, |cx| Workspace::test_new(project.clone(), cx))
|
||||
}
|
||||
|
||||
fn create_new_root_dir(&mut self) -> PathBuf {
|
||||
format!(
|
||||
"/{}-root-{}",
|
||||
self.username,
|
||||
util::post_inc(&mut self.next_root_dir_id)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestClient {
|
||||
|
||||
@@ -6,8 +6,9 @@ use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use collections::HashSet;
|
||||
use editor::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo,
|
||||
Rename, ToOffset, ToggleCodeActions, Undo,
|
||||
test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
|
||||
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions,
|
||||
Undo,
|
||||
};
|
||||
use fs::{FakeFs, Fs as _, LineEnding, RemoveOptions};
|
||||
use futures::StreamExt as _;
|
||||
@@ -15,11 +16,13 @@ use gpui::{
|
||||
executor::Deterministic, geometry::vector::vec2f, test::EmptyView, ModelHandle, TestAppContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||
LanguageConfig, OffsetRangeExt, Point, Rope,
|
||||
};
|
||||
use live_kit_client::MacOSDisplay;
|
||||
use lsp::LanguageServerId;
|
||||
use project::{search::SearchQuery, DiagnosticSummary, Project, ProjectPath};
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
@@ -29,7 +32,10 @@ use std::{
|
||||
env, future, mem,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use workspace::{
|
||||
@@ -1633,9 +1639,7 @@ async fn test_project_reconnect(
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a2
|
||||
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
deterministic.run_until_parked();
|
||||
let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
|
||||
assert!(tree.as_local().unwrap().is_shared());
|
||||
tree.id()
|
||||
@@ -1696,11 +1700,9 @@ async fn test_project_reconnect(
|
||||
.unwrap();
|
||||
|
||||
// While client A is disconnected, add and remove worktrees from client A's project.
|
||||
project_a1
|
||||
.update(cx_a, |project, cx| {
|
||||
project.remove_worktree(worktree2_id, cx)
|
||||
})
|
||||
.await;
|
||||
project_a1.update(cx_a, |project, cx| {
|
||||
project.remove_worktree(worktree2_id, cx)
|
||||
});
|
||||
let (worktree_a3, _) = project_a1
|
||||
.update(cx_a, |p, cx| {
|
||||
p.find_or_create_local_worktree("/root-1/dir3", true, cx)
|
||||
@@ -1824,18 +1826,14 @@ async fn test_project_reconnect(
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a4
|
||||
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
deterministic.run_until_parked();
|
||||
let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
|
||||
assert!(tree.as_local().unwrap().is_shared());
|
||||
tree.id()
|
||||
});
|
||||
project_a1
|
||||
.update(cx_a, |project, cx| {
|
||||
project.remove_worktree(worktree3_id, cx)
|
||||
})
|
||||
.await;
|
||||
project_a1.update(cx_a, |project, cx| {
|
||||
project.remove_worktree(worktree3_id, cx)
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
// While client B is disconnected, mutate a buffer on both the host and the guest.
|
||||
@@ -3050,6 +3048,104 @@ async fn test_editing_while_guest_opens_buffer(
|
||||
buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
||||
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);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
|
||||
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;
|
||||
|
||||
// Open a buffer as client A
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let (_, window_a) = cx_a.add_window(|_| EmptyView);
|
||||
let editor_a = cx_a.add_view(&window_a, |cx| {
|
||||
Editor::for_buffer(buffer_a, Some(project_a), cx)
|
||||
});
|
||||
let mut editor_cx_a = EditorTestContext {
|
||||
cx: cx_a,
|
||||
window_id: window_a.id(),
|
||||
editor: editor_a,
|
||||
};
|
||||
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let (_, window_b) = cx_b.add_window(|_| EmptyView);
|
||||
let editor_b = cx_b.add_view(&window_b, |cx| {
|
||||
Editor::for_buffer(buffer_b, Some(project_b), cx)
|
||||
});
|
||||
let mut editor_cx_b = EditorTestContext {
|
||||
cx: cx_b,
|
||||
window_id: window_b.id(),
|
||||
editor: editor_b,
|
||||
};
|
||||
|
||||
// Test newline above
|
||||
editor_cx_a.set_selections_state(indoc! {"
|
||||
Some textˇ
|
||||
"});
|
||||
editor_cx_b.set_selections_state(indoc! {"
|
||||
Some textˇ
|
||||
"});
|
||||
editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
|
||||
deterministic.run_until_parked();
|
||||
editor_cx_a.assert_editor_state(indoc! {"
|
||||
ˇ
|
||||
Some text
|
||||
"});
|
||||
editor_cx_b.assert_editor_state(indoc! {"
|
||||
|
||||
Some textˇ
|
||||
"});
|
||||
|
||||
// Test newline below
|
||||
editor_cx_a.set_selections_state(indoc! {"
|
||||
|
||||
Some textˇ
|
||||
"});
|
||||
editor_cx_b.set_selections_state(indoc! {"
|
||||
|
||||
Some textˇ
|
||||
"});
|
||||
editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
|
||||
deterministic.run_until_parked();
|
||||
editor_cx_a.assert_editor_state(indoc! {"
|
||||
|
||||
Some text
|
||||
ˇ
|
||||
"});
|
||||
editor_cx_b.assert_editor_state(indoc! {"
|
||||
|
||||
Some textˇ
|
||||
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_leaving_worktree_while_opening_buffer(
|
||||
deterministic: Arc<Deterministic>,
|
||||
@@ -3385,6 +3481,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
error_count: 1,
|
||||
warning_count: 0,
|
||||
@@ -3420,6 +3517,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
error_count: 1,
|
||||
warning_count: 0,
|
||||
@@ -3460,10 +3558,10 @@ async fn test_collaborating_with_diagnostics(
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
error_count: 1,
|
||||
warning_count: 1,
|
||||
..Default::default()
|
||||
},
|
||||
)]
|
||||
);
|
||||
@@ -3476,10 +3574,10 @@ async fn test_collaborating_with_diagnostics(
|
||||
worktree_id,
|
||||
path: Arc::from(Path::new("a.rs")),
|
||||
},
|
||||
LanguageServerId(0),
|
||||
DiagnosticSummary {
|
||||
error_count: 1,
|
||||
warning_count: 1,
|
||||
..Default::default()
|
||||
},
|
||||
)]
|
||||
);
|
||||
@@ -3543,6 +3641,141 @@ async fn test_collaborating_with_diagnostics(
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
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;
|
||||
|
||||
// Set up a fake language server.
|
||||
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 {
|
||||
disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
|
||||
disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry.add(Arc::new(language));
|
||||
|
||||
let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;",
|
||||
"two.rs": "const TWO: usize = 2;",
|
||||
"three.rs": "const THREE: usize = 3;",
|
||||
"four.rs": "const FOUR: usize = 3;",
|
||||
"five.rs": "const FIVE: usize = 3;",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/test", cx_a).await;
|
||||
|
||||
// Share a project as client A
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Join the project as client B and open all three files.
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
|
||||
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Simulate a language server reporting errors for a file.
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
fake_language_server
|
||||
.request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
|
||||
token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||
token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
|
||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
|
||||
lsp::WorkDoneProgressBegin {
|
||||
title: "Progress Began".into(),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
});
|
||||
for file_name in file_names {
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
source: Some("the-disk-based-diagnostics-source".into()),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
|
||||
message: "message one".to_string(),
|
||||
..Default::default()
|
||||
}],
|
||||
},
|
||||
);
|
||||
}
|
||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||
token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
|
||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
|
||||
lsp::WorkDoneProgressEnd { message: None },
|
||||
)),
|
||||
});
|
||||
|
||||
// When the "disk base diagnostics finished" message is received, the buffers'
|
||||
// diagnostics are expected to be present.
|
||||
let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
|
||||
project_b.update(cx_b, {
|
||||
let project_b = project_b.clone();
|
||||
let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
|
||||
move |_, cx| {
|
||||
cx.subscribe(&project_b, move |_, _, event, cx| {
|
||||
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
|
||||
disk_based_diagnostics_finished.store(true, SeqCst);
|
||||
for buffer in &guest_buffers {
|
||||
assert_eq!(
|
||||
buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.diagnostics_in_range::<_, usize>(0..5, false)
|
||||
.count(),
|
||||
1,
|
||||
"expected a diagnostic for buffer {:?}",
|
||||
buffer.read(cx).file().unwrap().path(),
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
|
||||
deterministic.run_until_parked();
|
||||
assert!(disk_based_diagnostics_finished.load(SeqCst));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_collaborating_with_completion(
|
||||
deterministic: Arc<Deterministic>,
|
||||
@@ -5870,10 +6103,17 @@ async fn test_basic_following(
|
||||
|
||||
// Client A updates their selections in those editors
|
||||
editor_a1.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
|
||||
editor.handle_input("a", cx);
|
||||
editor.handle_input("b", cx);
|
||||
editor.handle_input("c", cx);
|
||||
editor.select_left(&Default::default(), cx);
|
||||
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
|
||||
});
|
||||
editor_a2.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
|
||||
editor.handle_input("d", cx);
|
||||
editor.handle_input("e", cx);
|
||||
editor.select_left(&Default::default(), cx);
|
||||
assert_eq!(editor.selections.ranges(cx), vec![2..1]);
|
||||
});
|
||||
|
||||
// When client B starts following client A, all visible view states are replicated to client B.
|
||||
@@ -5886,6 +6126,27 @@ async fn test_basic_following(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx_c.foreground().run_until_parked();
|
||||
let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
cx_b.read(|cx| editor_b2.project_path(cx)),
|
||||
Some((worktree_id, "2.txt").into())
|
||||
);
|
||||
assert_eq!(
|
||||
editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
vec![2..1]
|
||||
);
|
||||
assert_eq!(
|
||||
editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
vec![3..2]
|
||||
);
|
||||
|
||||
cx_c.foreground().run_until_parked();
|
||||
let active_call_c = cx_c.read(ActiveCall::global);
|
||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||
@@ -6041,26 +6302,6 @@ async fn test_basic_following(
|
||||
});
|
||||
}
|
||||
|
||||
let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
cx_b.read(|cx| editor_b2.project_path(cx)),
|
||||
Some((worktree_id, "2.txt").into())
|
||||
);
|
||||
assert_eq!(
|
||||
editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
vec![2..3]
|
||||
);
|
||||
assert_eq!(
|
||||
editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
|
||||
vec![0..1]
|
||||
);
|
||||
|
||||
// When client A activates a different editor, client B does so as well.
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.activate_item(&editor_a1, cx)
|
||||
|
||||
@@ -43,8 +43,8 @@ anyhow = "1.0"
|
||||
futures = "0.3"
|
||||
log = "0.4"
|
||||
postage = { workspace = true }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
|
||||
@@ -15,8 +15,9 @@ use gpui::{
|
||||
geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
||||
impl_internal_actions,
|
||||
json::{self, ToJson},
|
||||
CursorStyle, Entity, ImageData, ModelHandle, MouseButton, MutableAppContext, RenderContext,
|
||||
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Entity, ImageData, ModelHandle, RenderContext, Subscription, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
@@ -40,7 +41,7 @@ impl_internal_actions!(collab, [LeaveCall]);
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub(crate) struct LeaveCall;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover);
|
||||
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
|
||||
cx.add_action(CollabTitlebarItem::share_project);
|
||||
@@ -301,25 +302,13 @@ impl CollabTitlebarItem {
|
||||
.with_style(item_style.container)
|
||||
.boxed()
|
||||
})),
|
||||
ContextMenuItem::Item {
|
||||
label: "Sign out".into(),
|
||||
action: Box::new(SignOut),
|
||||
},
|
||||
ContextMenuItem::Item {
|
||||
label: "Send Feedback".into(),
|
||||
action: Box::new(feedback::feedback_editor::GiveFeedback),
|
||||
},
|
||||
ContextMenuItem::item("Sign out", SignOut),
|
||||
ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
ContextMenuItem::Item {
|
||||
label: "Sign in".into(),
|
||||
action: Box::new(SignIn),
|
||||
},
|
||||
ContextMenuItem::Item {
|
||||
label: "Send Feedback".into(),
|
||||
action: Box::new(feedback::feedback_editor::GiveFeedback),
|
||||
},
|
||||
ContextMenuItem::item("Sign in", SignIn),
|
||||
ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
|
||||
]
|
||||
};
|
||||
|
||||
@@ -406,10 +395,10 @@ impl CollabTitlebarItem {
|
||||
let icon;
|
||||
let tooltip;
|
||||
if room.read(cx).is_screen_sharing() {
|
||||
icon = "icons/disable_screen_sharing_12.svg";
|
||||
icon = "icons/enable_screen_sharing_12.svg";
|
||||
tooltip = "Stop Sharing Screen"
|
||||
} else {
|
||||
icon = "icons/enable_screen_sharing_12.svg";
|
||||
icon = "icons/disable_screen_sharing_12.svg";
|
||||
tooltip = "Share Screen";
|
||||
}
|
||||
|
||||
@@ -470,9 +459,7 @@ impl CollabTitlebarItem {
|
||||
.with_child(
|
||||
MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistant width for both text variations
|
||||
let style = titlebar
|
||||
.share_button
|
||||
.style_for(state, self.contacts_popover.is_some());
|
||||
let style = titlebar.share_button.style_for(state, false);
|
||||
Label::new(label, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
@@ -721,11 +708,9 @@ impl CollabTitlebarItem {
|
||||
}
|
||||
})?;
|
||||
|
||||
let location = remote_participant.map(|p| p.location);
|
||||
|
||||
Some(Self::render_face(
|
||||
avatar.clone(),
|
||||
Self::location_style(workspace, location, follower_style, cx),
|
||||
follower_style,
|
||||
background_color,
|
||||
))
|
||||
}))
|
||||
|
||||
@@ -13,13 +13,13 @@ mod sharing_status_indicator;
|
||||
use anyhow::anyhow;
|
||||
use call::ActiveCall;
|
||||
pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
|
||||
use gpui::{actions, MutableAppContext, Task};
|
||||
use gpui::{actions, AppContext, Task};
|
||||
use std::sync::Arc;
|
||||
use workspace::{AppState, JoinProject, ToggleFollow, Workspace};
|
||||
|
||||
actions!(collab, [ToggleScreenSharing]);
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
collab_titlebar_item::init(cx);
|
||||
contact_notification::init(cx);
|
||||
contact_list::init(cx);
|
||||
@@ -35,7 +35,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext) {
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
@@ -48,13 +48,13 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut MutableAppContext
|
||||
}
|
||||
}
|
||||
|
||||
fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
let project_id = action.project_id;
|
||||
let follow_user_id = action.follow_user_id;
|
||||
cx.spawn(|mut cx| async move {
|
||||
let existing_workspace = cx.update(|cx| {
|
||||
cx.window_ids()
|
||||
.filter_map(|window_id| cx.root_view::<Workspace>(window_id))
|
||||
.filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
|
||||
.find(|workspace| {
|
||||
workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
|
||||
})
|
||||
|
||||
@@ -2,7 +2,8 @@ use call::ActiveCall;
|
||||
use client::UserStore;
|
||||
use gpui::Action;
|
||||
use gpui::{
|
||||
actions, elements::*, Entity, ModelHandle, MouseButton, RenderContext, View, ViewContext,
|
||||
actions, elements::*, platform::MouseButton, Entity, ModelHandle, RenderContext, View,
|
||||
ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use client::{ContactRequestStatus, User, UserStore};
|
||||
use gpui::{
|
||||
elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, MutableAppContext,
|
||||
RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, RenderContext, Task,
|
||||
View, ViewContext, ViewHandle,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use util::TryFutureExt;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
Picker::<ContactFinder>::init(cx);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl View for ContactFinder {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(self.picker.clone(), cx).boxed()
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -10,8 +10,8 @@ use gpui::{
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_actions, impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
AppContext, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, PromptLevel,
|
||||
RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
platform::{CursorStyle, MouseButton, PromptLevel},
|
||||
AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::Project;
|
||||
@@ -24,7 +24,7 @@ use workspace::{JoinProject, OpenSharedScreen};
|
||||
impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
|
||||
impl_internal_actions!(contact_list, [ToggleExpanded, Call]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ContactList::remove_contact);
|
||||
cx.add_action(ContactList::respond_to_contact_request);
|
||||
cx.add_action(ContactList::cancel);
|
||||
@@ -1339,7 +1339,7 @@ impl View for ContactList {
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
ChildView::new(self.filter_editor.clone(), cx)
|
||||
ChildView::new(&self.filter_editor, cx)
|
||||
.contained()
|
||||
.with_style(theme.contact_list.user_query_editor.container)
|
||||
.flex(1., true)
|
||||
|
||||
@@ -3,14 +3,14 @@ use std::sync::Arc;
|
||||
use crate::notifications::render_user_notification;
|
||||
use client::{ContactEventKind, User, UserStore};
|
||||
use gpui::{
|
||||
elements::*, impl_internal_actions, Entity, ModelHandle, MutableAppContext, RenderContext,
|
||||
View, ViewContext,
|
||||
elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, RenderContext, View,
|
||||
ViewContext,
|
||||
};
|
||||
use workspace::notifications::Notification;
|
||||
|
||||
impl_internal_actions!(contact_notifications, [Dismiss, RespondToContactRequest]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ContactNotification::dismiss);
|
||||
cx.add_action(ContactNotification::respond_to_contact_request);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu};
|
||||
use client::UserStore;
|
||||
use gpui::{
|
||||
actions, elements::*, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, View,
|
||||
ViewContext, ViewHandle,
|
||||
actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, RenderContext,
|
||||
View, ViewContext, ViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
|
||||
actions!(contacts_popover, [ToggleContactFinder]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ContactsPopover::toggle_contact_finder);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ use futures::StreamExt;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_internal_actions, CursorStyle, Entity, MouseButton, MutableAppContext, RenderContext,
|
||||
View, ViewContext, WindowBounds, WindowKind, WindowOptions,
|
||||
impl_internal_actions,
|
||||
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, Entity, RenderContext, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use util::ResultExt;
|
||||
@@ -13,7 +14,7 @@ use workspace::JoinProject;
|
||||
|
||||
impl_internal_actions!(incoming_call_notification, [RespondToCall]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(IncomingCallNotification::respond_to_call);
|
||||
|
||||
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use client::User;
|
||||
use gpui::{
|
||||
elements::*, platform::CursorStyle, Action, Element, ElementBox, MouseButton, RenderContext,
|
||||
View,
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, Element, ElementBox, RenderContext, View,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -5,8 +5,8 @@ use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
CursorStyle, Entity, MouseButton, MutableAppContext, RenderContext, View, ViewContext,
|
||||
WindowBounds, WindowKind, WindowOptions,
|
||||
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, Entity, RenderContext, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
@@ -14,7 +14,7 @@ use workspace::JoinProject;
|
||||
|
||||
actions!(project_shared_notification, [DismissProject]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ProjectSharedNotification::join);
|
||||
cx.add_action(ProjectSharedNotification::dismiss);
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@ use call::ActiveCall;
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{MouseEventHandler, Svg},
|
||||
Appearance, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext, View,
|
||||
platform::{Appearance, MouseButton},
|
||||
AppContext, Element, ElementBox, Entity, RenderContext, View,
|
||||
};
|
||||
use settings::Settings;
|
||||
|
||||
use crate::ToggleScreenSharing;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let active_call = ActiveCall::global(cx);
|
||||
|
||||
let mut status_indicator = None;
|
||||
|
||||
@@ -24,3 +24,10 @@ pub type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||
pub type HashSet<T> = std::collections::HashSet<T>;
|
||||
|
||||
pub use std::collections::*;
|
||||
|
||||
// NEW TYPES
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteFilter {
|
||||
pub filtered_namespaces: HashSet<&'static str>,
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ workspace = { path = "../workspace" }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde_json = { workspace = true }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.9"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use collections::HashSet;
|
||||
use collections::CommandPaletteFilter;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Flex, Label, ParentElement},
|
||||
keymap_matcher::Keystroke,
|
||||
Action, AnyViewHandle, Element, Entity, MouseState, MutableAppContext, RenderContext, View,
|
||||
Action, AnyViewHandle, AppContext, Element, Entity, MouseState, RenderContext, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
@@ -12,12 +12,7 @@ use settings::Settings;
|
||||
use std::cmp;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CommandPaletteFilter {
|
||||
pub filtered_namespaces: HashSet<&'static str>,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(CommandPalette::toggle);
|
||||
Picker::<CommandPalette>::init(cx);
|
||||
}
|
||||
@@ -90,7 +85,7 @@ impl CommandPalette {
|
||||
.unwrap_or_else(|| workspace.id());
|
||||
|
||||
cx.as_mut().defer(move |cx| {
|
||||
let this = cx.add_view(workspace.clone(), |cx| Self::new(focused_view_id, cx));
|
||||
let this = cx.add_view(&workspace, |cx| Self::new(focused_view_id, cx));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |_, cx| {
|
||||
cx.subscribe(&this, Self::on_event).detach();
|
||||
@@ -134,7 +129,7 @@ impl View for CommandPalette {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
|
||||
ChildView::new(self.picker.clone(), cx).boxed()
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
@@ -360,7 +355,7 @@ mod tests {
|
||||
});
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
cx.focus(editor.clone());
|
||||
cx.focus(&editor);
|
||||
workspace.add_item(Box::new(editor.clone()), cx)
|
||||
});
|
||||
|
||||
|
||||
@@ -13,4 +13,4 @@ gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
smallvec = "1.6"
|
||||
smallvec = { workspace = true }
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
use gpui::{
|
||||
elements::*, geometry::vector::Vector2F, impl_internal_actions, keymap_matcher::KeymapContext,
|
||||
platform::CursorStyle, Action, AnyViewHandle, AppContext, Axis, Entity, MouseButton,
|
||||
MutableAppContext, RenderContext, SizeConstraint, Subscription, View, ViewContext,
|
||||
elements::*,
|
||||
geometry::vector::Vector2F,
|
||||
impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, SizeConstraint,
|
||||
Subscription, View, ViewContext,
|
||||
};
|
||||
use menu::*;
|
||||
use settings::Settings;
|
||||
use std::{any::TypeId, borrow::Cow, time::Duration};
|
||||
|
||||
pub type StaticItem = Box<dyn Fn(&mut MutableAppContext) -> ElementBox>;
|
||||
pub type StaticItem = Box<dyn Fn(&mut AppContext) -> ElementBox>;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
struct Clicked;
|
||||
|
||||
impl_internal_actions!(context_menu, [Clicked]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ContextMenu::select_first);
|
||||
cx.add_action(ContextMenu::select_last);
|
||||
cx.add_action(ContextMenu::select_next);
|
||||
@@ -24,20 +28,71 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ContextMenu::cancel);
|
||||
}
|
||||
|
||||
type ContextMenuItemBuilder = Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> ElementBox>;
|
||||
|
||||
pub enum ContextMenuItemLabel {
|
||||
String(Cow<'static, str>),
|
||||
Element(ContextMenuItemBuilder),
|
||||
}
|
||||
|
||||
pub enum ContextMenuAction {
|
||||
ParentAction {
|
||||
action: Box<dyn Action>,
|
||||
},
|
||||
ViewAction {
|
||||
action: Box<dyn Action>,
|
||||
for_view: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl ContextMenuAction {
|
||||
fn id(&self) -> TypeId {
|
||||
match self {
|
||||
ContextMenuAction::ParentAction { action } => action.id(),
|
||||
ContextMenuAction::ViewAction { action, .. } => action.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuItem {
|
||||
Item {
|
||||
label: Cow<'static, str>,
|
||||
action: Box<dyn Action>,
|
||||
label: ContextMenuItemLabel,
|
||||
action: ContextMenuAction,
|
||||
},
|
||||
Static(StaticItem),
|
||||
Separator,
|
||||
}
|
||||
|
||||
impl ContextMenuItem {
|
||||
pub fn element_item(label: ContextMenuItemBuilder, action: impl 'static + Action) -> Self {
|
||||
Self::Item {
|
||||
label: ContextMenuItemLabel::Element(label),
|
||||
action: ContextMenuAction::ParentAction {
|
||||
action: Box::new(action),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item(label: impl Into<Cow<'static, str>>, action: impl 'static + Action) -> Self {
|
||||
Self::Item {
|
||||
label: label.into(),
|
||||
action: Box::new(action),
|
||||
label: ContextMenuItemLabel::String(label.into()),
|
||||
action: ContextMenuAction::ParentAction {
|
||||
action: Box::new(action),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_for_view(
|
||||
label: impl Into<Cow<'static, str>>,
|
||||
view_id: usize,
|
||||
action: impl 'static + Action,
|
||||
) -> Self {
|
||||
Self::Item {
|
||||
label: ContextMenuItemLabel::String(label.into()),
|
||||
action: ContextMenuAction::ViewAction {
|
||||
action: Box::new(action),
|
||||
for_view: view_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +223,15 @@ impl ContextMenu {
|
||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
|
||||
cx.dispatch_any_action(action.boxed_clone());
|
||||
match action {
|
||||
ContextMenuAction::ParentAction { action } => {
|
||||
cx.dispatch_any_action(action.boxed_clone())
|
||||
}
|
||||
ContextMenuAction::ViewAction { action, for_view } => {
|
||||
let window_id = cx.window_id();
|
||||
cx.dispatch_any_action_at(window_id, *for_view, action.boxed_clone())
|
||||
}
|
||||
};
|
||||
self.reset(cx);
|
||||
}
|
||||
}
|
||||
@@ -278,10 +341,17 @@ impl ContextMenu {
|
||||
Some(ix) == self.selected_index,
|
||||
);
|
||||
|
||||
Label::new(label.to_string(), style.label.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
match label {
|
||||
ContextMenuItemLabel::String(label) => {
|
||||
Label::new(label.to_string(), style.label.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
}
|
||||
ContextMenuItemLabel::Element(element) => {
|
||||
element(&mut Default::default(), style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(f) => f(cx),
|
||||
@@ -306,9 +376,18 @@ impl ContextMenu {
|
||||
&mut Default::default(),
|
||||
Some(ix) == self.selected_index,
|
||||
);
|
||||
let (action, view_id) = match action {
|
||||
ContextMenuAction::ParentAction { action } => {
|
||||
(action.boxed_clone(), self.parent_view_id)
|
||||
}
|
||||
ContextMenuAction::ViewAction { action, for_view } => {
|
||||
(action.boxed_clone(), *for_view)
|
||||
}
|
||||
};
|
||||
|
||||
KeystrokeLabel::new(
|
||||
window_id,
|
||||
self.parent_view_id,
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
@@ -347,22 +426,34 @@ impl ContextMenu {
|
||||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { label, action } => {
|
||||
let action = action.boxed_clone();
|
||||
let (action, view_id) = match action {
|
||||
ContextMenuAction::ParentAction { action } => {
|
||||
(action.boxed_clone(), self.parent_view_id)
|
||||
}
|
||||
ContextMenuAction::ViewAction { action, for_view } => {
|
||||
(action.boxed_clone(), *for_view)
|
||||
}
|
||||
};
|
||||
|
||||
MouseEventHandler::<MenuItem>::new(ix, cx, |state, _| {
|
||||
let style =
|
||||
style.item.style_for(state, Some(ix) == self.selected_index);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(label.clone(), style.label.clone())
|
||||
.contained()
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(match label {
|
||||
ContextMenuItemLabel::String(label) => {
|
||||
Label::new(label.clone(), style.label.clone())
|
||||
.contained()
|
||||
.boxed()
|
||||
}
|
||||
ContextMenuItemLabel::Element(element) => {
|
||||
element(state, style)
|
||||
}
|
||||
})
|
||||
.with_child({
|
||||
KeystrokeLabel::new(
|
||||
window_id,
|
||||
self.parent_view_id,
|
||||
view_id,
|
||||
action.boxed_clone(),
|
||||
style.keystroke.container,
|
||||
style.keystroke.text.clone(),
|
||||
@@ -375,9 +466,12 @@ impl ContextMenu {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_up(MouseButton::Left, |_, _| {}) // Capture these events
|
||||
.on_down(MouseButton::Left, |_, _| {}) // Capture these events
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(Clicked);
|
||||
cx.dispatch_any_action(action.boxed_clone());
|
||||
let window_id = cx.window_id();
|
||||
cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
|
||||
})
|
||||
.on_drag(MouseButton::Left, |_, _| {})
|
||||
.boxed()
|
||||
|
||||
50
crates/copilot/Cargo.toml
Normal file
@@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "copilot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/copilot.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"collections/test-support",
|
||||
"gpui/test-support",
|
||||
"language/test-support",
|
||||
"lsp/test-support",
|
||||
"settings/test-support",
|
||||
"util/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
lsp = { path = "../lsp" }
|
||||
node_runtime = { path = "../node_runtime"}
|
||||
util = { path = "../util" }
|
||||
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
||||
async-tar = "0.4.2"
|
||||
anyhow = "1.0"
|
||||
log = "0.4"
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
smol = "1.2.5"
|
||||
futures = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
1203
crates/copilot/src/copilot.rs
Normal file
225
crates/copilot/src/request.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub enum CheckStatus {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CheckStatusParams {
|
||||
pub local_checks_only: bool,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for CheckStatus {
|
||||
type Params = CheckStatusParams;
|
||||
type Result = SignInStatus;
|
||||
const METHOD: &'static str = "checkStatus";
|
||||
}
|
||||
|
||||
pub enum SignInInitiate {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SignInInitiateParams {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "status")]
|
||||
pub enum SignInInitiateResult {
|
||||
AlreadySignedIn { user: String },
|
||||
PromptUserDeviceFlow(PromptUserDeviceFlow),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PromptUserDeviceFlow {
|
||||
pub user_code: String,
|
||||
pub verification_uri: String,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for SignInInitiate {
|
||||
type Params = SignInInitiateParams;
|
||||
type Result = SignInInitiateResult;
|
||||
const METHOD: &'static str = "signInInitiate";
|
||||
}
|
||||
|
||||
pub enum SignInConfirm {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignInConfirmParams {
|
||||
pub user_code: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "status")]
|
||||
pub enum SignInStatus {
|
||||
#[serde(rename = "OK")]
|
||||
Ok {
|
||||
user: String,
|
||||
},
|
||||
MaybeOk {
|
||||
user: String,
|
||||
},
|
||||
AlreadySignedIn {
|
||||
user: String,
|
||||
},
|
||||
NotAuthorized {
|
||||
user: String,
|
||||
},
|
||||
NotSignedIn,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for SignInConfirm {
|
||||
type Params = SignInConfirmParams;
|
||||
type Result = SignInStatus;
|
||||
const METHOD: &'static str = "signInConfirm";
|
||||
}
|
||||
|
||||
pub enum SignOut {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignOutParams {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignOutResult {}
|
||||
|
||||
impl lsp::request::Request for SignOut {
|
||||
type Params = SignOutParams;
|
||||
type Result = SignOutResult;
|
||||
const METHOD: &'static str = "signOut";
|
||||
}
|
||||
|
||||
pub enum GetCompletions {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetCompletionsParams {
|
||||
pub doc: GetCompletionsDocument,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetCompletionsDocument {
|
||||
pub tab_size: u32,
|
||||
pub indent_size: u32,
|
||||
pub insert_spaces: bool,
|
||||
pub uri: lsp::Url,
|
||||
pub relative_path: String,
|
||||
pub position: lsp::Position,
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetCompletionsResult {
|
||||
pub completions: Vec<Completion>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Completion {
|
||||
pub text: String,
|
||||
pub position: lsp::Position,
|
||||
pub uuid: String,
|
||||
pub range: lsp::Range,
|
||||
pub display_text: String,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for GetCompletions {
|
||||
type Params = GetCompletionsParams;
|
||||
type Result = GetCompletionsResult;
|
||||
const METHOD: &'static str = "getCompletions";
|
||||
}
|
||||
|
||||
pub enum GetCompletionsCycling {}
|
||||
|
||||
impl lsp::request::Request for GetCompletionsCycling {
|
||||
type Params = GetCompletionsParams;
|
||||
type Result = GetCompletionsResult;
|
||||
const METHOD: &'static str = "getCompletionsCycling";
|
||||
}
|
||||
|
||||
pub enum LogMessage {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LogMessageParams {
|
||||
pub message: String,
|
||||
pub level: u8,
|
||||
pub metadata_str: String,
|
||||
pub extra: Vec<String>,
|
||||
}
|
||||
|
||||
impl lsp::notification::Notification for LogMessage {
|
||||
type Params = LogMessageParams;
|
||||
const METHOD: &'static str = "LogMessage";
|
||||
}
|
||||
|
||||
pub enum StatusNotification {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct StatusNotificationParams {
|
||||
pub message: String,
|
||||
pub status: String, // One of Normal/InProgress
|
||||
}
|
||||
|
||||
impl lsp::notification::Notification for StatusNotification {
|
||||
type Params = StatusNotificationParams;
|
||||
const METHOD: &'static str = "statusNotification";
|
||||
}
|
||||
|
||||
pub enum SetEditorInfo {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetEditorInfoParams {
|
||||
pub editor_info: EditorInfo,
|
||||
pub editor_plugin_info: EditorPluginInfo,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for SetEditorInfo {
|
||||
type Params = SetEditorInfoParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "setEditorInfo";
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorPluginInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
pub enum NotifyAccepted {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotifyAcceptedParams {
|
||||
pub uuid: String,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for NotifyAccepted {
|
||||
type Params = NotifyAcceptedParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "notifyAccepted";
|
||||
}
|
||||
|
||||
pub enum NotifyRejected {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotifyRejectedParams {
|
||||
pub uuids: Vec<String>,
|
||||
}
|
||||
|
||||
impl lsp::request::Request for NotifyRejected {
|
||||
type Params = NotifyRejectedParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "notifyRejected";
|
||||
}
|
||||
391
crates/copilot/src/sign_in.rs
Normal file
@@ -0,0 +1,391 @@
|
||||
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
|
||||
use gpui::{
|
||||
elements::*,
|
||||
geometry::rect::RectF,
|
||||
impl_internal_actions,
|
||||
platform::{WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use theme::ui::modal;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
struct ClickedConnect;
|
||||
|
||||
impl_internal_actions!(copilot_verification, [ClickedConnect]);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
struct CopyUserCode;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
struct OpenGithub;
|
||||
|
||||
const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
|
||||
cx.observe(&copilot, move |copilot, cx| {
|
||||
let status = copilot.read(cx).status();
|
||||
|
||||
match &status {
|
||||
crate::Status::SigningIn { prompt } => {
|
||||
if let Some(code_verification_handle) = code_verification.as_mut() {
|
||||
if cx.has_window(code_verification_handle.window_id()) {
|
||||
code_verification_handle.update(cx, |code_verification_view, cx| {
|
||||
code_verification_view.set_status(status, cx)
|
||||
});
|
||||
cx.activate_window(code_verification_handle.window_id());
|
||||
} else {
|
||||
create_copilot_auth_window(cx, &status, &mut code_verification);
|
||||
}
|
||||
} else if let Some(_prompt) = prompt {
|
||||
create_copilot_auth_window(cx, &status, &mut code_verification);
|
||||
}
|
||||
}
|
||||
Status::Authorized | Status::Unauthorized => {
|
||||
if let Some(code_verification) = code_verification.as_ref() {
|
||||
code_verification.update(cx, |code_verification, cx| {
|
||||
code_verification.set_status(status, cx)
|
||||
});
|
||||
|
||||
cx.platform().activate(true);
|
||||
cx.activate_window(code_verification.window_id());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(code_verification) = code_verification.take() {
|
||||
cx.remove_window(code_verification.window_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.add_action(
|
||||
|code_verification: &mut CopilotCodeVerification, _: &ClickedConnect, _| {
|
||||
code_verification.connect_clicked = true;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_copilot_auth_window(
|
||||
cx: &mut AppContext,
|
||||
status: &Status,
|
||||
code_verification: &mut Option<ViewHandle<CopilotCodeVerification>>,
|
||||
) {
|
||||
let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
|
||||
let window_options = WindowOptions {
|
||||
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
|
||||
titlebar: None,
|
||||
center: true,
|
||||
focus: true,
|
||||
kind: WindowKind::Normal,
|
||||
is_movable: true,
|
||||
screen: None,
|
||||
};
|
||||
let (_, view) = cx.add_window(window_options, |_cx| {
|
||||
CopilotCodeVerification::new(status.clone())
|
||||
});
|
||||
*code_verification = Some(view);
|
||||
}
|
||||
|
||||
pub struct CopilotCodeVerification {
|
||||
status: Status,
|
||||
connect_clicked: bool,
|
||||
}
|
||||
|
||||
impl CopilotCodeVerification {
|
||||
pub fn new(status: Status) -> Self {
|
||||
Self {
|
||||
status,
|
||||
connect_clicked: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
|
||||
self.status = status;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_device_code(
|
||||
data: &PromptUserDeviceFlow,
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
let copied = cx
|
||||
.read_from_clipboard()
|
||||
.map(|item| item.text() == &data.user_code)
|
||||
.unwrap_or(false);
|
||||
|
||||
let device_code_style = &style.auth.prompting.device_code;
|
||||
|
||||
MouseEventHandler::<Self>::new(0, cx, |state, _cx| {
|
||||
Flex::row()
|
||||
.with_children([
|
||||
Label::new(data.user_code.clone(), device_code_style.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(device_code_style.left_container)
|
||||
.constrained()
|
||||
.with_width(device_code_style.left)
|
||||
.boxed(),
|
||||
Label::new(
|
||||
if copied { "Copied!" } else { "Copy" },
|
||||
device_code_style.cta.style_for(state, false).text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(*device_code_style.right_container.style_for(state, false))
|
||||
.constrained()
|
||||
.with_width(device_code_style.right)
|
||||
.boxed(),
|
||||
])
|
||||
.contained()
|
||||
.with_style(device_code_style.cta.style_for(state, false).container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(gpui::platform::MouseButton::Left, {
|
||||
let user_code = data.user_code.clone();
|
||||
move |_, cx| {
|
||||
cx.platform()
|
||||
.write_to_clipboard(ClipboardItem::new(user_code.clone()));
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.with_cursor_style(gpui::platform::CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_prompting_modal(
|
||||
connect_clicked: bool,
|
||||
data: &PromptUserDeviceFlow,
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Label::new(
|
||||
"Enable Copilot by connecting",
|
||||
style.auth.prompting.subheading.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Label::new(
|
||||
"your existing license.",
|
||||
style.auth.prompting.subheading.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.contained()
|
||||
.with_style(style.auth.prompting.subheading.container)
|
||||
.boxed(),
|
||||
Self::render_device_code(data, &style, cx),
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Label::new(
|
||||
"Paste this code into GitHub after",
|
||||
style.auth.prompting.hint.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Label::new(
|
||||
"clicking the button below.",
|
||||
style.auth.prompting.hint.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.contained()
|
||||
.with_style(style.auth.prompting.hint.container.clone())
|
||||
.boxed(),
|
||||
theme::ui::cta_button_with_click(
|
||||
if connect_clicked {
|
||||
"Waiting for connection..."
|
||||
} else {
|
||||
"Connect to GitHub"
|
||||
},
|
||||
style.auth.content_width,
|
||||
&style.auth.cta_button,
|
||||
cx,
|
||||
{
|
||||
let verification_uri = data.verification_uri.clone();
|
||||
move |_, cx| {
|
||||
cx.platform().open_url(&verification_uri);
|
||||
cx.dispatch_action(ClickedConnect)
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
}
|
||||
fn render_enabled_modal(
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
let enabled_style = &style.auth.authorized;
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Label::new("Copilot Enabled!", enabled_style.subheading.text.clone())
|
||||
.contained()
|
||||
.with_style(enabled_style.subheading.container)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Label::new(
|
||||
"You can update your settings or",
|
||||
enabled_style.hint.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Label::new(
|
||||
"sign out from the Copilot menu in",
|
||||
enabled_style.hint.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Label::new("the status bar.", enabled_style.hint.text.clone())
|
||||
.aligned()
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.contained()
|
||||
.with_style(enabled_style.hint.container)
|
||||
.boxed(),
|
||||
theme::ui::cta_button_with_click(
|
||||
"Done",
|
||||
style.auth.content_width,
|
||||
&style.auth.cta_button,
|
||||
cx,
|
||||
|_, cx| {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id)
|
||||
},
|
||||
)
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
}
|
||||
fn render_unauthorized_modal(
|
||||
style: &theme::Copilot,
|
||||
cx: &mut gpui::RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
let unauthorized_style = &style.auth.not_authorized;
|
||||
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Label::new(
|
||||
"Enable Copilot by connecting",
|
||||
unauthorized_style.subheading.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Label::new(
|
||||
"your existing license.",
|
||||
unauthorized_style.subheading.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.contained()
|
||||
.with_style(unauthorized_style.subheading.container)
|
||||
.boxed(),
|
||||
Flex::column()
|
||||
.with_children([
|
||||
Label::new(
|
||||
"You must have an active copilot",
|
||||
unauthorized_style.warning.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
Label::new(
|
||||
"license to use it in Zed.",
|
||||
unauthorized_style.warning.text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.contained()
|
||||
.with_style(unauthorized_style.warning.container)
|
||||
.boxed(),
|
||||
theme::ui::cta_button_with_click(
|
||||
"Subscribe on GitHub",
|
||||
style.auth.content_width,
|
||||
&style.auth.cta_button,
|
||||
cx,
|
||||
|_, cx| {
|
||||
let window_id = cx.window_id();
|
||||
cx.remove_window(window_id);
|
||||
cx.platform().open_url(COPILOT_SIGN_UP_URL)
|
||||
},
|
||||
)
|
||||
.boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for CopilotCodeVerification {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for CopilotCodeVerification {
|
||||
fn ui_name() -> &'static str {
|
||||
"CopilotCodeVerification"
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
|
||||
let style = cx.global::<Settings>().theme.clone();
|
||||
|
||||
modal("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
|
||||
Flex::column()
|
||||
.with_children([
|
||||
theme::ui::icon(&style.copilot.auth.header).boxed(),
|
||||
match &self.status {
|
||||
Status::SigningIn {
|
||||
prompt: Some(prompt),
|
||||
} => Self::render_prompting_modal(
|
||||
self.connect_clicked,
|
||||
&prompt,
|
||||
&style.copilot,
|
||||
cx,
|
||||
),
|
||||
Status::Unauthorized => {
|
||||
self.connect_clicked = false;
|
||||
Self::render_unauthorized_modal(&style.copilot, cx)
|
||||
}
|
||||
Status::Authorized => {
|
||||
self.connect_clicked = false;
|
||||
Self::render_enabled_modal(&style.copilot, cx)
|
||||
}
|
||||
_ => Empty::new().boxed(),
|
||||
},
|
||||
])
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
}
|
||||
22
crates/copilot_button/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "copilot_button"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/copilot_button.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
copilot = { path = "../copilot" }
|
||||
editor = { path = "../editor" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
gpui = { path = "../gpui" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
anyhow = "1.0"
|
||||
smol = "1.2.5"
|
||||
futures = "0.3"
|
||||
387
crates/copilot_button/src/copilot_button.rs
Normal file
@@ -0,0 +1,387 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use context_menu::{ContextMenu, ContextMenuItem};
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
impl_internal_actions,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Element, ElementBox, Entity, MouseState, RenderContext, Subscription, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::{settings_file::SettingsFile, Settings};
|
||||
use workspace::{
|
||||
item::ItemHandle, notifications::simple_message_notification::OsOpen, DismissToast,
|
||||
StatusItemView,
|
||||
};
|
||||
|
||||
use copilot::{Copilot, Reinstall, SignIn, SignOut, Status};
|
||||
|
||||
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
|
||||
const COPILOT_STARTING_TOAST_ID: usize = 1337;
|
||||
const COPILOT_ERROR_TOAST_ID: usize = 1338;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct DeployCopilotMenu;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct DeployCopilotStartMenu;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct HideCopilot;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct InitiateSignIn;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ToggleCopilotForLanguage {
|
||||
language: Arc<str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ToggleCopilotGlobally;
|
||||
|
||||
// TODO: Make the other code path use `get_or_insert` logic for this modal
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct DeployCopilotModal;
|
||||
|
||||
impl_internal_actions!(
|
||||
copilot,
|
||||
[
|
||||
DeployCopilotMenu,
|
||||
DeployCopilotStartMenu,
|
||||
HideCopilot,
|
||||
InitiateSignIn,
|
||||
DeployCopilotModal,
|
||||
ToggleCopilotForLanguage,
|
||||
ToggleCopilotGlobally,
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(CopilotButton::deploy_copilot_menu);
|
||||
cx.add_action(CopilotButton::deploy_copilot_start_menu);
|
||||
cx.add_action(
|
||||
|_: &mut CopilotButton, action: &ToggleCopilotForLanguage, cx| {
|
||||
let language = action.language.clone();
|
||||
let show_copilot_suggestions = cx
|
||||
.global::<Settings>()
|
||||
.show_copilot_suggestions(Some(&language));
|
||||
|
||||
SettingsFile::update(cx, move |file_contents| {
|
||||
file_contents.languages.insert(
|
||||
language,
|
||||
settings::EditorSettings {
|
||||
show_copilot_suggestions: Some((!show_copilot_suggestions).into()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
cx.add_action(|_: &mut CopilotButton, _: &ToggleCopilotGlobally, cx| {
|
||||
let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None);
|
||||
SettingsFile::update(cx, move |file_contents| {
|
||||
file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
|
||||
})
|
||||
});
|
||||
|
||||
cx.add_action(|_: &mut CopilotButton, _: &HideCopilot, cx| {
|
||||
SettingsFile::update(cx, move |file_contents| {
|
||||
file_contents.features.copilot = Some(false)
|
||||
})
|
||||
});
|
||||
|
||||
cx.add_action(|_: &mut CopilotButton, _: &InitiateSignIn, cx| {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return;
|
||||
};
|
||||
let status = copilot.read(cx).status();
|
||||
|
||||
match status {
|
||||
Status::Starting { task } => {
|
||||
cx.dispatch_action(workspace::Toast::new(
|
||||
COPILOT_STARTING_TOAST_ID,
|
||||
"Copilot is starting...",
|
||||
));
|
||||
let window_id = cx.window_id();
|
||||
let task = task.to_owned();
|
||||
cx.spawn(|handle, mut cx| async move {
|
||||
task.await;
|
||||
cx.update(|cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
let status = copilot.read(cx).status();
|
||||
match status {
|
||||
Status::Authorized => cx.dispatch_action_at(
|
||||
window_id,
|
||||
handle.id(),
|
||||
workspace::Toast::new(
|
||||
COPILOT_STARTING_TOAST_ID,
|
||||
"Copilot has started!",
|
||||
),
|
||||
),
|
||||
_ => {
|
||||
cx.dispatch_action_at(
|
||||
window_id,
|
||||
handle.id(),
|
||||
DismissToast::new(COPILOT_STARTING_TOAST_ID),
|
||||
);
|
||||
cx.dispatch_action_at(window_id, handle.id(), SignIn)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
_ => cx.dispatch_action(SignIn),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct CopilotButton {
|
||||
popup_menu: ViewHandle<ContextMenu>,
|
||||
editor_subscription: Option<(Subscription, usize)>,
|
||||
editor_enabled: Option<bool>,
|
||||
language: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
impl Entity for CopilotButton {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for CopilotButton {
|
||||
fn ui_name() -> &'static str {
|
||||
"CopilotButton"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
if !settings.features.copilot {
|
||||
return Empty::new().boxed();
|
||||
}
|
||||
|
||||
let theme = settings.theme.clone();
|
||||
let active = self.popup_menu.read(cx).visible();
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return Empty::new().boxed();
|
||||
};
|
||||
let status = copilot.read(cx).status();
|
||||
|
||||
let enabled = self
|
||||
.editor_enabled
|
||||
.unwrap_or(settings.show_copilot_suggestions(None));
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self>::new(0, cx, {
|
||||
let theme = theme.clone();
|
||||
let status = status.clone();
|
||||
move |state, _cx| {
|
||||
let style = theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.sidebar_buttons
|
||||
.item
|
||||
.style_for(state, active);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new({
|
||||
match status {
|
||||
Status::Error(_) => "icons/copilot_error_16.svg",
|
||||
Status::Authorized => {
|
||||
if enabled {
|
||||
"icons/copilot_16.svg"
|
||||
} else {
|
||||
"icons/copilot_disabled_16.svg"
|
||||
}
|
||||
}
|
||||
_ => "icons/copilot_init_16.svg",
|
||||
}
|
||||
})
|
||||
.with_color(style.icon_color)
|
||||
.constrained()
|
||||
.with_width(style.icon_size)
|
||||
.aligned()
|
||||
.named("copilot-icon"),
|
||||
)
|
||||
.constrained()
|
||||
.with_height(style.icon_size)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, {
|
||||
let status = status.clone();
|
||||
move |_, cx| match status {
|
||||
Status::Authorized => cx.dispatch_action(DeployCopilotMenu),
|
||||
Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action(
|
||||
COPILOT_ERROR_TOAST_ID,
|
||||
format!("Copilot can't be started: {}", e),
|
||||
"Reinstall Copilot",
|
||||
Reinstall,
|
||||
)),
|
||||
_ => cx.dispatch_action(DeployCopilotStartMenu),
|
||||
}
|
||||
})
|
||||
.with_tooltip::<Self, _>(
|
||||
0,
|
||||
"GitHub Copilot".into(),
|
||||
None,
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
ChildView::new(&self.popup_menu, cx)
|
||||
.aligned()
|
||||
.top()
|
||||
.right()
|
||||
.boxed(),
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl CopilotButton {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
let menu = cx.add_view(|cx| {
|
||||
let mut menu = ContextMenu::new(cx);
|
||||
menu.set_position_mode(OverlayPositionMode::Local);
|
||||
menu
|
||||
});
|
||||
|
||||
cx.observe(&menu, |_, _, cx| cx.notify()).detach();
|
||||
|
||||
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
|
||||
|
||||
cx.observe_global::<Settings, _>(move |_, cx| cx.notify())
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
popup_menu: menu,
|
||||
editor_subscription: None,
|
||||
editor_enabled: None,
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deploy_copilot_start_menu(
|
||||
&mut self,
|
||||
_: &DeployCopilotStartMenu,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let mut menu_options = Vec::with_capacity(2);
|
||||
|
||||
menu_options.push(ContextMenuItem::item("Sign In", InitiateSignIn));
|
||||
menu_options.push(ContextMenuItem::item("Hide Copilot", HideCopilot));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
Default::default(),
|
||||
AnchorCorner::BottomRight,
|
||||
menu_options,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deploy_copilot_menu(&mut self, _: &DeployCopilotMenu, cx: &mut ViewContext<Self>) {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let mut menu_options = Vec::with_capacity(6);
|
||||
|
||||
if let Some(language) = &self.language {
|
||||
let language_enabled = settings.show_copilot_suggestions(Some(language.as_ref()));
|
||||
|
||||
menu_options.push(ContextMenuItem::item(
|
||||
format!(
|
||||
"{} Suggestions for {}",
|
||||
if language_enabled { "Hide" } else { "Show" },
|
||||
language
|
||||
),
|
||||
ToggleCopilotForLanguage {
|
||||
language: language.to_owned(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let globally_enabled = cx.global::<Settings>().show_copilot_suggestions(None);
|
||||
menu_options.push(ContextMenuItem::item(
|
||||
if globally_enabled {
|
||||
"Hide Suggestions for All Files"
|
||||
} else {
|
||||
"Show Suggestions for All Files"
|
||||
},
|
||||
ToggleCopilotGlobally,
|
||||
));
|
||||
|
||||
menu_options.push(ContextMenuItem::Separator);
|
||||
|
||||
let icon_style = settings.theme.copilot.out_link_icon.clone();
|
||||
menu_options.push(ContextMenuItem::element_item(
|
||||
Box::new(
|
||||
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
|
||||
Flex::row()
|
||||
.with_children([
|
||||
Label::new("Copilot Settings", style.label.clone()).boxed(),
|
||||
theme::ui::icon(icon_style.style_for(state, false)).boxed(),
|
||||
])
|
||||
.align_children_center()
|
||||
.boxed()
|
||||
},
|
||||
),
|
||||
OsOpen::new(COPILOT_SETTINGS_URL),
|
||||
));
|
||||
|
||||
menu_options.push(ContextMenuItem::item("Sign Out", SignOut));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
Default::default(),
|
||||
AnchorCorner::BottomRight,
|
||||
menu_options,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_enabled(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let settings = cx.global::<Settings>();
|
||||
let suggestion_anchor = editor.selections.newest_anchor().start;
|
||||
|
||||
let language_name = snapshot
|
||||
.language_at(suggestion_anchor)
|
||||
.map(|language| language.name());
|
||||
|
||||
self.language = language_name.clone();
|
||||
|
||||
self.editor_enabled = Some(settings.show_copilot_suggestions(language_name.as_deref()));
|
||||
|
||||
cx.notify()
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemView for CopilotButton {
|
||||
fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
|
||||
self.editor_subscription =
|
||||
Some((cx.observe(&editor, Self::update_enabled), editor.id()));
|
||||
self.update_enabled(editor, cx);
|
||||
} else {
|
||||
self.language = None;
|
||||
self.editor_subscription = None;
|
||||
self.editor_enabled = None;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ async-trait = "0.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
parking_lot = "0.11.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
smol = "1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -4,7 +4,7 @@ pub mod query;
|
||||
// Re-export
|
||||
pub use anyhow;
|
||||
use anyhow::Context;
|
||||
use gpui::MutableAppContext;
|
||||
use gpui::AppContext;
|
||||
pub use indoc::indoc;
|
||||
pub use lazy_static;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
@@ -239,7 +239,7 @@ macro_rules! define_connection {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write_and_log<F>(cx: &mut MutableAppContext, db_write: impl FnOnce() -> F + Send + 'static)
|
||||
pub fn write_and_log<F>(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send + 'static)
|
||||
where
|
||||
F: Future<Output = anyhow::Result<()>> + Send,
|
||||
{
|
||||
|
||||
@@ -10,10 +10,11 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smallvec = { workspace = true }
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
language = { path = "../language" }
|
||||
lsp = { path = "../lsp" }
|
||||
gpui = { path = "../gpui" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
@@ -27,6 +28,7 @@ unindent = "0.1"
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod items;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::{BTreeMap, HashSet};
|
||||
use collections::{BTreeSet, HashSet};
|
||||
use editor::{
|
||||
diagnostic_block_renderer,
|
||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
|
||||
@@ -11,19 +11,21 @@ use editor::{
|
||||
};
|
||||
use gpui::{
|
||||
actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
|
||||
AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
AppContext, Entity, ModelHandle, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
|
||||
SelectionGoal,
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::Cow,
|
||||
cmp::Ordering,
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
@@ -41,7 +43,7 @@ impl_internal_actions!(diagnostics, [Jump]);
|
||||
|
||||
const CONTEXT_LINE_COUNT: u32 = 1;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ProjectDiagnosticsEditor::deploy);
|
||||
items::init(cx);
|
||||
}
|
||||
@@ -55,7 +57,7 @@ struct ProjectDiagnosticsEditor {
|
||||
summary: DiagnosticSummary,
|
||||
excerpts: ModelHandle<MultiBuffer>,
|
||||
path_states: Vec<PathState>,
|
||||
paths_to_update: BTreeMap<ProjectPath, usize>,
|
||||
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
|
||||
}
|
||||
|
||||
struct PathState {
|
||||
@@ -71,6 +73,7 @@ struct Jump {
|
||||
}
|
||||
|
||||
struct DiagnosticGroupState {
|
||||
language_server_id: LanguageServerId,
|
||||
primary_diagnostic: DiagnosticEntry<language::Anchor>,
|
||||
primary_excerpt_ix: usize,
|
||||
excerpts: Vec<ExcerptId>,
|
||||
@@ -115,7 +118,7 @@ impl View for ProjectDiagnosticsEditor {
|
||||
}),
|
||||
"summary": self.summary,
|
||||
"paths_to_update": self.paths_to_update.iter().map(|(path, server_id)|
|
||||
(path.path.to_string_lossy(), server_id)
|
||||
(path.path.to_string_lossy(), server_id.0)
|
||||
).collect::<Vec<_>>(),
|
||||
"paths_states": self.path_states.iter().map(|state|
|
||||
json!({
|
||||
@@ -148,7 +151,7 @@ impl ProjectDiagnosticsEditor {
|
||||
path,
|
||||
} => {
|
||||
this.paths_to_update
|
||||
.insert(path.clone(), *language_server_id);
|
||||
.insert((path.clone(), *language_server_id));
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
@@ -167,7 +170,7 @@ impl ProjectDiagnosticsEditor {
|
||||
let project = project_handle.read(cx);
|
||||
let paths_to_update = project
|
||||
.diagnostic_summaries(cx)
|
||||
.map(|e| (e.0, e.1.language_server_id))
|
||||
.map(|(path, server_id, _)| (path, server_id))
|
||||
.collect();
|
||||
let summary = project.diagnostic_summary(cx);
|
||||
let mut this = Self {
|
||||
@@ -195,9 +198,13 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_excerpts(&mut self, language_server_id: Option<usize>, cx: &mut ViewContext<Self>) {
|
||||
fn update_excerpts(
|
||||
&mut self,
|
||||
language_server_id: Option<LanguageServerId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let mut paths = Vec::new();
|
||||
self.paths_to_update.retain(|path, server_id| {
|
||||
self.paths_to_update.retain(|(path, server_id)| {
|
||||
if language_server_id
|
||||
.map_or(true, |language_server_id| language_server_id == *server_id)
|
||||
{
|
||||
@@ -214,7 +221,9 @@ impl ProjectDiagnosticsEditor {
|
||||
let buffer = project
|
||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.populate_excerpts(path, buffer, cx))
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.populate_excerpts(path, language_server_id, buffer, cx)
|
||||
})
|
||||
}
|
||||
Result::<_, anyhow::Error>::Ok(())
|
||||
}
|
||||
@@ -226,6 +235,7 @@ impl ProjectDiagnosticsEditor {
|
||||
fn populate_excerpts(
|
||||
&mut self,
|
||||
path: ProjectPath,
|
||||
language_server_id: Option<LanguageServerId>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
@@ -264,9 +274,9 @@ impl ProjectDiagnosticsEditor {
|
||||
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
|
||||
let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
|
||||
let mut new_groups = snapshot
|
||||
.diagnostic_groups()
|
||||
.diagnostic_groups(language_server_id)
|
||||
.into_iter()
|
||||
.filter(|group| {
|
||||
.filter(|(_, group)| {
|
||||
group.entries[group.primary_ix].diagnostic.severity
|
||||
<= DiagnosticSeverity::WARNING
|
||||
})
|
||||
@@ -278,12 +288,27 @@ impl ProjectDiagnosticsEditor {
|
||||
match (old_groups.peek(), new_groups.peek()) {
|
||||
(None, None) => break,
|
||||
(None, Some(_)) => to_insert = new_groups.next(),
|
||||
(Some(_), None) => to_remove = old_groups.next(),
|
||||
(Some((_, old_group)), Some(new_group)) => {
|
||||
(Some((_, old_group)), None) => {
|
||||
if language_server_id.map_or(true, |id| id == old_group.language_server_id)
|
||||
{
|
||||
to_remove = old_groups.next();
|
||||
} else {
|
||||
to_keep = old_groups.next();
|
||||
}
|
||||
}
|
||||
(Some((_, old_group)), Some((_, new_group))) => {
|
||||
let old_primary = &old_group.primary_diagnostic;
|
||||
let new_primary = &new_group.entries[new_group.primary_ix];
|
||||
match compare_diagnostics(old_primary, new_primary, &snapshot) {
|
||||
Ordering::Less => to_remove = old_groups.next(),
|
||||
Ordering::Less => {
|
||||
if language_server_id
|
||||
.map_or(true, |id| id == old_group.language_server_id)
|
||||
{
|
||||
to_remove = old_groups.next();
|
||||
} else {
|
||||
to_keep = old_groups.next();
|
||||
}
|
||||
}
|
||||
Ordering::Equal => {
|
||||
to_keep = old_groups.next();
|
||||
new_groups.next();
|
||||
@@ -293,8 +318,9 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(group) = to_insert {
|
||||
if let Some((language_server_id, group)) = to_insert {
|
||||
let mut group_state = DiagnosticGroupState {
|
||||
language_server_id,
|
||||
primary_diagnostic: group.entries[group.primary_ix].clone(),
|
||||
primary_excerpt_ix: 0,
|
||||
excerpts: Default::default(),
|
||||
@@ -531,6 +557,10 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
.update(cx, |editor, cx| editor.navigate(data, cx))
|
||||
}
|
||||
|
||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
|
||||
Some("Project Diagnostics".into())
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &AppContext) -> bool {
|
||||
self.excerpts.read(cx).is_dirty(cx)
|
||||
}
|
||||
@@ -602,16 +632,16 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
))
|
||||
}
|
||||
|
||||
fn act_as_type(
|
||||
&self,
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &ViewHandle<Self>,
|
||||
_: &AppContext,
|
||||
) -> Option<AnyViewHandle> {
|
||||
self_handle: &'a ViewHandle<Self>,
|
||||
_: &'a AppContext,
|
||||
) -> Option<&AnyViewHandle> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.into())
|
||||
Some(self_handle)
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some((&self.editor).into())
|
||||
Some(&self.editor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -768,26 +798,24 @@ mod tests {
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use unindent::Unindent as _;
|
||||
use workspace::AppState;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
let app_state = cx.update(AppState::test);
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"consts.rs": "
|
||||
Settings::test_async(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"consts.rs": "
|
||||
const a: i32 = 'a';
|
||||
const b: i32 = c;
|
||||
"
|
||||
.unindent(),
|
||||
.unindent(),
|
||||
|
||||
"main.rs": "
|
||||
"main.rs": "
|
||||
fn main() {
|
||||
let x = vec![];
|
||||
let y = vec![];
|
||||
@@ -799,19 +827,20 @@ mod tests {
|
||||
d(x);
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
.unindent(),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let language_server_id = LanguageServerId(0);
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
// Create some diagnostics
|
||||
project.update(cx, |project, cx| {
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
0,
|
||||
language_server_id,
|
||||
PathBuf::from("/test/main.rs"),
|
||||
None,
|
||||
vec![
|
||||
@@ -960,10 +989,10 @@ mod tests {
|
||||
|
||||
// Diagnostics are added for another earlier path.
|
||||
project.update(cx, |project, cx| {
|
||||
project.disk_based_diagnostics_started(0, cx);
|
||||
project.disk_based_diagnostics_started(language_server_id, cx);
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
0,
|
||||
language_server_id,
|
||||
PathBuf::from("/test/consts.rs"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
@@ -980,7 +1009,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
project.disk_based_diagnostics_finished(0, cx);
|
||||
project.disk_based_diagnostics_finished(language_server_id, cx);
|
||||
});
|
||||
|
||||
view.next_notification(cx).await;
|
||||
@@ -1060,10 +1089,10 @@ mod tests {
|
||||
|
||||
// Diagnostics are added to the first path
|
||||
project.update(cx, |project, cx| {
|
||||
project.disk_based_diagnostics_started(0, cx);
|
||||
project.disk_based_diagnostics_started(language_server_id, cx);
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
0,
|
||||
language_server_id,
|
||||
PathBuf::from("/test/consts.rs"),
|
||||
None,
|
||||
vec![
|
||||
@@ -1096,7 +1125,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
project.disk_based_diagnostics_finished(0, cx);
|
||||
project.disk_based_diagnostics_finished(language_server_id, cx);
|
||||
});
|
||||
|
||||
view.next_notification(cx).await;
|
||||
@@ -1176,10 +1205,273 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
fn editor_blocks(
|
||||
editor: &ViewHandle<Editor>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Vec<(u32, String)> {
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"main.js": "
|
||||
a();
|
||||
b();
|
||||
c();
|
||||
d();
|
||||
e();
|
||||
".unindent()
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let server_id_1 = LanguageServerId(100);
|
||||
let server_id_2 = LanguageServerId(101);
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
let view = cx.add_view(&workspace, |cx| {
|
||||
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
|
||||
});
|
||||
|
||||
// Two language servers start updating diagnostics
|
||||
project.update(cx, |project, cx| {
|
||||
project.disk_based_diagnostics_started(server_id_1, cx);
|
||||
project.disk_based_diagnostics_started(server_id_2, cx);
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
server_id_1,
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "error 1".to_string(),
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
server_id_2,
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(1, 0))..Unclipped(PointUtf16::new(1, 1)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "warning 1".to_string(),
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// The first language server finishes
|
||||
project.update(cx, |project, cx| {
|
||||
project.disk_based_diagnostics_finished(server_id_1, cx);
|
||||
});
|
||||
|
||||
// Only the first language server's diagnostics are shown.
|
||||
cx.foreground().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", //
|
||||
"b();",
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// The second language server finishes
|
||||
project.update(cx, |project, cx| {
|
||||
project.disk_based_diagnostics_finished(server_id_2, cx);
|
||||
});
|
||||
|
||||
// Both language server's diagnostics are shown.
|
||||
cx.foreground().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(6, "collapsed context".into()),
|
||||
(7, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // context
|
||||
"b();\n", //
|
||||
"c();", // context
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// Both language servers start updating diagnostics, and the first server finishes.
|
||||
project.update(cx, |project, cx| {
|
||||
project.disk_based_diagnostics_started(server_id_1, cx);
|
||||
project.disk_based_diagnostics_started(server_id_2, cx);
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
server_id_1,
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(2, 0))..Unclipped(PointUtf16::new(2, 1)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "warning 2".to_string(),
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
server_id_2,
|
||||
PathBuf::from("/test/main.rs"),
|
||||
None,
|
||||
vec![],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
project.disk_based_diagnostics_finished(server_id_1, cx);
|
||||
});
|
||||
|
||||
// Only the first language server's diagnostics are updated.
|
||||
cx.foreground().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"c();\n", // context
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"b();\n", // context
|
||||
"c();\n", //
|
||||
"d();", // context
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// The second language server finishes.
|
||||
project.update(cx, |project, cx| {
|
||||
project
|
||||
.update_diagnostic_entries(
|
||||
server_id_2,
|
||||
PathBuf::from("/test/main.js"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: Unclipped(PointUtf16::new(3, 0))..Unclipped(PointUtf16::new(3, 1)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "warning 2".to_string(),
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
project.disk_based_diagnostics_finished(server_id_2, cx);
|
||||
});
|
||||
|
||||
// Both language servers' diagnostics are updated.
|
||||
cx.foreground().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"b();\n", // location
|
||||
"c();\n", //
|
||||
"d();\n", // context
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"c();\n", // context
|
||||
"d();\n", //
|
||||
"e();", // context
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
|
||||
let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
|
||||
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
|
||||
cx.render(editor, |editor, cx| {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use collections::HashSet;
|
||||
use editor::{Editor, GoToDiagnostic};
|
||||
use gpui::{
|
||||
elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, MouseButton,
|
||||
MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
serde_json, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use lsp::LanguageServerId;
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, StatusItemView};
|
||||
@@ -13,11 +16,11 @@ pub struct DiagnosticIndicator {
|
||||
summary: project::DiagnosticSummary,
|
||||
active_editor: Option<WeakViewHandle<Editor>>,
|
||||
current_diagnostic: Option<Diagnostic>,
|
||||
in_progress_checks: HashSet<usize>,
|
||||
in_progress_checks: HashSet<LanguageServerId>,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(DiagnosticIndicator::go_to_next_diagnostic);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ use collections::HashSet;
|
||||
use gpui::{
|
||||
elements::{Empty, MouseEventHandler, Overlay},
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::{MouseDown, MouseDrag},
|
||||
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
|
||||
View, WeakViewHandle,
|
||||
AppContext, Element, ElementBox, EventContext, RenderContext, View, WeakViewHandle,
|
||||
};
|
||||
|
||||
const DEAD_ZONE: f32 = 4.;
|
||||
@@ -261,7 +261,7 @@ impl<V: View> DragAndDrop<V> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
|
||||
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut AppContext) {
|
||||
if let Some(State::Dragging {
|
||||
payload, window_id, ..
|
||||
}) = &self.currently_dragged
|
||||
@@ -274,13 +274,13 @@ impl<V: View> DragAndDrop<V> {
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
|
||||
fn finish_dragging(&mut self, cx: &mut AppContext) {
|
||||
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
|
||||
self.notify_containers_for_window(window_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut MutableAppContext) {
|
||||
fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut AppContext) {
|
||||
self.containers.retain(|container| {
|
||||
if let Some(container) = container.upgrade(cx) {
|
||||
if container.window_id() == window_id {
|
||||
|
||||
@@ -11,6 +11,7 @@ doctest = false
|
||||
[features]
|
||||
test-support = [
|
||||
"rand",
|
||||
"copilot/test-support",
|
||||
"text/test-support",
|
||||
"language/test-support",
|
||||
"gpui/test-support",
|
||||
@@ -22,10 +23,10 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
drag_and_drop = { path = "../drag_and_drop" }
|
||||
text = { path = "../text" }
|
||||
clock = { path = "../clock" }
|
||||
copilot = { path = "../copilot" }
|
||||
db = { path = "../db" }
|
||||
drag_and_drop = { path = "../drag_and_drop" }
|
||||
collections = { path = "../collections" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
@@ -38,10 +39,12 @@ rpc = { path = "../rpc" }
|
||||
settings = { path = "../settings" }
|
||||
snippet = { path = "../snippet" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
text = { path = "../text" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
sqlez = { path = "../sqlez" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
aho-corasick = "0.7"
|
||||
anyhow = "1.0"
|
||||
futures = "0.3"
|
||||
@@ -54,8 +57,8 @@ parking_lot = "0.11"
|
||||
postage = { workspace = true }
|
||||
rand = { version = "0.8.3", optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
serde_derive = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
smol = "1.2"
|
||||
tree-sitter-rust = { version = "*", optional = true }
|
||||
tree-sitter-html = { version = "*", optional = true }
|
||||
@@ -63,6 +66,7 @@ tree-sitter-javascript = { version = "*", optional = true }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
copilot = { path = "../copilot", features = ["test-support"] }
|
||||
text = { path = "../text", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
|
||||
@@ -15,12 +15,9 @@ pub struct BlinkManager {
|
||||
|
||||
impl BlinkManager {
|
||||
pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
|
||||
let weak_handle = cx.weak_handle();
|
||||
cx.observe_global::<Settings, _>(move |_, cx| {
|
||||
if let Some(this) = weak_handle.upgrade(cx) {
|
||||
// Make sure we blink the cursors if the setting is re-enabled
|
||||
this.update(cx, |this, cx| this.blink_cursors(this.blink_epoch, cx));
|
||||
}
|
||||
cx.observe_global::<Settings, _>(move |this, cx| {
|
||||
// Make sure we blink the cursors if the setting is re-enabled
|
||||
this.blink_cursors(this.blink_epoch, cx)
|
||||
})
|
||||
.detach();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ mod wrap_map;
|
||||
use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
pub use block_map::{BlockMap, BlockPoint};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fold_map::FoldMap;
|
||||
use fold_map::{FoldMap, FoldOffset};
|
||||
use gpui::{
|
||||
color::Color,
|
||||
fonts::{FontId, HighlightStyle},
|
||||
@@ -19,7 +19,7 @@ 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, TabSnapshot};
|
||||
use tab_map::TabMap;
|
||||
use wrap_map::WrapMap;
|
||||
|
||||
pub use block_map::{
|
||||
@@ -230,23 +230,30 @@ 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>,
|
||||
) where
|
||||
) -> 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) = self.suggestion_map.replace(new_suggestion, 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 {
|
||||
@@ -382,7 +389,7 @@ 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)
|
||||
.chunks(display_row..self.max_point().row() + 1, false, None, None)
|
||||
.map(|h| h.text)
|
||||
}
|
||||
|
||||
@@ -390,7 +397,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)
|
||||
.chunks(row..row + 1, false, None, None)
|
||||
.map(|h| h.text)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
@@ -398,9 +405,18 @@ impl DisplaySnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn chunks(&self, display_rows: Range<u32>, language_aware: bool) -> DisplayChunks<'_> {
|
||||
self.block_snapshot
|
||||
.chunks(display_rows, language_aware, Some(&self.text_highlights))
|
||||
pub fn chunks(
|
||||
&self,
|
||||
display_rows: Range<u32>,
|
||||
language_aware: bool,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
) -> DisplayChunks<'_> {
|
||||
self.block_snapshot.chunks(
|
||||
display_rows,
|
||||
language_aware,
|
||||
Some(&self.text_highlights),
|
||||
suggestion_highlight,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn chars_at(
|
||||
@@ -634,25 +650,21 @@ impl DisplaySnapshot {
|
||||
.buffer_snapshot
|
||||
.buffer_line_for_row(buffer_row)
|
||||
.unwrap();
|
||||
let chars = buffer.chars_at(Point::new(range.start.row, 0));
|
||||
|
||||
let mut indent_size = 0;
|
||||
let mut is_blank = false;
|
||||
let indent_size = TabSnapshot::expand_tabs(
|
||||
chars.take_while(|c| {
|
||||
if *c == ' ' || *c == '\t' {
|
||||
true
|
||||
} else {
|
||||
if *c == '\n' {
|
||||
is_blank = true;
|
||||
}
|
||||
false
|
||||
for c in buffer.chars_at(Point::new(range.start.row, 0)) {
|
||||
if c == ' ' || c == '\t' {
|
||||
indent_size += 1;
|
||||
} else {
|
||||
if c == '\n' {
|
||||
is_blank = true;
|
||||
}
|
||||
}),
|
||||
buffer.line_len(buffer_row) as usize, // Never collapse
|
||||
self.tab_snapshot.tab_size,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(indent_size as u32, is_blank)
|
||||
(indent_size, is_blank)
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
@@ -831,7 +843,7 @@ pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterat
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::{movement, test::marked_display_snapshot};
|
||||
use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
|
||||
use gpui::{color::Color, elements::*, test::observe, AppContext};
|
||||
use language::{Buffer, Language, LanguageConfig, SelectionGoal};
|
||||
use rand::{prelude::*, Rng};
|
||||
use smol::stream::StreamExt;
|
||||
@@ -1105,7 +1117,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(retries = 5)]
|
||||
fn test_soft_wraps(cx: &mut MutableAppContext) {
|
||||
fn test_soft_wraps(cx: &mut AppContext) {
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
cx.foreground().forbid_parking();
|
||||
|
||||
@@ -1198,7 +1210,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_text_chunks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let text = sample_text(6, 6, 'a');
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
@@ -1497,9 +1509,9 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_clip_point(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_clip_point(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
|
||||
let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
|
||||
|
||||
match bias {
|
||||
@@ -1546,10 +1558,10 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(text: &str, cx: &mut gpui::AppContext) {
|
||||
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
|
||||
unmarked_snapshot.clip_at_line_ends = true;
|
||||
assert_eq!(
|
||||
@@ -1565,7 +1577,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
@@ -1626,7 +1638,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_max_point(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_max_point(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
|
||||
let font_cache = cx.font_cache();
|
||||
@@ -1675,7 +1687,7 @@ pub mod tests {
|
||||
rows: Range<u32>,
|
||||
map: &ModelHandle<DisplayMap>,
|
||||
theme: &'a SyntaxTheme,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<(String, Option<Color>)> {
|
||||
chunks(rows, map, theme, cx)
|
||||
.into_iter()
|
||||
@@ -1687,11 +1699,11 @@ pub mod tests {
|
||||
rows: Range<u32>,
|
||||
map: &ModelHandle<DisplayMap>,
|
||||
theme: &'a SyntaxTheme,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> 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) {
|
||||
for chunk in snapshot.chunks(rows, true, None) {
|
||||
let syntax_color = chunk
|
||||
.syntax_highlight_id
|
||||
.and_then(|id| id.style(theme)?.color);
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{ElementBox, RenderContext};
|
||||
use gpui::{fonts::HighlightStyle, ElementBox, RenderContext};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
@@ -572,7 +572,7 @@ impl<'a> BlockMapWriter<'a> {
|
||||
impl BlockSnapshot {
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(0..self.transforms.summary().output_rows, false, None)
|
||||
self.chunks(0..self.transforms.summary().output_rows, false, None, None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
@@ -582,6 +582,7 @@ impl BlockSnapshot {
|
||||
rows: Range<u32>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: 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)>();
|
||||
@@ -614,6 +615,7 @@ impl BlockSnapshot {
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
text_highlights,
|
||||
suggestion_highlight,
|
||||
),
|
||||
input_chunk: Default::default(),
|
||||
transforms: cursor,
|
||||
@@ -1013,7 +1015,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_basic_blocks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
let family_id = cx
|
||||
@@ -1189,7 +1191,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
let family_id = cx
|
||||
@@ -1239,7 +1241,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
|
||||
fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
let operations = env::var("OPERATIONS")
|
||||
@@ -1498,6 +1500,7 @@ mod tests {
|
||||
start_row as u32..blocks_snapshot.max_point().row + 1,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect::<String>();
|
||||
|
||||
@@ -1214,7 +1214,7 @@ mod tests {
|
||||
use Bias::{Left, Right};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_basic_folds(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_basic_folds(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
@@ -1287,7 +1287,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_adjacent_folds(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_adjacent_folds(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
@@ -1334,7 +1334,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_overlapping_folds(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_overlapping_folds(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
||||
@@ -1350,7 +1350,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_merging_folds_via_edit(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
@@ -1374,7 +1374,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_folds_in_range(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_folds_in_range(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
||||
@@ -1401,7 +1401,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_folds(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
|
||||
fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
@@ -1656,7 +1656,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_buffer_rows(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_buffer_rows(cx: &mut gpui::AppContext) {
|
||||
let text = sample_text(6, 6, 'a') + "\n";
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ impl SuggestionPoint {
|
||||
pub struct Suggestion<T> {
|
||||
pub position: T,
|
||||
pub text: Rope,
|
||||
pub highlight_style: HighlightStyle,
|
||||
}
|
||||
|
||||
pub struct SuggestionMap(Mutex<SuggestionSnapshot>);
|
||||
@@ -80,7 +79,11 @@ impl SuggestionMap {
|
||||
new_suggestion: Option<Suggestion<T>>,
|
||||
fold_snapshot: FoldSnapshot,
|
||||
fold_edits: Vec<FoldEdit>,
|
||||
) -> (SuggestionSnapshot, Vec<SuggestionEdit>)
|
||||
) -> (
|
||||
SuggestionSnapshot,
|
||||
Vec<SuggestionEdit>,
|
||||
Option<Suggestion<FoldOffset>>,
|
||||
)
|
||||
where
|
||||
T: ToPoint,
|
||||
{
|
||||
@@ -93,7 +96,6 @@ impl SuggestionMap {
|
||||
Suggestion {
|
||||
position: fold_offset,
|
||||
text: new_suggestion.text,
|
||||
highlight_style: new_suggestion.highlight_style,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -101,7 +103,8 @@ impl SuggestionMap {
|
||||
let mut snapshot = self.0.lock();
|
||||
|
||||
let mut patch = Patch::new(edits);
|
||||
if let Some(suggestion) = snapshot.suggestion.take() {
|
||||
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()),
|
||||
@@ -121,7 +124,7 @@ impl SuggestionMap {
|
||||
|
||||
snapshot.suggestion = new_suggestion;
|
||||
snapshot.version += 1;
|
||||
(snapshot.clone(), patch.into_inner())
|
||||
(snapshot.clone(), patch.into_inner(), old_suggestion)
|
||||
}
|
||||
|
||||
pub fn sync(
|
||||
@@ -173,6 +176,11 @@ impl SuggestionMap {
|
||||
|
||||
(snapshot.clone(), suggestion_edits)
|
||||
}
|
||||
|
||||
pub fn has_suggestion(&self) -> bool {
|
||||
let snapshot = self.0.lock();
|
||||
snapshot.suggestion.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -210,6 +218,32 @@ impl SuggestionSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -369,7 +403,7 @@ impl SuggestionSnapshot {
|
||||
|
||||
pub fn chars_at(&self, start: SuggestionPoint) -> impl '_ + Iterator<Item = char> {
|
||||
let start = self.to_offset(start);
|
||||
self.chunks(start..self.len(), false, None)
|
||||
self.chunks(start..self.len(), false, None, None)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
}
|
||||
|
||||
@@ -378,6 +412,7 @@ impl SuggestionSnapshot {
|
||||
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 =
|
||||
@@ -421,7 +456,7 @@ impl SuggestionSnapshot {
|
||||
prefix_chunks,
|
||||
suggestion_chunks,
|
||||
suffix_chunks,
|
||||
highlight_style: suggestion.highlight_style,
|
||||
highlight_style: suggestion_highlight,
|
||||
}
|
||||
} else {
|
||||
SuggestionChunks {
|
||||
@@ -432,7 +467,7 @@ impl SuggestionSnapshot {
|
||||
)),
|
||||
suggestion_chunks: None,
|
||||
suffix_chunks: None,
|
||||
highlight_style: Default::default(),
|
||||
highlight_style: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,7 +502,7 @@ impl SuggestionSnapshot {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(Default::default()..self.len(), false, None)
|
||||
self.chunks(Default::default()..self.len(), false, None, None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
@@ -477,7 +512,7 @@ pub struct SuggestionChunks<'a> {
|
||||
prefix_chunks: Option<FoldChunks<'a>>,
|
||||
suggestion_chunks: Option<text::Chunks<'a>>,
|
||||
suffix_chunks: Option<FoldChunks<'a>>,
|
||||
highlight_style: HighlightStyle,
|
||||
highlight_style: Option<HighlightStyle>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SuggestionChunks<'a> {
|
||||
@@ -497,7 +532,7 @@ impl<'a> Iterator for SuggestionChunks<'a> {
|
||||
return Some(Chunk {
|
||||
text: chunk,
|
||||
syntax_highlight_id: None,
|
||||
highlight_style: Some(self.highlight_style),
|
||||
highlight_style: self.highlight_style,
|
||||
diagnostic_severity: None,
|
||||
is_unnecessary: false,
|
||||
});
|
||||
@@ -543,7 +578,7 @@ impl<'a> Iterator for SuggestionBufferRows<'a> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{display_map::fold_map::FoldMap, MultiBuffer};
|
||||
use gpui::MutableAppContext;
|
||||
use gpui::AppContext;
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
@@ -552,18 +587,17 @@ mod tests {
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_basic(cx: &mut MutableAppContext) {
|
||||
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(
|
||||
let (suggestion_snapshot, _, _) = suggestion_map.replace(
|
||||
Some(Suggestion {
|
||||
position: 3,
|
||||
text: "123\n456".into(),
|
||||
highlight_style: Default::default(),
|
||||
}),
|
||||
fold_snapshot,
|
||||
Default::default(),
|
||||
@@ -598,7 +632,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_suggestions(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
@@ -692,7 +726,12 @@ mod tests {
|
||||
start = expected_text.clip_offset(start, Bias::Right);
|
||||
|
||||
let actual_text = suggestion_snapshot
|
||||
.chunks(SuggestionOffset(start)..SuggestionOffset(end), false, None)
|
||||
.chunks(
|
||||
SuggestionOffset(start)..SuggestionOffset(end),
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect::<String>();
|
||||
assert_eq!(
|
||||
@@ -816,12 +855,13 @@ mod tests {
|
||||
.collect::<String>()
|
||||
.as_str()
|
||||
.into(),
|
||||
highlight_style: Default::default(),
|
||||
})
|
||||
};
|
||||
|
||||
log::info!("replacing suggestion with {:?}", new_suggestion);
|
||||
self.replace(new_suggestion, fold_snapshot, Default::default())
|
||||
let (snapshot, edits, _) =
|
||||
self.replace(new_suggestion, fold_snapshot, Default::default());
|
||||
(snapshot, edits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ use super::{
|
||||
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>);
|
||||
|
||||
impl TabMap {
|
||||
@@ -15,11 +18,18 @@ impl TabMap {
|
||||
let snapshot = TabSnapshot {
|
||||
suggestion_snapshot: input,
|
||||
tab_size,
|
||||
max_expansion_column: MAX_EXPANSION_COLUMN,
|
||||
version: 0,
|
||||
};
|
||||
(Self(Mutex::new(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 sync(
|
||||
&self,
|
||||
suggestion_snapshot: SuggestionSnapshot,
|
||||
@@ -30,6 +40,7 @@ impl TabMap {
|
||||
let mut new_snapshot = TabSnapshot {
|
||||
suggestion_snapshot,
|
||||
tab_size,
|
||||
max_expansion_column: old_snapshot.max_expansion_column,
|
||||
version: old_snapshot.version,
|
||||
};
|
||||
|
||||
@@ -37,31 +48,66 @@ impl TabMap {
|
||||
new_snapshot.version += 1;
|
||||
}
|
||||
|
||||
let old_max_offset = old_snapshot.suggestion_snapshot.len();
|
||||
let mut tab_edits = Vec::with_capacity(suggestion_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 mut delta = 0;
|
||||
for chunk in old_snapshot.suggestion_snapshot.chunks(
|
||||
suggestion_edit.old.end..old_max_offset,
|
||||
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);
|
||||
|
||||
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,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
let patterns: &[_] = &['\t', '\n'];
|
||||
if let Some(ix) = chunk.text.find(patterns) {
|
||||
if &chunk.text[ix..ix + 1] == "\t" {
|
||||
suggestion_edit.old.end.0 += delta + ix + 1;
|
||||
suggestion_edit.new.end.0 += delta + ix + 1;
|
||||
for (ix, _) in chunk.text.match_indices('\t') {
|
||||
let offset_from_edit = offset_from_edit + (ix as u32);
|
||||
if first_tab_offset.is_none() {
|
||||
first_tab_offset = Some(offset_from_edit);
|
||||
}
|
||||
|
||||
break;
|
||||
let old_column = old_end.column() + offset_from_edit;
|
||||
let new_column = new_end.column() + offset_from_edit;
|
||||
let was_expanded = old_column < old_snapshot.max_expansion_column;
|
||||
let is_expanded = new_column < new_snapshot.max_expansion_column;
|
||||
if was_expanded != is_expanded {
|
||||
last_tab_with_changed_expansion_offset = Some(offset_from_edit);
|
||||
} else if !was_expanded && !is_expanded {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
delta += chunk.text.len();
|
||||
offset_from_edit += chunk.text.len() as u32;
|
||||
if old_end.column() + offset_from_edit >= old_snapshot.max_expansion_column
|
||||
&& new_end.column() + offset_from_edit >= new_snapshot.max_expansion_column
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -111,6 +157,7 @@ impl TabMap {
|
||||
pub struct TabSnapshot {
|
||||
pub suggestion_snapshot: SuggestionSnapshot,
|
||||
pub tab_size: NonZeroU32,
|
||||
pub max_expansion_column: u32,
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
@@ -122,14 +169,12 @@ impl TabSnapshot {
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
let max_point = self.max_point();
|
||||
if row < max_point.row() {
|
||||
self.chunks(
|
||||
TabPoint::new(row, 0)..TabPoint::new(row + 1, 0),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.map(|chunk| chunk.text.len() as u32)
|
||||
.sum::<u32>()
|
||||
- 1
|
||||
self.to_tab_point(SuggestionPoint::new(
|
||||
row,
|
||||
self.suggestion_snapshot.line_len(row),
|
||||
))
|
||||
.0
|
||||
.column
|
||||
} else {
|
||||
max_point.column()
|
||||
}
|
||||
@@ -153,7 +198,7 @@ impl TabSnapshot {
|
||||
self.max_point()
|
||||
};
|
||||
for c in self
|
||||
.chunks(range.start..line_end, false, None)
|
||||
.chunks(range.start..line_end, false, None, None)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
{
|
||||
if c == '\n' {
|
||||
@@ -167,7 +212,12 @@ impl TabSnapshot {
|
||||
last_line_chars = first_line_chars;
|
||||
} else {
|
||||
for _ in self
|
||||
.chunks(TabPoint::new(range.end.row(), 0)..range.end, false, None)
|
||||
.chunks(
|
||||
TabPoint::new(range.end.row(), 0)..range.end,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
{
|
||||
last_line_chars += 1;
|
||||
@@ -188,15 +238,17 @@ impl TabSnapshot {
|
||||
range: Range<TabPoint>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
) -> TabChunks<'a> {
|
||||
let (input_start, expanded_char_column, to_next_stop) =
|
||||
self.to_suggestion_point(range.start, Bias::Left);
|
||||
let input_column = input_start.column();
|
||||
let input_start = self.suggestion_snapshot.to_offset(input_start);
|
||||
let input_end = self
|
||||
.suggestion_snapshot
|
||||
.to_offset(self.to_suggestion_point(range.end, Bias::Right).0);
|
||||
let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 {
|
||||
(range.end.column() - range.start.column()) as usize
|
||||
let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
|
||||
range.end.column() - range.start.column()
|
||||
} else {
|
||||
to_next_stop
|
||||
};
|
||||
@@ -206,16 +258,19 @@ impl TabSnapshot {
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
text_highlights,
|
||||
suggestion_highlight,
|
||||
),
|
||||
input_column,
|
||||
column: expanded_char_column,
|
||||
max_expansion_column: self.max_expansion_column,
|
||||
output_position: range.start.0,
|
||||
max_output_position: range.end.0,
|
||||
tab_size: self.tab_size,
|
||||
chunk: Chunk {
|
||||
text: &SPACES[0..to_next_stop],
|
||||
text: &SPACES[0..(to_next_stop as usize)],
|
||||
..Default::default()
|
||||
},
|
||||
skip_leading_tab: to_next_stop > 0,
|
||||
inside_leading_tab: to_next_stop > 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +280,7 @@ impl TabSnapshot {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(TabPoint::zero()..self.max_point(), false, None)
|
||||
self.chunks(TabPoint::zero()..self.max_point(), false, None, None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
@@ -245,21 +300,17 @@ impl TabSnapshot {
|
||||
let chars = self
|
||||
.suggestion_snapshot
|
||||
.chars_at(SuggestionPoint::new(input.row(), 0));
|
||||
let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size);
|
||||
TabPoint::new(input.row(), expanded as u32)
|
||||
let expanded = self.expand_tabs(chars, input.column());
|
||||
TabPoint::new(input.row(), expanded)
|
||||
}
|
||||
|
||||
pub fn to_suggestion_point(
|
||||
&self,
|
||||
output: TabPoint,
|
||||
bias: Bias,
|
||||
) -> (SuggestionPoint, usize, usize) {
|
||||
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));
|
||||
let expanded = output.column() as usize;
|
||||
let expanded = output.column();
|
||||
let (collapsed, expanded_char_column, to_next_stop) =
|
||||
Self::collapse_tabs(chars, expanded, bias, self.tab_size);
|
||||
self.collapse_tabs(chars, expanded, bias);
|
||||
(
|
||||
SuggestionPoint::new(output.row(), collapsed as u32),
|
||||
expanded_char_column,
|
||||
@@ -282,38 +333,38 @@ impl TabSnapshot {
|
||||
fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot)
|
||||
}
|
||||
|
||||
pub fn expand_tabs(
|
||||
chars: impl Iterator<Item = char>,
|
||||
column: usize,
|
||||
tab_size: NonZeroU32,
|
||||
) -> usize {
|
||||
fn expand_tabs(&self, chars: impl Iterator<Item = char>, column: u32) -> u32 {
|
||||
let tab_size = self.tab_size.get();
|
||||
|
||||
let mut expanded_chars = 0;
|
||||
let mut expanded_bytes = 0;
|
||||
let mut collapsed_bytes = 0;
|
||||
let end_column = column.min(self.max_expansion_column);
|
||||
for c in chars {
|
||||
if collapsed_bytes == column {
|
||||
if collapsed_bytes >= end_column {
|
||||
break;
|
||||
}
|
||||
if c == '\t' {
|
||||
let tab_size = tab_size.get() as usize;
|
||||
let tab_len = tab_size - expanded_chars % tab_size;
|
||||
expanded_bytes += tab_len;
|
||||
expanded_chars += tab_len;
|
||||
} else {
|
||||
expanded_bytes += c.len_utf8();
|
||||
expanded_bytes += c.len_utf8() as u32;
|
||||
expanded_chars += 1;
|
||||
}
|
||||
collapsed_bytes += c.len_utf8();
|
||||
collapsed_bytes += c.len_utf8() as u32;
|
||||
}
|
||||
expanded_bytes
|
||||
expanded_bytes + column.saturating_sub(collapsed_bytes)
|
||||
}
|
||||
|
||||
fn collapse_tabs(
|
||||
&self,
|
||||
chars: impl Iterator<Item = char>,
|
||||
column: usize,
|
||||
column: u32,
|
||||
bias: Bias,
|
||||
tab_size: NonZeroU32,
|
||||
) -> (usize, usize, usize) {
|
||||
) -> (u32, u32, u32) {
|
||||
let tab_size = self.tab_size.get();
|
||||
|
||||
let mut expanded_bytes = 0;
|
||||
let mut expanded_chars = 0;
|
||||
let mut collapsed_bytes = 0;
|
||||
@@ -321,9 +372,11 @@ impl TabSnapshot {
|
||||
if expanded_bytes >= column {
|
||||
break;
|
||||
}
|
||||
if collapsed_bytes >= self.max_expansion_column {
|
||||
break;
|
||||
}
|
||||
|
||||
if c == '\t' {
|
||||
let tab_size = tab_size.get() as usize;
|
||||
let tab_len = tab_size - (expanded_chars % tab_size);
|
||||
expanded_chars += tab_len;
|
||||
expanded_bytes += tab_len;
|
||||
@@ -336,7 +389,7 @@ impl TabSnapshot {
|
||||
}
|
||||
} else {
|
||||
expanded_chars += 1;
|
||||
expanded_bytes += c.len_utf8();
|
||||
expanded_bytes += c.len_utf8() as u32;
|
||||
}
|
||||
|
||||
if expanded_bytes > column && matches!(bias, Bias::Left) {
|
||||
@@ -344,9 +397,13 @@ impl TabSnapshot {
|
||||
break;
|
||||
}
|
||||
|
||||
collapsed_bytes += c.len_utf8();
|
||||
collapsed_bytes += c.len_utf8() as u32;
|
||||
}
|
||||
(collapsed_bytes, expanded_chars, 0)
|
||||
(
|
||||
collapsed_bytes + column.saturating_sub(expanded_bytes),
|
||||
expanded_chars,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,11 +491,13 @@ const SPACES: &str = " ";
|
||||
pub struct TabChunks<'a> {
|
||||
suggestion_chunks: SuggestionChunks<'a>,
|
||||
chunk: Chunk<'a>,
|
||||
column: usize,
|
||||
column: u32,
|
||||
max_expansion_column: u32,
|
||||
output_position: Point,
|
||||
input_column: u32,
|
||||
max_output_position: Point,
|
||||
tab_size: NonZeroU32,
|
||||
skip_leading_tab: bool,
|
||||
inside_leading_tab: bool,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TabChunks<'a> {
|
||||
@@ -448,9 +507,10 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
if self.chunk.text.is_empty() {
|
||||
if let Some(chunk) = self.suggestion_chunks.next() {
|
||||
self.chunk = chunk;
|
||||
if self.skip_leading_tab {
|
||||
if self.inside_leading_tab {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
self.skip_leading_tab = false;
|
||||
self.inside_leading_tab = false;
|
||||
self.input_column += 1;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
@@ -469,27 +529,36 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
});
|
||||
} else {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
let tab_size = self.tab_size.get() as u32;
|
||||
let mut len = tab_size - self.column as u32 % tab_size;
|
||||
let tab_size = if self.input_column < self.max_expansion_column {
|
||||
self.tab_size.get() as u32
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let mut len = tab_size - self.column % tab_size;
|
||||
let next_output_position = cmp::min(
|
||||
self.output_position + Point::new(0, len),
|
||||
self.max_output_position,
|
||||
);
|
||||
len = next_output_position.column - self.output_position.column;
|
||||
self.column += len as usize;
|
||||
self.column += len;
|
||||
self.input_column += 1;
|
||||
self.output_position = next_output_position;
|
||||
return Some(Chunk {
|
||||
text: &SPACES[0..len as usize],
|
||||
text: &SPACES[..len as usize],
|
||||
..self.chunk
|
||||
});
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
self.column = 0;
|
||||
self.input_column = 0;
|
||||
self.output_position += Point::new(1, 0);
|
||||
}
|
||||
_ => {
|
||||
self.column += 1;
|
||||
if !self.inside_leading_tab {
|
||||
self.input_column += c.len_utf8() as u32;
|
||||
}
|
||||
self.output_position.column += c.len_utf8() as u32;
|
||||
}
|
||||
}
|
||||
@@ -508,24 +577,85 @@ mod tests {
|
||||
};
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
|
||||
#[test]
|
||||
fn test_expand_tabs() {
|
||||
assert_eq!(
|
||||
TabSnapshot::expand_tabs("\t".chars(), 0, 4.try_into().unwrap()),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
TabSnapshot::expand_tabs("\t".chars(), 1, 4.try_into().unwrap()),
|
||||
4
|
||||
);
|
||||
assert_eq!(
|
||||
TabSnapshot::expand_tabs("\ta".chars(), 2, 4.try_into().unwrap()),
|
||||
5
|
||||
);
|
||||
#[gpui::test]
|
||||
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());
|
||||
|
||||
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0);
|
||||
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4);
|
||||
assert_eq!(tab_snapshot.expand_tabs("\ta".chars(), 2), 5);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_long_lines(cx: &mut gpui::AppContext) {
|
||||
let max_expansion_column = 12;
|
||||
let input = "A\tBC\tDEF\tG\tHI\tJ\tK\tL\tM";
|
||||
let output = "A BC DEF G HI J K L M";
|
||||
|
||||
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());
|
||||
|
||||
tab_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(tab_snapshot.text(), output);
|
||||
|
||||
for (ix, c) in input.char_indices() {
|
||||
assert_eq!(
|
||||
tab_snapshot
|
||||
.chunks(
|
||||
TabPoint::new(0, ix as u32)..tab_snapshot.max_point(),
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>(),
|
||||
&output[ix..],
|
||||
"text from index {ix}"
|
||||
);
|
||||
|
||||
if c != '\t' {
|
||||
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)),
|
||||
TabPoint(output_point),
|
||||
"to_tab_point({input_point:?})"
|
||||
);
|
||||
assert_eq!(
|
||||
tab_snapshot
|
||||
.to_suggestion_point(TabPoint(output_point), Bias::Left)
|
||||
.0,
|
||||
SuggestionPoint(input_point),
|
||||
"to_suggestion_point({output_point:?})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_long_lines_with_character_spanning_max_expansion_column(cx: &mut gpui::AppContext) {
|
||||
let max_expansion_column = 8;
|
||||
let input = "abcdefg⋯hij";
|
||||
|
||||
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());
|
||||
|
||||
tab_snapshot.max_expansion_column = max_expansion_column;
|
||||
assert_eq!(tab_snapshot.text(), input);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_tabs(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
|
||||
fn test_random_tabs(cx: &mut gpui::AppContext, mut rng: StdRng) {
|
||||
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
|
||||
let len = rng.gen_range(0..30);
|
||||
let buffer = if rng.gen() {
|
||||
@@ -547,7 +677,9 @@ mod tests {
|
||||
let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng);
|
||||
log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text());
|
||||
|
||||
let (_, tabs_snapshot) = TabMap::new(suggestion_snapshot.clone(), tab_size);
|
||||
let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
|
||||
let text = text::Rope::from(tabs_snapshot.text().as_str());
|
||||
log::info!(
|
||||
"TabMap text (tab size: {}): {:?}",
|
||||
@@ -572,11 +704,11 @@ mod tests {
|
||||
.collect::<String>();
|
||||
let expected_summary = TextSummary::from(expected_text.as_str());
|
||||
assert_eq!(
|
||||
expected_text,
|
||||
tabs_snapshot
|
||||
.chunks(start..end, false, None)
|
||||
.chunks(start..end, false, None, None)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>(),
|
||||
expected_text,
|
||||
"chunks({:?}..{:?})",
|
||||
start,
|
||||
end
|
||||
@@ -591,7 +723,11 @@ mod tests {
|
||||
}
|
||||
|
||||
for row in 0..=text.max_point().row {
|
||||
assert_eq!(tabs_snapshot.line_len(row), text.line_len(row));
|
||||
assert_eq!(
|
||||
tabs_snapshot.line_len(row),
|
||||
text.line_len(row),
|
||||
"line_len({row})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ use super::{
|
||||
};
|
||||
use crate::MultiBufferSnapshot;
|
||||
use gpui::{
|
||||
fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||
Task,
|
||||
fonts::{FontId, HighlightStyle},
|
||||
text_layout::LineWrapper,
|
||||
AppContext, Entity, ModelContext, ModelHandle, Task,
|
||||
};
|
||||
use language::{Chunk, Point};
|
||||
use lazy_static::lazy_static;
|
||||
@@ -78,7 +79,7 @@ impl WrapMap {
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
wrap_width: Option<f32>,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> (ModelHandle<Self>, WrapSnapshot) {
|
||||
let handle = cx.add_model(|cx| {
|
||||
let mut this = Self {
|
||||
@@ -444,6 +445,7 @@ impl WrapSnapshot {
|
||||
TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let mut edit_transforms = Vec::<Transform>::new();
|
||||
for _ in edit.new_rows.start..edit.new_rows.end {
|
||||
@@ -573,6 +575,7 @@ impl WrapSnapshot {
|
||||
rows: Range<u32>,
|
||||
language_aware: bool,
|
||||
text_highlights: Option<&'a TextHighlights>,
|
||||
suggestion_highlight: Option<HighlightStyle>,
|
||||
) -> WrapChunks<'a> {
|
||||
let output_start = WrapPoint::new(rows.start, 0);
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
@@ -590,6 +593,7 @@ impl WrapSnapshot {
|
||||
input_start..input_end,
|
||||
language_aware,
|
||||
text_highlights,
|
||||
suggestion_highlight,
|
||||
),
|
||||
input_chunk: Default::default(),
|
||||
output_position: output_start,
|
||||
@@ -1089,7 +1093,8 @@ mod tests {
|
||||
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, tabs_snapshot) = TabMap::new(suggestion_snapshot.clone(), tab_size);
|
||||
let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system);
|
||||
@@ -1315,7 +1320,7 @@ 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)
|
||||
self.chunks(wrap_row..self.max_point().row() + 1, false, None, None)
|
||||
.map(|h| h.text)
|
||||
}
|
||||
|
||||
@@ -1339,7 +1344,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let actual_text = self
|
||||
.chunks(start_row..end_row, true, None)
|
||||
.chunks(start_row..end_row, true, None, None)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>();
|
||||
assert_eq!(
|
||||
|
||||
@@ -20,10 +20,11 @@ mod editor_tests;
|
||||
pub mod test;
|
||||
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use blink_manager::BlinkManager;
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||
use copilot::Copilot;
|
||||
pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use element::*;
|
||||
@@ -38,11 +39,10 @@ use gpui::{
|
||||
geometry::vector::Vector2F,
|
||||
impl_actions, impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::CursorStyle,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
serde_json::{self, json},
|
||||
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
|
||||
ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HideHover, HoverState};
|
||||
@@ -94,6 +94,7 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const MAX_LINE_LEN: usize = 1024;
|
||||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
||||
const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
||||
|
||||
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
|
||||
@@ -183,6 +184,7 @@ actions!(
|
||||
Backspace,
|
||||
Delete,
|
||||
Newline,
|
||||
NewlineAbove,
|
||||
NewlineBelow,
|
||||
GoToDiagnostic,
|
||||
GoToPrevDiagnostic,
|
||||
@@ -259,6 +261,8 @@ actions!(
|
||||
Format,
|
||||
ToggleSoftWrap,
|
||||
RevealInFinder,
|
||||
CopyPath,
|
||||
CopyRelativePath,
|
||||
CopyHighlightJson
|
||||
]
|
||||
);
|
||||
@@ -293,11 +297,12 @@ pub enum Direction {
|
||||
Next,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Editor::new_file);
|
||||
cx.add_action(Editor::select);
|
||||
cx.add_action(Editor::cancel);
|
||||
cx.add_action(Editor::newline);
|
||||
cx.add_action(Editor::newline_above);
|
||||
cx.add_action(Editor::newline_below);
|
||||
cx.add_action(Editor::backspace);
|
||||
cx.add_action(Editor::delete);
|
||||
@@ -379,6 +384,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(Editor::jump);
|
||||
cx.add_action(Editor::toggle_soft_wrap);
|
||||
cx.add_action(Editor::reveal_in_finder);
|
||||
cx.add_action(Editor::copy_path);
|
||||
cx.add_action(Editor::copy_relative_path);
|
||||
cx.add_action(Editor::copy_highlight_json);
|
||||
cx.add_async_action(Editor::format);
|
||||
cx.add_action(Editor::restart_language_server);
|
||||
@@ -388,6 +395,9 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_async_action(Editor::rename);
|
||||
cx.add_async_action(Editor::confirm_rename);
|
||||
cx.add_async_action(Editor::find_all_references);
|
||||
cx.add_action(Editor::next_copilot_suggestion);
|
||||
cx.add_action(Editor::previous_copilot_suggestion);
|
||||
cx.add_action(Editor::copilot_suggest);
|
||||
|
||||
hover_popover::init(cx);
|
||||
link_go_to_definition::init(cx);
|
||||
@@ -506,6 +516,7 @@ pub struct Editor {
|
||||
hover_state: HoverState,
|
||||
gutter_hovered: bool,
|
||||
link_go_to_definition_state: LinkGoToDefinitionState,
|
||||
copilot_state: CopilotState,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -1003,6 +1014,108 @@ impl CodeActionsMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CopilotState {
|
||||
excerpt_id: Option<ExcerptId>,
|
||||
pending_refresh: Task<Option<()>>,
|
||||
pending_cycling_refresh: Task<Option<()>>,
|
||||
cycled: bool,
|
||||
completions: Vec<copilot::Completion>,
|
||||
active_completion_index: usize,
|
||||
}
|
||||
|
||||
impl Default for CopilotState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
excerpt_id: None,
|
||||
pending_cycling_refresh: Task::ready(Some(())),
|
||||
pending_refresh: Task::ready(Some(())),
|
||||
completions: Default::default(),
|
||||
active_completion_index: 0,
|
||||
cycled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CopilotState {
|
||||
fn active_completion(&self) -> Option<&copilot::Completion> {
|
||||
self.completions.get(self.active_completion_index)
|
||||
}
|
||||
|
||||
fn text_for_active_completion(
|
||||
&self,
|
||||
cursor: Anchor,
|
||||
buffer: &MultiBufferSnapshot,
|
||||
) -> Option<&str> {
|
||||
use language::ToOffset as _;
|
||||
|
||||
let completion = self.active_completion()?;
|
||||
let excerpt_id = self.excerpt_id?;
|
||||
let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
|
||||
if excerpt_id != cursor.excerpt_id
|
||||
|| !completion.range.start.is_valid(completion_buffer)
|
||||
|| !completion.range.end.is_valid(completion_buffer)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut completion_range = completion.range.to_offset(&completion_buffer);
|
||||
let prefix_len = Self::common_prefix(
|
||||
completion_buffer.chars_for_range(completion_range.clone()),
|
||||
completion.text.chars(),
|
||||
);
|
||||
completion_range.start += prefix_len;
|
||||
let suffix_len = Self::common_prefix(
|
||||
completion_buffer.reversed_chars_for_range(completion_range.clone()),
|
||||
completion.text[prefix_len..].chars().rev(),
|
||||
);
|
||||
completion_range.end = completion_range.end.saturating_sub(suffix_len);
|
||||
|
||||
if completion_range.is_empty()
|
||||
&& completion_range.start == cursor.text_anchor.to_offset(&completion_buffer)
|
||||
{
|
||||
Some(&completion.text[prefix_len..completion.text.len() - suffix_len])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn cycle_completions(&mut self, direction: Direction) {
|
||||
match direction {
|
||||
Direction::Prev => {
|
||||
self.active_completion_index = if self.active_completion_index == 0 {
|
||||
self.completions.len().saturating_sub(1)
|
||||
} else {
|
||||
self.active_completion_index - 1
|
||||
};
|
||||
}
|
||||
Direction::Next => {
|
||||
if self.completions.len() == 0 {
|
||||
self.active_completion_index = 0
|
||||
} else {
|
||||
self.active_completion_index =
|
||||
(self.active_completion_index + 1) % self.completions.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_completion(&mut self, new_completion: copilot::Completion) {
|
||||
for completion in &self.completions {
|
||||
if completion.text == new_completion.text && completion.range == new_completion.range {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.completions.push(new_completion);
|
||||
}
|
||||
|
||||
fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {
|
||||
a.zip(b)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.len_utf8())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ActiveDiagnosticGroup {
|
||||
primary_range: Range<Anchor>,
|
||||
@@ -1176,12 +1289,14 @@ impl Editor {
|
||||
remote_id: None,
|
||||
hover_state: Default::default(),
|
||||
link_go_to_definition_state: Default::default(),
|
||||
copilot_state: Default::default(),
|
||||
gutter_hovered: false,
|
||||
_subscriptions: vec![
|
||||
cx.observe(&buffer, Self::on_buffer_changed),
|
||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||
cx.observe(&display_map, Self::on_display_map_changed),
|
||||
cx.observe(&blink_manager, |_, _, cx| cx.notify()),
|
||||
cx.observe_global::<Settings, _>(Self::settings_changed),
|
||||
],
|
||||
};
|
||||
this.end_selection(cx);
|
||||
@@ -1234,7 +1349,7 @@ impl Editor {
|
||||
self.buffer().read(cx).title(cx)
|
||||
}
|
||||
|
||||
pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> EditorSnapshot {
|
||||
pub fn snapshot(&mut self, cx: &mut AppContext) -> EditorSnapshot {
|
||||
EditorSnapshot {
|
||||
mode: self.mode,
|
||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||
@@ -1385,6 +1500,7 @@ impl Editor {
|
||||
self.refresh_code_actions(cx);
|
||||
self.refresh_document_highlights(cx);
|
||||
refresh_matching_bracket_highlights(self, cx);
|
||||
self.discard_copilot_suggestion(cx);
|
||||
}
|
||||
|
||||
self.blink_manager.update(cx, BlinkManager::pause_blinking);
|
||||
@@ -1758,6 +1874,10 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.discard_copilot_suggestion(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.snippet_stack.pop().is_some() {
|
||||
return;
|
||||
}
|
||||
@@ -1933,8 +2053,18 @@ impl Editor {
|
||||
}
|
||||
|
||||
drop(snapshot);
|
||||
let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||
this.trigger_completion_on_input(&text, cx);
|
||||
|
||||
if had_active_copilot_suggestion {
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
if !this.has_active_copilot_suggestion(cx) {
|
||||
this.trigger_completion_on_input(&text, cx);
|
||||
}
|
||||
} else {
|
||||
this.trigger_completion_on_input(&text, cx);
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2015,6 +2145,66 @@ impl Editor {
|
||||
.collect();
|
||||
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut rows = Vec::new();
|
||||
let mut rows_inserted = 0;
|
||||
|
||||
for selection in self.selections.all_adjusted(cx) {
|
||||
let cursor = selection.head();
|
||||
let row = cursor.row;
|
||||
|
||||
let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
|
||||
|
||||
let newline = "\n".to_string();
|
||||
edits.push((start_of_line..start_of_line, newline));
|
||||
|
||||
rows.push(row + rows_inserted);
|
||||
rows_inserted += 1;
|
||||
}
|
||||
|
||||
self.transact(cx, |editor, cx| {
|
||||
editor.edit(edits, cx);
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let mut index = 0;
|
||||
s.move_cursors_with(|map, _, _| {
|
||||
let row = rows[index];
|
||||
index += 1;
|
||||
|
||||
let point = Point::new(row, 0);
|
||||
let boundary = map.next_line_boundary(point).1;
|
||||
let clipped = map.clip_point(boundary, Bias::Left);
|
||||
|
||||
(clipped, SelectionGoal::None)
|
||||
});
|
||||
});
|
||||
|
||||
let mut indent_edits = Vec::new();
|
||||
let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
for row in rows {
|
||||
let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
|
||||
for (row, indent) in indents {
|
||||
if indent.len == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let text = match indent.kind {
|
||||
IndentKind::Space => " ".repeat(indent.len as usize),
|
||||
IndentKind::Tab => "\t".repeat(indent.len as usize),
|
||||
};
|
||||
let point = Point::new(row, 0);
|
||||
indent_edits.push((point..point, text));
|
||||
}
|
||||
}
|
||||
editor.edit(indent_edits, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2030,19 +2220,18 @@ impl Editor {
|
||||
let cursor = selection.head();
|
||||
let row = cursor.row;
|
||||
|
||||
let end_of_line = snapshot
|
||||
.clip_point(Point::new(row, snapshot.line_len(row)), Bias::Left)
|
||||
.to_point(&snapshot);
|
||||
let point = Point::new(row + 1, 0);
|
||||
let start_of_line = snapshot.clip_point(point, Bias::Left);
|
||||
|
||||
let newline = "\n".to_string();
|
||||
edits.push((end_of_line..end_of_line, newline));
|
||||
edits.push((start_of_line..start_of_line, newline));
|
||||
|
||||
rows_inserted += 1;
|
||||
rows.push(row + rows_inserted);
|
||||
}
|
||||
|
||||
self.transact(cx, |editor, cx| {
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
editor.edit(edits, cx);
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let mut index = 0;
|
||||
@@ -2057,10 +2246,44 @@ impl Editor {
|
||||
(clipped, SelectionGoal::None)
|
||||
});
|
||||
});
|
||||
|
||||
let mut indent_edits = Vec::new();
|
||||
let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
for row in rows {
|
||||
let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
|
||||
for (row, indent) in indents {
|
||||
if indent.len == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let text = match indent.kind {
|
||||
IndentKind::Space => " ".repeat(indent.len as usize),
|
||||
IndentKind::Tab => "\t".repeat(indent.len as usize),
|
||||
};
|
||||
let point = Point::new(row, 0);
|
||||
indent_edits.push((point..point, text));
|
||||
}
|
||||
}
|
||||
editor.edit(indent_edits, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||
self.insert_with_autoindent_mode(
|
||||
text,
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns: Vec::new(),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn insert_with_autoindent_mode(
|
||||
&mut self,
|
||||
text: &str,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let text: Arc<str> = text.into();
|
||||
self.transact(cx, |this, cx| {
|
||||
let old_selections = this.selections.all_adjusted(cx);
|
||||
@@ -2079,9 +2302,7 @@ impl Editor {
|
||||
old_selections
|
||||
.iter()
|
||||
.map(|s| (s.start..s.end, text.clone())),
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns: Vec::new(),
|
||||
}),
|
||||
autoindent_mode,
|
||||
cx,
|
||||
);
|
||||
anchors
|
||||
@@ -2239,53 +2460,66 @@ impl Editor {
|
||||
let id = post_inc(&mut self.next_completion_id);
|
||||
let task = cx.spawn_weak(|this, mut cx| {
|
||||
async move {
|
||||
let completions = completions.await?;
|
||||
if completions.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut menu = CompletionsMenu {
|
||||
id,
|
||||
initial_position: position,
|
||||
match_candidates: completions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, completion)| {
|
||||
StringMatchCandidate::new(
|
||||
id,
|
||||
completion.label.text[completion.label.filter_range.clone()].into(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
buffer,
|
||||
completions: completions.into(),
|
||||
matches: Vec::new().into(),
|
||||
selected_item: 0,
|
||||
list: Default::default(),
|
||||
let menu = if let Some(completions) = completions.await.log_err() {
|
||||
let mut menu = CompletionsMenu {
|
||||
id,
|
||||
initial_position: position,
|
||||
match_candidates: completions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, completion)| {
|
||||
StringMatchCandidate::new(
|
||||
id,
|
||||
completion.label.text[completion.label.filter_range.clone()]
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
buffer,
|
||||
completions: completions.into(),
|
||||
matches: Vec::new().into(),
|
||||
selected_item: 0,
|
||||
list: Default::default(),
|
||||
};
|
||||
menu.filter(query.as_deref(), cx.background()).await;
|
||||
if menu.matches.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(menu)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
menu.filter(query.as_deref(), cx.background()).await;
|
||||
let this = this
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("editor was dropped"))?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.completion_tasks.retain(|(task_id, _)| *task_id > id);
|
||||
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
match this.context_menu.as_ref() {
|
||||
None => {}
|
||||
Some(ContextMenu::Completions(prev_menu)) => {
|
||||
if prev_menu.id > menu.id {
|
||||
return;
|
||||
}
|
||||
match this.context_menu.as_ref() {
|
||||
None => {}
|
||||
Some(ContextMenu::Completions(prev_menu)) => {
|
||||
if prev_menu.id > id {
|
||||
return;
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
this.completion_tasks.retain(|(id, _)| *id > menu.id);
|
||||
if this.focused {
|
||||
this.show_context_menu(ContextMenu::Completions(menu), cx);
|
||||
if this.focused && menu.is_some() {
|
||||
let menu = menu.unwrap();
|
||||
this.show_context_menu(ContextMenu::Completions(menu), cx);
|
||||
} else if this.completion_tasks.is_empty() {
|
||||
// If there are no more completion tasks and the last menu was
|
||||
// empty, we should hide it. If it was already hidden, we should
|
||||
// also show the copilot suggestion when available.
|
||||
if this.hide_context_menu(cx).is_none() {
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
.log_err()
|
||||
@@ -2385,6 +2619,8 @@ impl Editor {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
|
||||
let project = self.project.clone()?;
|
||||
@@ -2677,6 +2913,212 @@ impl Editor {
|
||||
None
|
||||
}
|
||||
|
||||
fn refresh_copilot_suggestions(
|
||||
&mut self,
|
||||
debounce: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<()> {
|
||||
let copilot = Copilot::global(cx)?;
|
||||
if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() {
|
||||
self.clear_copilot_suggestions(cx);
|
||||
return None;
|
||||
}
|
||||
self.update_visible_copilot_suggestion(cx);
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let language_name = snapshot.language_at(cursor).map(|language| language.name());
|
||||
if !cx
|
||||
.global::<Settings>()
|
||||
.show_copilot_suggestions(language_name.as_deref())
|
||||
{
|
||||
self.clear_copilot_suggestions(cx);
|
||||
return None;
|
||||
}
|
||||
|
||||
let (buffer, buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
self.copilot_state.pending_refresh = cx.spawn_weak(|this, mut cx| async move {
|
||||
if debounce {
|
||||
cx.background().timer(COPILOT_DEBOUNCE_TIMEOUT).await;
|
||||
}
|
||||
|
||||
let completions = copilot
|
||||
.update(&mut cx, |copilot, cx| {
|
||||
copilot.completions(&buffer, buffer_position, cx)
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect_vec();
|
||||
|
||||
this.upgrade(&cx)?.update(&mut cx, |this, cx| {
|
||||
if !completions.is_empty() {
|
||||
this.copilot_state.cycled = false;
|
||||
this.copilot_state.pending_cycling_refresh = Task::ready(None);
|
||||
this.copilot_state.completions.clear();
|
||||
this.copilot_state.active_completion_index = 0;
|
||||
this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
|
||||
for completion in completions {
|
||||
this.copilot_state.push_completion(completion);
|
||||
}
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
});
|
||||
|
||||
Some(())
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn cycle_copilot_suggestions(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<()> {
|
||||
let copilot = Copilot::global(cx)?;
|
||||
if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.copilot_state.cycled {
|
||||
self.copilot_state.cycle_completions(direction);
|
||||
self.update_visible_copilot_suggestion(cx);
|
||||
} else {
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let (buffer, buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
self.copilot_state.pending_cycling_refresh = cx.spawn_weak(|this, mut cx| async move {
|
||||
let completions = copilot
|
||||
.update(&mut cx, |copilot, cx| {
|
||||
copilot.completions_cycling(&buffer, buffer_position, cx)
|
||||
})
|
||||
.await;
|
||||
|
||||
this.upgrade(&cx)?.update(&mut cx, |this, cx| {
|
||||
this.copilot_state.cycled = true;
|
||||
for completion in completions.log_err().into_iter().flatten() {
|
||||
this.copilot_state.push_completion(completion);
|
||||
}
|
||||
this.copilot_state.cycle_completions(direction);
|
||||
this.update_visible_copilot_suggestion(cx);
|
||||
});
|
||||
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn copilot_suggest(&mut self, _: &copilot::Suggest, cx: &mut ViewContext<Self>) {
|
||||
if !self.has_active_copilot_suggestion(cx) {
|
||||
self.refresh_copilot_suggestions(false, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
self.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
|
||||
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
|
||||
if self.has_active_copilot_suggestion(cx) {
|
||||
self.cycle_copilot_suggestions(Direction::Next, cx);
|
||||
} else {
|
||||
self.refresh_copilot_suggestions(false, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn previous_copilot_suggestion(
|
||||
&mut self,
|
||||
_: &copilot::PreviousSuggestion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.has_active_copilot_suggestion(cx) {
|
||||
self.cycle_copilot_suggestions(Direction::Prev, cx);
|
||||
} else {
|
||||
self.refresh_copilot_suggestions(false, cx);
|
||||
}
|
||||
}
|
||||
|
||||
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((copilot, completion)) =
|
||||
Copilot::global(cx).zip(self.copilot_state.active_completion())
|
||||
{
|
||||
copilot
|
||||
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
|
||||
cx.notify();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if self.has_active_copilot_suggestion(cx) {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
copilot
|
||||
.update(cx, |copilot, cx| {
|
||||
copilot.discard_completions(&self.copilot_state.completions, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
|
||||
cx.notify();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
|
||||
self.display_map.read(cx).has_suggestion()
|
||||
}
|
||||
|
||||
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let selection = self.selections.newest_anchor();
|
||||
let cursor = selection.head();
|
||||
|
||||
if self.context_menu.is_some()
|
||||
|| !self.completion_tasks.is_empty()
|
||||
|| selection.start != selection.end
|
||||
{
|
||||
self.discard_copilot_suggestion(cx);
|
||||
} else if let Some(text) = self
|
||||
.copilot_state
|
||||
.text_for_active_completion(cursor, &snapshot)
|
||||
{
|
||||
self.display_map.update(cx, move |map, cx| {
|
||||
map.replace_suggestion(
|
||||
Some(Suggestion {
|
||||
position: cursor,
|
||||
text: text.trim_end().into(),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.notify();
|
||||
} else {
|
||||
self.discard_copilot_suggestion(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.copilot_state = Default::default();
|
||||
self.discard_copilot_suggestion(cx);
|
||||
}
|
||||
|
||||
pub fn render_code_actions_indicator(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
@@ -2792,13 +3234,18 @@ impl Editor {
|
||||
self.completion_tasks.clear();
|
||||
}
|
||||
self.context_menu = Some(menu);
|
||||
self.discard_copilot_suggestion(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
|
||||
cx.notify();
|
||||
self.completion_tasks.clear();
|
||||
self.context_menu.take()
|
||||
let context_menu = self.context_menu.take();
|
||||
if context_menu.is_some() {
|
||||
self.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
context_menu
|
||||
}
|
||||
|
||||
pub fn insert_snippet(
|
||||
@@ -2957,6 +3404,7 @@ impl Editor {
|
||||
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
this.insert("", cx);
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2972,6 +3420,7 @@ impl Editor {
|
||||
})
|
||||
});
|
||||
this.insert("", cx);
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3013,8 +3462,8 @@ impl Editor {
|
||||
// If the selection is empty and the cursor is in the leading whitespace before the
|
||||
// suggested indentation, then auto-indent the line.
|
||||
let cursor = selection.head();
|
||||
let current_indent = snapshot.indent_size_for_line(cursor.row);
|
||||
if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() {
|
||||
let current_indent = snapshot.indent_size_for_line(cursor.row);
|
||||
if cursor.column < suggested_indent.len
|
||||
&& cursor.column <= current_indent.len
|
||||
&& current_indent.len <= suggested_indent.len
|
||||
@@ -3033,6 +3482,16 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
// Accept copilot suggestion if there is only one selection and the cursor is not
|
||||
// in the leading whitespace.
|
||||
if self.selections.count() == 1
|
||||
&& cursor.column >= current_indent.len
|
||||
&& self.has_active_copilot_suggestion(cx)
|
||||
{
|
||||
self.accept_copilot_suggestion(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, insert a hard or soft tab.
|
||||
let settings = cx.global::<Settings>();
|
||||
let language_name = buffer.language_at(cursor, cx).map(|l| l.name());
|
||||
@@ -3056,7 +3515,8 @@ impl Editor {
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections))
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3736,6 +4196,7 @@ impl Editor {
|
||||
}
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
self.unmark_text(cx);
|
||||
self.refresh_copilot_suggestions(true, cx);
|
||||
cx.emit(Event::Edited);
|
||||
}
|
||||
}
|
||||
@@ -3750,6 +4211,7 @@ impl Editor {
|
||||
}
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
self.unmark_text(cx);
|
||||
self.refresh_copilot_suggestions(true, cx);
|
||||
cx.emit(Event::Edited);
|
||||
}
|
||||
}
|
||||
@@ -5421,7 +5883,7 @@ impl Editor {
|
||||
render: Arc::new({
|
||||
let editor = rename_editor.clone();
|
||||
move |cx: &mut BlockContext| {
|
||||
ChildView::new(editor.clone(), cx)
|
||||
ChildView::new(&editor, cx)
|
||||
.contained()
|
||||
.with_padding_left(cx.anchor_x)
|
||||
.boxed()
|
||||
@@ -5904,13 +6366,13 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn longest_row(&self, cx: &mut MutableAppContext) -> u32 {
|
||||
pub fn longest_row(&self, cx: &mut AppContext) -> u32 {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.snapshot(cx))
|
||||
.longest_row()
|
||||
}
|
||||
|
||||
pub fn max_point(&self, cx: &mut MutableAppContext) -> DisplayPoint {
|
||||
pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.snapshot(cx))
|
||||
.max_point()
|
||||
@@ -5930,7 +6392,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn display_text(&self, cx: &mut MutableAppContext) -> String {
|
||||
pub fn display_text(&self, cx: &mut AppContext) -> String {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.snapshot(cx))
|
||||
.text()
|
||||
@@ -5962,7 +6424,7 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut MutableAppContext) -> bool {
|
||||
pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut AppContext) -> bool {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
||||
}
|
||||
@@ -5988,6 +6450,26 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
|
||||
if let Some(path) = file.abs_path(cx).to_str() {
|
||||
cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
|
||||
if let Some(path) = file.path().to_str() {
|
||||
cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
|
||||
self.highlighted_rows = rows;
|
||||
}
|
||||
@@ -6157,6 +6639,9 @@ impl Editor {
|
||||
multi_buffer::Event::Edited => {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
self.refresh_code_actions(cx);
|
||||
if self.has_active_copilot_suggestion(cx) {
|
||||
self.update_visible_copilot_suggestion(cx);
|
||||
}
|
||||
cx.emit(Event::BufferEdited);
|
||||
}
|
||||
multi_buffer::Event::ExcerptsAdded {
|
||||
@@ -6180,6 +6665,7 @@ impl Editor {
|
||||
multi_buffer::Event::DiagnosticsUpdated => {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
}
|
||||
multi_buffer::Event::LanguageChanged => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6187,6 +6673,10 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.refresh_copilot_suggestions(true, cx);
|
||||
}
|
||||
|
||||
pub fn set_searchable(&mut self, searchable: bool) {
|
||||
self.searchable = searchable;
|
||||
}
|
||||
@@ -6325,13 +6815,15 @@ impl Editor {
|
||||
.as_singleton()
|
||||
.and_then(|b| b.read(cx).file()),
|
||||
) {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let extension = Path::new(file.file_name(cx))
|
||||
.extension()
|
||||
.and_then(|e| e.to_str());
|
||||
project.read(cx).client().report_event(
|
||||
name,
|
||||
json!({ "File Extension": extension }),
|
||||
cx.global::<Settings>().telemetry(),
|
||||
json!({ "File Extension": extension, "Vim Mode": settings.vim_mode }),
|
||||
settings.telemetry(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6495,7 +6987,7 @@ pub struct EditorReleased(pub WeakViewHandle<Editor>);
|
||||
impl Entity for Editor {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, cx: &mut MutableAppContext) {
|
||||
fn release(&mut self, cx: &mut AppContext) {
|
||||
cx.emit_global(EditorReleased(self.handle.clone()));
|
||||
}
|
||||
}
|
||||
@@ -6571,7 +7063,7 @@ impl View for Editor {
|
||||
|
||||
fn modifiers_changed(
|
||||
&mut self,
|
||||
event: &gpui::ModifiersChangedEvent,
|
||||
event: &gpui::platform::ModifiersChangedEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
let pending_selection = self.has_pending_selection();
|
||||
|
||||
@@ -16,7 +16,7 @@ use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegist
|
||||
use parking_lot::Mutex;
|
||||
use project::FakeFs;
|
||||
use settings::EditorSettings;
|
||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
assert_set_eq,
|
||||
@@ -28,7 +28,7 @@ use workspace::{
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_events(cx: &mut MutableAppContext) {
|
||||
fn test_edit_events(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = language::Buffer::new(0, "123456", cx);
|
||||
@@ -155,7 +155,7 @@ fn test_edit_events(cx: &mut MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_undo_redo_with_selection_restoration(cx: &mut MutableAppContext) {
|
||||
fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let mut now = Instant::now();
|
||||
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
|
||||
@@ -225,7 +225,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_ime_composition(cx: &mut MutableAppContext) {
|
||||
fn test_ime_composition(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = language::Buffer::new(0, "abcde", cx);
|
||||
@@ -327,7 +327,7 @@ fn test_ime_composition(cx: &mut MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
|
||||
@@ -392,7 +392,7 @@ fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -424,7 +424,7 @@ fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_clone(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_clone(cx: &mut gpui::AppContext) {
|
||||
let (text, selection_ranges) = marked_text_ranges(
|
||||
indoc! {"
|
||||
one
|
||||
@@ -480,7 +480,7 @@ fn test_clone(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_navigation_history(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
cx.set_global(DragAndDrop::<Workspace>::default());
|
||||
use workspace::item::Item;
|
||||
@@ -492,7 +492,7 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||
let handle = cx.handle();
|
||||
editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
|
||||
|
||||
fn pop_history(editor: &mut Editor, cx: &mut MutableAppContext) -> Option<NavigationEntry> {
|
||||
fn pop_history(editor: &mut Editor, cx: &mut AppContext) -> Option<NavigationEntry> {
|
||||
editor.nav_history.as_mut().unwrap().pop_backward(cx)
|
||||
}
|
||||
|
||||
@@ -590,7 +590,7 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_cancel(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_cancel(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -630,7 +630,7 @@ fn test_cancel(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_fold(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_fold_action(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
&"
|
||||
@@ -717,7 +717,7 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_move_cursor(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
@@ -793,7 +793,7 @@ fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
@@ -895,7 +895,7 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
@@ -942,7 +942,7 @@ fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\n def", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -1102,7 +1102,7 @@ fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -1151,7 +1151,7 @@ fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -1330,7 +1330,7 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("one two three four", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
@@ -1365,7 +1365,7 @@ fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_newline(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
|
||||
@@ -1385,7 +1385,7 @@ fn test_newline(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(
|
||||
"
|
||||
@@ -1467,6 +1467,55 @@ fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_newline_above(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<Settings, _, _>(|settings, _| {
|
||||
settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
|
||||
});
|
||||
});
|
||||
|
||||
let language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
||||
.unwrap(),
|
||||
);
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
const a: ˇA = (
|
||||
(ˇ
|
||||
«const_functionˇ»(ˇ),
|
||||
so«mˇ»et«hˇ»ing_ˇelse,ˇ
|
||||
)ˇ
|
||||
ˇ);ˇ
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
ˇ
|
||||
const a: A = (
|
||||
ˇ
|
||||
(
|
||||
ˇ
|
||||
ˇ
|
||||
const_function(),
|
||||
ˇ
|
||||
ˇ
|
||||
ˇ
|
||||
ˇ
|
||||
something_else,
|
||||
ˇ
|
||||
)
|
||||
ˇ
|
||||
ˇ
|
||||
);
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_newline_below(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
@@ -1517,7 +1566,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_insert_with_old_selections(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_insert_with_old_selections(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
|
||||
let (_, editor) = cx.add_window(Default::default(), |cx| {
|
||||
@@ -1836,7 +1885,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(
|
||||
Settings::test(cx)
|
||||
.with_language_defaults(
|
||||
@@ -2022,7 +2071,7 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_delete_line(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_delete_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -2062,7 +2111,7 @@ fn test_delete_line(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_duplicate_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -2110,7 +2159,7 @@ fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_move_line_up_down(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -2206,7 +2255,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
@@ -2230,7 +2279,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_transpose(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_transpose(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
_ = cx
|
||||
@@ -2524,7 +2573,7 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_select_all(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_select_all(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -2538,7 +2587,7 @@ fn test_select_all(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_select_line(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_select_line(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -2582,7 +2631,7 @@ fn test_select_line(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -2650,7 +2699,7 @@ fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_add_selection_above_below(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||
@@ -4322,7 +4371,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||
cx.set_state(
|
||||
&[
|
||||
"one ", //
|
||||
"twoˇ", //
|
||||
"twoˇ", //
|
||||
"three ", //
|
||||
"four", //
|
||||
]
|
||||
@@ -4397,7 +4446,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||
&[
|
||||
"one", //
|
||||
"", //
|
||||
"twoˇ", //
|
||||
"twoˇ", //
|
||||
"", //
|
||||
"three", //
|
||||
"four", //
|
||||
@@ -4412,7 +4461,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||
cx.assert_editor_state(
|
||||
&[
|
||||
"one ", //
|
||||
"twoˇ", //
|
||||
"twoˇ", //
|
||||
"three ", //
|
||||
"four", //
|
||||
]
|
||||
@@ -4585,81 +4634,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||
cx.assert_editor_state("editor.closeˇ");
|
||||
handle_resolve_completion_request(&mut cx, None).await;
|
||||
apply_additional_edits.await.unwrap();
|
||||
|
||||
// Handle completion request passing a marked string specifying where the completion
|
||||
// should be triggered from using '|' character, what range should be replaced, and what completions
|
||||
// should be returned using '<' and '>' to delimit the range
|
||||
async fn handle_completion_request<'a>(
|
||||
cx: &mut EditorLspTestContext<'a>,
|
||||
marked_string: &str,
|
||||
completions: Vec<&'static str>,
|
||||
) {
|
||||
let complete_from_marker: TextRangeMarker = '|'.into();
|
||||
let replace_range_marker: TextRangeMarker = ('<', '>').into();
|
||||
let (_, mut marked_ranges) = marked_text_ranges_by(
|
||||
marked_string,
|
||||
vec![complete_from_marker.clone(), replace_range_marker.clone()],
|
||||
);
|
||||
|
||||
let complete_from_position =
|
||||
cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
|
||||
let replace_range =
|
||||
cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
|
||||
|
||||
cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
|
||||
let completions = completions.clone();
|
||||
async move {
|
||||
assert_eq!(params.text_document_position.text_document.uri, url.clone());
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
complete_from_position
|
||||
);
|
||||
Ok(Some(lsp::CompletionResponse::Array(
|
||||
completions
|
||||
.iter()
|
||||
.map(|completion_text| lsp::CompletionItem {
|
||||
label: completion_text.to_string(),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: replace_range,
|
||||
new_text: completion_text.to_string(),
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
)))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_resolve_completion_request<'a>(
|
||||
cx: &mut EditorLspTestContext<'a>,
|
||||
edits: Option<Vec<(&'static str, &'static str)>>,
|
||||
) {
|
||||
let edits = edits.map(|edits| {
|
||||
edits
|
||||
.iter()
|
||||
.map(|(marked_string, new_text)| {
|
||||
let (_, marked_ranges) = marked_text_ranges(marked_string, false);
|
||||
let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
|
||||
lsp::TextEdit::new(replace_range, new_text.to_string())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
|
||||
let edits = edits.clone();
|
||||
async move {
|
||||
Ok(lsp::CompletionItem {
|
||||
additional_text_edits: edits,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -5003,7 +4977,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
@@ -5050,7 +5024,7 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let markers = vec![('[', ']').into(), ('(', ')').into()];
|
||||
let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
|
||||
@@ -5123,7 +5097,7 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_refresh_selections(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
|
||||
let mut excerpt1_id = None;
|
||||
@@ -5209,7 +5183,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
|
||||
let mut excerpt1_id = None;
|
||||
@@ -5342,7 +5316,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_highlighted_ranges(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
|
||||
|
||||
cx.set_global(Settings::test(cx));
|
||||
@@ -5956,6 +5930,423 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
cx.update(|cx| cx.set_global(copilot));
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// When inserting, ensure autocompletion is favored over Copilot suggestions.
|
||||
cx.set_state(indoc! {"
|
||||
oneˇ
|
||||
two
|
||||
three
|
||||
"});
|
||||
cx.simulate_keystroke(".");
|
||||
let _ = handle_completion_request(
|
||||
&mut cx,
|
||||
indoc! {"
|
||||
one.|<>
|
||||
two
|
||||
three
|
||||
"},
|
||||
vec!["completion_a", "completion_b"],
|
||||
);
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "one.copilot1".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
|
||||
// Confirming a completion inserts it and hides the context menu, without showing
|
||||
// the copilot suggestion afterwards.
|
||||
editor
|
||||
.confirm_completion(&Default::default(), cx)
|
||||
.unwrap()
|
||||
.detach();
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
|
||||
assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// Ensure Copilot suggestions are shown right away if no autocompletion is available.
|
||||
cx.set_state(indoc! {"
|
||||
oneˇ
|
||||
two
|
||||
three
|
||||
"});
|
||||
cx.simulate_keystroke(".");
|
||||
let _ = handle_completion_request(
|
||||
&mut cx,
|
||||
indoc! {"
|
||||
one.|<>
|
||||
two
|
||||
three
|
||||
"},
|
||||
vec![],
|
||||
);
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "one.copilot1".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
|
||||
cx.set_state(indoc! {"
|
||||
oneˇ
|
||||
two
|
||||
three
|
||||
"});
|
||||
cx.simulate_keystroke(".");
|
||||
let _ = handle_completion_request(
|
||||
&mut cx,
|
||||
indoc! {"
|
||||
one.|<>
|
||||
two
|
||||
three
|
||||
"},
|
||||
vec!["completion_a", "completion_b"],
|
||||
);
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "one.copilot1".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
|
||||
// When hiding the context menu, the Copilot suggestion becomes visible.
|
||||
editor.hide_context_menu(cx);
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// Ensure existing completion is interpolated when inserting again.
|
||||
cx.simulate_keystroke("c");
|
||||
deterministic.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// After debouncing, new Copilot completions should be requested.
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "one.copilot2".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
|
||||
// Canceling should remove the active Copilot suggestion.
|
||||
editor.cancel(&Default::default(), cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
|
||||
// After canceling, tabbing shouldn't insert the previously shown suggestion.
|
||||
editor.tab(&Default::default(), cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
|
||||
|
||||
// When undoing the previously active suggestion is shown again.
|
||||
editor.undo(&Default::default(), cx);
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
|
||||
cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||
|
||||
// Tabbing when there is an active suggestion inserts it.
|
||||
editor.tab(&Default::default(), cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
|
||||
// When undoing the previously active suggestion is shown again.
|
||||
editor.undo(&Default::default(), cx);
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||
|
||||
// Hide suggestion.
|
||||
editor.cancel(&Default::default(), cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// If an edit occurs outside of this editor but no suggestion is being shown,
|
||||
// we won't make it visible.
|
||||
cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
// Reset the editor to verify how suggestions behave when tabbing on leading indentation.
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_text("fn foo() {\n \n}", cx);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
|
||||
});
|
||||
});
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: " let x = 4;".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
||||
|
||||
// Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
|
||||
editor.tab(&Default::default(), cx);
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
|
||||
// Tabbing again accepts the suggestion.
|
||||
editor.tab(&Default::default(), cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copilot_completion_invalidation(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
cx.update(|cx| cx.set_global(copilot));
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
one
|
||||
twˇ
|
||||
three
|
||||
"});
|
||||
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "two.foo()".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
|
||||
|
||||
editor.backspace(&Default::default(), cx);
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one\nt\nthree\n");
|
||||
|
||||
editor.backspace(&Default::default(), cx);
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one\n\nthree\n");
|
||||
|
||||
// Deleting across the original suggestion range invalidates it.
|
||||
editor.backspace(&Default::default(), cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one\nthree\n");
|
||||
|
||||
// Undoing the deletion restores the suggestion.
|
||||
editor.undo(&Default::default(), cx);
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one\n\nthree\n");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copilot_multibuffer(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||
cx.update(|cx| {
|
||||
cx.set_global(Settings::test(cx));
|
||||
cx.set_global(copilot)
|
||||
});
|
||||
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(2, 0),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(2, 0),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
|
||||
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "b = 2 + a".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Ensure copilot suggestions are shown for the first excerpt.
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
|
||||
});
|
||||
editor.next_copilot_suggestion(&Default::default(), cx);
|
||||
});
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
|
||||
);
|
||||
assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
|
||||
});
|
||||
|
||||
handle_copilot_completion_request(
|
||||
&copilot_lsp,
|
||||
vec![copilot::request::Completion {
|
||||
text: "d = 4 + c".into(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
|
||||
..Default::default()
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Move to another excerpt, ensuring the suggestion gets cleared.
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
||||
});
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
|
||||
);
|
||||
assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
|
||||
|
||||
// Type a character, ensuring we don't even try to interpolate the previous suggestion.
|
||||
editor.handle_input(" ", cx);
|
||||
assert!(!editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
|
||||
);
|
||||
assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
|
||||
});
|
||||
|
||||
// Ensure the new suggestion is displayed when the debounce timeout expires.
|
||||
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert!(editor.has_active_copilot_suggestion(cx));
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
|
||||
);
|
||||
assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(row as u32, column as u32);
|
||||
point..point
|
||||
@@ -5971,3 +6362,106 @@ fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewCo
|
||||
marked_text
|
||||
);
|
||||
}
|
||||
|
||||
/// Handle completion request passing a marked string specifying where the completion
|
||||
/// should be triggered from using '|' character, what range should be replaced, and what completions
|
||||
/// should be returned using '<' and '>' to delimit the range
|
||||
fn handle_completion_request<'a>(
|
||||
cx: &mut EditorLspTestContext<'a>,
|
||||
marked_string: &str,
|
||||
completions: Vec<&'static str>,
|
||||
) -> impl Future<Output = ()> {
|
||||
let complete_from_marker: TextRangeMarker = '|'.into();
|
||||
let replace_range_marker: TextRangeMarker = ('<', '>').into();
|
||||
let (_, mut marked_ranges) = marked_text_ranges_by(
|
||||
marked_string,
|
||||
vec![complete_from_marker.clone(), replace_range_marker.clone()],
|
||||
);
|
||||
|
||||
let complete_from_position =
|
||||
cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
|
||||
let replace_range =
|
||||
cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
|
||||
|
||||
let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
|
||||
let completions = completions.clone();
|
||||
async move {
|
||||
assert_eq!(params.text_document_position.text_document.uri, url.clone());
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
complete_from_position
|
||||
);
|
||||
Ok(Some(lsp::CompletionResponse::Array(
|
||||
completions
|
||||
.iter()
|
||||
.map(|completion_text| lsp::CompletionItem {
|
||||
label: completion_text.to_string(),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: replace_range,
|
||||
new_text: completion_text.to_string(),
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
||||
async move {
|
||||
request.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_resolve_completion_request<'a>(
|
||||
cx: &mut EditorLspTestContext<'a>,
|
||||
edits: Option<Vec<(&'static str, &'static str)>>,
|
||||
) -> impl Future<Output = ()> {
|
||||
let edits = edits.map(|edits| {
|
||||
edits
|
||||
.iter()
|
||||
.map(|(marked_string, new_text)| {
|
||||
let (_, marked_ranges) = marked_text_ranges(marked_string, false);
|
||||
let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
|
||||
lsp::TextEdit::new(replace_range, new_text.to_string())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let mut request =
|
||||
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
|
||||
let edits = edits.clone();
|
||||
async move {
|
||||
Ok(lsp::CompletionItem {
|
||||
additional_text_edits: edits,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
async move {
|
||||
request.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_copilot_completion_request(
|
||||
lsp: &lsp::FakeLanguageServer,
|
||||
completions: Vec<copilot::request::Completion>,
|
||||
completions_cycling: Vec<copilot::request::Completion>,
|
||||
) {
|
||||
lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
|
||||
let completions = completions.clone();
|
||||
async move {
|
||||
Ok(copilot::request::GetCompletionsResult {
|
||||
completions: completions.clone(),
|
||||
})
|
||||
}
|
||||
});
|
||||
lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
|
||||
let completions_cycling = completions_cycling.clone();
|
||||
async move {
|
||||
Ok(copilot::request::GetCompletionsResult {
|
||||
completions: completions_cycling.clone(),
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,11 +29,10 @@ use gpui::{
|
||||
PathBuilder,
|
||||
},
|
||||
json::{self, ToJson},
|
||||
platform::CursorStyle,
|
||||
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
|
||||
text_layout::{self, Line, RunStyle, TextLayoutCache},
|
||||
AppContext, Axis, Border, CursorRegion, Element, ElementBox, EventContext, LayoutContext,
|
||||
Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MutableAppContext,
|
||||
PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle,
|
||||
MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use json::json;
|
||||
@@ -103,14 +102,14 @@ impl EditorElement {
|
||||
self.view.upgrade(cx).unwrap().read(cx)
|
||||
}
|
||||
|
||||
fn update_view<F, T>(&self, cx: &mut MutableAppContext, f: F) -> T
|
||||
fn update_view<F, T>(&self, cx: &mut AppContext, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
|
||||
{
|
||||
self.view.upgrade(cx).unwrap().update(cx, f)
|
||||
}
|
||||
|
||||
fn snapshot(&self, cx: &mut MutableAppContext) -> EditorSnapshot {
|
||||
fn snapshot(&self, cx: &mut AppContext) -> EditorSnapshot {
|
||||
self.update_view(cx, |view, cx| view.snapshot(cx))
|
||||
}
|
||||
|
||||
@@ -1318,45 +1317,47 @@ impl EditorElement {
|
||||
.collect()
|
||||
} else {
|
||||
let style = &self.style;
|
||||
let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| {
|
||||
let mut highlight_style = chunk
|
||||
.syntax_highlight_id
|
||||
.and_then(|id| id.style(&style.syntax));
|
||||
let chunks = snapshot
|
||||
.chunks(rows.clone(), true, Some(style.theme.suggestion))
|
||||
.map(|chunk| {
|
||||
let mut highlight_style = chunk
|
||||
.syntax_highlight_id
|
||||
.and_then(|id| id.style(&style.syntax));
|
||||
|
||||
if let Some(chunk_highlight) = chunk.highlight_style {
|
||||
if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(chunk_highlight);
|
||||
} else {
|
||||
highlight_style = Some(chunk_highlight);
|
||||
}
|
||||
}
|
||||
|
||||
let mut diagnostic_highlight = HighlightStyle::default();
|
||||
|
||||
if chunk.is_unnecessary {
|
||||
diagnostic_highlight.fade_out = Some(style.unnecessary_code_fade);
|
||||
}
|
||||
|
||||
if let Some(severity) = chunk.diagnostic_severity {
|
||||
// Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
|
||||
if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
|
||||
let diagnostic_style = super::diagnostic_style(severity, true, style);
|
||||
diagnostic_highlight.underline = Some(Underline {
|
||||
color: Some(diagnostic_style.message.text.color),
|
||||
thickness: 1.0.into(),
|
||||
squiggly: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(chunk_highlight) = chunk.highlight_style {
|
||||
if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(chunk_highlight);
|
||||
highlight_style.highlight(diagnostic_highlight);
|
||||
} else {
|
||||
highlight_style = Some(chunk_highlight);
|
||||
highlight_style = Some(diagnostic_highlight);
|
||||
}
|
||||
}
|
||||
|
||||
let mut diagnostic_highlight = HighlightStyle::default();
|
||||
|
||||
if chunk.is_unnecessary {
|
||||
diagnostic_highlight.fade_out = Some(style.unnecessary_code_fade);
|
||||
}
|
||||
|
||||
if let Some(severity) = chunk.diagnostic_severity {
|
||||
// Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
|
||||
if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
|
||||
let diagnostic_style = super::diagnostic_style(severity, true, style);
|
||||
diagnostic_highlight.underline = Some(Underline {
|
||||
color: Some(diagnostic_style.message.text.color),
|
||||
thickness: 1.0.into(),
|
||||
squiggly: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(diagnostic_highlight);
|
||||
} else {
|
||||
highlight_style = Some(diagnostic_highlight);
|
||||
}
|
||||
|
||||
(chunk.text, highlight_style)
|
||||
});
|
||||
(chunk.text, highlight_style)
|
||||
});
|
||||
layout_highlighted_chunks(
|
||||
chunks,
|
||||
&style.text,
|
||||
@@ -2520,7 +2521,7 @@ mod tests {
|
||||
use util::test::sample_text;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_layout_line_numbers(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
@@ -2540,7 +2541,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
|
||||
@@ -3,9 +3,8 @@ use gpui::{
|
||||
actions,
|
||||
elements::{Flex, MouseEventHandler, Padding, Text},
|
||||
impl_internal_actions,
|
||||
platform::CursorStyle,
|
||||
Axis, Element, ElementBox, ModelHandle, MouseButton, MutableAppContext, RenderContext, Task,
|
||||
ViewContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Axis, Element, ElementBox, ModelHandle, RenderContext, Task, ViewContext,
|
||||
};
|
||||
use language::{Bias, DiagnosticEntry, DiagnosticSeverity};
|
||||
use project::{HoverBlock, Project};
|
||||
@@ -36,7 +35,7 @@ pub struct HideHover;
|
||||
actions!(editor, [Hover]);
|
||||
impl_internal_actions!(editor, [HoverAt, HideHover]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(hover);
|
||||
cx.add_action(hover_at);
|
||||
cx.add_action(hide_hover);
|
||||
@@ -437,6 +436,7 @@ mod tests {
|
||||
use indoc::indoc;
|
||||
|
||||
use language::{Diagnostic, DiagnosticSet};
|
||||
use lsp::LanguageServerId;
|
||||
use project::HoverBlock;
|
||||
use smol::stream::StreamExt;
|
||||
|
||||
@@ -621,7 +621,7 @@ mod tests {
|
||||
}],
|
||||
&snapshot,
|
||||
);
|
||||
buffer.update_diagnostics(set, cx);
|
||||
buffer.update_diagnostics(LanguageServerId(0), set, cx);
|
||||
});
|
||||
|
||||
// Hover pops diagnostic immediately
|
||||
|
||||
@@ -3,11 +3,11 @@ use crate::{
|
||||
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
|
||||
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use collections::HashSet;
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{
|
||||
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
|
||||
elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
|
||||
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
@@ -48,7 +48,7 @@ impl FollowableItem for Editor {
|
||||
project: ModelHandle<Project>,
|
||||
remote_id: ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Task<Result<ViewHandle<Self>>>> {
|
||||
let Some(proto::view::Variant::Editor(_)) = state else { return None };
|
||||
let Some(proto::view::Variant::Editor(state)) = state.take() else { unreachable!() };
|
||||
@@ -72,11 +72,11 @@ impl FollowableItem for Editor {
|
||||
let editor = pane.read_with(&cx, |pane, cx| {
|
||||
let mut editors = pane.items_of_type::<Self>();
|
||||
editors.find(|editor| {
|
||||
editor.remote_id(&client, cx) == Some(remote_id)
|
||||
|| state.singleton
|
||||
&& buffers.len() == 1
|
||||
&& editor.read(cx).buffer.read(cx).as_singleton().as_ref()
|
||||
== Some(&buffers[0])
|
||||
let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
|
||||
let singleton_buffer_matches = state.singleton
|
||||
&& buffers.first()
|
||||
== editor.read(cx).buffer.read(cx).as_singleton().as_ref();
|
||||
ids_match || singleton_buffer_matches
|
||||
})
|
||||
});
|
||||
|
||||
@@ -115,46 +115,29 @@ impl FollowableItem for Editor {
|
||||
multibuffer
|
||||
});
|
||||
|
||||
cx.add_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), cx))
|
||||
cx.add_view(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
|
||||
editor.remote_id = Some(remote_id);
|
||||
editor
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.remote_id = Some(remote_id);
|
||||
let buffer = editor.buffer.read(cx).read(cx);
|
||||
let selections = state
|
||||
.selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
deserialize_selection(&buffer, selection)
|
||||
.ok_or_else(|| anyhow!("invalid selection"))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let pending_selection = state
|
||||
.pending_selection
|
||||
.map(|selection| deserialize_selection(&buffer, selection))
|
||||
.flatten();
|
||||
let scroll_top_anchor = state
|
||||
.scroll_top_anchor
|
||||
.and_then(|anchor| deserialize_anchor(&buffer, anchor));
|
||||
drop(buffer);
|
||||
|
||||
if !selections.is_empty() || pending_selection.is_some() {
|
||||
editor.set_selections_from_remote(selections, pending_selection, cx);
|
||||
}
|
||||
|
||||
if let Some(scroll_top_anchor) = scroll_top_anchor {
|
||||
editor.set_scroll_anchor_remote(
|
||||
ScrollAnchor {
|
||||
top_anchor: scroll_top_anchor,
|
||||
offset: vec2f(state.scroll_x, state.scroll_y),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
update_editor_from_message(
|
||||
editor.clone(),
|
||||
project,
|
||||
proto::update_view::Editor {
|
||||
selections: state.selections,
|
||||
pending_selection: state.pending_selection,
|
||||
scroll_top_anchor: state.scroll_top_anchor,
|
||||
scroll_x: state.scroll_x,
|
||||
scroll_y: state.scroll_y,
|
||||
..Default::default()
|
||||
},
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(editor)
|
||||
}))
|
||||
@@ -299,96 +282,9 @@ impl FollowableItem for Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let update_view::Variant::Editor(message) = message;
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let multibuffer = multibuffer.read(cx);
|
||||
|
||||
let buffer_ids = message
|
||||
.inserted_excerpts
|
||||
.iter()
|
||||
.filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut removals = message
|
||||
.deleted_excerpts
|
||||
.into_iter()
|
||||
.map(ExcerptId::from_proto)
|
||||
.collect::<Vec<_>>();
|
||||
removals.sort_by(|a, b| a.cmp(&b, &multibuffer));
|
||||
|
||||
let selections = message
|
||||
.selections
|
||||
.into_iter()
|
||||
.filter_map(|selection| deserialize_selection(&multibuffer, selection))
|
||||
.collect::<Vec<_>>();
|
||||
let pending_selection = message
|
||||
.pending_selection
|
||||
.and_then(|selection| deserialize_selection(&multibuffer, selection));
|
||||
|
||||
let scroll_top_anchor = message
|
||||
.scroll_top_anchor
|
||||
.and_then(|anchor| deserialize_anchor(&multibuffer, anchor));
|
||||
drop(multibuffer);
|
||||
|
||||
let buffers = project.update(cx, |project, cx| {
|
||||
buffer_ids
|
||||
.into_iter()
|
||||
.map(|id| project.open_buffer_by_id(id, cx))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let project = project.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let _buffers = try_join_all(buffers).await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.buffer.update(cx, |multibuffer, cx| {
|
||||
let mut insertions = message.inserted_excerpts.into_iter().peekable();
|
||||
while let Some(insertion) = insertions.next() {
|
||||
let Some(excerpt) = insertion.excerpt else { continue };
|
||||
let Some(previous_excerpt_id) = insertion.previous_excerpt_id else { continue };
|
||||
let buffer_id = excerpt.buffer_id;
|
||||
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else { continue };
|
||||
|
||||
let adjacent_excerpts = iter::from_fn(|| {
|
||||
let insertion = insertions.peek()?;
|
||||
if insertion.previous_excerpt_id.is_none()
|
||||
&& insertion.excerpt.as_ref()?.buffer_id == buffer_id
|
||||
{
|
||||
insertions.next()?.excerpt
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
multibuffer.insert_excerpts_with_ids_after(
|
||||
ExcerptId::from_proto(previous_excerpt_id),
|
||||
buffer,
|
||||
[excerpt]
|
||||
.into_iter()
|
||||
.chain(adjacent_excerpts)
|
||||
.filter_map(|excerpt| {
|
||||
Some((
|
||||
ExcerptId::from_proto(excerpt.id),
|
||||
deserialize_excerpt_range(excerpt)?,
|
||||
))
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
multibuffer.remove_excerpts(removals, cx);
|
||||
});
|
||||
|
||||
if !selections.is_empty() || pending_selection.is_some() {
|
||||
this.set_selections_from_remote(selections, pending_selection, cx);
|
||||
this.request_autoscroll_remotely(Autoscroll::newest(), cx);
|
||||
} else if let Some(anchor) = scroll_top_anchor {
|
||||
this.set_scroll_anchor_remote(ScrollAnchor {
|
||||
top_anchor: anchor,
|
||||
offset: vec2f(message.scroll_x, message.scroll_y)
|
||||
}, cx);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
update_editor_from_message(this, project, message, &mut cx).await
|
||||
})
|
||||
}
|
||||
|
||||
@@ -402,6 +298,128 @@ impl FollowableItem for Editor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_editor_from_message(
|
||||
this: ViewHandle<Editor>,
|
||||
project: ModelHandle<Project>,
|
||||
message: proto::update_view::Editor,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
// Open all of the buffers of which excerpts were added to the editor.
|
||||
let inserted_excerpt_buffer_ids = message
|
||||
.inserted_excerpts
|
||||
.iter()
|
||||
.filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
|
||||
.collect::<HashSet<_>>();
|
||||
let inserted_excerpt_buffers = project.update(cx, |project, cx| {
|
||||
inserted_excerpt_buffer_ids
|
||||
.into_iter()
|
||||
.map(|id| project.open_buffer_by_id(id, cx))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
|
||||
|
||||
// Update the editor's excerpts.
|
||||
this.update(cx, |editor, cx| {
|
||||
editor.buffer.update(cx, |multibuffer, cx| {
|
||||
let mut removed_excerpt_ids = message
|
||||
.deleted_excerpts
|
||||
.into_iter()
|
||||
.map(ExcerptId::from_proto)
|
||||
.collect::<Vec<_>>();
|
||||
removed_excerpt_ids.sort_by({
|
||||
let multibuffer = multibuffer.read(cx);
|
||||
move |a, b| a.cmp(&b, &multibuffer)
|
||||
});
|
||||
|
||||
let mut insertions = message.inserted_excerpts.into_iter().peekable();
|
||||
while let Some(insertion) = insertions.next() {
|
||||
let Some(excerpt) = insertion.excerpt else { continue };
|
||||
let Some(previous_excerpt_id) = insertion.previous_excerpt_id else { continue };
|
||||
let buffer_id = excerpt.buffer_id;
|
||||
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else { continue };
|
||||
|
||||
let adjacent_excerpts = iter::from_fn(|| {
|
||||
let insertion = insertions.peek()?;
|
||||
if insertion.previous_excerpt_id.is_none()
|
||||
&& insertion.excerpt.as_ref()?.buffer_id == buffer_id
|
||||
{
|
||||
insertions.next()?.excerpt
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
multibuffer.insert_excerpts_with_ids_after(
|
||||
ExcerptId::from_proto(previous_excerpt_id),
|
||||
buffer,
|
||||
[excerpt]
|
||||
.into_iter()
|
||||
.chain(adjacent_excerpts)
|
||||
.filter_map(|excerpt| {
|
||||
Some((
|
||||
ExcerptId::from_proto(excerpt.id),
|
||||
deserialize_excerpt_range(excerpt)?,
|
||||
))
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
multibuffer.remove_excerpts(removed_excerpt_ids, cx);
|
||||
});
|
||||
});
|
||||
|
||||
// Deserialize the editor state.
|
||||
let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer.read(cx).read(cx);
|
||||
let selections = message
|
||||
.selections
|
||||
.into_iter()
|
||||
.filter_map(|selection| deserialize_selection(&buffer, selection))
|
||||
.collect::<Vec<_>>();
|
||||
let pending_selection = message
|
||||
.pending_selection
|
||||
.and_then(|selection| deserialize_selection(&buffer, selection));
|
||||
let scroll_top_anchor = message
|
||||
.scroll_top_anchor
|
||||
.and_then(|anchor| deserialize_anchor(&buffer, anchor));
|
||||
anyhow::Ok((selections, pending_selection, scroll_top_anchor))
|
||||
})?;
|
||||
|
||||
// Wait until the buffer has received all of the operations referenced by
|
||||
// the editor's new state.
|
||||
this.update(cx, |editor, cx| {
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
buffer.wait_for_anchors(
|
||||
selections
|
||||
.iter()
|
||||
.chain(pending_selection.as_ref())
|
||||
.flat_map(|selection| [selection.start, selection.end])
|
||||
.chain(scroll_top_anchor),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Update the editor's state.
|
||||
this.update(cx, |editor, cx| {
|
||||
if !selections.is_empty() || pending_selection.is_some() {
|
||||
editor.set_selections_from_remote(selections, pending_selection, cx);
|
||||
editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
|
||||
} else if let Some(scroll_top_anchor) = scroll_top_anchor {
|
||||
editor.set_scroll_anchor_remote(
|
||||
ScrollAnchor {
|
||||
top_anchor: scroll_top_anchor,
|
||||
offset: vec2f(message.scroll_x, message.scroll_y),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_excerpt(
|
||||
buffer_id: u64,
|
||||
id: &ExcerptId,
|
||||
@@ -514,7 +532,24 @@ impl Item for Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
|
||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
|
||||
let file_path = self
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()?
|
||||
.read(cx)
|
||||
.file()
|
||||
.and_then(|f| f.as_local())?
|
||||
.abs_path(cx);
|
||||
|
||||
let file_path = util::paths::compact(&file_path)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
Some(file_path.into())
|
||||
}
|
||||
|
||||
fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<str>> {
|
||||
match path_for_buffer(&self.buffer, detail, true, cx)? {
|
||||
Cow::Borrowed(path) => Some(path.to_string_lossy()),
|
||||
Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()),
|
||||
@@ -747,11 +782,15 @@ impl Item for Editor {
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "untitled".to_string());
|
||||
|
||||
let mut breadcrumbs = vec![Label::new(filename, theme.breadcrumbs.text.clone()).boxed()];
|
||||
let filename_label = Label::new(filename, theme.workspace.breadcrumbs.default.text.clone());
|
||||
let mut breadcrumbs = vec![filename_label.boxed()];
|
||||
breadcrumbs.extend(symbols.into_iter().map(|symbol| {
|
||||
Text::new(symbol.text, theme.breadcrumbs.text.clone())
|
||||
.with_highlights(symbol.highlight_ranges)
|
||||
.boxed()
|
||||
Text::new(
|
||||
symbol.text,
|
||||
theme.workspace.breadcrumbs.default.text.clone(),
|
||||
)
|
||||
.with_highlights(symbol.highlight_ranges)
|
||||
.boxed()
|
||||
}));
|
||||
Some(breadcrumbs)
|
||||
}
|
||||
@@ -765,7 +804,7 @@ impl Item for Editor {
|
||||
buffer: ModelHandle<Buffer>,
|
||||
workspace_id: WorkspaceId,
|
||||
item_id: ItemId,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||
let path = file.abs_path(cx);
|
||||
@@ -831,7 +870,7 @@ impl Item for Editor {
|
||||
.context("Project item at stored path was not a buffer")?;
|
||||
|
||||
Ok(cx.update(|cx| {
|
||||
cx.add_view(pane, |cx| {
|
||||
cx.add_view(&pane, |cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
|
||||
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
|
||||
editor
|
||||
@@ -1157,7 +1196,7 @@ fn path_for_file<'a>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::MutableAppContext;
|
||||
use gpui::AppContext;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
@@ -1165,7 +1204,7 @@ mod tests {
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_path_for_file(cx: &mut MutableAppContext) {
|
||||
fn test_path_for_file(cx: &mut AppContext) {
|
||||
let file = TestFile {
|
||||
path: Path::new("").into(),
|
||||
full_path: PathBuf::from(""),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::{impl_internal_actions, MutableAppContext, Task, ViewContext};
|
||||
use gpui::{impl_internal_actions, AppContext, Task, ViewContext};
|
||||
use language::{Bias, ToOffset};
|
||||
use project::LocationLink;
|
||||
use settings::Settings;
|
||||
@@ -38,7 +38,7 @@ impl_internal_actions!(
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(update_go_to_definition_link);
|
||||
cx.add_action(go_to_fetched_definition);
|
||||
cx.add_action(go_to_fetched_type_definition);
|
||||
@@ -358,7 +358,10 @@ fn go_to_fetched_definition_of_kind(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::StreamExt;
|
||||
use gpui::{Modifiers, ModifiersChangedEvent, View};
|
||||
use gpui::{
|
||||
platform::{self, Modifiers, ModifiersChangedEvent},
|
||||
View,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use lsp::request::{GotoDefinition, GotoTypeDefinition};
|
||||
|
||||
@@ -430,7 +433,7 @@ mod tests {
|
||||
// Unpress shift causes highlight to go away (normal goto-definition is not valid here)
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.modifiers_changed(
|
||||
&gpui::ModifiersChangedEvent {
|
||||
&platform::ModifiersChangedEvent {
|
||||
modifiers: Modifiers {
|
||||
cmd: true,
|
||||
..Default::default()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use context_menu::ContextMenuItem;
|
||||
use gpui::{
|
||||
elements::AnchorCorner, geometry::vector::Vector2F, impl_internal_actions, MutableAppContext,
|
||||
elements::AnchorCorner, geometry::vector::Vector2F, impl_internal_actions, AppContext,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct DeployMouseContextMenu {
|
||||
|
||||
impl_internal_actions!(editor, [DeployMouseContextMenu]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(deploy_context_menu);
|
||||
}
|
||||
|
||||
|
||||
@@ -372,9 +372,9 @@ mod tests {
|
||||
use settings::Settings;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_previous_word_start(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_previous_word_start(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
previous_word_start(&snapshot, display_points[1]),
|
||||
@@ -399,9 +399,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_previous_subword_start(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_previous_subword_start(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
previous_subword_start(&snapshot, display_points[1]),
|
||||
@@ -433,11 +433,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_find_preceding_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(
|
||||
marked_text: &str,
|
||||
cx: &mut gpui::MutableAppContext,
|
||||
cx: &mut gpui::AppContext,
|
||||
is_boundary: impl FnMut(char, char) -> bool,
|
||||
) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
@@ -465,9 +465,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_next_word_end(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_next_word_end(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
next_word_end(&snapshot, display_points[0]),
|
||||
@@ -489,9 +489,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_next_subword_end(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_next_subword_end(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
next_subword_end(&snapshot, display_points[0]),
|
||||
@@ -522,11 +522,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_find_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_find_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(
|
||||
marked_text: &str,
|
||||
cx: &mut gpui::MutableAppContext,
|
||||
cx: &mut gpui::AppContext,
|
||||
is_boundary: impl FnMut(char, char) -> bool,
|
||||
) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
@@ -554,9 +554,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_surrounding_word(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_surrounding_word(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
surrounding_word(&snapshot, display_points[1]),
|
||||
@@ -575,7 +575,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_up_and_down_with_excerpts(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_move_up_and_down_with_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let family_id = cx
|
||||
.font_cache()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod anchor;
|
||||
|
||||
pub use anchor::{Anchor, AnchorRangeExt};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, SinkExt};
|
||||
@@ -16,7 +17,9 @@ use language::{
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Ref, RefCell},
|
||||
cmp, fmt, io,
|
||||
cmp, fmt,
|
||||
future::Future,
|
||||
io,
|
||||
iter::{self, FromIterator},
|
||||
mem,
|
||||
ops::{Range, RangeBounds, Sub},
|
||||
@@ -61,6 +64,7 @@ pub enum Event {
|
||||
},
|
||||
Edited,
|
||||
Reloaded,
|
||||
LanguageChanged,
|
||||
Reparsed,
|
||||
Saved,
|
||||
FileHandleChanged,
|
||||
@@ -1238,6 +1242,39 @@ impl MultiBuffer {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn wait_for_anchors<'a>(
|
||||
&self,
|
||||
anchors: impl 'a + Iterator<Item = Anchor>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> impl 'static + Future<Output = Result<()>> {
|
||||
let borrow = self.buffers.borrow();
|
||||
let mut error = None;
|
||||
let mut futures = Vec::new();
|
||||
for anchor in anchors {
|
||||
if let Some(buffer_id) = anchor.buffer_id {
|
||||
if let Some(buffer) = borrow.get(&buffer_id) {
|
||||
buffer.buffer.update(cx, |buffer, _| {
|
||||
futures.push(buffer.wait_for_anchors([anchor.text_anchor]))
|
||||
});
|
||||
} else {
|
||||
error = Some(anyhow!(
|
||||
"buffer {buffer_id} is not part of this multi-buffer"
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async move {
|
||||
if let Some(error) = error {
|
||||
Err(error)?;
|
||||
}
|
||||
for future in futures {
|
||||
future.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_anchor_for_position<T: ToOffset>(
|
||||
&self,
|
||||
position: T,
|
||||
@@ -1266,6 +1303,7 @@ impl MultiBuffer {
|
||||
language::Event::Saved => Event::Saved,
|
||||
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
||||
language::Event::Reloaded => Event::Reloaded,
|
||||
language::Event::LanguageChanged => Event::LanguageChanged,
|
||||
language::Event::Reparsed => Event::Reparsed,
|
||||
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
||||
language::Event::Closed => Event::Closed,
|
||||
@@ -1489,15 +1527,12 @@ impl MultiBuffer {
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl MultiBuffer {
|
||||
pub fn build_simple(text: &str, cx: &mut gpui::MutableAppContext) -> ModelHandle<Self> {
|
||||
pub fn build_simple(text: &str, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||
}
|
||||
|
||||
pub fn build_random(
|
||||
rng: &mut impl rand::Rng,
|
||||
cx: &mut gpui::MutableAppContext,
|
||||
) -> ModelHandle<Self> {
|
||||
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
|
||||
cx.add_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
let mutation_count = rng.gen_range(1..=5);
|
||||
@@ -2729,6 +2764,15 @@ impl MultiBufferSnapshot {
|
||||
.and_then(|(buffer, offset)| buffer.language_scope_at(offset))
|
||||
}
|
||||
|
||||
pub fn language_indent_size_at<T: ToOffset>(
|
||||
&self,
|
||||
position: T,
|
||||
cx: &AppContext,
|
||||
) -> Option<IndentSize> {
|
||||
let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?;
|
||||
Some(buffer_snapshot.language_indent_size_at(offset, cx))
|
||||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.is_dirty
|
||||
}
|
||||
@@ -2756,7 +2800,7 @@ impl MultiBufferSnapshot {
|
||||
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
|
||||
where
|
||||
T: 'a + ToOffset,
|
||||
O: 'a + text::FromAnchor,
|
||||
O: 'a + text::FromAnchor + Ord,
|
||||
{
|
||||
self.as_singleton()
|
||||
.into_iter()
|
||||
@@ -2933,6 +2977,10 @@ impl MultiBufferSnapshot {
|
||||
Some(self.excerpt(excerpt_id)?.buffer_id)
|
||||
}
|
||||
|
||||
pub fn buffer_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&BufferSnapshot> {
|
||||
Some(&self.excerpt(excerpt_id)?.buffer)
|
||||
}
|
||||
|
||||
fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
@@ -3725,7 +3773,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::StreamExt;
|
||||
use gpui::{MutableAppContext, TestAppContext};
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use language::{Buffer, Rope};
|
||||
use rand::prelude::*;
|
||||
use settings::Settings;
|
||||
@@ -3735,7 +3783,7 @@ mod tests {
|
||||
use util::test::sample_text;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_singleton(cx: &mut MutableAppContext) {
|
||||
fn test_singleton(cx: &mut AppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
|
||||
let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
|
||||
@@ -3762,7 +3810,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_remote(cx: &mut MutableAppContext) {
|
||||
fn test_remote(cx: &mut AppContext) {
|
||||
let host_buffer = cx.add_model(|cx| Buffer::new(0, "a", cx));
|
||||
let guest_buffer = cx.add_model(|cx| {
|
||||
let state = host_buffer.read(cx).to_proto();
|
||||
@@ -3793,7 +3841,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpt_boundaries_and_clipping(cx: &mut MutableAppContext) {
|
||||
fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
@@ -4017,7 +4065,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_excerpt_events(cx: &mut MutableAppContext) {
|
||||
fn test_excerpt_events(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(10, 3, 'a'), cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(10, 3, 'm'), cx));
|
||||
|
||||
@@ -4094,7 +4142,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_push_excerpts_with_context_lines(cx: &mut MutableAppContext) {
|
||||
fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(20, 3, 'a'), cx));
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
|
||||
@@ -4168,7 +4216,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_empty_multibuffer(cx: &mut MutableAppContext) {
|
||||
fn test_empty_multibuffer(cx: &mut AppContext) {
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
@@ -4178,7 +4226,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_singleton_multibuffer_anchors(cx: &mut MutableAppContext) {
|
||||
fn test_singleton_multibuffer_anchors(cx: &mut AppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abcd", cx));
|
||||
let multibuffer = cx.add_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
let old_snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
@@ -4198,7 +4246,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_multibuffer_anchors(cx: &mut MutableAppContext) {
|
||||
fn test_multibuffer_anchors(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "abcd", cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "efghi", cx));
|
||||
let multibuffer = cx.add_model(|cx| {
|
||||
@@ -4256,7 +4304,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut MutableAppContext) {
|
||||
fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) {
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "abcd", cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "ABCDEFGHIJKLMNOP", cx));
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
@@ -4559,7 +4607,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_multibuffer(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
@@ -4976,7 +5024,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_history(cx: &mut MutableAppContext) {
|
||||
fn test_history(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx));
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
|
||||
use gpui::{
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
Axis, MutableAppContext, Task, ViewContext,
|
||||
AppContext, Axis, Task, ViewContext,
|
||||
};
|
||||
use language::{Bias, Point};
|
||||
use util::ResultExt;
|
||||
@@ -369,7 +369,7 @@ impl Editor {
|
||||
/// Ordering::Equal => on screen
|
||||
/// Ordering::Less => above the screen
|
||||
/// Ordering::Greater => below the screen
|
||||
pub fn newest_selection_on_screen(&self, cx: &mut MutableAppContext) -> Ordering {
|
||||
pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
|
||||
let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let newest_head = self
|
||||
.selections
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use gpui::{
|
||||
actions, geometry::vector::Vector2F, impl_internal_actions, Axis, MutableAppContext,
|
||||
ViewContext,
|
||||
actions, geometry::vector::Vector2F, impl_internal_actions, AppContext, Axis, ViewContext,
|
||||
};
|
||||
use language::Bias;
|
||||
|
||||
@@ -32,7 +31,7 @@ pub struct Scroll {
|
||||
|
||||
impl_internal_actions!(editor, [Scroll]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Editor::next_screen);
|
||||
cx.add_action(Editor::scroll);
|
||||
cx.add_action(Editor::scroll_cursor_top);
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
};
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, ModelHandle, MutableAppContext};
|
||||
use gpui::{AppContext, ModelHandle};
|
||||
use itertools::Itertools;
|
||||
use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint};
|
||||
use util::post_inc;
|
||||
@@ -53,7 +53,7 @@ impl SelectionsCollection {
|
||||
}
|
||||
}
|
||||
|
||||
fn display_map(&self, cx: &mut MutableAppContext) -> DisplaySnapshot {
|
||||
fn display_map(&self, cx: &mut AppContext) -> DisplaySnapshot {
|
||||
self.display_map.update(cx, |map, cx| map.snapshot(cx))
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ impl SelectionsCollection {
|
||||
}
|
||||
|
||||
// Returns all of the selections, adjusted to take into account the selection line_mode
|
||||
pub fn all_adjusted(&self, cx: &mut MutableAppContext) -> Vec<Selection<Point>> {
|
||||
pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec<Selection<Point>> {
|
||||
let mut selections = self.all::<Point>(cx);
|
||||
if self.line_mode {
|
||||
let map = self.display_map(cx);
|
||||
@@ -151,7 +151,7 @@ impl SelectionsCollection {
|
||||
|
||||
pub fn all_adjusted_display(
|
||||
&self,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
|
||||
if self.line_mode {
|
||||
let selections = self.all::<Point>(cx);
|
||||
@@ -198,7 +198,7 @@ impl SelectionsCollection {
|
||||
|
||||
pub fn all_display(
|
||||
&self,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
|
||||
let display_map = self.display_map(cx);
|
||||
let selections = self
|
||||
@@ -224,7 +224,7 @@ impl SelectionsCollection {
|
||||
resolve(self.newest_anchor(), &self.buffer(cx))
|
||||
}
|
||||
|
||||
pub fn newest_display(&self, cx: &mut MutableAppContext) -> Selection<DisplayPoint> {
|
||||
pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
|
||||
let display_map = self.display_map(cx);
|
||||
let selection = self
|
||||
.newest_anchor()
|
||||
@@ -279,7 +279,7 @@ impl SelectionsCollection {
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn display_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
|
||||
pub fn display_ranges(&self, cx: &mut AppContext) -> Vec<Range<DisplayPoint>> {
|
||||
let display_map = self.display_map(cx);
|
||||
self.disjoint_anchors()
|
||||
.iter()
|
||||
@@ -324,7 +324,7 @@ impl SelectionsCollection {
|
||||
|
||||
pub(crate) fn change_with<R>(
|
||||
&mut self,
|
||||
cx: &mut MutableAppContext,
|
||||
cx: &mut AppContext,
|
||||
change: impl FnOnce(&mut MutableSelectionsCollection) -> R,
|
||||
) -> (bool, R) {
|
||||
let mut mutable_collection = MutableSelectionsCollection {
|
||||
@@ -345,7 +345,7 @@ impl SelectionsCollection {
|
||||
pub struct MutableSelectionsCollection<'a> {
|
||||
collection: &'a mut SelectionsCollection,
|
||||
selections_changed: bool,
|
||||
cx: &'a mut MutableAppContext,
|
||||
cx: &'a mut AppContext,
|
||||
}
|
||||
|
||||
impl<'a> MutableSelectionsCollection<'a> {
|
||||
|
||||
@@ -21,7 +21,7 @@ fn init_logger() {
|
||||
// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
|
||||
pub fn marked_display_snapshot(
|
||||
text: &str,
|
||||
cx: &mut gpui::MutableAppContext,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
||||
let (unmarked_text, markers) = marked_text_offsets(text);
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ impl<'a> EditorTestContext<'a> {
|
||||
///
|
||||
/// See the `util::test::marked_text_ranges` function for more information.
|
||||
pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
let _state_context = self.add_assertion_context(format!(
|
||||
let state_context = self.add_assertion_context(format!(
|
||||
"Initial Editor State: \"{}\"",
|
||||
marked_text.escape_debug().to_string()
|
||||
));
|
||||
@@ -178,7 +178,23 @@ impl<'a> EditorTestContext<'a> {
|
||||
s.select_ranges(selection_ranges)
|
||||
})
|
||||
});
|
||||
_state_context
|
||||
state_context
|
||||
}
|
||||
|
||||
/// Only change the editor's selections
|
||||
pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
let state_context = self.add_assertion_context(format!(
|
||||
"Initial Editor State: \"{}\"",
|
||||
marked_text.escape_debug().to_string()
|
||||
));
|
||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
self.editor.update(self.cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges(selection_ranges)
|
||||
})
|
||||
});
|
||||
state_context
|
||||
}
|
||||
|
||||
/// Make an assertion about the editor's text and the ranges and directions
|
||||
@@ -189,10 +205,11 @@ impl<'a> EditorTestContext<'a> {
|
||||
pub fn assert_editor_state(&mut self, marked_text: &str) {
|
||||
let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
|
||||
let buffer_text = self.buffer_text();
|
||||
assert_eq!(
|
||||
buffer_text, unmarked_text,
|
||||
"Unmarked text doesn't match buffer text"
|
||||
);
|
||||
|
||||
if buffer_text != unmarked_text {
|
||||
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
|
||||
}
|
||||
|
||||
self.assert_selections(expected_selections, marked_text.to_string())
|
||||
}
|
||||
|
||||
@@ -254,10 +271,10 @@ impl<'a> EditorTestContext<'a> {
|
||||
panic!(
|
||||
indoc! {"
|
||||
{}Editor has unexpected selections.
|
||||
|
||||
|
||||
Expected selections:
|
||||
{}
|
||||
|
||||
|
||||
Actual selections:
|
||||
{}
|
||||
"},
|
||||
|
||||
@@ -24,8 +24,8 @@ lazy_static = "1.4.0"
|
||||
postage = { workspace = true }
|
||||
project = { path = "../project" }
|
||||
search = { path = "../search" }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
settings = { path = "../settings" }
|
||||
sysinfo = "0.27.1"
|
||||
theme = { path = "../theme" }
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use gpui::{elements::*, CursorStyle, Entity, MouseButton, RenderContext, View, ViewContext};
|
||||
use gpui::{
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Entity, RenderContext, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, StatusItemView};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ pub mod submit_feedback_button;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod system_specs;
|
||||
use gpui::{actions, impl_actions, ClipboardItem, MutableAppContext, PromptLevel, ViewContext};
|
||||
use gpui::{actions, impl_actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext};
|
||||
use serde::Deserialize;
|
||||
use system_specs::SystemSpecs;
|
||||
use workspace::{AppState, Workspace};
|
||||
@@ -28,7 +28,7 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
let system_specs = SystemSpecs::new(&cx);
|
||||
let system_specs_text = system_specs.to_string();
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
|
||||
"https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
|
||||
urlencoding::encode(&system_specs_text)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::Cow,
|
||||
ops::{Range, RangeInclusive},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -11,8 +12,9 @@ use futures::AsyncReadExt;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Flex, Label, ParentElement, Svg},
|
||||
serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle,
|
||||
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
platform::PromptLevel,
|
||||
serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext,
|
||||
Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use isahc::Request;
|
||||
use language::Buffer;
|
||||
@@ -35,7 +37,7 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
||||
|
||||
actions!(feedback, [GiveFeedback, SubmitFeedback]);
|
||||
|
||||
pub fn init(system_specs: SystemSpecs, app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
pub fn init(system_specs: SystemSpecs, app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
cx.add_action({
|
||||
move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext<Workspace>| {
|
||||
FeedbackEditor::deploy(system_specs.clone(), workspace, app_state.clone(), cx);
|
||||
@@ -247,6 +249,10 @@ impl Entity for FeedbackEditor {
|
||||
}
|
||||
|
||||
impl Item for FeedbackEditor {
|
||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
|
||||
Some("Send Feedback".into())
|
||||
}
|
||||
|
||||
fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> ElementBox {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
@@ -333,16 +339,16 @@ impl Item for FeedbackEditor {
|
||||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
|
||||
fn act_as_type(
|
||||
&self,
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &ViewHandle<Self>,
|
||||
_: &AppContext,
|
||||
) -> Option<AnyViewHandle> {
|
||||
self_handle: &'a ViewHandle<Self>,
|
||||
_: &'a AppContext,
|
||||
) -> Option<&'a AnyViewHandle> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.into())
|
||||
Some(self_handle)
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some((&self.editor).into())
|
||||
Some(&self.editor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
|
||||
CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext,
|
||||
ViewHandle,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{
|
||||
elements::{Label, MouseEventHandler},
|
||||
CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext,
|
||||
ViewHandle,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use client::ZED_APP_VERSION;
|
||||
use gpui::{AppContext, AppVersion};
|
||||
use gpui::{platform::AppVersion, AppContext};
|
||||
use human_bytes::human_bytes;
|
||||
use serde::Serialize;
|
||||
use std::{env, fmt::Display};
|
||||
|
||||
@@ -23,7 +23,7 @@ postage = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde_json = { workspace = true }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.9"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
|
||||
MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||
@@ -31,7 +31,7 @@ pub struct FileFinder {
|
||||
|
||||
actions!(file_finder, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(FileFinder::toggle);
|
||||
Picker::<FileFinder>::init(cx);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ impl View for FileFinder {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
ChildView::new(self.picker.clone(), cx).boxed()
|
||||
ChildView::new(&self.picker, cx).boxed()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
@@ -352,8 +352,8 @@ mod tests {
|
||||
let active_item = active_pane.read(cx).active_item().unwrap();
|
||||
assert_eq!(
|
||||
active_item
|
||||
.to_any()
|
||||
.downcast::<Editor>()
|
||||
.as_any()
|
||||
.downcast_ref::<Editor>()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.title(cx),
|
||||
|
||||
@@ -24,7 +24,7 @@ smol = "1.2.5"
|
||||
regex = "1.5"
|
||||
git2 = { version = "0.15", default-features = false }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
libc = "0.2"
|
||||
|
||||