Compare commits
20 Commits
v0.144.4
...
linux-sche
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
408fe83b90 | ||
|
|
77b31d1845 | ||
|
|
15b8790a2c | ||
|
|
b693cbfcb7 | ||
|
|
73d7f70ff6 | ||
|
|
be5b7b2e70 | ||
|
|
4434353f73 | ||
|
|
95637a0320 | ||
|
|
ee623f77c1 | ||
|
|
c732865fc5 | ||
|
|
8a659af82c | ||
|
|
07dc4050bf | ||
|
|
896b9bda23 | ||
|
|
9282bf97ae | ||
|
|
33a67ad6b9 | ||
|
|
d4ddc4c62c | ||
|
|
8944af7406 | ||
|
|
1662993811 | ||
|
|
7ef64fe6db | ||
|
|
f147722fe0 |
204
Cargo.lock
generated
204
Cargo.lock
generated
@@ -87,7 +87,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d1ea4484c8676f295307a4892d478c70ac8da1dbd8c7c10830a504b7f1022f"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"home",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -110,7 +110,7 @@ version = "0.24.1-dev"
|
||||
source = "git+https://github.com/alacritty/alacritty?rev=cacdb5bb3b72bad2c729227537979d95af75978f#cacdb5bb3b72bad2c729227537979d95af75978f"
|
||||
dependencies = [
|
||||
"base64 0.22.0",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"home",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -341,9 +341,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/bilelmoussaoui/ashpd?rev=29f2e1a#29f2e1a6f4b0911f504658f5f4630c02e01b13f2"
|
||||
dependencies = [
|
||||
"async-fs 2.1.1",
|
||||
"async-net 2.0.0",
|
||||
@@ -378,6 +377,7 @@ dependencies = [
|
||||
"cargo_toml",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"ctor",
|
||||
@@ -420,6 +420,7 @@ dependencies = [
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"text",
|
||||
"theme",
|
||||
"tiktoken-rs",
|
||||
"toml 0.8.10",
|
||||
@@ -1582,16 +1583,7 @@ version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
|
||||
dependencies = [
|
||||
"bit-vec 0.7.0",
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1600,12 +1592,6 @@ version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
@@ -1620,9 +1606,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1648,11 +1634,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
|
||||
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"block",
|
||||
"bytemuck",
|
||||
"codespan-reporting",
|
||||
@@ -1678,7 +1664,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
|
||||
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1688,7 +1674,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
|
||||
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -1936,7 +1922,7 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"log",
|
||||
"polling 3.3.2",
|
||||
"rustix 0.38.32",
|
||||
@@ -2421,6 +2407,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -2479,6 +2466,7 @@ version = "0.44.0"
|
||||
dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"assistant",
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"audio",
|
||||
@@ -2871,7 +2859,7 @@ name = "cosmic-text"
|
||||
version = "0.11.2"
|
||||
source = "git+https://github.com/pop-os/cosmic-text?rev=542b20c#542b20ca4376a3b5de5fa629db1a4ace44e18e0c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"fontdb",
|
||||
"log",
|
||||
"rangemap",
|
||||
@@ -4024,7 +4012,7 @@ version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05"
|
||||
dependencies = [
|
||||
"bit-set 0.5.3",
|
||||
"bit-set",
|
||||
"regex",
|
||||
]
|
||||
|
||||
@@ -4081,7 +4069,7 @@ name = "feedback"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"client",
|
||||
"db",
|
||||
"editor",
|
||||
@@ -4281,7 +4269,7 @@ checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"memmap2 0.9.4",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser",
|
||||
@@ -4415,7 +4403,7 @@ dependencies = [
|
||||
name = "fsevent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"core-foundation",
|
||||
"fsevent-sys 3.1.0",
|
||||
"parking_lot",
|
||||
@@ -4726,7 +4714,7 @@ version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
@@ -4837,7 +4825,7 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"gpu-alloc-types",
|
||||
]
|
||||
|
||||
@@ -4858,7 +4846,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4908,7 +4896,6 @@ dependencies = [
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"oo7",
|
||||
"open",
|
||||
"parking",
|
||||
"parking_lot",
|
||||
"pathfinder_geometry",
|
||||
@@ -5116,7 +5103,7 @@ version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f7acb9683d7c7068aa46d47557bfa4e35a277964b350d9504a87b03610163fd"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"byteorder",
|
||||
"heed-traits",
|
||||
"heed-types",
|
||||
@@ -5720,25 +5707,6 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-wsl"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||
dependencies = [
|
||||
"is-docker",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isahc"
|
||||
version = "1.7.2"
|
||||
@@ -6556,6 +6524,15 @@ dependencies = [
|
||||
"rustix 0.38.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.4"
|
||||
@@ -6689,17 +6666,17 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gfx-rs/wgpu?rev=425526828f738c95ec50b016c6a761bc00d2fb25#425526828f738c95ec50b016c6a761bc00d2fb25"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae585df4b6514cf8842ac0f1ab4992edc975892704835b549cf818dc0191249e"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set 0.6.0",
|
||||
"bitflags 2.6.0",
|
||||
"cfg_aliases",
|
||||
"bit-set",
|
||||
"bitflags 2.4.2",
|
||||
"codespan-reporting",
|
||||
"hexf-parse",
|
||||
"indexmap 2.2.6",
|
||||
"log",
|
||||
"num-traits",
|
||||
"rustc-hash",
|
||||
"spirv",
|
||||
"termcolor",
|
||||
@@ -6795,7 +6772,7 @@ version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
@@ -6807,7 +6784,7 @@ version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -6876,7 +6853,7 @@ version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys 4.1.0",
|
||||
@@ -7089,15 +7066,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvim-rs"
|
||||
version = "0.6.0-pre"
|
||||
@@ -7227,17 +7195,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3"
|
||||
dependencies = [
|
||||
"is-wsl",
|
||||
"libc",
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "open_ai"
|
||||
version = "0.1.0"
|
||||
@@ -7258,7 +7215,7 @@ version = "0.10.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
@@ -8272,7 +8229,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
@@ -8317,6 +8274,7 @@ dependencies = [
|
||||
"assistant",
|
||||
"editor",
|
||||
"gpui",
|
||||
"repl",
|
||||
"search",
|
||||
"settings",
|
||||
"ui",
|
||||
@@ -9083,7 +9041,7 @@ version = "0.38.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"errno 0.3.8",
|
||||
"itoa",
|
||||
"libc",
|
||||
@@ -9158,7 +9116,7 @@ version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"bytemuck",
|
||||
"libm",
|
||||
"smallvec",
|
||||
@@ -9381,7 +9339,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"any_vec",
|
||||
"anyhow",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"client",
|
||||
"collections",
|
||||
"editor",
|
||||
@@ -9809,13 +9767,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "simplelog"
|
||||
version = "0.12.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
|
||||
checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
"termcolor",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9968,11 +9926,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spirv"
|
||||
version = "0.3.0+sdk-1.3.268.0"
|
||||
version = "0.2.0+1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
|
||||
checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 1.3.2",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10148,7 +10107,7 @@ dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.7",
|
||||
"bigdecimal",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"byteorder",
|
||||
"bytes 1.5.0",
|
||||
"chrono",
|
||||
@@ -10195,7 +10154,7 @@ dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.7",
|
||||
"bigdecimal",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"crc",
|
||||
@@ -10617,7 +10576,7 @@ version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aef1f9d4c1dbdd1cb3a63be9efd2f04d8ddbc919d46112982c76818ffc2f1a7"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"cap-fs-ext",
|
||||
"cap-std",
|
||||
"fd-lock",
|
||||
@@ -10758,9 +10717,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -10920,18 +10879,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -10982,9 +10941,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
@@ -11343,7 +11300,7 @@ version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"bytes 1.5.0",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -12114,7 +12071,7 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"cursor-icon",
|
||||
"log",
|
||||
"serde",
|
||||
@@ -12266,7 +12223,7 @@ version = "0.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"indexmap 2.2.6",
|
||||
"semver",
|
||||
]
|
||||
@@ -12533,7 +12490,7 @@ checksum = "371d828b6849ea06d598ae7dd1c316e8dd9e99b76f77d93d5886cb25c7f8e188"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"bytes 1.5.0",
|
||||
"cap-fs-ext",
|
||||
"cap-net-ext",
|
||||
@@ -12620,7 +12577,7 @@ version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"rustix 0.38.32",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
@@ -12643,7 +12600,7 @@ version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
@@ -12655,7 +12612,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -12774,7 +12731,7 @@ checksum = "ae1136a209614ace00b0c11f04dc7cf42540773be3b22eff6ad165110aba29c1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"wasmtime",
|
||||
@@ -13204,7 +13161,7 @@ version = "0.36.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -13223,7 +13180,7 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"wit-bindgen-rt",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
@@ -13279,7 +13236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
"indexmap 2.2.6",
|
||||
"log",
|
||||
"serde",
|
||||
@@ -13485,17 +13442,18 @@ name = "xim-parser"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/npmania/xim-rs?rev=27132caffc5b9bc9c432ca4afad184ab6e7c16af#27132caffc5b9bc9c432ca4afad184ab6e7c16af"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bitflags 2.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/ConradIrwin/xkbcommon-rs?rev=fcbb4612185cc129ceeff51d22f7fb51810a03b2#fcbb4612185cc129ceeff51d22f7fb51810a03b2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
|
||||
dependencies = [
|
||||
"as-raw-xcb-connection",
|
||||
"libc",
|
||||
"memmap2",
|
||||
"memmap2 0.8.0",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
@@ -13628,7 +13586,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.144.4"
|
||||
version = "0.145.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -274,7 +274,7 @@ zed_actions = { path = "crates/zed_actions" }
|
||||
alacritty_terminal = "0.23"
|
||||
any_vec = "0.13"
|
||||
anyhow = "1.0.57"
|
||||
ashpd = "0.9.1"
|
||||
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "29f2e1a" }
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = { version = "0.1" }
|
||||
async-fs = "1.6"
|
||||
@@ -284,10 +284,10 @@ async-trait = "0.1"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.13"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
|
||||
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
|
||||
blade-util = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
|
||||
bitflags = "2.4.2"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||
cap-std = "3.0"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
@@ -365,7 +365,6 @@ shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
signal-hook = "0.3.17"
|
||||
similar = "1.3"
|
||||
simplelog = "0.12.2"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
@@ -519,7 +518,7 @@ single_range_in_vec_init = "allow"
|
||||
|
||||
# There are a bunch of rules currently failing in the `style` group, so
|
||||
# allow all of those, for now.
|
||||
style = "allow"
|
||||
style = { level = "allow", priority = -1 }
|
||||
|
||||
# Individual rules that have violations in the codebase:
|
||||
almost_complete_range = "allow"
|
||||
|
||||
14
assets/icons/repl_neutral.svg
Normal file
14
assets/icons/repl_neutral.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_32_58)">
|
||||
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_32_58">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
20
assets/icons/repl_off.svg
Normal file
20
assets/icons/repl_off.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_39_129)">
|
||||
<path d="M22.0209 11.9553C22.0059 10.0068 21.4219 8.10512 20.3408 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.1001 2.18C11.355 1.93537 12.1493 1.93674 13.5027 2.10594" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.8198 10.1C22.0644 11.3548 22.0644 12.6451 21.8198 13.9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M20.2898 17.6C19.5716 18.6622 18.6548 19.5757 17.5898 20.29" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.9008 21.82C12.6459 22.0644 11.6432 22.1543 10.3883 21.91" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.18005 13.9C1.93543 12.6451 1.93543 11.3548 2.18005 10.1" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.70996 6.40002C4.42822 5.33775 5.34503 4.42433 6.40996 3.71002" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.99072 12.0748C2.00804 14.0118 2.58758 15.9021 3.65891 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_39_129">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
15
assets/icons/repl_pause.svg
Normal file
15
assets/icons/repl_pause.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_32_70)">
|
||||
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 15V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 15V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_32_70">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
14
assets/icons/repl_play.svg
Normal file
14
assets/icons/repl_play.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_32_64)">
|
||||
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 8.56055C10 8.32095 10.267 8.17803 10.4664 8.31094L15.6256 11.7504C15.8037 11.8691 15.8037 12.1309 15.6256 12.2496L10.4664 15.6891C10.267 15.822 10 15.6791 10 15.4394V8.56055Z" fill="white" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_32_64">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -397,7 +397,7 @@
|
||||
"bindings": {
|
||||
"ctrl-shift-k": "editor::DeleteLine",
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-shift-j": "editor::JoinLines",
|
||||
"ctrl-j": "editor::JoinLines",
|
||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
|
||||
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
|
||||
|
||||
@@ -569,7 +569,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && jupyter",
|
||||
"context": "Editor && jupyter && !ContextEditor",
|
||||
"bindings": {
|
||||
"cmd-enter": "repl::Run"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"ctrl-,": "editor::GoToPrevHunk",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
||||
"cmd-shift-j": "editor::JoinLines",
|
||||
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"j": "vim::Down",
|
||||
"down": "vim::Down",
|
||||
"enter": "vim::NextLineStart",
|
||||
"ctrl-m": "vim::NextLineStart",
|
||||
"tab": "vim::Tab",
|
||||
"shift-tab": "vim::Tab",
|
||||
"k": "vim::Up",
|
||||
|
||||
@@ -128,7 +128,14 @@
|
||||
// The default number of lines to expand excerpts in the multibuffer by.
|
||||
"expand_excerpt_lines": 3,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
||||
"private_files": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
"**/*.key",
|
||||
"**/*.cert",
|
||||
"**/*.crt",
|
||||
"**/secrets.yml"
|
||||
],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
@@ -707,12 +714,10 @@
|
||||
}
|
||||
},
|
||||
"C": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false
|
||||
"format_on_save": "off"
|
||||
},
|
||||
"C++": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false
|
||||
"format_on_save": "off"
|
||||
},
|
||||
"CSS": {
|
||||
"prettier": {
|
||||
@@ -764,7 +769,6 @@
|
||||
},
|
||||
"Markdown": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false,
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@
|
||||
"info": "#5c78e2ff",
|
||||
"info.background": "#e2e2faff",
|
||||
"info.border": "#cbcdf6ff",
|
||||
"modified": "#dec184ff",
|
||||
"modified": "#a47a23ff",
|
||||
"modified.background": "#faf2e6ff",
|
||||
"modified.border": "#f4e7d1ff",
|
||||
"predictive": "#9b9ec6ff",
|
||||
|
||||
@@ -12,6 +12,14 @@ workspace = true
|
||||
path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"editor/test-support",
|
||||
"language/test-support",
|
||||
"project/test-support",
|
||||
"text/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
@@ -21,6 +29,7 @@ breadcrumbs.workspace = true
|
||||
cargo_toml.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
editor.workspace = true
|
||||
@@ -72,7 +81,9 @@ picker.workspace = true
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
log.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
pub mod assistant_panel;
|
||||
pub mod assistant_settings;
|
||||
mod completion_provider;
|
||||
mod context_store;
|
||||
mod context;
|
||||
pub mod context_store;
|
||||
mod inline_assistant;
|
||||
mod model_selector;
|
||||
mod prompt_library;
|
||||
@@ -16,8 +17,9 @@ use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaMo
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::{proto, Client};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
pub(crate) use completion_provider::*;
|
||||
pub(crate) use context_store::*;
|
||||
pub use completion_provider::*;
|
||||
pub use context::*;
|
||||
pub use context_store::*;
|
||||
use fs::Fs;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
@@ -57,10 +59,14 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
struct MessageId(usize);
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct MessageId(clock::Lamport);
|
||||
|
||||
impl MessageId {
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0.as_u64()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -71,8 +77,26 @@ pub enum Role {
|
||||
}
|
||||
|
||||
impl Role {
|
||||
pub fn cycle(&mut self) {
|
||||
*self = match self {
|
||||
pub fn from_proto(role: i32) -> Role {
|
||||
match proto::LanguageModelRole::from_i32(role) {
|
||||
Some(proto::LanguageModelRole::LanguageModelUser) => Role::User,
|
||||
Some(proto::LanguageModelRole::LanguageModelAssistant) => Role::Assistant,
|
||||
Some(proto::LanguageModelRole::LanguageModelSystem) => Role::System,
|
||||
Some(proto::LanguageModelRole::LanguageModelTool) => Role::System,
|
||||
None => Role::User,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::LanguageModelRole {
|
||||
match self {
|
||||
Role::User => proto::LanguageModelRole::LanguageModelUser,
|
||||
Role::Assistant => proto::LanguageModelRole::LanguageModelAssistant,
|
||||
Role::System => proto::LanguageModelRole::LanguageModelSystem,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle(self) -> Role {
|
||||
match self {
|
||||
Role::User => Role::Assistant,
|
||||
Role::Assistant => Role::System,
|
||||
Role::System => Role::User,
|
||||
@@ -151,11 +175,7 @@ pub struct LanguageModelRequestMessage {
|
||||
impl LanguageModelRequestMessage {
|
||||
pub fn to_proto(&self) -> proto::LanguageModelRequestMessage {
|
||||
proto::LanguageModelRequestMessage {
|
||||
role: match self.role {
|
||||
Role::User => proto::LanguageModelRole::LanguageModelUser,
|
||||
Role::Assistant => proto::LanguageModelRole::LanguageModelAssistant,
|
||||
Role::System => proto::LanguageModelRole::LanguageModelSystem,
|
||||
} as i32,
|
||||
role: self.role.to_proto() as i32,
|
||||
content: self.content.clone(),
|
||||
tool_calls: Vec::new(),
|
||||
tool_call_id: None,
|
||||
@@ -222,19 +242,48 @@ pub struct LanguageModelChoiceDelta {
|
||||
pub finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct MessageMetadata {
|
||||
role: Role,
|
||||
status: MessageStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum MessageStatus {
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MessageStatus {
|
||||
Pending,
|
||||
Done,
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
impl MessageStatus {
|
||||
pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
|
||||
match status.variant {
|
||||
Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
|
||||
Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
|
||||
Some(proto::context_message_status::Variant::Error(error)) => {
|
||||
MessageStatus::Error(error.message.into())
|
||||
}
|
||||
None => MessageStatus::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::ContextMessageStatus {
|
||||
match self {
|
||||
MessageStatus::Pending => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Pending(
|
||||
proto::context_message_status::Pending {},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Done => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Done(
|
||||
proto::context_message_status::Done {},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Error(message) => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Error(
|
||||
proto::context_message_status::Error {
|
||||
message: message.to_string(),
|
||||
},
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state pertaining to the Assistant.
|
||||
#[derive(Default)]
|
||||
struct Assistant {
|
||||
@@ -287,6 +336,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
|
||||
})
|
||||
.detach();
|
||||
|
||||
context_store::init(&client);
|
||||
prompt_library::init(cx);
|
||||
completion_provider::init(client.clone(), cx);
|
||||
assistant_slash_command::init(cx);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
mod anthropic;
|
||||
mod cloud;
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
mod fake;
|
||||
mod ollama;
|
||||
mod open_ai;
|
||||
|
||||
pub use anthropic::*;
|
||||
pub use cloud::*;
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use fake::*;
|
||||
pub use ollama::*;
|
||||
pub use open_ai::*;
|
||||
|
||||
@@ -13,7 +13,6 @@ pub struct FakeCompletionProvider {
|
||||
}
|
||||
|
||||
impl FakeCompletionProvider {
|
||||
#[cfg(test)]
|
||||
pub fn setup_test(cx: &mut AppContext) -> Self {
|
||||
use crate::CompletionProvider;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
3009
crates/assistant/src/context.rs
Normal file
3009
crates/assistant/src/context.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,97 +1,117 @@
|
||||
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use collections::HashMap;
|
||||
use crate::{
|
||||
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
||||
SavedContextMetadata,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||
use clock::ReplicaId;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{AppContext, Model, ModelContext, Task};
|
||||
use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, Task, WeakModel};
|
||||
use language::LanguageRegistry;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
||||
use ui::Context;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ffi::OsStr,
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedMessage {
|
||||
pub id: MessageId,
|
||||
pub start: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedContext {
|
||||
pub id: Option<String>,
|
||||
pub zed: String,
|
||||
pub version: String,
|
||||
pub text: String,
|
||||
pub messages: Vec<SavedMessage>,
|
||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
pub summary: String,
|
||||
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
|
||||
}
|
||||
|
||||
impl SavedContext {
|
||||
pub const VERSION: &'static str = "0.3.0";
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedContextV0_2_0 {
|
||||
pub id: Option<String>,
|
||||
pub zed: String,
|
||||
pub version: String,
|
||||
pub text: String,
|
||||
pub messages: Vec<SavedMessage>,
|
||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
pub summary: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_1_0 {
|
||||
id: Option<String>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
messages: Vec<SavedMessage>,
|
||||
message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: String,
|
||||
api_url: Option<String>,
|
||||
model: OpenAiModel,
|
||||
pub fn init(client: &Arc<Client>) {
|
||||
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
|
||||
client.add_model_request_handler(ContextStore::handle_open_context);
|
||||
client.add_model_message_handler(ContextStore::handle_update_context);
|
||||
client.add_model_request_handler(ContextStore::handle_synchronize_contexts);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedContextMetadata {
|
||||
pub title: String,
|
||||
pub path: PathBuf,
|
||||
pub mtime: chrono::DateTime<chrono::Local>,
|
||||
pub struct RemoteContextMetadata {
|
||||
pub id: ContextId,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ContextStore {
|
||||
contexts: Vec<ContextHandle>,
|
||||
contexts_metadata: Vec<SavedContextMetadata>,
|
||||
host_contexts: Vec<RemoteContextMetadata>,
|
||||
fs: Arc<dyn Fs>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
_watch_updates: Task<Option<()>>,
|
||||
client: Arc<Client>,
|
||||
project: Model<Project>,
|
||||
project_is_shared: bool,
|
||||
client_subscription: Option<client::Subscription>,
|
||||
_project_subscriptions: Vec<gpui::Subscription>,
|
||||
}
|
||||
|
||||
enum ContextHandle {
|
||||
Weak(WeakModel<Context>),
|
||||
Strong(Model<Context>),
|
||||
}
|
||||
|
||||
impl ContextHandle {
|
||||
fn upgrade(&self) -> Option<Model<Context>> {
|
||||
match self {
|
||||
ContextHandle::Weak(weak) => weak.upgrade(),
|
||||
ContextHandle::Strong(strong) => Some(strong.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn downgrade(&self) -> WeakModel<Context> {
|
||||
match self {
|
||||
ContextHandle::Weak(weak) => weak.clone(),
|
||||
ContextHandle::Strong(strong) => strong.downgrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextStore {
|
||||
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
||||
pub fn new(project: Model<Project>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let languages = project.read(cx).languages().clone();
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
|
||||
contexts_metadata: Vec::new(),
|
||||
fs,
|
||||
_watch_updates: cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
while events.next().await.is_some() {
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
let mut this = Self {
|
||||
contexts: Vec::new(),
|
||||
contexts_metadata: Vec::new(),
|
||||
host_contexts: Vec::new(),
|
||||
fs,
|
||||
languages,
|
||||
telemetry,
|
||||
_watch_updates: cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
while events.next().await.is_some() {
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
}),
|
||||
.log_err()
|
||||
}),
|
||||
client_subscription: None,
|
||||
_project_subscriptions: vec![
|
||||
cx.observe(&project, Self::handle_project_changed),
|
||||
cx.subscribe(&project, Self::handle_project_event),
|
||||
],
|
||||
project_is_shared: false,
|
||||
client: project.read(cx).client(),
|
||||
project: project.clone(),
|
||||
};
|
||||
this.handle_project_changed(project, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
this
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||
.await
|
||||
@@ -100,54 +120,433 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load(&self, path: PathBuf, cx: &AppContext) -> Task<Result<SavedContext>> {
|
||||
async fn handle_advertise_contexts(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::AdvertiseContexts>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.host_contexts = envelope
|
||||
.payload
|
||||
.contexts
|
||||
.into_iter()
|
||||
.map(|context| RemoteContextMetadata {
|
||||
id: ContextId::from_proto(context.context_id),
|
||||
summary: context.summary,
|
||||
})
|
||||
.collect();
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_open_context(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::OpenContext>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::OpenContextResponse> {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
let operations = this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_remote() {
|
||||
return Err(anyhow!("only the host contexts can be opened"));
|
||||
}
|
||||
|
||||
let context = this
|
||||
.loaded_context_for_id(&context_id, cx)
|
||||
.context("context not found")?;
|
||||
if context.read(cx).replica_id() != ReplicaId::default() {
|
||||
return Err(anyhow!("context must be opened via the host"));
|
||||
}
|
||||
|
||||
anyhow::Ok(
|
||||
context
|
||||
.read(cx)
|
||||
.serialize_ops(&ContextVersion::default(), cx),
|
||||
)
|
||||
})??;
|
||||
let operations = operations.await;
|
||||
Ok(proto::OpenContextResponse {
|
||||
context: Some(proto::Context { operations }),
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_update_context(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateContext>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
let operation_proto = envelope.payload.operation.context("invalid operation")?;
|
||||
let operation = ContextOperation::from_proto(operation_proto)?;
|
||||
context.update(cx, |context, cx| context.apply_ops([operation], cx))?;
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
|
||||
async fn handle_synchronize_contexts(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::SynchronizeContexts>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::SynchronizeContextsResponse> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_remote() {
|
||||
return Err(anyhow!("only the host can synchronize contexts"));
|
||||
}
|
||||
|
||||
let mut local_versions = Vec::new();
|
||||
for remote_version_proto in envelope.payload.contexts {
|
||||
let remote_version = ContextVersion::from_proto(&remote_version_proto);
|
||||
let context_id = ContextId::from_proto(remote_version_proto.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
let context = context.read(cx);
|
||||
let operations = context.serialize_ops(&remote_version, cx);
|
||||
local_versions.push(context.version(cx).to_proto(context_id.clone()));
|
||||
let client = this.client.clone();
|
||||
let project_id = envelope.payload.project_id;
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let operations = operations.await;
|
||||
for operation in operations {
|
||||
client.send(proto::UpdateContext {
|
||||
project_id,
|
||||
context_id: context_id.to_proto(),
|
||||
operation: Some(operation),
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
this.advertise_contexts(cx);
|
||||
|
||||
anyhow::Ok(proto::SynchronizeContextsResponse {
|
||||
contexts: local_versions,
|
||||
})
|
||||
})?
|
||||
}
|
||||
|
||||
fn handle_project_changed(&mut self, _: Model<Project>, cx: &mut ModelContext<Self>) {
|
||||
let is_shared = self.project.read(cx).is_shared();
|
||||
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
|
||||
if is_shared == was_shared {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_shared {
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
*context = ContextHandle::Strong(strong_context);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let remote_id = self.project.read(cx).remote_id().unwrap();
|
||||
self.client_subscription = self
|
||||
.client
|
||||
.subscribe_to_entity(remote_id)
|
||||
.log_err()
|
||||
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
|
||||
self.advertise_contexts(cx);
|
||||
} else {
|
||||
self.client_subscription = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_project_event(
|
||||
&mut self,
|
||||
_: Model<Project>,
|
||||
event: &project::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
project::Event::Reshared => {
|
||||
self.advertise_contexts(cx);
|
||||
}
|
||||
project::Event::HostReshared | project::Event::Rejoined => {
|
||||
self.synchronize_contexts(cx);
|
||||
}
|
||||
project::Event::DisconnectedFromHost => {
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
*context = ContextHandle::Weak(context.downgrade());
|
||||
strong_context.update(cx, |context, cx| {
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
context.set_capability(language::Capability::ReadOnly, cx);
|
||||
}
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
self.host_contexts.clear();
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> {
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::local(self.languages.clone(), Some(self.telemetry.clone()), cx)
|
||||
});
|
||||
self.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
|
||||
pub fn open_local_context(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Result<Model<Context>>> {
|
||||
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
|
||||
return Task::ready(Ok(existing_context));
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let saved_context = fs.load(&path).await?;
|
||||
let saved_context_json = serde_json::from_str::<serde_json::Value>(&saved_context)?;
|
||||
match saved_context_json
|
||||
.get("version")
|
||||
.ok_or_else(|| anyhow!("version not found"))?
|
||||
{
|
||||
serde_json::Value::String(version) => match version.as_str() {
|
||||
SavedContext::VERSION => {
|
||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||
}
|
||||
"0.2.0" => {
|
||||
let saved_context =
|
||||
serde_json::from_value::<SavedContextV0_2_0>(saved_context_json)?;
|
||||
Ok(SavedContext {
|
||||
id: saved_context.id,
|
||||
zed: saved_context.zed,
|
||||
version: saved_context.version,
|
||||
text: saved_context.text,
|
||||
messages: saved_context.messages,
|
||||
message_metadata: saved_context.message_metadata,
|
||||
summary: saved_context.summary,
|
||||
slash_command_output_sections: Vec::new(),
|
||||
})
|
||||
}
|
||||
"0.1.0" => {
|
||||
let saved_context =
|
||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||
Ok(SavedContext {
|
||||
id: saved_context.id,
|
||||
zed: saved_context.zed,
|
||||
version: saved_context.version,
|
||||
text: saved_context.text,
|
||||
messages: saved_context.messages,
|
||||
message_metadata: saved_context.message_metadata,
|
||||
summary: saved_context.summary,
|
||||
slash_command_output_sections: Vec::new(),
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
||||
},
|
||||
_ => Err(anyhow!("version not found on saved context")),
|
||||
let languages = self.languages.clone();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let load = cx.background_executor().spawn({
|
||||
let path = path.clone();
|
||||
async move {
|
||||
let saved_context = fs.load(&path).await?;
|
||||
SavedContext::from_json(&saved_context)
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let saved_context = load.await?;
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::deserialize(saved_context, path.clone(), languages, Some(telemetry), cx)
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn loaded_context_for_path(&self, path: &Path, cx: &AppContext) -> Option<Model<Context>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
if context.read(cx).path() == Some(path) {
|
||||
Some(context)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
if context.read(cx).id() == id {
|
||||
Some(context)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_remote_context(
|
||||
&mut self,
|
||||
context_id: ContextId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Context>>> {
|
||||
let project = self.project.read(cx);
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
return Task::ready(Err(anyhow!("project was not remote")));
|
||||
};
|
||||
if project.is_local() {
|
||||
return Task::ready(Err(anyhow!("cannot open remote contexts as the host")));
|
||||
}
|
||||
|
||||
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
|
||||
return Task::ready(Ok(context));
|
||||
}
|
||||
|
||||
let replica_id = project.replica_id();
|
||||
let capability = project.capability();
|
||||
let language_registry = self.languages.clone();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let request = self.client.request(proto::OpenContext {
|
||||
project_id,
|
||||
context_id: context_id.to_proto(),
|
||||
});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
let context = cx.new_model(|cx| {
|
||||
Context::new(
|
||||
context_id.clone(),
|
||||
replica_id,
|
||||
capability,
|
||||
language_registry,
|
||||
Some(telemetry),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
let operations = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))??;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn register_context(&mut self, context: &Model<Context>, cx: &mut ModelContext<Self>) {
|
||||
let handle = if self.project_is_shared {
|
||||
ContextHandle::Strong(context.clone())
|
||||
} else {
|
||||
ContextHandle::Weak(context.downgrade())
|
||||
};
|
||||
self.contexts.push(handle);
|
||||
self.advertise_contexts(cx);
|
||||
cx.subscribe(context, Self::handle_context_event).detach();
|
||||
}
|
||||
|
||||
fn handle_context_event(
|
||||
&mut self,
|
||||
context: Model<Context>,
|
||||
event: &ContextEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match event {
|
||||
ContextEvent::SummaryChanged => {
|
||||
self.advertise_contexts(cx);
|
||||
}
|
||||
ContextEvent::Operation(operation) => {
|
||||
let context_id = context.read(cx).id().to_proto();
|
||||
let operation = operation.to_proto();
|
||||
self.client
|
||||
.send(proto::UpdateContext {
|
||||
project_id,
|
||||
context_id,
|
||||
operation: Some(operation),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn advertise_contexts(&self, cx: &AppContext) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// For now, only the host can advertise their open contexts.
|
||||
if self.project.read(cx).is_remote() {
|
||||
return;
|
||||
}
|
||||
|
||||
let contexts = self
|
||||
.contexts
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|context| {
|
||||
let context = context.upgrade()?.read(cx);
|
||||
if context.replica_id() == ReplicaId::default() {
|
||||
Some(proto::ContextMetadata {
|
||||
context_id: context.id().to_proto(),
|
||||
summary: context.summary().map(|summary| summary.text.clone()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.client
|
||||
.send(proto::AdvertiseContexts {
|
||||
project_id,
|
||||
contexts,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn synchronize_contexts(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let contexts = self
|
||||
.contexts
|
||||
.iter()
|
||||
.filter_map(|context| {
|
||||
let context = context.upgrade()?.read(cx);
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
Some(context.version(cx).to_proto(context.id().clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let client = self.client.clone();
|
||||
let request = self.client.request(proto::SynchronizeContexts {
|
||||
project_id,
|
||||
contexts,
|
||||
});
|
||||
cx.spawn(|this, cx| async move {
|
||||
let response = request.await?;
|
||||
|
||||
let mut context_ids = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
this.read_with(&cx, |this, cx| {
|
||||
for context_version_proto in response.contexts {
|
||||
let context_version = ContextVersion::from_proto(&context_version_proto);
|
||||
let context_id = ContextId::from_proto(context_version_proto.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
context_ids.push(context_id);
|
||||
operations.push(context.read(cx).serialize_ops(&context_version, cx));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
let operations = futures::future::join_all(operations).await;
|
||||
for (context_id, operations) in context_ids.into_iter().zip(operations) {
|
||||
for operation in operations {
|
||||
client.send(proto::UpdateContext {
|
||||
project_id,
|
||||
context_id: context_id.to_proto(),
|
||||
operation: Some(operation),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> {
|
||||
let metadata = self.contexts_metadata.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
@@ -178,6 +577,10 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn host_contexts(&self) -> &[RemoteContextMetadata] {
|
||||
&self.host_contexts
|
||||
}
|
||||
|
||||
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::{
|
||||
InlineAssist, InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
@@ -448,7 +447,6 @@ impl PromptLibrary {
|
||||
self.set_active_prompt(Some(prompt_id), cx);
|
||||
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
|
||||
let language_registry = self.language_registry.clone();
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
let prompt = self.store.load(prompt_id);
|
||||
self.pending_load = cx.spawn(|this, mut cx| async move {
|
||||
let prompt = prompt.await;
|
||||
@@ -477,7 +475,7 @@ impl PromptLibrary {
|
||||
editor.set_use_modal_editing(false);
|
||||
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
||||
editor.set_completion_provider(Box::new(
|
||||
SlashCommandCompletionProvider::new(commands, None, None),
|
||||
SlashCommandCompletionProvider::new(None, None),
|
||||
));
|
||||
if focus {
|
||||
editor.focus(cx);
|
||||
|
||||
@@ -31,7 +31,6 @@ pub mod tabs_command;
|
||||
pub mod term_command;
|
||||
|
||||
pub(crate) struct SlashCommandCompletionProvider {
|
||||
commands: Arc<SlashCommandRegistry>,
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
@@ -46,14 +45,12 @@ pub(crate) struct SlashCommandLine {
|
||||
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
commands: Arc<SlashCommandRegistry>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
|
||||
editor,
|
||||
commands,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
@@ -65,8 +62,8 @@ impl SlashCommandCompletionProvider {
|
||||
name_range: Range<Anchor>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<project::Completion>>> {
|
||||
let candidates = self
|
||||
.commands
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
let candidates = commands
|
||||
.command_names()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@@ -76,7 +73,6 @@ impl SlashCommandCompletionProvider {
|
||||
char_bag: def.as_ref().into(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let commands = self.commands.clone();
|
||||
let command_name = command_name.to_string();
|
||||
let editor = self.editor.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
@@ -155,7 +151,8 @@ impl SlashCommandCompletionProvider {
|
||||
flag.store(true, SeqCst);
|
||||
*flag = new_cancel_flag.clone();
|
||||
|
||||
if let Some(command) = self.commands.command(command_name) {
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
if let Some(command) = commands.command(command_name) {
|
||||
let completions = command.complete_argument(
|
||||
argument,
|
||||
new_cancel_flag.clone(),
|
||||
|
||||
@@ -67,7 +67,7 @@ pub struct SlashCommandOutput {
|
||||
pub run_commands_in_text: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SlashCommandOutputSection<T> {
|
||||
pub range: Range<T>,
|
||||
pub icon: IconName,
|
||||
|
||||
@@ -72,7 +72,7 @@ impl Render for Breadcrumbs {
|
||||
.into_any()
|
||||
});
|
||||
let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
|
||||
Label::new("›").color(Color::Muted).into_any_element()
|
||||
Label::new("›").color(Color::Placeholder).into_any_element()
|
||||
});
|
||||
|
||||
let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
|
||||
@@ -83,7 +83,7 @@ impl Render for Breadcrumbs {
|
||||
Some(editor) => element.child(
|
||||
ButtonLike::new("toggle outline view")
|
||||
.child(breadcrumbs_stack)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.on_click(move |_, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||
|
||||
@@ -217,6 +217,9 @@ pub struct Client {
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
rpc_url: RwLock<Option<Url>>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -527,6 +530,8 @@ impl Client {
|
||||
authenticate: Default::default(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
establish_connection: Default::default(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
rpc_url: RwLock::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -584,6 +589,12 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn override_rpc_url(&self, url: Url) -> &Self {
|
||||
*self.rpc_url.write() = Some(url);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
cx.global::<GlobalClient>().0.clone()
|
||||
}
|
||||
@@ -1086,38 +1097,50 @@ impl Client {
|
||||
self.establish_websocket_connection(credentials, cx)
|
||||
}
|
||||
|
||||
async fn get_rpc_url(
|
||||
fn rpc_url(
|
||||
&self,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> Result<Url> {
|
||||
if let Some(url) = &*ZED_RPC_URL {
|
||||
return Url::parse(url).context("invalid rpc url");
|
||||
}
|
||||
) -> impl Future<Output = Result<Url>> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let url_override = self.rpc_url.read().clone();
|
||||
|
||||
let mut url = http.build_url("/rpc");
|
||||
if let Some(preview_param) =
|
||||
release_channel.and_then(|channel| channel.release_query_param())
|
||||
{
|
||||
url += "?";
|
||||
url += preview_param;
|
||||
}
|
||||
let response = http.get(&url, Default::default(), false).await?;
|
||||
let collab_url = if response.status().is_redirection() {
|
||||
response
|
||||
.headers()
|
||||
.get("Location")
|
||||
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
|
||||
.to_str()
|
||||
.map_err(EstablishConnectionError::other)?
|
||||
.to_string()
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"unexpected /rpc response status {}",
|
||||
response.status()
|
||||
))?
|
||||
};
|
||||
async move {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(url) = url_override {
|
||||
return Ok(url);
|
||||
}
|
||||
|
||||
Url::parse(&collab_url).context("invalid rpc url")
|
||||
if let Some(url) = &*ZED_RPC_URL {
|
||||
return Url::parse(url).context("invalid rpc url");
|
||||
}
|
||||
|
||||
let mut url = http.build_url("/rpc");
|
||||
if let Some(preview_param) =
|
||||
release_channel.and_then(|channel| channel.release_query_param())
|
||||
{
|
||||
url += "?";
|
||||
url += preview_param;
|
||||
}
|
||||
|
||||
let response = http.get(&url, Default::default(), false).await?;
|
||||
let collab_url = if response.status().is_redirection() {
|
||||
response
|
||||
.headers()
|
||||
.get("Location")
|
||||
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
|
||||
.to_str()
|
||||
.map_err(EstablishConnectionError::other)?
|
||||
.to_string()
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"unexpected /rpc response status {}",
|
||||
response.status()
|
||||
))?
|
||||
};
|
||||
|
||||
Url::parse(&collab_url).context("invalid rpc url")
|
||||
}
|
||||
}
|
||||
|
||||
fn establish_websocket_connection(
|
||||
@@ -1144,8 +1167,9 @@ impl Client {
|
||||
);
|
||||
|
||||
let http = self.http.clone();
|
||||
let rpc_url = self.rpc_url(http, release_channel);
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
|
||||
let mut rpc_url = rpc_url.await?;
|
||||
let rpc_host = rpc_url
|
||||
.host_str()
|
||||
.zip(rpc_url.port_or_known_default())
|
||||
@@ -1186,6 +1210,7 @@ impl Client {
|
||||
cx: &AsyncAppContext,
|
||||
) -> Task<Result<Credentials>> {
|
||||
let http = self.http.clone();
|
||||
let this = self.clone();
|
||||
cx.spawn(|cx| async move {
|
||||
let background = cx.background_executor().clone();
|
||||
|
||||
@@ -1215,7 +1240,8 @@ impl Client {
|
||||
{
|
||||
eprintln!("authenticate as admin {login}, {token}");
|
||||
|
||||
return Self::authenticate_as_admin(http, login.clone(), token.clone())
|
||||
return this
|
||||
.authenticate_as_admin(http, login.clone(), token.clone())
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -1303,6 +1329,7 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn authenticate_as_admin(
|
||||
self: &Arc<Self>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
login: String,
|
||||
mut api_token: String,
|
||||
@@ -1319,7 +1346,7 @@ impl Client {
|
||||
|
||||
// Use the collab server's admin API to retrieve the id
|
||||
// of the impersonated user.
|
||||
let mut url = Self::get_rpc_url(http.clone(), None).await?;
|
||||
let mut url = self.rpc_url(http.clone(), None).await?;
|
||||
url.set_path("/user");
|
||||
url.set_query(Some(&format!("github_login={login}")));
|
||||
let request = Request::get(url.as_str())
|
||||
|
||||
@@ -18,4 +18,5 @@ test-support = ["dep:parking_lot"]
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
parking_lot = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod system_clock;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
@@ -16,7 +17,7 @@ pub type Seq = u32;
|
||||
|
||||
/// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp),
|
||||
/// used to determine the ordering of events in the editor.
|
||||
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Lamport {
|
||||
pub replica_id: ReplicaId,
|
||||
pub value: Seq,
|
||||
@@ -161,6 +162,10 @@ impl Lamport {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_u64(self) -> u64 {
|
||||
((self.value as u64) << 32) | (self.replica_id as u64)
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> Self {
|
||||
let timestamp = *self;
|
||||
self.value += 1;
|
||||
|
||||
@@ -71,6 +71,7 @@ util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assistant = { workspace = true, features = ["test-support"] }
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -562,7 +562,7 @@ fn test_fuzzy_like_string() {
|
||||
assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
|
||||
}
|
||||
|
||||
#[cfg(target = "macos")]
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test]
|
||||
async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = tests::TestDb::postgres(cx.executor());
|
||||
|
||||
@@ -595,6 +595,14 @@ impl Server {
|
||||
.add_message_handler(user_message_handler(acknowledge_channel_message))
|
||||
.add_message_handler(user_message_handler(acknowledge_buffer_version))
|
||||
.add_request_handler(user_handler(get_supermaven_api_key))
|
||||
.add_request_handler(user_handler(
|
||||
forward_mutating_project_request::<proto::OpenContext>,
|
||||
))
|
||||
.add_request_handler(user_handler(
|
||||
forward_mutating_project_request::<proto::SynchronizeContexts>,
|
||||
))
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||
.add_message_handler(update_context)
|
||||
.add_streaming_request_handler({
|
||||
let app_state = app_state.clone();
|
||||
move |request, response, session| {
|
||||
@@ -3056,6 +3064,53 @@ async fn update_buffer(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_context(message: proto::UpdateContext, session: Session) -> Result<()> {
|
||||
let project_id = ProjectId::from_proto(message.project_id);
|
||||
|
||||
let operation = message.operation.as_ref().context("invalid operation")?;
|
||||
let capability = match operation.variant.as_ref() {
|
||||
Some(proto::context_operation::Variant::BufferOperation(buffer_op)) => {
|
||||
if let Some(buffer_op) = buffer_op.operation.as_ref() {
|
||||
match buffer_op.variant {
|
||||
None | Some(proto::operation::Variant::UpdateSelections(_)) => {
|
||||
Capability::ReadOnly
|
||||
}
|
||||
_ => Capability::ReadWrite,
|
||||
}
|
||||
} else {
|
||||
Capability::ReadWrite
|
||||
}
|
||||
}
|
||||
Some(_) => Capability::ReadWrite,
|
||||
None => Capability::ReadOnly,
|
||||
};
|
||||
|
||||
let guard = session
|
||||
.db()
|
||||
.await
|
||||
.connections_for_buffer_update(
|
||||
project_id,
|
||||
session.principal_id(),
|
||||
session.connection_id,
|
||||
capability,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (host, guests) = &*guard;
|
||||
|
||||
broadcast(
|
||||
Some(session.connection_id),
|
||||
guests.iter().chain([host]).copied(),
|
||||
|connection_id| {
|
||||
session
|
||||
.peer
|
||||
.forward_send(session.connection_id, connection_id, message.clone())
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Notify other participants that a project has been updated.
|
||||
async fn broadcast_project_message_from_host<T: EntityMessage<Entity = ShareProject>>(
|
||||
request: T,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant::ContextStore;
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -6449,3 +6450,123 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert!(!pane.can_navigate_forward());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_context_collaboration_with_reconnect(
|
||||
executor: BackgroundExecutor,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let mut server = TestServer::start(executor.clone()).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("/a", Default::default()).await;
|
||||
let (project_a, _) = client_a.build_local_project("/a", 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_dev_server_project(project_id, cx_b).await;
|
||||
|
||||
// Client A sees that a guest has joined.
|
||||
executor.run_until_parked();
|
||||
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
assert_eq!(project.collaborators().len(), 1);
|
||||
});
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
assert_eq!(project.collaborators().len(), 1);
|
||||
});
|
||||
|
||||
let context_store_a = cx_a
|
||||
.update(|cx| ContextStore::new(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let context_store_b = cx_b
|
||||
.update(|cx| ContextStore::new(project_b.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A creates a new context.
|
||||
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
// Client B retrieves host's contexts and joins one.
|
||||
let context_b = context_store_b
|
||||
.update(cx_b, |store, cx| {
|
||||
let host_contexts = store.host_contexts().to_vec();
|
||||
assert_eq!(host_contexts.len(), 1);
|
||||
store.open_remote_context(host_contexts[0].id.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Host and guest make changes
|
||||
context_a.update(cx_a, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Host change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
context_b.update(cx_b, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Guest change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
|
||||
"Guest change\nHost change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
|
||||
"Guest change\nHost change\n"
|
||||
);
|
||||
|
||||
// Disconnect client A and make some changes while disconnected.
|
||||
server.disconnect_client(client_a.peer_id().unwrap());
|
||||
server.forbid_connections();
|
||||
context_a.update(cx_a, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Host offline change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
context_b.update(cx_b, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Guest offline change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
|
||||
"Host offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
|
||||
"Guest offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
|
||||
// Allow client A to reconnect and verify that contexts converge.
|
||||
server.allow_connections();
|
||||
executor.advance_clock(RECEIVE_TIMEOUT);
|
||||
assert_eq!(
|
||||
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
|
||||
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
|
||||
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
|
||||
// Client A disconnects without being able to reconnect. Context B becomes readonly.
|
||||
server.forbid_connections();
|
||||
server.disconnect_client(client_a.peer_id().unwrap());
|
||||
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
||||
context_b.read_with(cx_b, |context, cx| {
|
||||
assert!(context.buffer().read(cx).read_only());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -294,6 +294,8 @@ impl TestServer {
|
||||
menu::init();
|
||||
dev_server_projects::init(client.clone(), cx);
|
||||
settings::KeymapFile::load_asset(os_keymap, cx).unwrap();
|
||||
assistant::FakeCompletionProvider::setup_test(cx);
|
||||
assistant::context_store::init(&client);
|
||||
});
|
||||
|
||||
client
|
||||
|
||||
@@ -11093,6 +11093,7 @@ impl Editor {
|
||||
if *singleton_buffer_edited {
|
||||
if let Some(project) = &self.project {
|
||||
let project = project.read(cx);
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let languages_affected = multibuffer
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
|
||||
@@ -49,11 +49,13 @@ fn display_ranges<'a>(
|
||||
.pending
|
||||
.as_ref()
|
||||
.map(|pending| &pending.selection);
|
||||
selections
|
||||
.disjoint
|
||||
.iter()
|
||||
.chain(pending)
|
||||
.map(move |s| s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map))
|
||||
selections.disjoint.iter().chain(pending).map(move |s| {
|
||||
if s.reversed {
|
||||
s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
|
||||
} else {
|
||||
s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deploy_context_menu(
|
||||
|
||||
@@ -12,7 +12,7 @@ use editor::{Editor, EditorElement, EditorStyle};
|
||||
use extension::{ExtensionManifest, ExtensionOperation, ExtensionStore};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, uniform_list, AnyElement, AppContext, EventEmitter, Flatten, FocusableView, FontStyle,
|
||||
actions, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle,
|
||||
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
@@ -24,6 +24,7 @@ use std::time::Duration;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
|
||||
use util::ResultExt as _;
|
||||
use workspace::item::TabContentParams;
|
||||
use workspace::{
|
||||
item::{Item, ItemEvent},
|
||||
@@ -57,23 +58,9 @@ pub fn init(cx: &mut AppContext) {
|
||||
multiple: false,
|
||||
});
|
||||
|
||||
let workspace_handle = cx.view().downgrade();
|
||||
cx.deref_mut()
|
||||
.spawn(|mut cx| async move {
|
||||
let extension_path =
|
||||
match Flatten::flatten(prompt.await.map_err(|e| e.into())) {
|
||||
Ok(Some(mut paths)) => paths.pop()?,
|
||||
Ok(None) => return None,
|
||||
Err(err) => {
|
||||
workspace_handle
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let extension_path = prompt.await.log_err()??.pop()?;
|
||||
store
|
||||
.update(&mut cx, |store, cx| {
|
||||
store
|
||||
|
||||
@@ -124,7 +124,6 @@ wayland-protocols = { version = "0.31.2", features = [
|
||||
] }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = ["client"] }
|
||||
oo7 = "0.3.0"
|
||||
open = "5.2.0"
|
||||
filedescriptor = "0.8.2"
|
||||
x11rb = { version = "0.13.0", features = [
|
||||
"allow-unsafe-code",
|
||||
@@ -135,10 +134,7 @@ x11rb = { version = "0.13.0", features = [
|
||||
"resource_manager",
|
||||
"sync",
|
||||
] }
|
||||
xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "fcbb4612185cc129ceeff51d22f7fb51810a03b2", features = [
|
||||
"wayland",
|
||||
"x11",
|
||||
] }
|
||||
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
||||
xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
|
||||
"x11rb-xcb",
|
||||
"x11rb-client",
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::env;
|
||||
|
||||
fn main() {
|
||||
let target = env::var("CARGO_CFG_TARGET_OS");
|
||||
|
||||
println!("cargo::rustc-check-cfg=cfg(gles)");
|
||||
match target.as_deref() {
|
||||
Ok("macos") => {
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -2,63 +2,209 @@ use gpui::*;
|
||||
|
||||
struct WindowContent {
|
||||
text: SharedString,
|
||||
bounds: Bounds<Pixels>,
|
||||
bg: Hsla,
|
||||
}
|
||||
|
||||
impl Render for WindowContent {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let window_bounds = cx.bounds();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.bg(rgb(0x1e2025))
|
||||
.flex_col()
|
||||
.bg(self.bg)
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.text_xl()
|
||||
.text_color(rgb(0xffffff))
|
||||
.child(self.text.clone())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.text_sm()
|
||||
.items_center()
|
||||
.size_full()
|
||||
.child(format!(
|
||||
"origin: {}, {} size: {}, {}",
|
||||
self.bounds.origin.x,
|
||||
self.bounds.origin.y,
|
||||
self.bounds.size.width,
|
||||
self.bounds.size.height
|
||||
))
|
||||
.child(format!(
|
||||
"cx.bounds() origin: {}, {} size {}, {}",
|
||||
window_bounds.origin.x,
|
||||
window_bounds.origin.y,
|
||||
window_bounds.size.width,
|
||||
window_bounds.size.height
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> WindowOptions {
|
||||
WindowOptions {
|
||||
// Set the bounds of the window in screen coordinates
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
// Specify the display_id to ensure the window is created on the correct screen
|
||||
display_id: Some(display_id),
|
||||
titlebar: None,
|
||||
window_background: WindowBackgroundAppearance::Transparent,
|
||||
focus: false,
|
||||
show: true,
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
// Create several new windows, positioned in the top right corner of each screen
|
||||
let size = Size {
|
||||
width: px(350.),
|
||||
height: px(75.),
|
||||
};
|
||||
let margin_offset = px(150.);
|
||||
|
||||
for screen in cx.displays() {
|
||||
let options = {
|
||||
let margin_right = px(16.);
|
||||
let margin_height = px(-48.);
|
||||
|
||||
let size = Size {
|
||||
width: px(400.),
|
||||
height: px(72.),
|
||||
};
|
||||
|
||||
let bounds = gpui::Bounds::<Pixels> {
|
||||
origin: screen.bounds().upper_right()
|
||||
- point(size.width + margin_right, margin_height),
|
||||
size,
|
||||
};
|
||||
|
||||
WindowOptions {
|
||||
// Set the bounds of the window in screen coordinates
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
// Specify the display_id to ensure the window is created on the correct screen
|
||||
display_id: Some(screen.id()),
|
||||
|
||||
titlebar: None,
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
focus: false,
|
||||
show: true,
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
}
|
||||
let bounds = Bounds {
|
||||
origin: point(margin_offset, margin_offset),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(options, |cx| {
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("{:?}", screen.id()).into(),
|
||||
text: format!("Top Left {:?}", screen.id()).into(),
|
||||
bg: gpui::red(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: screen.bounds().upper_right()
|
||||
- point(size.width + margin_offset, -margin_offset),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Top Right {:?}", screen.id()).into(),
|
||||
bg: gpui::red(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: screen.bounds().lower_left()
|
||||
- point(-margin_offset, size.height + margin_offset),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Bottom Left {:?}", screen.id()).into(),
|
||||
bg: gpui::blue(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: screen.bounds().lower_right()
|
||||
- point(size.width + margin_offset, size.height + margin_offset),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Bottom Right {:?}", screen.id()).into(),
|
||||
bg: gpui::blue(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: point(screen.bounds().center().x - size.center().x, margin_offset),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Top Center {:?}", screen.id()).into(),
|
||||
bg: gpui::black(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: point(margin_offset, screen.bounds().center().y - size.center().y),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Left Center {:?}", screen.id()).into(),
|
||||
bg: gpui::black(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: point(
|
||||
screen.bounds().center().x - size.center().x,
|
||||
screen.bounds().center().y - size.center().y,
|
||||
),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Center {:?}", screen.id()).into(),
|
||||
bg: gpui::black(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: point(
|
||||
screen.bounds().size.width - size.width - margin_offset,
|
||||
screen.bounds().center().y - size.center().y,
|
||||
),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Right Center {:?}", screen.id()).into(),
|
||||
bg: gpui::black(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let bounds = Bounds {
|
||||
origin: point(
|
||||
screen.bounds().center().x - size.center().x,
|
||||
screen.bounds().size.height - size.height - margin_offset,
|
||||
),
|
||||
size,
|
||||
};
|
||||
|
||||
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("Bottom Center {:?}", screen.id()).into(),
|
||||
bg: gpui::black(),
|
||||
bounds,
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -612,11 +612,10 @@ impl AppContext {
|
||||
/// Displays a platform modal for selecting paths.
|
||||
/// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel.
|
||||
/// If cancelled, a `None` will be relayed instead.
|
||||
/// May return an error on Linux if the file picker couldn't be opened.
|
||||
pub fn prompt_for_paths(
|
||||
&self,
|
||||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
|
||||
self.platform.prompt_for_paths(options)
|
||||
}
|
||||
|
||||
@@ -624,11 +623,7 @@ impl AppContext {
|
||||
/// The provided directory will be used to set the initial location.
|
||||
/// When a path is selected, it is relayed asynchronously via the returned oneshot channel.
|
||||
/// If cancelled, a `None` will be relayed instead.
|
||||
/// May return an error on Linux if the file picker couldn't be opened.
|
||||
pub fn prompt_for_new_path(
|
||||
&self,
|
||||
directory: &Path,
|
||||
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
|
||||
pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
|
||||
self.platform.prompt_for_new_path(directory)
|
||||
}
|
||||
|
||||
|
||||
@@ -137,8 +137,8 @@ pub(crate) trait Platform: 'static {
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
|
||||
fn reveal_path(&self, path: &Path);
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>);
|
||||
@@ -318,6 +318,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
) -> Option<oneshot::Receiver<usize>>;
|
||||
fn activate(&self);
|
||||
fn is_active(&self) -> bool;
|
||||
fn is_hovered(&self) -> bool;
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
|
||||
fn minimize(&self);
|
||||
@@ -327,6 +328,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
||||
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
||||
|
||||
@@ -21,7 +21,6 @@ use std::{
|
||||
use anyhow::anyhow;
|
||||
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
|
||||
use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest};
|
||||
use ashpd::desktop::ResponseError;
|
||||
use ashpd::{url, ActivationToken};
|
||||
use async_task::Runnable;
|
||||
use calloop::channel::Channel;
|
||||
@@ -55,9 +54,6 @@ pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
|
||||
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
|
||||
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
|
||||
|
||||
const FILE_PICKER_PORTAL_MISSING: &str =
|
||||
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
|
||||
|
||||
pub trait LinuxClient {
|
||||
fn compositor_name(&self) -> &'static str;
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
||||
@@ -260,7 +256,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
@@ -278,7 +274,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
}
|
||||
};
|
||||
|
||||
let request = match OpenFileRequest::default()
|
||||
let result = OpenFileRequest::default()
|
||||
.modal(true)
|
||||
.title(title)
|
||||
.accept_label("Select")
|
||||
@@ -286,68 +282,49 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
.directory(options.directories)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(request) => request,
|
||||
Err(err) => {
|
||||
let result = match err {
|
||||
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
|
||||
err => err.into(),
|
||||
};
|
||||
done_tx.send(Err(result));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = match request.response() {
|
||||
Ok(response) => Ok(Some(
|
||||
.ok()
|
||||
.and_then(|request| request.response().ok())
|
||||
.and_then(|response| {
|
||||
response
|
||||
.uris()
|
||||
.iter()
|
||||
.filter_map(|uri| uri.to_file_path().ok())
|
||||
.collect::<Vec<_>>(),
|
||||
)),
|
||||
Err(ashpd::Error::Response(ResponseError::Cancelled)) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
};
|
||||
.map(|uri| uri.to_file_path().ok())
|
||||
.collect()
|
||||
});
|
||||
|
||||
done_tx.send(result);
|
||||
})
|
||||
.detach();
|
||||
done_rx
|
||||
}
|
||||
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
let directory = directory.to_owned();
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
let request = match SaveFileRequest::default()
|
||||
let request = SaveFileRequest::default()
|
||||
.modal(true)
|
||||
.title("Select new path")
|
||||
.accept_label("Accept")
|
||||
.current_folder(directory)
|
||||
.expect("pathbuf should not be nul terminated")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(request) => request,
|
||||
Err(err) => {
|
||||
let result = match err {
|
||||
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
|
||||
err => err.into(),
|
||||
};
|
||||
done_tx.send(Err(result));
|
||||
return;
|
||||
}
|
||||
.current_folder(directory);
|
||||
|
||||
let result = if let Ok(request) = request {
|
||||
request
|
||||
.send()
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|request| request.response().ok())
|
||||
.and_then(|response| {
|
||||
response
|
||||
.uris()
|
||||
.first()
|
||||
.and_then(|uri| uri.to_file_path().ok())
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result = match request.response() {
|
||||
Ok(response) => Ok(response
|
||||
.uris()
|
||||
.first()
|
||||
.and_then(|uri| uri.to_file_path().ok())),
|
||||
Err(ashpd::Error::Response(ResponseError::Cancelled)) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
};
|
||||
done_tx.send(result);
|
||||
})
|
||||
.detach();
|
||||
@@ -525,26 +502,11 @@ pub(super) fn open_uri_internal(
|
||||
if let Some(uri) = url::Url::parse(uri).log_err() {
|
||||
executor
|
||||
.spawn(async move {
|
||||
match OpenUriRequest::default()
|
||||
.activation_token(activation_token.clone().map(ActivationToken::from))
|
||||
OpenUriRequest::default()
|
||||
.activation_token(activation_token.map(ActivationToken::from))
|
||||
.send_uri(&uri)
|
||||
.await
|
||||
{
|
||||
Ok(_) => return,
|
||||
Err(e) => log::error!("Failed to open with dbus: {}", e),
|
||||
}
|
||||
|
||||
for mut command in open::commands(uri.to_string()) {
|
||||
if let Some(token) = activation_token.as_ref() {
|
||||
command.env("XDG_ACTIVATION_TOKEN", token);
|
||||
}
|
||||
match command.spawn() {
|
||||
Ok(_) => return,
|
||||
Err(e) => {
|
||||
log::error!("Failed to open with {:?}: {}", command.get_program(), e)
|
||||
}
|
||||
}
|
||||
}
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -557,20 +519,12 @@ pub(super) fn reveal_path_internal(
|
||||
) {
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Some(dir) = File::open(path.clone()).log_err() {
|
||||
match OpenDirectoryRequest::default()
|
||||
if let Some(dir) = File::open(path).log_err() {
|
||||
OpenDirectoryRequest::default()
|
||||
.activation_token(activation_token.map(ActivationToken::from))
|
||||
.send(&dir.as_fd())
|
||||
.await
|
||||
{
|
||||
Ok(_) => return,
|
||||
Err(e) => log::error!("Failed to open with dbus: {}", e),
|
||||
}
|
||||
if path.is_dir() {
|
||||
open::that_detached(path).log_err();
|
||||
} else {
|
||||
open::that_detached(path.parent().unwrap_or(Path::new(""))).log_err();
|
||||
}
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -11,7 +11,6 @@ use calloop_wayland_source::WaylandSource;
|
||||
use collections::HashMap;
|
||||
use filedescriptor::Pipe;
|
||||
|
||||
use http::Url;
|
||||
use smallvec::SmallVec;
|
||||
use util::ResultExt;
|
||||
use wayland_backend::client::ObjectId;
|
||||
@@ -1404,6 +1403,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
|
||||
if let Some(window) = get_window(&mut state, &surface.id()) {
|
||||
state.mouse_focused_window = Some(window.clone());
|
||||
|
||||
if state.enter_token.is_some() {
|
||||
state.enter_token = None;
|
||||
}
|
||||
@@ -1417,7 +1417,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
}
|
||||
}
|
||||
drop(state);
|
||||
window.set_focused(true);
|
||||
window.set_hovered(true);
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
@@ -1433,7 +1433,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
|
||||
drop(state);
|
||||
focused_window.handle_input(input);
|
||||
focused_window.set_focused(false);
|
||||
focused_window.set_hovered(false);
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::Motion {
|
||||
@@ -1792,8 +1792,7 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
|
||||
|
||||
let paths: SmallVec<[_; 2]> = file_list
|
||||
.lines()
|
||||
.filter_map(|path| Url::parse(path).log_err())
|
||||
.filter_map(|url| url.to_file_path().log_err())
|
||||
.map(|path| PathBuf::from(path.replace("file://", "")))
|
||||
.collect();
|
||||
let position = Point::new(x.into(), y.into());
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ pub(crate) struct Callbacks {
|
||||
request_frame: Option<Box<dyn FnMut()>>,
|
||||
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
|
||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||
hover_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
moved: Option<Box<dyn FnMut()>>,
|
||||
should_close: Option<Box<dyn FnMut() -> bool>>,
|
||||
@@ -97,6 +98,7 @@ pub struct WaylandWindowState {
|
||||
client: WaylandClientStatePtr,
|
||||
handle: AnyWindowHandle,
|
||||
active: bool,
|
||||
hovered: bool,
|
||||
in_progress_configure: Option<InProgressConfigure>,
|
||||
in_progress_window_controls: Option<WindowControls>,
|
||||
window_controls: WindowControls,
|
||||
@@ -181,6 +183,7 @@ impl WaylandWindowState {
|
||||
appearance,
|
||||
handle,
|
||||
active: false,
|
||||
hovered: false,
|
||||
in_progress_window_controls: None,
|
||||
// Assume that we can do anything, unless told otherwise
|
||||
window_controls: WindowControls {
|
||||
@@ -700,6 +703,12 @@ impl WaylandWindowStatePtr {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_hovered(&self, focus: bool) {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().hover_status_change {
|
||||
fun(focus);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
|
||||
self.state.borrow_mut().appearance = appearance;
|
||||
|
||||
@@ -845,6 +854,10 @@ impl PlatformWindow for WaylandWindow {
|
||||
self.borrow().active
|
||||
}
|
||||
|
||||
fn is_hovered(&self) -> bool {
|
||||
self.borrow().hovered
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.borrow().toplevel.set_title(title.to_string());
|
||||
}
|
||||
@@ -899,6 +912,10 @@ impl PlatformWindow for WaylandWindow {
|
||||
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.borrow_mut().hover_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.callbacks.borrow_mut().resize = Some(callback);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use x11rb::xcb_ffi::XCBConnection;
|
||||
use xim::{x11rb::X11rbClient, Client};
|
||||
use xim::{AttributeName, InputStyle};
|
||||
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
|
||||
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
|
||||
use xkbcommon::xkb as xkbc;
|
||||
|
||||
use crate::platform::linux::LinuxClient;
|
||||
use crate::platform::{LinuxCommon, PlatformWindow};
|
||||
@@ -94,13 +94,6 @@ impl From<xim::ClientError> for EventHandlerError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct XKBStateNotiy {
|
||||
depressed_layout: LayoutIndex,
|
||||
latched_layout: LayoutIndex,
|
||||
locked_layout: LayoutIndex,
|
||||
}
|
||||
|
||||
pub struct X11ClientState {
|
||||
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
|
||||
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
|
||||
@@ -117,9 +110,9 @@ pub struct X11ClientState {
|
||||
pub(crate) _resource_database: Database,
|
||||
pub(crate) atoms: XcbAtoms,
|
||||
pub(crate) windows: HashMap<xproto::Window, WindowRef>,
|
||||
pub(crate) focused_window: Option<xproto::Window>,
|
||||
pub(crate) mouse_focused_window: Option<xproto::Window>,
|
||||
pub(crate) keyboard_focused_window: Option<xproto::Window>,
|
||||
pub(crate) xkb: xkbc::State,
|
||||
previous_xkb_state: XKBStateNotiy,
|
||||
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
|
||||
pub(crate) xim_handler: Option<XimHandler>,
|
||||
pub modifiers: Modifiers,
|
||||
@@ -152,7 +145,12 @@ impl X11ClientStatePtr {
|
||||
if let Some(window_ref) = state.windows.remove(&x_window) {
|
||||
state.loop_handle.remove(window_ref.refresh_event_token);
|
||||
}
|
||||
|
||||
if state.mouse_focused_window == Some(x_window) {
|
||||
state.mouse_focused_window = None;
|
||||
}
|
||||
if state.keyboard_focused_window == Some(x_window) {
|
||||
state.keyboard_focused_window = None;
|
||||
}
|
||||
state.cursor_styles.remove(&x_window);
|
||||
|
||||
if state.windows.is_empty() {
|
||||
@@ -349,9 +347,9 @@ impl X11Client {
|
||||
_resource_database: resource_database,
|
||||
atoms,
|
||||
windows: HashMap::default(),
|
||||
focused_window: None,
|
||||
mouse_focused_window: None,
|
||||
keyboard_focused_window: None,
|
||||
xkb: xkb_state,
|
||||
previous_xkb_state: XKBStateNotiy::default(),
|
||||
ximc,
|
||||
xim_handler,
|
||||
|
||||
@@ -511,7 +509,7 @@ impl X11Client {
|
||||
.push(AttributeName::ClientWindow, xim_handler.window)
|
||||
.push(AttributeName::FocusWindow, xim_handler.window);
|
||||
|
||||
let window_id = state.focused_window;
|
||||
let window_id = state.keyboard_focused_window;
|
||||
drop(state);
|
||||
if let Some(window_id) = window_id {
|
||||
let window = self.get_window(window_id).unwrap();
|
||||
@@ -595,17 +593,17 @@ impl X11Client {
|
||||
}
|
||||
Event::FocusIn(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
window.set_focused(true);
|
||||
window.set_active(true);
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.focused_window = Some(event.event);
|
||||
state.keyboard_focused_window = Some(event.event);
|
||||
drop(state);
|
||||
self.enable_ime();
|
||||
}
|
||||
Event::FocusOut(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
window.set_focused(false);
|
||||
window.set_active(false);
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.focused_window = None;
|
||||
state.keyboard_focused_window = None;
|
||||
if let Some(compose_state) = state.compose_state.as_mut() {
|
||||
compose_state.reset();
|
||||
}
|
||||
@@ -624,16 +622,12 @@ impl X11Client {
|
||||
event.latched_group as u32,
|
||||
event.locked_group.into(),
|
||||
);
|
||||
state.previous_xkb_state = XKBStateNotiy {
|
||||
depressed_layout: event.base_group as u32,
|
||||
latched_layout: event.latched_group as u32,
|
||||
locked_layout: event.locked_group.into(),
|
||||
};
|
||||
|
||||
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||
if state.modifiers == modifiers {
|
||||
drop(state);
|
||||
} else {
|
||||
let focused_window_id = state.focused_window?;
|
||||
let focused_window_id = state.keyboard_focused_window?;
|
||||
state.modifiers = modifiers;
|
||||
drop(state);
|
||||
|
||||
@@ -650,18 +644,11 @@ impl X11Client {
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
state.modifiers = modifiers;
|
||||
state.pre_ime_key_down.take();
|
||||
|
||||
let keystroke = {
|
||||
let code = event.detail.into();
|
||||
let xkb_state = state.previous_xkb_state.clone();
|
||||
state.xkb.update_mask(
|
||||
event.state.bits() as ModMask,
|
||||
0,
|
||||
0,
|
||||
xkb_state.depressed_layout,
|
||||
xkb_state.latched_layout,
|
||||
xkb_state.locked_layout,
|
||||
);
|
||||
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkbc::KeyDirection::Down);
|
||||
let keysym = state.xkb.key_get_one_sym(code);
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
@@ -720,16 +707,8 @@ impl X11Client {
|
||||
|
||||
let keystroke = {
|
||||
let code = event.detail.into();
|
||||
let xkb_state = state.previous_xkb_state.clone();
|
||||
state.xkb.update_mask(
|
||||
event.state.bits() as ModMask,
|
||||
0,
|
||||
0,
|
||||
xkb_state.depressed_layout,
|
||||
xkb_state.latched_layout,
|
||||
xkb_state.locked_layout,
|
||||
);
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkbc::KeyDirection::Up);
|
||||
let keysym = state.xkb.key_get_one_sym(code);
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
@@ -899,12 +878,18 @@ impl X11Client {
|
||||
valuator_idx += 1;
|
||||
}
|
||||
}
|
||||
Event::XinputEnter(event) if event.mode == xinput::NotifyMode::NORMAL => {
|
||||
let window = self.get_window(event.event)?;
|
||||
window.set_hovered(true);
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.mouse_focused_window = Some(event.event);
|
||||
}
|
||||
Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
|
||||
self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
|
||||
self.0.borrow_mut().scroll_y = None;
|
||||
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.mouse_focused_window = None;
|
||||
let pressed_button = pressed_button_from_mask(event.buttons[0]);
|
||||
let position = point(
|
||||
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
@@ -914,11 +899,13 @@ impl X11Client {
|
||||
state.modifiers = modifiers;
|
||||
drop(state);
|
||||
|
||||
let window = self.get_window(event.event)?;
|
||||
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
modifiers,
|
||||
}));
|
||||
window.set_hovered(false);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -975,17 +962,20 @@ impl X11Client {
|
||||
fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
|
||||
let window = self.get_window(window).unwrap();
|
||||
let mut state = self.0.borrow_mut();
|
||||
let keystroke = state.pre_ime_key_down.take();
|
||||
if !state.composing {
|
||||
if let Some(keystroke) = state.pre_ime_key_down.take() {
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
}));
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
state.composing = false;
|
||||
drop(state);
|
||||
if let Some(mut keystroke) = keystroke {
|
||||
keystroke.ime_key = Some(text.clone());
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
}));
|
||||
}
|
||||
|
||||
window.handle_ime_commit(text);
|
||||
Some(())
|
||||
}
|
||||
|
||||
@@ -1165,7 +1155,7 @@ impl LinuxClient for X11Client {
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
let Some(focused_window) = state.focused_window else {
|
||||
let Some(focused_window) = state.mouse_focused_window else {
|
||||
return;
|
||||
};
|
||||
let current_style = state
|
||||
@@ -1179,13 +1169,10 @@ impl LinuxClient for X11Client {
|
||||
let cursor = match state.cursor_cache.get(&style) {
|
||||
Some(cursor) => *cursor,
|
||||
None => {
|
||||
let Some(cursor) = state
|
||||
let cursor = state
|
||||
.cursor_handle
|
||||
.load_cursor(&state.xcb_connection, &style.to_icon_name())
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
.expect("failed to load cursor");
|
||||
state.cursor_cache.insert(style, cursor);
|
||||
cursor
|
||||
}
|
||||
@@ -1300,7 +1287,7 @@ impl LinuxClient for X11Client {
|
||||
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
let state = self.0.borrow();
|
||||
state.focused_window.and_then(|focused_window| {
|
||||
state.keyboard_focused_window.and_then(|focused_window| {
|
||||
state
|
||||
.windows
|
||||
.get(&focused_window)
|
||||
|
||||
@@ -211,6 +211,7 @@ pub struct Callbacks {
|
||||
request_frame: Option<Box<dyn FnMut()>>,
|
||||
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
|
||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||
hovered_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
moved: Option<Box<dyn FnMut()>>,
|
||||
should_close: Option<Box<dyn FnMut() -> bool>>,
|
||||
@@ -238,6 +239,7 @@ pub struct X11WindowState {
|
||||
maximized_horizontal: bool,
|
||||
hidden: bool,
|
||||
active: bool,
|
||||
hovered: bool,
|
||||
fullscreen: bool,
|
||||
client_side_decorations_supported: bool,
|
||||
decorations: WindowDecorations,
|
||||
@@ -451,6 +453,7 @@ impl X11WindowState {
|
||||
xinput::XIEventMask::MOTION
|
||||
| xinput::XIEventMask::BUTTON_PRESS
|
||||
| xinput::XIEventMask::BUTTON_RELEASE
|
||||
| xinput::XIEventMask::ENTER
|
||||
| xinput::XIEventMask::LEAVE,
|
||||
],
|
||||
}],
|
||||
@@ -507,6 +510,7 @@ impl X11WindowState {
|
||||
atoms: *atoms,
|
||||
input_handler: None,
|
||||
active: false,
|
||||
hovered: false,
|
||||
fullscreen: false,
|
||||
maximized_vertical: false,
|
||||
maximized_horizontal: false,
|
||||
@@ -777,6 +781,15 @@ impl X11WindowStatePtr {
|
||||
state.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
let hovered_window = self
|
||||
.xcb_connection
|
||||
.query_pointer(state.x_root_window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap()
|
||||
.child;
|
||||
self.set_hovered(hovered_window == self.x_window);
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
@@ -912,12 +925,18 @@ impl X11WindowStatePtr {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_focused(&self, focus: bool) {
|
||||
pub fn set_active(&self, focus: bool) {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
|
||||
fun(focus);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_hovered(&self, focus: bool) {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
|
||||
fun(focus);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.appearance = appearance;
|
||||
@@ -1046,6 +1065,10 @@ impl PlatformWindow for X11Window {
|
||||
self.0.state.borrow().active
|
||||
}
|
||||
|
||||
fn is_hovered(&self) -> bool {
|
||||
self.0.state.borrow().hovered
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0
|
||||
.xcb_connection
|
||||
@@ -1162,6 +1185,10 @@ impl PlatformWindow for X11Window {
|
||||
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.callbacks.borrow_mut().resize = Some(callback);
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ impl Platform for MacPlatform {
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
@@ -632,7 +632,7 @@ impl Platform for MacPlatform {
|
||||
};
|
||||
|
||||
if let Some(done_tx) = done_tx.take() {
|
||||
let _ = done_tx.send(Ok(result));
|
||||
let _ = done_tx.send(result);
|
||||
}
|
||||
});
|
||||
let block = block.copy();
|
||||
@@ -643,7 +643,7 @@ impl Platform for MacPlatform {
|
||||
done_rx
|
||||
}
|
||||
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
|
||||
let directory = directory.to_owned();
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
self.foreground_executor()
|
||||
@@ -665,7 +665,7 @@ impl Platform for MacPlatform {
|
||||
}
|
||||
|
||||
if let Some(done_tx) = done_tx.take() {
|
||||
let _ = done_tx.send(Ok(result));
|
||||
let _ = done_tx.send(result);
|
||||
}
|
||||
});
|
||||
let block = block.copy();
|
||||
|
||||
@@ -452,7 +452,7 @@ impl MacWindowState {
|
||||
let bounds = Bounds::new(
|
||||
point(
|
||||
px((window_frame.origin.x - screen_frame.origin.x) as f32),
|
||||
px((window_frame.origin.y - screen_frame.origin.y) as f32),
|
||||
px((window_frame.origin.y + screen_frame.origin.y) as f32),
|
||||
),
|
||||
size(
|
||||
px(window_frame.size.width as f32),
|
||||
@@ -546,7 +546,7 @@ impl MacWindow {
|
||||
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||
for i in 0..count {
|
||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||
let frame = NSScreen::visibleFrame(screen);
|
||||
let frame = NSScreen::frame(screen);
|
||||
let display_id = display_id_for_screen(screen);
|
||||
if display_id == display.0 {
|
||||
screen_frame = Some(frame);
|
||||
@@ -557,7 +557,7 @@ impl MacWindow {
|
||||
let screen_frame = screen_frame.unwrap_or_else(|| {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
target_screen = screen;
|
||||
NSScreen::visibleFrame(screen)
|
||||
NSScreen::frame(screen)
|
||||
});
|
||||
|
||||
let window_rect = NSRect::new(
|
||||
@@ -940,6 +940,11 @@ impl PlatformWindow for MacWindow {
|
||||
unsafe { self.0.lock().native_window.isKeyWindow() == YES }
|
||||
}
|
||||
|
||||
// is_hovered is unused on macOS. See WindowContext::is_window_hovered.
|
||||
fn is_hovered(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
unsafe {
|
||||
let app = NSApplication::sharedApplication(nil);
|
||||
@@ -1061,6 +1066,8 @@ impl PlatformWindow for MacWindow {
|
||||
self.0.as_ref().lock().activate_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.as_ref().lock().resize_callback = Some(callback);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ pub(crate) struct TestPlatform {
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TestPrompts {
|
||||
multiple_choice: VecDeque<oneshot::Sender<usize>>,
|
||||
new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
|
||||
new_path: VecDeque<(PathBuf, oneshot::Sender<Option<PathBuf>>)>,
|
||||
}
|
||||
|
||||
impl TestPlatform {
|
||||
@@ -80,7 +80,7 @@ impl TestPlatform {
|
||||
.new_path
|
||||
.pop_front()
|
||||
.expect("no pending new path prompt");
|
||||
tx.send(Ok(select_path(&path))).ok();
|
||||
tx.send(select_path(&path)).ok();
|
||||
}
|
||||
|
||||
pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) {
|
||||
@@ -216,14 +216,14 @@ impl Platform for TestPlatform {
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
_options: crate::PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
|
||||
) -> oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn prompt_for_new_path(
|
||||
&self,
|
||||
directory: &std::path::Path,
|
||||
) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
|
||||
) -> oneshot::Receiver<Option<std::path::PathBuf>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.prompts
|
||||
.borrow_mut()
|
||||
|
||||
@@ -23,6 +23,7 @@ pub(crate) struct TestWindowState {
|
||||
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
|
||||
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
|
||||
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||
hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
moved_callback: Option<Box<dyn FnMut()>>,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
@@ -66,6 +67,7 @@ impl TestWindow {
|
||||
should_close_handler: None,
|
||||
input_callback: None,
|
||||
active_status_change_callback: None,
|
||||
hover_status_change_callback: None,
|
||||
resize_callback: None,
|
||||
moved_callback: None,
|
||||
input_handler: None,
|
||||
@@ -182,6 +184,10 @@ impl PlatformWindow for TestWindow {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_hovered(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0.lock().title = Some(title.to_owned());
|
||||
}
|
||||
@@ -225,6 +231,10 @@ impl PlatformWindow for TestWindow {
|
||||
self.0.lock().active_status_change_callback = Some(callback)
|
||||
}
|
||||
|
||||
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.lock().hover_status_change_callback = Some(callback)
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.lock().resize_callback = Some(callback)
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ struct DirectWriteComponent {
|
||||
|
||||
struct GlyphRenderContext {
|
||||
params: IDWriteRenderingParams3,
|
||||
normal_dc_target: ID2D1DeviceContext4,
|
||||
emoji_dc_target: ID2D1DeviceContext4,
|
||||
dc_target: ID2D1DeviceContext4,
|
||||
}
|
||||
|
||||
// All use of the IUnknown methods should be "thread-safe".
|
||||
@@ -128,16 +127,7 @@ impl GlyphRenderContext {
|
||||
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC,
|
||||
grid_fit_mode,
|
||||
)?;
|
||||
let normal_dc_target = {
|
||||
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
|
||||
DXGI_FORMAT_A8_UNORM,
|
||||
D2D1_ALPHA_MODE_STRAIGHT,
|
||||
))?;
|
||||
let target = target.cast::<ID2D1DeviceContext4>()?;
|
||||
target.SetTextRenderingParams(¶ms);
|
||||
target
|
||||
};
|
||||
let emoji_dc_target = {
|
||||
let dc_target = {
|
||||
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
D2D1_ALPHA_MODE_PREMULTIPLIED,
|
||||
@@ -147,11 +137,7 @@ impl GlyphRenderContext {
|
||||
target
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
params,
|
||||
normal_dc_target,
|
||||
emoji_dc_target,
|
||||
})
|
||||
Ok(Self { params, dc_target })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,11 +557,7 @@ impl DirectWriteState {
|
||||
}
|
||||
|
||||
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let render_target = if params.is_emoji {
|
||||
&self.components.render_context.emoji_dc_target
|
||||
} else {
|
||||
&self.components.render_context.normal_dc_target
|
||||
};
|
||||
let render_target = &self.components.render_context.dc_target;
|
||||
unsafe {
|
||||
render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
|
||||
render_target.SetDpi(96.0 * params.scale_factor, 96.0 * params.scale_factor);
|
||||
|
||||
@@ -316,10 +316,7 @@ impl Platform for WindowsPlatform {
|
||||
self.state.borrow_mut().callbacks.open_urls = Some(callback);
|
||||
}
|
||||
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
options: PathPromptOptions,
|
||||
) -> Receiver<Result<Option<Vec<PathBuf>>>> {
|
||||
fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
self.foreground_executor()
|
||||
@@ -358,7 +355,7 @@ impl Platform for WindowsPlatform {
|
||||
if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
|
||||
// user canceled error
|
||||
if let Some(tx) = tx.take() {
|
||||
tx.send(Ok(None)).unwrap();
|
||||
tx.send(None).unwrap();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -377,10 +374,10 @@ impl Platform for WindowsPlatform {
|
||||
}
|
||||
|
||||
if let Some(tx) = tx.take() {
|
||||
if paths.is_empty() {
|
||||
tx.send(Ok(None)).unwrap();
|
||||
if paths.len() == 0 {
|
||||
tx.send(None).unwrap();
|
||||
} else {
|
||||
tx.send(Ok(Some(paths))).unwrap();
|
||||
tx.send(Some(paths)).unwrap();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -389,27 +386,27 @@ impl Platform for WindowsPlatform {
|
||||
rx
|
||||
}
|
||||
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> {
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
|
||||
let directory = directory.to_owned();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let Ok(dialog) = show_savefile_dialog(directory) else {
|
||||
let _ = tx.send(Ok(None));
|
||||
let _ = tx.send(None);
|
||||
return;
|
||||
};
|
||||
let Ok(_) = dialog.Show(None) else {
|
||||
let _ = tx.send(Ok(None)); // user cancel
|
||||
let _ = tx.send(None); // user cancel
|
||||
return;
|
||||
};
|
||||
if let Ok(shell_item) = dialog.GetResult() {
|
||||
if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
|
||||
let _ = tx.send(Ok(Some(PathBuf::from(file.to_string().unwrap()))));
|
||||
let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let _ = tx.send(Ok(None));
|
||||
let _ = tx.send(None);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -503,6 +503,11 @@ impl PlatformWindow for WindowsWindow {
|
||||
self.0.hwnd == unsafe { GetActiveWindow() }
|
||||
}
|
||||
|
||||
// is_hovered is unused on Windows. See WindowContext::is_window_hovered.
|
||||
fn is_hovered(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
|
||||
.inspect_err(|e| log::error!("Set title failed: {e}"))
|
||||
@@ -604,6 +609,8 @@ impl PlatformWindow for WindowsWindow {
|
||||
self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.state.borrow_mut().callbacks.resize = Some(callback);
|
||||
}
|
||||
|
||||
@@ -658,26 +658,6 @@ impl Hash for RenderGlyphParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters for rendering an emoji glyph.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RenderEmojiParams {
|
||||
pub(crate) font_id: FontId,
|
||||
pub(crate) glyph_id: GlyphId,
|
||||
pub(crate) font_size: Pixels,
|
||||
pub(crate) scale_factor: f32,
|
||||
}
|
||||
|
||||
impl Eq for RenderEmojiParams {}
|
||||
|
||||
impl Hash for RenderEmojiParams {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.font_id.0.hash(state);
|
||||
self.glyph_id.0.hash(state);
|
||||
self.font_size.0.to_bits().hash(state);
|
||||
self.scale_factor.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration details for identifying a specific font.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Font {
|
||||
|
||||
@@ -49,9 +49,17 @@ impl LineWrapper {
|
||||
continue;
|
||||
}
|
||||
|
||||
if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
if Self::is_word_char(c) {
|
||||
if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
}
|
||||
} else {
|
||||
// CJK may not be space separated, e.g.: `Hello world你好世界`
|
||||
if c != ' ' && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
}
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
@@ -90,6 +98,31 @@ impl LineWrapper {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_word_char(c: char) -> bool {
|
||||
// ASCII alphanumeric characters, for English, numbers: `Hello123`, etc.
|
||||
c.is_ascii_alphanumeric() ||
|
||||
// Latin script in Unicode for French, German, Spanish, etc.
|
||||
// Latin-1 Supplement
|
||||
// https://en.wikipedia.org/wiki/Latin-1_Supplement
|
||||
matches!(c, '\u{00C0}'..='\u{00FF}') ||
|
||||
// Latin Extended-A
|
||||
// https://en.wikipedia.org/wiki/Latin_Extended-A
|
||||
matches!(c, '\u{0100}'..='\u{017F}') ||
|
||||
// Latin Extended-B
|
||||
// https://en.wikipedia.org/wiki/Latin_Extended-B
|
||||
matches!(c, '\u{0180}'..='\u{024F}') ||
|
||||
// Cyrillic for Russian, Ukrainian, etc.
|
||||
// https://en.wikipedia.org/wiki/Cyrillic_script_in_Unicode
|
||||
matches!(c, '\u{0400}'..='\u{04FF}') ||
|
||||
// Some other known special characters that should be treated as word characters,
|
||||
// e.g. `a-b`, `var_name`, `I'm`, '@mention`, `#hashtag`, `100%`, `3.1415`, `2^3`, `a~b`, etc.
|
||||
matches!(c, '-' | '_' | '.' | '\'' | '$' | '%' | '@' | '#' | '^' | '~') ||
|
||||
// Characters that used in URL, e.g. `https://github.com/zed-industries/zed?a=1&b=2` for better wrapping a long URL.
|
||||
matches!(c, '/' | ':' | '?' | '&' | '=') ||
|
||||
// `⋯` character is special used in Zed, to keep this at the end of the line.
|
||||
matches!(c, '⋯')
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn width_for_char(&mut self, c: char) -> Pixels {
|
||||
if (c as u32) < 128 {
|
||||
@@ -219,6 +252,59 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_word_char() {
|
||||
#[track_caller]
|
||||
fn assert_word(word: &str) {
|
||||
for c in word.chars() {
|
||||
assert!(LineWrapper::is_word_char(c), "assertion failed for '{}'", c);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_not_word(word: &str) {
|
||||
let found = word.chars().any(|c| !LineWrapper::is_word_char(c));
|
||||
assert!(found, "assertion failed for '{}'", word);
|
||||
}
|
||||
|
||||
assert_word("Hello123");
|
||||
assert_word("non-English");
|
||||
assert_word("var_name");
|
||||
assert_word("123456");
|
||||
assert_word("3.1415");
|
||||
assert_word("10^2");
|
||||
assert_word("1~2");
|
||||
assert_word("100%");
|
||||
assert_word("@mention");
|
||||
assert_word("#hashtag");
|
||||
assert_word("$variable");
|
||||
assert_word("more⋯");
|
||||
|
||||
// Space
|
||||
assert_not_word("foo bar");
|
||||
|
||||
// URL case
|
||||
assert_word("https://github.com/zed-industries/zed/");
|
||||
assert_word("github.com");
|
||||
assert_word("a=1&b=2");
|
||||
|
||||
// Latin-1 Supplement
|
||||
assert_word("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ");
|
||||
// Latin Extended-A
|
||||
assert_word("ĀāĂ㥹ĆćĈĉĊċČčĎď");
|
||||
// Latin Extended-B
|
||||
assert_word("ƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏ");
|
||||
// Cyrillic
|
||||
assert_word("АБВГДЕЖЗИЙКЛМНОП");
|
||||
|
||||
// non-word characters
|
||||
assert_not_word("你好");
|
||||
assert_not_word("안녕하세요");
|
||||
assert_not_word("こんにちは");
|
||||
assert_not_word("😀😁😂");
|
||||
assert_not_word("()[]{}<>");
|
||||
}
|
||||
|
||||
// For compatibility with the test macro
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate as gpui;
|
||||
|
||||
@@ -541,6 +541,7 @@ pub struct Window {
|
||||
appearance: WindowAppearance,
|
||||
appearance_observers: SubscriberSet<(), AnyObserver>,
|
||||
active: Rc<Cell<bool>>,
|
||||
hovered: Rc<Cell<bool>>,
|
||||
pub(crate) dirty: Rc<Cell<bool>>,
|
||||
pub(crate) needs_present: Rc<Cell<bool>>,
|
||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||
@@ -672,6 +673,7 @@ impl Window {
|
||||
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
|
||||
let dirty = Rc::new(Cell::new(true));
|
||||
let active = Rc::new(Cell::new(platform_window.is_active()));
|
||||
let hovered = Rc::new(Cell::new(platform_window.is_hovered()));
|
||||
let needs_present = Rc::new(Cell::new(false));
|
||||
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
|
||||
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
|
||||
@@ -778,7 +780,17 @@ impl Window {
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
|
||||
platform_window.on_hover_status_change(Box::new({
|
||||
let mut cx = cx.to_async();
|
||||
move |active| {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.window.hovered.set(active);
|
||||
cx.refresh();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
platform_window.on_input({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move |event| {
|
||||
@@ -829,6 +841,7 @@ impl Window {
|
||||
appearance,
|
||||
appearance_observers: SubscriberSet::new(),
|
||||
active,
|
||||
hovered,
|
||||
dirty,
|
||||
needs_present,
|
||||
last_input_timestamp,
|
||||
@@ -1222,6 +1235,17 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.active.get()
|
||||
}
|
||||
|
||||
/// Returns whether this window is considered to be the window
|
||||
/// that currently owns the mouse cursor.
|
||||
/// On mac, this is equivalent to `is_window_active`.
|
||||
pub fn is_window_hovered(&self) -> bool {
|
||||
if cfg!(target_os = "linux") {
|
||||
self.window.hovered.get()
|
||||
} else {
|
||||
self.is_window_active()
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle zoom on the window.
|
||||
pub fn zoom_window(&self) {
|
||||
self.window.platform_window.zoom();
|
||||
@@ -2980,7 +3004,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
fn reset_cursor_style(&self) {
|
||||
// Set the cursor only if we're the active window.
|
||||
if self.is_window_active() {
|
||||
if self.is_window_hovered() {
|
||||
let style = self
|
||||
.window
|
||||
.rendered_frame
|
||||
|
||||
@@ -16,4 +16,4 @@ doctest = false
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.66"
|
||||
quote = "1.0.9"
|
||||
syn = { version = "1.0.72", features = ["full"] }
|
||||
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
|
||||
|
||||
@@ -1903,6 +1903,10 @@ impl Buffer {
|
||||
self.deferred_ops.insert(deferred_ops);
|
||||
}
|
||||
|
||||
pub fn has_deferred_ops(&self) -> bool {
|
||||
!self.deferred_ops.is_empty() || self.text.has_deferred_ops()
|
||||
}
|
||||
|
||||
fn can_apply_op(&self, operation: &Operation) -> bool {
|
||||
match operation {
|
||||
Operation::Buffer(_) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Handles conversions of `language` items to and from the [`rpc`] protocol.
|
||||
|
||||
use crate::{diagnostic_set::DiagnosticEntry, CursorShape, Diagnostic};
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use clock::ReplicaId;
|
||||
use lsp::{DiagnosticSeverity, LanguageServerId};
|
||||
use rpc::proto;
|
||||
@@ -231,6 +231,21 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_anchor_range(range: Range<Anchor>) -> proto::AnchorRange {
|
||||
proto::AnchorRange {
|
||||
start: Some(serialize_anchor(&range.start)),
|
||||
end: Some(serialize_anchor(&range.end)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes an [`Range<Anchor>`] from the RPC representation.
|
||||
pub fn deserialize_anchor_range(range: proto::AnchorRange) -> Result<Range<Anchor>> {
|
||||
Ok(
|
||||
deserialize_anchor(range.start.context("invalid anchor")?).context("invalid anchor")?
|
||||
..deserialize_anchor(range.end.context("invalid anchor")?).context("invalid anchor")?,
|
||||
)
|
||||
}
|
||||
|
||||
// This behavior is currently copied in the collab database, for snapshotting channel notes
|
||||
/// Deserializes an [`crate::Operation`] from the RPC representation.
|
||||
pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operation> {
|
||||
|
||||
@@ -1207,7 +1207,7 @@ fn get_injections(
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
depth: usize,
|
||||
changed_ranges: &[Range<usize>],
|
||||
combined_injection_ranges: &mut HashMap<Arc<Language>, Vec<tree_sitter::Range>>,
|
||||
combined_injection_ranges: &mut HashMap<LanguageId, (Arc<Language>, Vec<tree_sitter::Range>)>,
|
||||
queue: &mut BinaryHeap<ParseStep>,
|
||||
) {
|
||||
let mut query_cursor = QueryCursorHandle::new();
|
||||
@@ -1223,7 +1223,7 @@ fn get_injections(
|
||||
.now_or_never()
|
||||
.and_then(|language| language.ok())
|
||||
{
|
||||
combined_injection_ranges.insert(language, Vec::new());
|
||||
combined_injection_ranges.insert(language.id, (language, Vec::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1276,8 +1276,9 @@ fn get_injections(
|
||||
if let Some(language) = language {
|
||||
if combined {
|
||||
combined_injection_ranges
|
||||
.entry(language.clone())
|
||||
.or_default()
|
||||
.entry(language.id)
|
||||
.or_insert_with(|| (language.clone(), vec![]))
|
||||
.1
|
||||
.extend(content_ranges);
|
||||
} else {
|
||||
queue.push(ParseStep {
|
||||
@@ -1303,7 +1304,7 @@ fn get_injections(
|
||||
}
|
||||
}
|
||||
|
||||
for (language, mut included_ranges) in combined_injection_ranges.drain() {
|
||||
for (_, (language, mut included_ranges)) in combined_injection_ranges.drain() {
|
||||
included_ranges.sort_unstable_by(|a, b| {
|
||||
Ord::cmp(&a.start_byte, &b.start_byte).then_with(|| Ord::cmp(&a.end_byte, &b.end_byte))
|
||||
});
|
||||
|
||||
@@ -518,7 +518,7 @@ impl ContextProvider for GoContextProvider {
|
||||
"test".into(),
|
||||
GO_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
"-run".into(),
|
||||
format!("^{}\\$", VariableName::Symbol.template_value(),),
|
||||
format!("'^{}$'", VariableName::Symbol.template_value(),),
|
||||
],
|
||||
tags: vec!["go-test".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
@@ -549,7 +549,7 @@ impl ContextProvider for GoContextProvider {
|
||||
"-v".into(),
|
||||
"-run".into(),
|
||||
format!(
|
||||
"^{}\\$/^{}\\$",
|
||||
"'^{}$/^{}$'",
|
||||
VariableName::Symbol.template_value(),
|
||||
GO_SUBTEST_NAME_TASK_VARIABLE.template_value(),
|
||||
),
|
||||
@@ -570,7 +570,7 @@ impl ContextProvider for GoContextProvider {
|
||||
"-benchmem".into(),
|
||||
"-run=^$".into(),
|
||||
"-bench".into(),
|
||||
format!("^{}\\$", VariableName::Symbol.template_value()),
|
||||
format!("'^{}$'", VariableName::Symbol.template_value()),
|
||||
],
|
||||
tags: vec!["go-benchmark".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
|
||||
@@ -57,7 +57,7 @@ gpui = { workspace = true, features = ["test-support"] }
|
||||
live_kit_server.workspace = true
|
||||
nanoid.workspace = true
|
||||
sha2.workspace = true
|
||||
simplelog.workspace = true
|
||||
simplelog = "0.9"
|
||||
|
||||
[build-dependencies]
|
||||
serde.workspace = true
|
||||
|
||||
@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
|
||||
use collections::HashMap;
|
||||
use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use parking_lot::Mutex;
|
||||
use postage::{barrier, prelude::Stream};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde_json::{json, value::RawValue, Value};
|
||||
@@ -24,7 +24,6 @@ use std::{
|
||||
ffi::OsString,
|
||||
fmt,
|
||||
io::Write,
|
||||
ops::DerefMut,
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
@@ -70,7 +69,7 @@ pub struct LanguageServer {
|
||||
next_id: AtomicI32,
|
||||
outbound_tx: channel::Sender<String>,
|
||||
name: Arc<str>,
|
||||
capabilities: RwLock<ServerCapabilities>,
|
||||
capabilities: ServerCapabilities,
|
||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
||||
@@ -641,19 +640,12 @@ impl LanguageServer {
|
||||
..Default::default()
|
||||
}),
|
||||
formatting: Some(DynamicRegistrationClientCapabilities {
|
||||
dynamic_registration: Some(true),
|
||||
}),
|
||||
range_formatting: Some(DynamicRegistrationClientCapabilities {
|
||||
dynamic_registration: Some(true),
|
||||
dynamic_registration: None,
|
||||
}),
|
||||
on_type_formatting: Some(DynamicRegistrationClientCapabilities {
|
||||
dynamic_registration: Some(true),
|
||||
dynamic_registration: None,
|
||||
}),
|
||||
synchronization: Some(TextDocumentSyncClientCapabilities {
|
||||
did_save: Some(true),
|
||||
..TextDocumentSyncClientCapabilities::default()
|
||||
}),
|
||||
..TextDocumentClientCapabilities::default()
|
||||
..Default::default()
|
||||
}),
|
||||
experimental: Some(json!({
|
||||
"serverStatusNotification": true,
|
||||
@@ -684,7 +676,7 @@ impl LanguageServer {
|
||||
if let Some(info) = response.server_info {
|
||||
self.name = info.name.into();
|
||||
}
|
||||
self.capabilities = RwLock::new(response.capabilities);
|
||||
self.capabilities = response.capabilities;
|
||||
|
||||
self.notify::<notification::Initialized>(InitializedParams {})?;
|
||||
Ok(Arc::new(self))
|
||||
@@ -899,12 +891,8 @@ impl LanguageServer {
|
||||
}
|
||||
|
||||
/// Get the reported capabilities of the running language server.
|
||||
pub fn capabilities(&self) -> ServerCapabilities {
|
||||
self.capabilities.read().clone()
|
||||
}
|
||||
|
||||
pub fn update_capabilities(&self, update: impl FnOnce(&mut ServerCapabilities)) {
|
||||
update(self.capabilities.write().deref_mut());
|
||||
pub fn capabilities(&self) -> &ServerCapabilities {
|
||||
&self.capabilities
|
||||
}
|
||||
|
||||
/// Get the id of the running language server.
|
||||
|
||||
@@ -1622,7 +1622,11 @@ impl OutlinePanel {
|
||||
ExcerptOutlines::Invalidated(_) => ExcerptOutlines::NotFetched,
|
||||
ExcerptOutlines::NotFetched => ExcerptOutlines::NotFetched,
|
||||
},
|
||||
None => ExcerptOutlines::NotFetched,
|
||||
None => {
|
||||
new_collapsed_entries
|
||||
.insert(CollapsedEntry::Excerpt(buffer_id, excerpt_id));
|
||||
ExcerptOutlines::NotFetched
|
||||
}
|
||||
};
|
||||
new_excerpts.entry(buffer_id).or_default().insert(
|
||||
excerpt_id,
|
||||
@@ -1670,6 +1674,11 @@ impl OutlinePanel {
|
||||
.insert(CollapsedEntry::ExternalFile(buffer_id));
|
||||
}
|
||||
}
|
||||
|
||||
for excerpt_id in &excerpts {
|
||||
new_collapsed_entries
|
||||
.insert(CollapsedEntry::Excerpt(buffer_id, *excerpt_id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(worktree) = worktree {
|
||||
|
||||
@@ -84,7 +84,7 @@ impl Prettier {
|
||||
path_to_check.pop();
|
||||
}
|
||||
|
||||
let mut closest_package_json_path = None;
|
||||
let mut project_path_with_prettier_dependency = None;
|
||||
loop {
|
||||
if installed_prettiers.contains(&path_to_check) {
|
||||
log::debug!("Found prettier path {path_to_check:?} in installed prettiers");
|
||||
@@ -92,44 +92,61 @@ impl Prettier {
|
||||
} else if let Some(package_json_contents) =
|
||||
read_package_json(fs, &path_to_check).await?
|
||||
{
|
||||
if has_prettier_in_node_modules(fs, &path_to_check).await? {
|
||||
log::debug!("Found prettier path {path_to_check:?} in the node_modules");
|
||||
return Ok(ControlFlow::Continue(Some(path_to_check)));
|
||||
} else {
|
||||
match &closest_package_json_path {
|
||||
None => closest_package_json_path = Some(path_to_check.clone()),
|
||||
Some(closest_package_json_path) => {
|
||||
match package_json_contents.get("workspaces") {
|
||||
Some(serde_json::Value::Array(workspaces)) => {
|
||||
let subproject_path = closest_package_json_path.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
|
||||
if workspaces.iter().filter_map(|value| {
|
||||
if let serde_json::Value::String(s) = value {
|
||||
Some(s.clone())
|
||||
} else {
|
||||
log::warn!("Skipping non-string 'workspaces' value: {value:?}");
|
||||
None
|
||||
}
|
||||
}).any(|workspace_definition| {
|
||||
workspace_definition == subproject_path.to_string_lossy() || PathMatcher::new(&[workspace_definition]).ok().map_or(false, |path_matcher| path_matcher.is_match(subproject_path))
|
||||
}) {
|
||||
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Path {path_to_check:?} is the workspace root for project in {closest_package_json_path:?}, but it has no prettier installed");
|
||||
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {closest_package_json_path:?}");
|
||||
return Ok(ControlFlow::Continue(Some(path_to_check)));
|
||||
} else {
|
||||
log::warn!("Skipping path {path_to_check:?} workspace root with workspaces {workspaces:?} that have no prettier installed");
|
||||
}
|
||||
},
|
||||
Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
|
||||
None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
|
||||
}
|
||||
}
|
||||
if has_prettier_in_package_json(&package_json_contents) {
|
||||
if has_prettier_in_node_modules(fs, &path_to_check).await? {
|
||||
log::debug!("Found prettier path {path_to_check:?} in both package.json and node_modules");
|
||||
return Ok(ControlFlow::Continue(Some(path_to_check)));
|
||||
} else if project_path_with_prettier_dependency.is_none() {
|
||||
project_path_with_prettier_dependency = Some(path_to_check.clone());
|
||||
}
|
||||
} else {
|
||||
match package_json_contents.get("workspaces") {
|
||||
Some(serde_json::Value::Array(workspaces)) => {
|
||||
match &project_path_with_prettier_dependency {
|
||||
Some(project_path_with_prettier_dependency) => {
|
||||
let subproject_path = project_path_with_prettier_dependency.strip_prefix(&path_to_check).expect("traversing path parents, should be able to strip prefix");
|
||||
if workspaces.iter().filter_map(|value| {
|
||||
if let serde_json::Value::String(s) = value {
|
||||
Some(s.clone())
|
||||
} else {
|
||||
log::warn!("Skipping non-string 'workspaces' value: {value:?}");
|
||||
None
|
||||
}
|
||||
}).any(|workspace_definition| {
|
||||
if let Some(path_matcher) = PathMatcher::new(&[workspace_definition.clone()]).ok() {
|
||||
path_matcher.is_match(subproject_path)
|
||||
} else {
|
||||
workspace_definition == subproject_path.to_string_lossy()
|
||||
}
|
||||
}) {
|
||||
anyhow::ensure!(has_prettier_in_node_modules(fs, &path_to_check).await?, "Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}, but it's not installed into workspace root's node_modules");
|
||||
log::info!("Found prettier path {path_to_check:?} in the workspace root for project in {project_path_with_prettier_dependency:?}");
|
||||
return Ok(ControlFlow::Continue(Some(path_to_check)));
|
||||
} else {
|
||||
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but is not included in its package.json workspaces {workspaces:?}");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("Skipping path {path_to_check:?} that has prettier in its 'node_modules' subdirectory, but has no prettier in its package.json");
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(unknown) => log::error!("Failed to parse workspaces for {path_to_check:?} from package.json, got {unknown:?}. Skipping."),
|
||||
None => log::warn!("Skipping path {path_to_check:?} that has no prettier dependency and no workspaces section in its package.json"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !path_to_check.pop() {
|
||||
log::debug!("Found no prettier in ancestors of {locate_from:?}");
|
||||
return Ok(ControlFlow::Continue(None));
|
||||
match project_path_with_prettier_dependency {
|
||||
Some(closest_prettier_discovered) => {
|
||||
anyhow::bail!("No prettier found in node_modules for ancestors of {locate_from:?}, but discovered prettier package.json dependency in {closest_prettier_discovered:?}")
|
||||
}
|
||||
None => {
|
||||
log::debug!("Found no prettier in ancestors of {locate_from:?}");
|
||||
return Ok(ControlFlow::Continue(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,6 +448,22 @@ async fn read_package_json(
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn has_prettier_in_package_json(
|
||||
package_json_contents: &HashMap<String, serde_json::Value>,
|
||||
) -> bool {
|
||||
if let Some(serde_json::Value::Object(o)) = package_json_contents.get("dependencies") {
|
||||
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(serde_json::Value::Object(o)) = package_json_contents.get("devDependencies") {
|
||||
if o.contains_key(PRETTIER_PACKAGE_NAME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
enum Format {}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
@@ -515,36 +548,40 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/.config/zed/settings.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
ControlFlow::Continue(None),
|
||||
"Should find no prettier for path hierarchy without it"
|
||||
assert!(
|
||||
matches!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/.config/zed/settings.json"),
|
||||
)
|
||||
.await,
|
||||
Ok(ControlFlow::Continue(None))
|
||||
),
|
||||
"Should successfully find no prettier for path hierarchy without it"
|
||||
);
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/project/src/index.js")
|
||||
)
|
||||
.await.unwrap(),
|
||||
ControlFlow::Continue(Some(PathBuf::from("/root/work/project"))),
|
||||
"Should successfully find a prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
|
||||
assert!(
|
||||
matches!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/project/src/index.js")
|
||||
)
|
||||
.await,
|
||||
Ok(ControlFlow::Continue(None))
|
||||
),
|
||||
"Should successfully find no prettier for path hierarchy that has node_modules with prettier, but no package.json mentions of it"
|
||||
);
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/project/node_modules/expect/build/print.js")
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
ControlFlow::Break(()),
|
||||
assert!(
|
||||
matches!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/project/node_modules/expect/build/print.js")
|
||||
)
|
||||
.await,
|
||||
Ok(ControlFlow::Break(()))
|
||||
),
|
||||
"Should not format files inside node_modules/"
|
||||
);
|
||||
}
|
||||
@@ -654,17 +691,18 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/web_blog/pages/[slug].tsx")
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
ControlFlow::Continue(None),
|
||||
"Should find no prettier when node_modules don't have it"
|
||||
);
|
||||
match Prettier::locate_prettier_installation(
|
||||
fs.as_ref(),
|
||||
&HashSet::default(),
|
||||
Path::new("/root/work/web_blog/pages/[slug].tsx")
|
||||
)
|
||||
.await {
|
||||
Ok(path) => panic!("Expected to fail for prettier in package.json but not in node_modules found, but got path {path:?}"),
|
||||
Err(e) => {
|
||||
let message = e.to_string();
|
||||
assert!(message.contains("/root/work/web_blog"), "Error message should mention which project had prettier defined");
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Prettier::locate_prettier_installation(
|
||||
|
||||
@@ -2471,7 +2471,7 @@ impl LspCommand for InlayHints {
|
||||
lsp_adapter.name.0.as_ref() == "typescript-language-server";
|
||||
|
||||
let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
|
||||
let resolve_state = if InlayHints::can_resolve_inlays(&lsp_server.capabilities()) {
|
||||
let resolve_state = if InlayHints::can_resolve_inlays(lsp_server.capabilities()) {
|
||||
ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
|
||||
} else {
|
||||
ResolveState::Resolved
|
||||
|
||||
@@ -355,6 +355,9 @@ pub enum Event {
|
||||
},
|
||||
CollaboratorJoined(proto::PeerId),
|
||||
CollaboratorLeft(proto::PeerId),
|
||||
HostReshared,
|
||||
Reshared,
|
||||
Rejoined,
|
||||
RefreshInlayHints,
|
||||
RevealInProjectPanel(ProjectEntryId),
|
||||
SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
|
||||
@@ -1716,6 +1719,7 @@ impl Project {
|
||||
self.shared_buffers.clear();
|
||||
self.set_collaborators_from_proto(message.collaborators, cx)?;
|
||||
self.metadata_changed(cx);
|
||||
cx.emit(Event::Reshared);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1753,6 +1757,7 @@ impl Project {
|
||||
.collect();
|
||||
self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
|
||||
.unwrap();
|
||||
cx.emit(Event::Rejoined);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1805,9 +1810,11 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
self.client.send(proto::UnshareProject {
|
||||
project_id: remote_id,
|
||||
})?;
|
||||
self.client
|
||||
.send(proto::UnshareProject {
|
||||
project_id: remote_id,
|
||||
})
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -2853,21 +2860,15 @@ impl Project {
|
||||
};
|
||||
|
||||
for (_, _, server) in self.language_servers_for_worktree(worktree_id) {
|
||||
if let Some(include_text) = include_text(server.as_ref()) {
|
||||
let text = if include_text {
|
||||
Some(buffer.read(cx).text())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
server
|
||||
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||
lsp::DidSaveTextDocumentParams {
|
||||
text_document: text_document.clone(),
|
||||
text,
|
||||
},
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
let text = include_text(server.as_ref()).then(|| buffer.read(cx).text());
|
||||
server
|
||||
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||
lsp::DidSaveTextDocumentParams {
|
||||
text_document: text_document.clone(),
|
||||
text,
|
||||
},
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
|
||||
for language_server_id in self.language_server_ids_for_buffer(buffer.read(cx), cx) {
|
||||
@@ -3502,7 +3503,7 @@ impl Project {
|
||||
}
|
||||
|
||||
async fn setup_pending_language_server(
|
||||
project: WeakModel<Self>,
|
||||
this: WeakModel<Self>,
|
||||
override_options: Option<serde_json::Value>,
|
||||
pending_server: PendingLanguageServer,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
@@ -3521,7 +3522,7 @@ impl Project {
|
||||
language_server
|
||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||
let adapter = adapter.clone();
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
move |mut params, mut cx| {
|
||||
let adapter = adapter.clone();
|
||||
if let Some(this) = this.upgrade() {
|
||||
@@ -3575,7 +3576,7 @@ impl Project {
|
||||
// to these requests when initializing.
|
||||
language_server
|
||||
.on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
move |params, mut cx| {
|
||||
let this = this.clone();
|
||||
async move {
|
||||
@@ -3596,103 +3597,20 @@ impl Project {
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::RegisterCapability, _, _>({
|
||||
let project = project.clone();
|
||||
let this = this.clone();
|
||||
move |params, mut cx| {
|
||||
let project = project.clone();
|
||||
let this = this.clone();
|
||||
async move {
|
||||
for reg in params.registrations {
|
||||
match reg.method.as_str() {
|
||||
"workspace/didChangeWatchedFiles" => {
|
||||
if let Some(options) = reg.register_options {
|
||||
let options = serde_json::from_value(options)?;
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project.on_lsp_did_change_watched_files(
|
||||
server_id, ®.id, options, cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
if reg.method == "workspace/didChangeWatchedFiles" {
|
||||
if let Some(options) = reg.register_options {
|
||||
let options = serde_json::from_value(options)?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_did_change_watched_files(
|
||||
server_id, ®.id, options, cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
project.update(&mut cx, |project, _| {
|
||||
if let Some(server) =
|
||||
project.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<
|
||||
lsp::DocumentRangeFormattingOptions,
|
||||
>(
|
||||
options
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let provider = match options {
|
||||
None => OneOf::Left(true),
|
||||
Some(options) => OneOf::Right(options),
|
||||
};
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_range_formatting_provider =
|
||||
Some(provider);
|
||||
})
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
"textDocument/onTypeFormatting" => {
|
||||
project.update(&mut cx, |project, _| {
|
||||
if let Some(server) =
|
||||
project.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<
|
||||
lsp::DocumentOnTypeFormattingOptions,
|
||||
>(
|
||||
options
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
if let Some(options) = options {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities
|
||||
.document_on_type_formatting_provider =
|
||||
Some(options);
|
||||
})
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
"textDocument/formatting" => {
|
||||
project.update(&mut cx, |project, _| {
|
||||
if let Some(server) =
|
||||
project.language_server_for_id(server_id)
|
||||
{
|
||||
let options = reg
|
||||
.register_options
|
||||
.map(|options| {
|
||||
serde_json::from_value::<
|
||||
lsp::DocumentFormattingOptions,
|
||||
>(
|
||||
options
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let provider = match options {
|
||||
None => OneOf::Left(true),
|
||||
Some(options) => OneOf::Right(options),
|
||||
};
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_formatting_provider =
|
||||
Some(provider);
|
||||
})
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
}
|
||||
_ => log::warn!("unhandled capability registration: {reg:?}"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -3703,55 +3621,17 @@ impl Project {
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::UnregisterCapability, _, _>({
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
move |params, mut cx| {
|
||||
let project = this.clone();
|
||||
let this = this.clone();
|
||||
async move {
|
||||
for unreg in params.unregisterations.iter() {
|
||||
match unreg.method.as_str() {
|
||||
"workspace/didChangeWatchedFiles" => {
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project.on_lsp_unregister_did_change_watched_files(
|
||||
server_id, &unreg.id, cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
"textDocument/rangeFormatting" => {
|
||||
project.update(&mut cx, |project, _| {
|
||||
if let Some(server) =
|
||||
project.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_range_formatting_provider =
|
||||
None
|
||||
})
|
||||
}
|
||||
})?;
|
||||
}
|
||||
"textDocument/onTypeFormatting" => {
|
||||
project.update(&mut cx, |project, _| {
|
||||
if let Some(server) =
|
||||
project.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_on_type_formatting_provider =
|
||||
None;
|
||||
})
|
||||
}
|
||||
})?;
|
||||
}
|
||||
"textDocument/formatting" => {
|
||||
project.update(&mut cx, |project, _| {
|
||||
if let Some(server) =
|
||||
project.language_server_for_id(server_id)
|
||||
{
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.document_formatting_provider = None;
|
||||
})
|
||||
}
|
||||
})?;
|
||||
}
|
||||
_ => log::warn!("unhandled capability unregistration: {unreg:?}"),
|
||||
if unreg.method == "workspace/didChangeWatchedFiles" {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_unregister_did_change_watched_files(
|
||||
server_id, &unreg.id, cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -3763,7 +3643,7 @@ impl Project {
|
||||
language_server
|
||||
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
|
||||
let adapter = adapter.clone();
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
move |params, cx| {
|
||||
Self::on_lsp_workspace_edit(
|
||||
this.clone(),
|
||||
@@ -3778,7 +3658,7 @@ impl Project {
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
move |(), mut cx| {
|
||||
let this = this.clone();
|
||||
async move {
|
||||
@@ -3797,7 +3677,7 @@ impl Project {
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
let name = name.to_string();
|
||||
move |params, mut cx| {
|
||||
let this = this.clone();
|
||||
@@ -3836,7 +3716,7 @@ impl Project {
|
||||
|
||||
language_server
|
||||
.on_notification::<ServerStatus, _>({
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
let name = name.to_string();
|
||||
move |params, mut cx| {
|
||||
let this = this.clone();
|
||||
@@ -3879,7 +3759,7 @@ impl Project {
|
||||
.detach();
|
||||
language_server
|
||||
.on_notification::<lsp::notification::ShowMessage, _>({
|
||||
let this = project.clone();
|
||||
let this = this.clone();
|
||||
let name = name.to_string();
|
||||
move |params, mut cx| {
|
||||
let this = this.clone();
|
||||
@@ -3906,7 +3786,7 @@ impl Project {
|
||||
.detach();
|
||||
language_server
|
||||
.on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
|
||||
if let Some(this) = project.upgrade() {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_progress(
|
||||
params,
|
||||
@@ -4226,6 +4106,7 @@ impl Project {
|
||||
return;
|
||||
}
|
||||
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let language_server_lookup_info: HashSet<(Model<Worktree>, Arc<Language>)> = buffers
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
@@ -7153,7 +7034,7 @@ impl Project {
|
||||
} else {
|
||||
return Task::ready(Ok(hint));
|
||||
};
|
||||
if !InlayHints::can_resolve_inlays(&lang_server.capabilities()) {
|
||||
if !InlayHints::can_resolve_inlays(lang_server.capabilities()) {
|
||||
return Task::ready(Ok(hint));
|
||||
}
|
||||
|
||||
@@ -7565,7 +7446,7 @@ impl Project {
|
||||
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
||||
let status = request.status();
|
||||
return cx.spawn(move |this, cx| async move {
|
||||
if !request.check_capabilities(&language_server.capabilities()) {
|
||||
if !request.check_capabilities(language_server.capabilities()) {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
@@ -7659,7 +7540,7 @@ impl Project {
|
||||
let scope = position.and_then(|position| snapshot.language_scope_at(position));
|
||||
let mut response_results = self
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.filter(|(_, server)| server_capabilities_check(&server.capabilities()))
|
||||
.filter(|(_, server)| server_capabilities_check(server.capabilities()))
|
||||
.filter(|(adapter, _)| {
|
||||
scope
|
||||
.as_ref()
|
||||
@@ -8937,6 +8818,7 @@ impl Project {
|
||||
.retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
|
||||
this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
|
||||
.unwrap();
|
||||
cx.emit(Event::HostReshared);
|
||||
}
|
||||
|
||||
cx.emit(Event::CollaboratorUpdated {
|
||||
@@ -11185,6 +11067,7 @@ async fn populate_labels_for_symbols(
|
||||
lsp_adapter: Option<Arc<CachedLspAdapter>>,
|
||||
output: &mut Vec<Symbol>,
|
||||
) {
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let mut symbols_by_language = HashMap::<Option<Arc<Language>>, Vec<CoreSymbol>>::default();
|
||||
|
||||
let mut unknown_path = None;
|
||||
@@ -11840,27 +11723,20 @@ fn is_not_found_error(error: &anyhow::Error) -> bool {
|
||||
.is_some_and(|err| err.kind() == io::ErrorKind::NotFound)
|
||||
}
|
||||
|
||||
fn include_text(server: &lsp::LanguageServer) -> Option<bool> {
|
||||
match server.capabilities().text_document_sync.as_ref()? {
|
||||
lsp::TextDocumentSyncCapability::Kind(kind) => match kind {
|
||||
&lsp::TextDocumentSyncKind::NONE => None,
|
||||
&lsp::TextDocumentSyncKind::FULL => Some(true),
|
||||
&lsp::TextDocumentSyncKind::INCREMENTAL => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
lsp::TextDocumentSyncCapability::Options(options) => match options.save.as_ref()? {
|
||||
lsp::TextDocumentSyncSaveOptions::Supported(supported) => {
|
||||
if *supported {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
lsp::TextDocumentSyncSaveOptions::SaveOptions(save_options) => {
|
||||
Some(save_options.include_text.unwrap_or(false))
|
||||
}
|
||||
},
|
||||
}
|
||||
fn include_text(server: &lsp::LanguageServer) -> bool {
|
||||
server
|
||||
.capabilities()
|
||||
.text_document_sync
|
||||
.as_ref()
|
||||
.and_then(|sync| match sync {
|
||||
lsp::TextDocumentSyncCapability::Kind(_) => None,
|
||||
lsp::TextDocumentSyncCapability::Options(options) => options.save.as_ref(),
|
||||
})
|
||||
.and_then(|save_options| match save_options {
|
||||
lsp::TextDocumentSyncSaveOptions::Supported(_) => None,
|
||||
lsp::TextDocumentSyncSaveOptions::SaveOptions(options) => options.include_text,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
async fn load_shell_environment(dir: &Path) -> Result<HashMap<String, String>> {
|
||||
|
||||
@@ -322,12 +322,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(
|
||||
lsp::TextDocumentSyncOptions {
|
||||
save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -342,12 +336,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
text_document_sync: Some(lsp::TextDocumentSyncCapability::Options(
|
||||
lsp::TextDocumentSyncOptions {
|
||||
save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
||||
@@ -444,11 +444,6 @@ mod test_inventory {
|
||||
|
||||
use super::{task_source_kind_preference, TaskSourceKind, UnboundedSender};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TestTask {
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub(super) fn static_test_source(
|
||||
task_names: impl IntoIterator<Item = String>,
|
||||
updates: UnboundedSender<()>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
fn main() {
|
||||
let mut build = prost_build::Config::new();
|
||||
build
|
||||
.type_attribute(".", "#[derive(serde::Serialize)]")
|
||||
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
|
||||
.compile_protos(&["proto/zed.proto"], &["proto"])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -255,7 +255,14 @@ message Envelope {
|
||||
TaskTemplates task_templates = 206;
|
||||
|
||||
LinkedEditingRange linked_editing_range = 209;
|
||||
LinkedEditingRangeResponse linked_editing_range_response = 210; // current max
|
||||
LinkedEditingRangeResponse linked_editing_range_response = 210;
|
||||
|
||||
AdvertiseContexts advertise_contexts = 211;
|
||||
OpenContext open_context = 212;
|
||||
OpenContextResponse open_context_response = 213;
|
||||
UpdateContext update_context = 214;
|
||||
SynchronizeContexts synchronize_contexts = 215;
|
||||
SynchronizeContextsResponse synchronize_contexts_response = 216; // current max
|
||||
}
|
||||
|
||||
reserved 158 to 161;
|
||||
@@ -2222,3 +2229,117 @@ message TaskSourceKind {
|
||||
string name = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ContextMessageStatus {
|
||||
oneof variant {
|
||||
Done done = 1;
|
||||
Pending pending = 2;
|
||||
Error error = 3;
|
||||
}
|
||||
|
||||
message Done {}
|
||||
|
||||
message Pending {}
|
||||
|
||||
message Error {
|
||||
string message = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ContextMessage {
|
||||
LamportTimestamp id = 1;
|
||||
Anchor start = 2;
|
||||
LanguageModelRole role = 3;
|
||||
ContextMessageStatus status = 4;
|
||||
}
|
||||
|
||||
message SlashCommandOutputSection {
|
||||
AnchorRange range = 1;
|
||||
string icon_name = 2;
|
||||
string label = 3;
|
||||
}
|
||||
|
||||
message ContextOperation {
|
||||
oneof variant {
|
||||
InsertMessage insert_message = 1;
|
||||
UpdateMessage update_message = 2;
|
||||
UpdateSummary update_summary = 3;
|
||||
SlashCommandFinished slash_command_finished = 4;
|
||||
BufferOperation buffer_operation = 5;
|
||||
}
|
||||
|
||||
message InsertMessage {
|
||||
ContextMessage message = 1;
|
||||
repeated VectorClockEntry version = 2;
|
||||
}
|
||||
|
||||
message UpdateMessage {
|
||||
LamportTimestamp message_id = 1;
|
||||
LanguageModelRole role = 2;
|
||||
ContextMessageStatus status = 3;
|
||||
LamportTimestamp timestamp = 4;
|
||||
repeated VectorClockEntry version = 5;
|
||||
}
|
||||
|
||||
message UpdateSummary {
|
||||
string summary = 1;
|
||||
bool done = 2;
|
||||
LamportTimestamp timestamp = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message SlashCommandFinished {
|
||||
LamportTimestamp id = 1;
|
||||
AnchorRange output_range = 2;
|
||||
repeated SlashCommandOutputSection sections = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message BufferOperation {
|
||||
Operation operation = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message Context {
|
||||
repeated ContextOperation operations = 1;
|
||||
}
|
||||
|
||||
message ContextMetadata {
|
||||
string context_id = 1;
|
||||
optional string summary = 2;
|
||||
}
|
||||
|
||||
message AdvertiseContexts {
|
||||
uint64 project_id = 1;
|
||||
repeated ContextMetadata contexts = 2;
|
||||
}
|
||||
|
||||
message OpenContext {
|
||||
uint64 project_id = 1;
|
||||
string context_id = 2;
|
||||
}
|
||||
|
||||
message OpenContextResponse {
|
||||
Context context = 1;
|
||||
}
|
||||
|
||||
message UpdateContext {
|
||||
uint64 project_id = 1;
|
||||
string context_id = 2;
|
||||
ContextOperation operation = 3;
|
||||
}
|
||||
|
||||
message ContextVersion {
|
||||
string context_id = 1;
|
||||
repeated VectorClockEntry context_version = 2;
|
||||
repeated VectorClockEntry buffer_version = 3;
|
||||
}
|
||||
|
||||
message SynchronizeContexts {
|
||||
uint64 project_id = 1;
|
||||
repeated ContextVersion contexts = 2;
|
||||
}
|
||||
|
||||
message SynchronizeContextsResponse {
|
||||
repeated ContextVersion contexts = 1;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ pub use error::*;
|
||||
pub use typed_envelope::*;
|
||||
|
||||
use collections::HashMap;
|
||||
pub use prost::Message;
|
||||
pub use prost::{DecodeError, Message};
|
||||
use serde::Serialize;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::time::Instant;
|
||||
@@ -337,7 +337,13 @@ messages!(
|
||||
(OpenNewBuffer, Foreground),
|
||||
(RestartLanguageServers, Foreground),
|
||||
(LinkedEditingRange, Background),
|
||||
(LinkedEditingRangeResponse, Background)
|
||||
(LinkedEditingRangeResponse, Background),
|
||||
(AdvertiseContexts, Foreground),
|
||||
(OpenContext, Foreground),
|
||||
(OpenContextResponse, Foreground),
|
||||
(UpdateContext, Foreground),
|
||||
(SynchronizeContexts, Foreground),
|
||||
(SynchronizeContextsResponse, Foreground),
|
||||
);
|
||||
|
||||
request_messages!(
|
||||
@@ -449,7 +455,9 @@ request_messages!(
|
||||
(DeleteDevServerProject, Ack),
|
||||
(RegenerateDevServerToken, RegenerateDevServerTokenResponse),
|
||||
(RenameDevServer, Ack),
|
||||
(RestartLanguageServers, Ack)
|
||||
(RestartLanguageServers, Ack),
|
||||
(OpenContext, OpenContextResponse),
|
||||
(SynchronizeContexts, SynchronizeContextsResponse),
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
@@ -511,6 +519,10 @@ entity_messages!(
|
||||
UpdateWorktree,
|
||||
UpdateWorktreeSettings,
|
||||
LspExtExpandMacro,
|
||||
AdvertiseContexts,
|
||||
OpenContext,
|
||||
UpdateContext,
|
||||
SynchronizeContexts,
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
||||
@@ -20,6 +20,7 @@ search.workspace = true
|
||||
settings.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
repl.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -20,8 +20,11 @@ use workspace::{
|
||||
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
};
|
||||
|
||||
mod repl_menu;
|
||||
|
||||
pub struct QuickActionBar {
|
||||
buffer_search_bar: View<BufferSearchBar>,
|
||||
repl_menu: Option<View<ContextMenu>>,
|
||||
toggle_settings_menu: Option<View<ContextMenu>>,
|
||||
toggle_selections_menu: Option<View<ContextMenu>>,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
@@ -40,6 +43,7 @@ impl QuickActionBar {
|
||||
buffer_search_bar,
|
||||
toggle_settings_menu: None,
|
||||
toggle_selections_menu: None,
|
||||
repl_menu: None,
|
||||
active_item: None,
|
||||
_inlay_hints_enabled_subscription: None,
|
||||
workspace: workspace.weak_handle(),
|
||||
@@ -290,9 +294,13 @@ impl Render for QuickActionBar {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
.children(self.render_repl_menu(cx))
|
||||
.children(editor_selections_dropdown)
|
||||
.child(editor_settings_dropdown),
|
||||
)
|
||||
.when_some(self.repl_menu.as_ref(), |el, repl_menu| {
|
||||
el.child(Self::render_menu_overlay(repl_menu))
|
||||
})
|
||||
.when_some(
|
||||
self.toggle_settings_menu.as_ref(),
|
||||
|el, toggle_settings_menu| {
|
||||
|
||||
116
crates/quick_action_bar/src/repl_menu.rs
Normal file
116
crates/quick_action_bar/src/repl_menu.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use gpui::AnyElement;
|
||||
use repl::{
|
||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, Session,
|
||||
SessionSupport,
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, IconWithIndicator, IntoElement, Tooltip};
|
||||
|
||||
use crate::QuickActionBar;
|
||||
|
||||
const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl";
|
||||
|
||||
impl QuickActionBar {
|
||||
pub fn render_repl_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let workspace = self.workspace.upgrade()?.read(cx);
|
||||
|
||||
let (editor, repl_panel) = if let (Some(editor), Some(repl_panel)) =
|
||||
(self.active_editor(), workspace.panel::<RuntimePanel>(cx))
|
||||
{
|
||||
(editor, repl_panel)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let session = repl_panel.update(cx, |repl_panel, cx| {
|
||||
repl_panel.session(editor.downgrade(), cx)
|
||||
});
|
||||
|
||||
let session = match session {
|
||||
SessionSupport::ActiveSession(session) => session.read(cx),
|
||||
SessionSupport::Inactive(spec) => {
|
||||
return self.render_repl_launch_menu(spec, cx);
|
||||
}
|
||||
SessionSupport::RequiresSetup(language) => {
|
||||
return self.render_repl_setup(&language, cx);
|
||||
}
|
||||
SessionSupport::Unsupported => return None,
|
||||
};
|
||||
|
||||
let kernel_name: SharedString = session.kernel_specification.name.clone().into();
|
||||
let kernel_language: SharedString = session
|
||||
.kernel_specification
|
||||
.kernelspec
|
||||
.language
|
||||
.clone()
|
||||
.into();
|
||||
|
||||
let tooltip = |session: &Session| match &session.kernel {
|
||||
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
|
||||
ExecutionState::Idle => {
|
||||
format!("Run code on {} ({})", kernel_name, kernel_language)
|
||||
}
|
||||
ExecutionState::Busy => format!("Interrupt {} ({})", kernel_name, kernel_language),
|
||||
},
|
||||
Kernel::StartingKernel(_) => format!("{} is starting", kernel_name),
|
||||
Kernel::ErroredLaunch(e) => format!("Error with kernel {}: {}", kernel_name, e),
|
||||
Kernel::ShuttingDown => format!("{} is shutting down", kernel_name),
|
||||
Kernel::Shutdown => "Nothing running".to_string(),
|
||||
};
|
||||
|
||||
let tooltip_text: SharedString = SharedString::from(tooltip(&session).clone());
|
||||
|
||||
let button = ButtonLike::new("toggle_repl_icon")
|
||||
.child(
|
||||
IconWithIndicator::new(Icon::new(IconName::Play), Some(session.kernel.dot()))
|
||||
.indicator_border_color(Some(cx.theme().colors().border)),
|
||||
)
|
||||
.size(ButtonSize::Compact)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip_text.clone(), cx))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.into_any_element();
|
||||
|
||||
Some(button)
|
||||
}
|
||||
|
||||
pub fn render_repl_launch_menu(
|
||||
&self,
|
||||
kernel_specification: KernelSpecification,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
let tooltip: SharedString =
|
||||
SharedString::from(format!("Start REPL for {}", kernel_specification.name));
|
||||
|
||||
Some(
|
||||
IconButton::new("toggle_repl_icon", IconName::Play)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_repl_setup(
|
||||
&self,
|
||||
language: &str,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
|
||||
Some(
|
||||
IconButton::new("toggle_repl_icon", IconName::Play)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
||||
.on_click(|_, cx| cx.open_url(ZED_REPL_DOCUMENTATION))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ pub enum Kernel {
|
||||
}
|
||||
|
||||
impl Kernel {
|
||||
pub fn dot(&mut self) -> Indicator {
|
||||
pub fn dot(&self) -> Indicator {
|
||||
match self {
|
||||
Kernel::RunningKernel(kernel) => match kernel.execution_state {
|
||||
ExecutionState::Idle => Indicator::dot().color(Color::Success),
|
||||
|
||||
@@ -11,7 +11,11 @@ mod session;
|
||||
mod stdio;
|
||||
|
||||
pub use jupyter_settings::JupyterSettings;
|
||||
pub use runtime_panel::RuntimePanel;
|
||||
pub use kernels::{Kernel, KernelSpecification};
|
||||
pub use runtime_panel::Run;
|
||||
pub use runtime_panel::{RuntimePanel, SessionSupport};
|
||||
pub use runtimelib::ExecutionState;
|
||||
pub use session::Session;
|
||||
|
||||
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
||||
struct ZedDispatcher {
|
||||
|
||||
@@ -241,6 +241,17 @@ impl RuntimePanel {
|
||||
Some((selected_text, language_name, anchor_range))
|
||||
}
|
||||
|
||||
pub fn language(
|
||||
&self,
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Arc<str>> {
|
||||
match self.snippet(editor, cx) {
|
||||
Some((_, language, _)) => Some(language),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
|
||||
let kernel_specifications = kernel_specifications(self.fs.clone());
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
@@ -336,6 +347,50 @@ impl RuntimePanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(View<Session>),
|
||||
Inactive(KernelSpecification),
|
||||
RequiresSetup(String),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl RuntimePanel {
|
||||
pub fn session(
|
||||
&mut self,
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> SessionSupport {
|
||||
let entity_id = editor.entity_id();
|
||||
let session = self.sessions.get(&entity_id).cloned();
|
||||
|
||||
match session {
|
||||
Some(session) => SessionSupport::ActiveSession(session),
|
||||
None => {
|
||||
let language = self.language(editor, cx);
|
||||
let language = match language {
|
||||
Some(language) => language,
|
||||
None => return SessionSupport::Unsupported,
|
||||
};
|
||||
// Check for kernelspec
|
||||
let kernelspec = self.kernelspec(&language, cx);
|
||||
|
||||
match kernelspec {
|
||||
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
|
||||
None => {
|
||||
let language: String = language.to_lowercase();
|
||||
// If no kernelspec but language is one of typescript, python, r, or julia
|
||||
// then we return RequiresSetup
|
||||
match language.as_str() {
|
||||
"typescript" | "python" => SessionSupport::RequiresSetup(language),
|
||||
_ => SessionSupport::Unsupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for RuntimePanel {
|
||||
fn persistent_name() -> &'static str {
|
||||
"RuntimePanel"
|
||||
|
||||
@@ -22,11 +22,11 @@ use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label};
|
||||
|
||||
pub struct Session {
|
||||
editor: WeakView<Editor>,
|
||||
kernel: Kernel,
|
||||
pub editor: WeakView<Editor>,
|
||||
pub kernel: Kernel,
|
||||
blocks: HashMap<String, EditorBlock>,
|
||||
messaging_task: Task<()>,
|
||||
kernel_specification: KernelSpecification,
|
||||
pub messaging_task: Task<()>,
|
||||
pub kernel_specification: KernelSpecification,
|
||||
}
|
||||
|
||||
struct EditorBlock {
|
||||
@@ -310,7 +310,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
match &mut self.kernel {
|
||||
Kernel::RunningKernel(_kernel) => {
|
||||
self.send(InterruptRequest {}.into(), cx).ok();
|
||||
@@ -322,7 +322,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let kernel = std::mem::replace(&mut self.kernel, Kernel::ShuttingDown);
|
||||
|
||||
match kernel {
|
||||
|
||||
@@ -29,7 +29,7 @@ picker.workspace = true
|
||||
project.workspace = true
|
||||
rust-embed.workspace = true
|
||||
settings.workspace = true
|
||||
simplelog.workspace = true
|
||||
simplelog = "0.9"
|
||||
story.workspace = true
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme.workspace = true
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, HashSet};
|
||||
|
||||
pub struct Network<T: Clone, R: rand::Rng> {
|
||||
inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
|
||||
all_messages: Vec<T>,
|
||||
inboxes: BTreeMap<ReplicaId, Vec<Envelope<T>>>,
|
||||
disconnected_peers: HashSet<ReplicaId>,
|
||||
rng: R,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct Envelope<T: Clone> {
|
||||
message: T,
|
||||
}
|
||||
@@ -14,8 +17,8 @@ struct Envelope<T: Clone> {
|
||||
impl<T: Clone, R: rand::Rng> Network<T, R> {
|
||||
pub fn new(rng: R) -> Self {
|
||||
Network {
|
||||
inboxes: Default::default(),
|
||||
all_messages: Vec::new(),
|
||||
inboxes: BTreeMap::default(),
|
||||
disconnected_peers: HashSet::default(),
|
||||
rng,
|
||||
}
|
||||
}
|
||||
@@ -24,6 +27,24 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
|
||||
self.inboxes.insert(id, Vec::new());
|
||||
}
|
||||
|
||||
pub fn disconnect_peer(&mut self, id: ReplicaId) {
|
||||
self.disconnected_peers.insert(id);
|
||||
self.inboxes.get_mut(&id).unwrap().clear();
|
||||
}
|
||||
|
||||
pub fn reconnect_peer(&mut self, id: ReplicaId, replicate_from: ReplicaId) {
|
||||
assert!(self.disconnected_peers.remove(&id));
|
||||
self.replicate(replicate_from, id);
|
||||
}
|
||||
|
||||
pub fn is_disconnected(&self, id: ReplicaId) -> bool {
|
||||
self.disconnected_peers.contains(&id)
|
||||
}
|
||||
|
||||
pub fn contains_disconnected_peers(&self) -> bool {
|
||||
!self.disconnected_peers.is_empty()
|
||||
}
|
||||
|
||||
pub fn replicate(&mut self, old_replica_id: ReplicaId, new_replica_id: ReplicaId) {
|
||||
self.inboxes
|
||||
.insert(new_replica_id, self.inboxes[&old_replica_id].clone());
|
||||
@@ -34,8 +55,13 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
|
||||
}
|
||||
|
||||
pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
|
||||
// Drop messages from disconnected peers.
|
||||
if self.disconnected_peers.contains(&sender) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (replica, inbox) in self.inboxes.iter_mut() {
|
||||
if *replica != sender {
|
||||
if *replica != sender && !self.disconnected_peers.contains(replica) {
|
||||
for message in &messages {
|
||||
// Insert one or more duplicates of this message, potentially *before* the previous
|
||||
// message sent by this peer to simulate out-of-order delivery.
|
||||
@@ -51,7 +77,6 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.all_messages.extend(messages);
|
||||
}
|
||||
|
||||
pub fn has_unreceived(&self, receiver: ReplicaId) -> bool {
|
||||
|
||||
@@ -1265,6 +1265,10 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_deferred_ops(&self) -> bool {
|
||||
!self.deferred_ops.is_empty()
|
||||
}
|
||||
|
||||
pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> {
|
||||
self.history.undo_stack.last()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ schemars = { workspace = true, features = ["indexmap"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
simplelog.workspace= true
|
||||
simplelog = "0.9"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme.workspace = true
|
||||
vscode_theme = "0.2.0"
|
||||
|
||||
@@ -12,7 +12,6 @@ use indexmap::IndexMap;
|
||||
use log::LevelFilter;
|
||||
use schemars::schema_for;
|
||||
use serde::Deserialize;
|
||||
use simplelog::ColorChoice;
|
||||
use simplelog::{TermLogger, TerminalMode};
|
||||
use theme::{Appearance, AppearanceContent, ThemeFamilyContent};
|
||||
|
||||
@@ -95,6 +94,11 @@ fn main() -> Result<()> {
|
||||
|
||||
let log_config = {
|
||||
let mut config = simplelog::ConfigBuilder::new();
|
||||
config
|
||||
.set_level_color(log::Level::Trace, simplelog::Color::Cyan)
|
||||
.set_level_color(log::Level::Info, simplelog::Color::Blue)
|
||||
.set_level_color(log::Level::Warn, simplelog::Color::Yellow)
|
||||
.set_level_color(log::Level::Error, simplelog::Color::Red);
|
||||
|
||||
if !args.warn_on_missing {
|
||||
config.add_filter_ignore_str("theme_printer");
|
||||
@@ -103,13 +107,8 @@ fn main() -> Result<()> {
|
||||
config.build()
|
||||
};
|
||||
|
||||
TermLogger::init(
|
||||
LevelFilter::Trace,
|
||||
log_config,
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
)
|
||||
.expect("could not initialize logger");
|
||||
TermLogger::init(LevelFilter::Trace, log_config, TerminalMode::Mixed)
|
||||
.expect("could not initialize logger");
|
||||
|
||||
if let Some(command) = args.command {
|
||||
match command {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::EnumIter;
|
||||
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||
|
||||
use crate::{prelude::*, Indicator};
|
||||
|
||||
@@ -90,7 +90,9 @@ impl IconSize {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug, Eq, PartialEq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum IconName {
|
||||
Ai,
|
||||
ArrowCircle,
|
||||
@@ -158,11 +160,11 @@ pub enum IconName {
|
||||
Font,
|
||||
FontSize,
|
||||
FontWeight,
|
||||
Github,
|
||||
GenericMinimize,
|
||||
GenericMaximize,
|
||||
GenericClose,
|
||||
GenericMaximize,
|
||||
GenericMinimize,
|
||||
GenericRestore,
|
||||
Github,
|
||||
Hash,
|
||||
HistoryRerun,
|
||||
Indicator,
|
||||
@@ -192,6 +194,10 @@ pub enum IconName {
|
||||
PullRequest,
|
||||
Quote,
|
||||
Regex,
|
||||
ReplPlay,
|
||||
ReplOff,
|
||||
ReplPause,
|
||||
ReplNeutral,
|
||||
Replace,
|
||||
ReplaceAll,
|
||||
ReplaceNext,
|
||||
@@ -229,12 +235,12 @@ pub enum IconName {
|
||||
Trash,
|
||||
TriangleRight,
|
||||
Update,
|
||||
Visible,
|
||||
WholeWord,
|
||||
XCircle,
|
||||
ZedAssistant,
|
||||
ZedAssistantFilled,
|
||||
ZedXCopilot,
|
||||
Visible,
|
||||
}
|
||||
|
||||
impl IconName {
|
||||
@@ -306,11 +312,11 @@ impl IconName {
|
||||
IconName::Font => "icons/font.svg",
|
||||
IconName::FontSize => "icons/font_size.svg",
|
||||
IconName::FontWeight => "icons/font_weight.svg",
|
||||
IconName::Github => "icons/github.svg",
|
||||
IconName::GenericMinimize => "icons/generic_minimize.svg",
|
||||
IconName::GenericMaximize => "icons/generic_maximize.svg",
|
||||
IconName::GenericClose => "icons/generic_close.svg",
|
||||
IconName::GenericMaximize => "icons/generic_maximize.svg",
|
||||
IconName::GenericMinimize => "icons/generic_minimize.svg",
|
||||
IconName::GenericRestore => "icons/generic_restore.svg",
|
||||
IconName::Github => "icons/github.svg",
|
||||
IconName::Hash => "icons/hash.svg",
|
||||
IconName::HistoryRerun => "icons/history_rerun.svg",
|
||||
IconName::Indicator => "icons/indicator.svg",
|
||||
@@ -340,6 +346,10 @@ impl IconName {
|
||||
IconName::PullRequest => "icons/pull_request.svg",
|
||||
IconName::Quote => "icons/quote.svg",
|
||||
IconName::Regex => "icons/regex.svg",
|
||||
IconName::ReplPlay => "icons/repl_play.svg",
|
||||
IconName::ReplPause => "icons/repl_pause.svg",
|
||||
IconName::ReplNeutral => "icons/repl_neutral.svg",
|
||||
IconName::ReplOff => "icons/repl_off.svg",
|
||||
IconName::Replace => "icons/replace.svg",
|
||||
IconName::ReplaceAll => "icons/replace_all.svg",
|
||||
IconName::ReplaceNext => "icons/replace_next.svg",
|
||||
@@ -377,12 +387,12 @@ impl IconName {
|
||||
IconName::Trash => "icons/trash.svg",
|
||||
IconName::TriangleRight => "icons/triangle_right.svg",
|
||||
IconName::Update => "icons/update.svg",
|
||||
IconName::Visible => "icons/visible.svg",
|
||||
IconName::WholeWord => "icons/word_search.svg",
|
||||
IconName::XCircle => "icons/error.svg",
|
||||
IconName::ZedAssistant => "icons/zed_assistant.svg",
|
||||
IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
|
||||
IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
|
||||
IconName::Visible => "icons/visible.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,22 +123,20 @@ impl Render for WelcomePage {
|
||||
.ok();
|
||||
})),
|
||||
)
|
||||
.when(cfg!(target_os = "macos"), |el| {
|
||||
el.child(
|
||||
Button::new("install-cli", "Install the CLI")
|
||||
.full_width()
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"welcome page: install cli".to_string(),
|
||||
);
|
||||
cx.app_mut()
|
||||
.spawn(|cx| async move {
|
||||
install_cli::install_cli(&cx).await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Button::new("install-cli", "Install the CLI")
|
||||
.full_width()
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"welcome page: install cli".to_string(),
|
||||
);
|
||||
cx.app_mut()
|
||||
.spawn(|cx| async move {
|
||||
install_cli::install_cli(&cx).await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("sign-in-to-copilot", "Sign in to GitHub Copilot")
|
||||
.full_width()
|
||||
|
||||
@@ -160,23 +160,14 @@ impl Workspace {
|
||||
self.show_notification(
|
||||
NotificationId::unique::<WorkspaceErrorNotification>(),
|
||||
cx,
|
||||
|cx| cx.new_view(|_cx| ErrorMessagePrompt::new(format!("Error: {err:#}"))),
|
||||
|cx| {
|
||||
cx.new_view(|_cx| {
|
||||
simple_message_notification::MessageNotification::new(format!("Error: {err:#}"))
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn show_portal_error(&mut self, err: String, cx: &mut ViewContext<Self>) {
|
||||
struct PortalError;
|
||||
|
||||
self.show_notification(NotificationId::unique::<PortalError>(), cx, |cx| {
|
||||
cx.new_view(|_cx| {
|
||||
ErrorMessagePrompt::new(err.to_string()).with_link_button(
|
||||
"See docs",
|
||||
"https://zed.dev/docs/linux#i-cant-open-any-files",
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn dismiss_notification(&mut self, id: &NotificationId, cx: &mut ViewContext<Self>) {
|
||||
self.dismiss_notification_internal(id, cx)
|
||||
}
|
||||
@@ -358,84 +349,6 @@ impl Render for LanguageServerPrompt {
|
||||
|
||||
impl EventEmitter<DismissEvent> for LanguageServerPrompt {}
|
||||
|
||||
pub struct ErrorMessagePrompt {
|
||||
message: SharedString,
|
||||
label_and_url_button: Option<(SharedString, SharedString)>,
|
||||
}
|
||||
|
||||
impl ErrorMessagePrompt {
|
||||
pub fn new<S>(message: S) -> Self
|
||||
where
|
||||
S: Into<SharedString>,
|
||||
{
|
||||
Self {
|
||||
message: message.into(),
|
||||
label_and_url_button: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_link_button<S>(mut self, label: S, url: S) -> Self
|
||||
where
|
||||
S: Into<SharedString>,
|
||||
{
|
||||
self.label_and_url_button = Some((label.into(), url.into()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ErrorMessagePrompt {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("error_message_prompt_notification")
|
||||
.occlude()
|
||||
.elevation_3(cx)
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.w_full()
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(
|
||||
svg()
|
||||
.size(cx.text_style().font_size)
|
||||
.flex_none()
|
||||
.mr_2()
|
||||
.mt(px(-2.0))
|
||||
.map(|icon| {
|
||||
icon.path(IconName::ExclamationTriangle.path())
|
||||
.text_color(Color::Error.color(cx))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
ui::IconButton::new("close", ui::IconName::Close)
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.max_w_80()
|
||||
.child(Label::new(self.message.clone()).size(LabelSize::Small)),
|
||||
)
|
||||
.when_some(self.label_and_url_button.clone(), |elm, (label, url)| {
|
||||
elm.child(
|
||||
div().mt_2().child(
|
||||
ui::Button::new("error_message_prompt_notification_button", label)
|
||||
.on_click(move |_, cx| cx.open_url(&url)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ErrorMessagePrompt {}
|
||||
|
||||
pub mod simple_message_notification {
|
||||
use gpui::{
|
||||
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
|
||||
|
||||
@@ -30,10 +30,10 @@ use gpui::{
|
||||
action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size,
|
||||
transparent_black, Action, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext,
|
||||
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
|
||||
EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
|
||||
ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
|
||||
ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds,
|
||||
WindowHandle, WindowOptions,
|
||||
EventEmitter, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView,
|
||||
Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge,
|
||||
Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle,
|
||||
WindowOptions,
|
||||
};
|
||||
use item::{
|
||||
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||
@@ -307,31 +307,13 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
cx.spawn(move |cx| async move {
|
||||
match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
||||
Ok(Some(paths)) => {
|
||||
cx.update(|cx| {
|
||||
open_paths(&paths, app_state, OpenOptions::default(), cx)
|
||||
.detach_and_log_err(cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
cx.update(|cx| {
|
||||
if let Some(workspace_window) = cx
|
||||
.active_window()
|
||||
.and_then(|window| window.downcast::<Workspace>())
|
||||
{
|
||||
workspace_window
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
if let Some(paths) = paths.await.log_err().flatten() {
|
||||
cx.update(|cx| {
|
||||
open_paths(&paths, app_state, OpenOptions::default(), cx)
|
||||
.detach_and_log_err(cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -1314,15 +1296,7 @@ impl Workspace {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let abs_path = cx.prompt_for_new_path(&start_abs_path);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let abs_path: Option<PathBuf> =
|
||||
Flatten::flatten(abs_path.await.map_err(|e| e.into())).map_err(|err| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
err
|
||||
})?;
|
||||
|
||||
let abs_path = abs_path.await?;
|
||||
let project_path = abs_path.and_then(|abs_path| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
@@ -1611,16 +1585,8 @@ impl Workspace {
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let paths = match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
||||
Ok(Some(paths)) => paths,
|
||||
Ok(None) => return,
|
||||
Err(err) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
let Some(paths) = paths.await.log_err().flatten() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(task) = this
|
||||
@@ -1782,14 +1748,7 @@ impl Workspace {
|
||||
multiple: true,
|
||||
});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let paths = Flatten::flatten(paths.await.map_err(|e| e.into())).map_err(|err| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
err
|
||||
})?;
|
||||
if let Some(paths) = paths {
|
||||
if let Some(paths) = paths.await.log_err().flatten() {
|
||||
let results = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_paths(paths, OpenVisible::All, None, cx)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.144.4"
|
||||
version = "0.145.0"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
@@ -85,7 +85,7 @@ search.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
simplelog.workspace = true
|
||||
simplelog = "0.9"
|
||||
smol.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
tab_switcher.workspace = true
|
||||
|
||||
@@ -1 +1 @@
|
||||
stable
|
||||
dev
|
||||
@@ -54,27 +54,32 @@
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Zed with a large project open, showing language server and gitblame support</caption>
|
||||
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-1.png</image>
|
||||
<image>https://zed.dev/img/flatpak/flatpak-1.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Zed with a file open and a channel message thread in the right sidebar</caption>
|
||||
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-2.png</image>
|
||||
<image>https://zed.dev/img/flatpak/flatpak-2.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Example of a channel's shared document</caption>
|
||||
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-3.png</image>
|
||||
<image>https://zed.dev/img/flatpak/flatpak-3.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Zed's extension list</caption>
|
||||
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-4.png</image>
|
||||
<image>https://zed.dev/img/flatpak/flatpak-4.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Theme switcher UI and example theme</caption>
|
||||
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-5.png</image>
|
||||
<image>https://zed.dev/img/flatpak/flatpak-5.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
@release_info@
|
||||
@release_info@
|
||||
<release version="0.0.0" date="1970-01-01">
|
||||
<description>
|
||||
<p>Dummy release to keep flatpak-builder AppStream metadata validation from complaining</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -10,7 +10,7 @@ Exec=$APP_CLI $APP_ARGS
|
||||
Icon=$APP_ICON
|
||||
Categories=Utility;TextEditor;Development;IDE;
|
||||
Keywords=zed;
|
||||
MimeType=text/plain;inode/directory;
|
||||
MimeType=text/plain;inode/directory;x-scheme-handler/zed;
|
||||
|
||||
[Desktop Action NewWorkspace]
|
||||
Exec=$APP_CLI --new $APP_ARGS
|
||||
|
||||
@@ -80,9 +80,7 @@ fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
|
||||
}
|
||||
|
||||
fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
|
||||
eprintln!(
|
||||
"Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
|
||||
);
|
||||
eprintln!("Zed failed to open a window: {e:?}");
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
process::exit(1);
|
||||
@@ -101,12 +99,7 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
|
||||
.add_notification(
|
||||
notification_id,
|
||||
Notification::new("Zed failed to launch")
|
||||
.body(Some(
|
||||
format!(
|
||||
"{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
|
||||
)
|
||||
.as_str(),
|
||||
))
|
||||
.body(Some(format!("{e:?}").as_str()))
|
||||
.priority(Priority::High)
|
||||
.icon(ashpd::desktop::Icon::with_names(&[
|
||||
"dialog-question-symbolic",
|
||||
@@ -715,8 +708,8 @@ fn init_logger() {
|
||||
Ok(log_file) => {
|
||||
let mut config_builder = ConfigBuilder::new();
|
||||
|
||||
config_builder.set_time_format_rfc3339();
|
||||
config_builder.set_time_offset_to_local().log_err();
|
||||
config_builder.set_time_format_str("%Y-%m-%dT%T%:z");
|
||||
config_builder.set_time_to_local(true);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
- [Collaboration](./collaboration.md)
|
||||
- [Tasks](./tasks.md)
|
||||
- [Remote Development](./remote-development.md)
|
||||
- [Repl](./repl.md)
|
||||
|
||||
# Language Support
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ mkdir -p ~/.local
|
||||
# extract zed to ~/.local/zed.app/
|
||||
tar -xvf <path/to/download>.tar.gz -C ~/.local
|
||||
# link the zed binary to ~/.local/bin (or another directory in your $PATH)
|
||||
ln -sf ~/.local/bin/zed ~/.local/zed.app/bin/zed
|
||||
ln -sf ~/.local/zed.app/bin/zed ~/.local/bin/zed
|
||||
```
|
||||
|
||||
If you'd like integration with an XDG-compatible desktop environment, you will also need to install the `.desktop` file:
|
||||
|
||||
72
docs/src/repl.md
Normal file
72
docs/src/repl.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# REPL
|
||||
|
||||
Read. Eval. Print. Loop.
|
||||
|
||||
<div class="warning">
|
||||
|
||||
This feature is in active development. Details may change. We're delighted to get feedback as the REPL feature evolves.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
The built-in REPL for Zed allows you to run code interactively in your editor similarly to a notebook with your own text files.
|
||||
|
||||
<!-- TODO: Include GIF in action -->
|
||||
|
||||
To start using the REPL, add the following to your Zed `settings.json` to bring the power of [Jupyter kernels](https://docs.jupyter.org/en/latest/projects/kernels.html) to your editor:
|
||||
|
||||
```json
|
||||
{
|
||||
"jupyter": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After that, install any of the supported kernels:
|
||||
|
||||
* [Python](#python)
|
||||
* [TypeScript via Deno](#deno)
|
||||
|
||||
## Python
|
||||
|
||||
### Global environment
|
||||
|
||||
To setup your current python to have an available kernel, run:
|
||||
|
||||
```
|
||||
python -m ipykernel install --user
|
||||
```
|
||||
|
||||
### Conda Environment
|
||||
|
||||
```
|
||||
source activate myenv
|
||||
conda install ipykernel
|
||||
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
|
||||
```
|
||||
|
||||
|
||||
### Virtualenv with pip
|
||||
|
||||
```
|
||||
source activate myenv
|
||||
pip install ipykernel
|
||||
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
|
||||
```
|
||||
|
||||
## Deno
|
||||
|
||||
[Install Deno](https://docs.deno.com/runtime/manual/getting_started/installation/) and then install the Deno jupyter kernel:
|
||||
|
||||
```
|
||||
deno jupyter --unstable --install
|
||||
```
|
||||
|
||||
## Other languages
|
||||
|
||||
* [Julia](https://github.com/JuliaLang/IJulia.jl)
|
||||
* R
|
||||
- [Ark Kernel from Positron, formerly RStudio](https://github.com/posit-dev/ark)
|
||||
- [Xeus-R](https://github.com/jupyter-xeus/xeus-r)
|
||||
* [Scala](https://almond.sh/docs/quick-start-install)
|
||||
@@ -12,19 +12,19 @@ channel=$(<crates/zed/RELEASE_CHANNEL)
|
||||
export CHANNEL="$channel"
|
||||
export ARCHIVE="$archive"
|
||||
if [[ "$channel" == "dev" ]]; then
|
||||
export APP_ID="dev.zed.Zed-Dev"
|
||||
export APP_ID="dev.zed.ZedDev"
|
||||
export APP_NAME="Zed Devel"
|
||||
export BRANDING_LIGHT="#99c1f1"
|
||||
export BRANDING_DARK="#1a5fb4"
|
||||
export ICON_FILE="app-icon-dev"
|
||||
elif [[ "$channel" == "nightly" ]]; then
|
||||
export APP_ID="dev.zed.Zed-Nightly"
|
||||
export APP_ID="dev.zed.ZedNightly"
|
||||
export APP_NAME="Zed Nightly"
|
||||
export BRANDING_LIGHT="#e9aa6a"
|
||||
export BRANDING_DARK="#1a5fb4"
|
||||
export ICON_FILE="app-icon-nightly"
|
||||
elif [[ "$channel" == "preview" ]]; then
|
||||
export APP_ID="dev.zed.Zed-Preview"
|
||||
export APP_ID="dev.zed.ZedPreview"
|
||||
export APP_NAME="Zed Preview"
|
||||
export BRANDING_LIGHT="#99c1f1"
|
||||
export BRANDING_DARK="#1a5fb4"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
# Downloads the latest tarball from https://zed.dev/releases and unpacks it
|
||||
# into ~/.local/. If you'd prefer to do this manually, instructions are at
|
||||
# https://zed.dev/docs/linux.
|
||||
|
||||
main() {
|
||||
platform="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
@@ -11,7 +15,6 @@ main() {
|
||||
platform="macos"
|
||||
elif [ "$platform" = "Linux" ]; then
|
||||
platform="linux"
|
||||
channel="${ZED_CHANNEL:-preview}"
|
||||
else
|
||||
echo "Unsupported platform $platform"
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user