mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Add initial support for defining language server adapters in WebAssembly-based extensions (#8645)
This PR adds **internal** ability to run arbitrary language servers via WebAssembly extensions. The functionality isn't exposed yet - we're just landing this in this early state because there have been a lot of changes to the `LspAdapter` trait, and other language server logic. ## Next steps * Currently, wasm extensions can only define how to *install* and run a language server, they can't yet implement the other LSP adapter methods, such as formatting completion labels and workspace symbols. * We don't have an automatic way to install or develop these types of extensions * We don't have a way to package these types of extensions in our extensions repo, to make them available via our extensions API. * The Rust extension API crate, `zed-extension-api` has not yet been published to crates.io, because we still consider the API a work in progress. Release Notes: - N/A --------- Co-authored-by: Marshall <marshall@zed.dev> Co-authored-by: Nathan <nathan@zed.dev> Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
parent
f3f2225a8e
commit
268fa1cbaf
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -86,6 +86,12 @@ jobs:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Install cargo-component
|
||||
run: |
|
||||
if ! which cargo-component > /dev/null; then
|
||||
cargo install cargo-component
|
||||
fi
|
||||
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@
|
||||
/assets/*licenses.md
|
||||
**/venv
|
||||
.build
|
||||
*.wasm
|
||||
Packages
|
||||
*.xcodeproj
|
||||
xcuserdata/
|
||||
|
681
Cargo.lock
generated
681
Cargo.lock
generated
@ -157,6 +157,12 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ambient-authority"
|
||||
version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@ -1657,6 +1663,83 @@ dependencies = [
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-fs-ext"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88e341d15ac1029aadce600be764a1a1edafe40e03cde23285bc1d261b3a4866"
|
||||
dependencies = [
|
||||
"cap-primitives",
|
||||
"cap-std",
|
||||
"io-lifetimes 2.0.3",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-net-ext"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434168fe6533055f0f4204039abe3ff6d7db338ef46872a5fa39e9d5ad5ab7a9"
|
||||
dependencies = [
|
||||
"cap-primitives",
|
||||
"cap-std",
|
||||
"rustix 0.38.30",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-primitives"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe16767ed8eee6d3f1f00d6a7576b81c226ab917eb54b96e5f77a5216ef67abb"
|
||||
dependencies = [
|
||||
"ambient-authority",
|
||||
"fs-set-times",
|
||||
"io-extras",
|
||||
"io-lifetimes 2.0.3",
|
||||
"ipnet",
|
||||
"maybe-owned",
|
||||
"rustix 0.38.30",
|
||||
"windows-sys 0.52.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-rand"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20e5695565f0cd7106bc3c7170323597540e772bb73e0be2cd2c662a0f8fa4ca"
|
||||
dependencies = [
|
||||
"ambient-authority",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-std"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "593db20e4c51f62d3284bae7ee718849c3214f93a3b94ea1899ad85ba119d330"
|
||||
dependencies = [
|
||||
"cap-primitives",
|
||||
"io-extras",
|
||||
"io-lifetimes 2.0.3",
|
||||
"rustix 0.38.30",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cap-time-ext"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03261630f291f425430a36f38c847828265bc928f517cdd2004c56f4b02f002b"
|
||||
dependencies = [
|
||||
"ambient-authority",
|
||||
"cap-primitives",
|
||||
"iana-time-zone",
|
||||
"once_cell",
|
||||
"rustix 0.38.30",
|
||||
"winx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.1.2"
|
||||
@ -2437,6 +2520,15 @@ dependencies = [
|
||||
"windows 0.46.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpp_demangle"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
@ -2784,6 +2876,15 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.8.6"
|
||||
@ -2923,6 +3024,16 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "3.0.2"
|
||||
@ -3252,13 +3363,16 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-tar",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -3266,6 +3380,9 @@ dependencies = [
|
||||
"theme",
|
||||
"toml 0.8.10",
|
||||
"util",
|
||||
"wasmparser",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3331,6 +3448,17 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"rustix 0.38.30",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "feature_flags"
|
||||
version = "0.1.0"
|
||||
@ -3607,6 +3735,7 @@ name = "fs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tar",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"fsevent",
|
||||
@ -3631,6 +3760,17 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs-set-times"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb"
|
||||
dependencies = [
|
||||
"io-lifetimes 2.0.3",
|
||||
"rustix 0.38.30",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "2.0.2"
|
||||
@ -3846,6 +3986,28 @@ dependencies = [
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxprof-processed-profile"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"debugid",
|
||||
"fxhash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@ -4435,6 +4597,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
@ -4558,6 +4726,16 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-extras"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c301e73fb90e8a29e600a9f402d095765f74310d582916a952f618836a1bd1ed"
|
||||
dependencies = [
|
||||
"io-lifetimes 2.0.3",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
@ -4569,6 +4747,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c"
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
@ -4673,6 +4857,26 @@ version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "ittapi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ittapi-sys",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ittapi-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
@ -5327,6 +5531,12 @@ dependencies = [
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-owned"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.5"
|
||||
@ -7683,7 +7893,7 @@ checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"io-lifetimes 1.0.11",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
@ -7700,6 +7910,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.12",
|
||||
"once_cell",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@ -8531,6 +8742,15 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
@ -9098,6 +9318,22 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-interface"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0682e006dd35771e392a6623ac180999a9a854b1d4a6c12fb2e804941c2b1f58"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cap-fs-ext",
|
||||
"cap-std",
|
||||
"fd-lock",
|
||||
"io-lifetimes 2.0.3",
|
||||
"rustix 0.38.30",
|
||||
"windows-sys 0.52.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "taffy"
|
||||
version = "0.3.11"
|
||||
@ -10715,6 +10951,32 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi-common"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "082a661fe31df4dbb34409f4835ad3d8ba65036bf74aaec9b21fde779978aba7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
"cap-fs-ext",
|
||||
"cap-rand",
|
||||
"cap-std",
|
||||
"cap-time-ext",
|
||||
"fs-set-times",
|
||||
"io-extras",
|
||||
"io-lifetimes 2.0.3",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustix 0.38.30",
|
||||
"system-interface",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"wasmtime",
|
||||
"wiggle",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.87"
|
||||
@ -10790,6 +11052,31 @@ dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.200.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e3fb0c8fbddd78aa6095b850dfeedbc7506cf5f81e633f69cf8f2333ab84b9"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18ebaa7bd0f9e7a5e5dd29b9a998acf21c4abed74265524dd7e85934597bfb10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.121.2"
|
||||
@ -10801,33 +11088,57 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmprinter"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60e73986a6b7fdfedb7c5bf9e7eb71135486507c8fbc4c0c42cffcb6532988b7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06f80b13fdeba0ea5267813d0f06af822309f7125fc8db6094bcd485f0a4ae7"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"cfg-if 1.0.0",
|
||||
"encoding_rs",
|
||||
"fxprof-processed-profile",
|
||||
"gimli",
|
||||
"indexmap 2.0.0",
|
||||
"ittapi",
|
||||
"libc",
|
||||
"log",
|
||||
"object",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"rayon",
|
||||
"rustix 0.38.30",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"target-lexicon",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasmparser",
|
||||
"wasmtime-cache",
|
||||
"wasmtime-component-macro",
|
||||
"wasmtime-component-util",
|
||||
"wasmtime-cranelift",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-fiber",
|
||||
"wasmtime-jit-debug",
|
||||
"wasmtime-jit-icache-coherence",
|
||||
"wasmtime-runtime",
|
||||
"wasmtime-winch",
|
||||
"wat",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@ -10864,6 +11175,47 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-cache"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0a78f86b27f099bea3aaa0894464e22e84a08cadf3d8cd353378d3d15385535"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.4",
|
||||
"bincode",
|
||||
"directories-next",
|
||||
"log",
|
||||
"rustix 0.38.30",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2 0.10.7",
|
||||
"toml 0.5.11",
|
||||
"windows-sys 0.52.0",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-component-macro"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e54483c542e304e17fa73d3f9263bf071e21915c8f048c7d42916da5b4bfd6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"wasmtime-component-util",
|
||||
"wasmtime-wit-bindgen",
|
||||
"wit-parser 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-component-util"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9f72619f484df95fc03162cdef9cb98778abc4103811849501bb34e79a3aac"
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-cranelift"
|
||||
version = "18.0.1"
|
||||
@ -10913,19 +11265,51 @@ checksum = "e8da991421528c2767053cb0cfa70b5d28279100dbcf70ed7f74b51abe1656ef"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"cpp_demangle",
|
||||
"cranelift-entity",
|
||||
"gimli",
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"target-lexicon",
|
||||
"thiserror",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasmparser",
|
||||
"wasmprinter",
|
||||
"wasmtime-component-util",
|
||||
"wasmtime-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-fiber"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fdd780272515bfcdf316e2efe20231719ec40223d67fcdd7d17068a16d39384"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"rustix 0.38.30",
|
||||
"wasmtime-asm-macros",
|
||||
"wasmtime-versioned-export-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-jit-debug"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87be9ed561dbe2aca3bde30d442c292fda53748343d0220873d1df65270c8fcf"
|
||||
dependencies = [
|
||||
"object",
|
||||
"once_cell",
|
||||
"rustix 0.38.30",
|
||||
"wasmtime-versioned-export-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-jit-icache-coherence"
|
||||
version = "18.0.1"
|
||||
@ -10946,6 +11330,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"encoding_rs",
|
||||
"indexmap 2.0.0",
|
||||
"libc",
|
||||
"log",
|
||||
@ -10956,9 +11341,11 @@ dependencies = [
|
||||
"psm",
|
||||
"rustix 0.38.30",
|
||||
"sptr",
|
||||
"wasm-encoder",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasmtime-asm-macros",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-fiber",
|
||||
"wasmtime-jit-debug",
|
||||
"wasmtime-versioned-export-macros",
|
||||
"wasmtime-wmemcheck",
|
||||
"windows-sys 0.52.0",
|
||||
@ -10988,12 +11375,105 @@ dependencies = [
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-wasi"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f7d9cfaf9f70e83a164f5d772e376fafa2d7b7b0ca2ef88f9bcaf8b2363a38b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.4.1",
|
||||
"bytes 1.5.0",
|
||||
"cap-fs-ext",
|
||||
"cap-net-ext",
|
||||
"cap-rand",
|
||||
"cap-std",
|
||||
"cap-time-ext",
|
||||
"fs-set-times",
|
||||
"futures 0.3.28",
|
||||
"io-extras",
|
||||
"io-lifetimes 2.0.3",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustix 0.38.30",
|
||||
"system-interface",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasi-common",
|
||||
"wasmtime",
|
||||
"wiggle",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-winch"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f773a904d2bd5ecd8ad095f4c965ad56a836929d8c26368621f75328d500649"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cranelift-codegen",
|
||||
"gimli",
|
||||
"object",
|
||||
"target-lexicon",
|
||||
"wasmparser",
|
||||
"wasmtime-cranelift-shared",
|
||||
"wasmtime-environ",
|
||||
"winch-codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-wit-bindgen"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff6e9754e0a526238ea66da9ba21965a54846a2b22d9de89a298fb8998389507"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.4.1",
|
||||
"indexmap 2.0.0",
|
||||
"wit-parser 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-wmemcheck"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdf5b8da6ebf7549dad0cd32ca4a3a0461449ef4feec9d0d8450d8da9f51f9b"
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "35.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "200.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1810d14e6b03ebb8fb05eef4009ad5749c989b65197d83bce7de7172ed91366"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"leb128",
|
||||
"memchr",
|
||||
"unicode-width",
|
||||
"wasm-encoder 0.200.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.200.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "776cbd10e217f83869beaa3f40e312bb9e91d5eee29bbf6f560db1261b6a4c3d"
|
||||
dependencies = [
|
||||
"wast 200.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.3"
|
||||
@ -11133,6 +11613,48 @@ version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
|
||||
|
||||
[[package]]
|
||||
name = "wiggle"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "454570f4fecadb881f0ba157e98b575a2850607a9eac79d8868f3ab70633f632"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bitflags 2.4.1",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"wasmtime",
|
||||
"wiggle-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiggle-generate"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443ac1ebb753ca22bca98d01742762de1243ff722839907c35ea683a8264c74e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"shellexpand",
|
||||
"syn 2.0.48",
|
||||
"witx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiggle-macro"
|
||||
version = "18.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e9e2f1f06ae07bac15273774782c04ab14e9adfbf414762fc84dbbfcf7fb1ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"wiggle-generate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
@ -11176,6 +11698,22 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "winch-codegen"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52f7eaac56988f986181099c15860946fea93ed826322a1f92c4ff04541b7744"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cranelift-codegen",
|
||||
"gimli",
|
||||
"regalloc2",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
"wasmparser",
|
||||
"wasmtime-environ",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.46.0"
|
||||
@ -11420,6 +11958,16 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winx"
|
||||
version = "0.36.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wio"
|
||||
version = "0.2.2"
|
||||
@ -11429,6 +11977,119 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5408d742fcdf418b766f23b2393f0f4d9b10b72b7cd96d9525626943593e8cc0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7146725463d08ccf9c6c5357a7a6c1fff96185d95d6e84e7c75c92e5b1273c93"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-parser 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb5fefcf93ff2ea03c8fe9b9db2caee3096103c0e3cd62ed54f6f9493aa6b405"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.4.1",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce4059a1adc671e4457f457cb638ed2f766a1a462bb7daa3b638c6fb1fda156e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be60cd1b2ff7919305301d0c27528d4867bd793afe890ba3837743da9655d91b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.1",
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "316b36a9f0005f5aa4b03c39bc3728d045df136f8c13a73b7db4510dec725e08"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee4ad7310367bf272507c0c8e0c74a80b4ed586b833f7c7ca0b7588f686f11a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "witx"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"thiserror",
|
||||
"wast 35.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "workspace"
|
||||
version = "0.1.0"
|
||||
@ -11727,6 +12388,20 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_gleam"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"zed_extension_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.2.3"
|
||||
|
@ -23,6 +23,7 @@ members = [
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
"crates/extensions_ui",
|
||||
"crates/feature_flags",
|
||||
"crates/feedback",
|
||||
@ -91,6 +92,7 @@ members = [
|
||||
"crates/workspace",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
"extensions/gleam",
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
resolver = "2"
|
||||
@ -298,7 +300,9 @@ unindent = "0.1.7"
|
||||
unicase = "2.6"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmparser = "0.121"
|
||||
wasmtime = "18.0"
|
||||
wasmtime-wasi = "18.0"
|
||||
which = "6.0.0"
|
||||
sys-locale = "0.3.1"
|
||||
|
||||
|
@ -6,7 +6,7 @@ use gpui::{
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
|
||||
ViewContext, VisualContext as _,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc};
|
||||
@ -30,7 +30,7 @@ pub struct ActivityIndicator {
|
||||
}
|
||||
|
||||
struct LspStatus {
|
||||
name: Arc<str>,
|
||||
name: LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
}
|
||||
|
||||
@ -58,13 +58,10 @@ impl ActivityIndicator {
|
||||
let this = cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let mut status_events = languages.language_server_binary_statuses();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
while let Some((language, event)) = status_events.next().await {
|
||||
while let Some((name, status)) = status_events.next().await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.statuses.retain(|s| s.name != language.name());
|
||||
this.statuses.push(LspStatus {
|
||||
name: language.name(),
|
||||
status: event,
|
||||
});
|
||||
this.statuses.retain(|s| s.name != name);
|
||||
this.statuses.push(LspStatus { name, status });
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
@ -114,7 +111,7 @@ impl ActivityIndicator {
|
||||
self.statuses.retain(|status| {
|
||||
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
||||
cx.emit(Event::ShowError {
|
||||
lsp_name: status.name.clone(),
|
||||
lsp_name: status.name.0.clone(),
|
||||
error: error.clone(),
|
||||
});
|
||||
false
|
||||
@ -202,11 +199,12 @@ impl ActivityIndicator {
|
||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||
let mut failed = SmallVec::<[_; 3]>::new();
|
||||
for status in &self.statuses {
|
||||
let name = status.name.clone();
|
||||
match status.status {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
|
||||
LanguageServerBinaryStatus::Downloading => downloading.push(name),
|
||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
|
||||
LanguageServerBinaryStatus::CheckingForUpdate => {
|
||||
checking_for_update.push(status.name.0.as_ref())
|
||||
}
|
||||
LanguageServerBinaryStatus::Downloading => downloading.push(status.name.0.as_ref()),
|
||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.0.as_ref()),
|
||||
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
|
||||
}
|
||||
}
|
||||
@ -214,34 +212,28 @@ impl ActivityIndicator {
|
||||
if !downloading.is_empty() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
message: format!(
|
||||
"Downloading {} language server{}...",
|
||||
downloading.join(", "),
|
||||
if downloading.len() > 1 { "s" } else { "" }
|
||||
),
|
||||
message: format!("Downloading {}...", downloading.join(", "),),
|
||||
on_click: None,
|
||||
};
|
||||
} else if !checking_for_update.is_empty() {
|
||||
}
|
||||
|
||||
if !checking_for_update.is_empty() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
message: format!(
|
||||
"Checking for updates to {} language server{}...",
|
||||
"Checking for updates to {}...",
|
||||
checking_for_update.join(", "),
|
||||
if checking_for_update.len() > 1 {
|
||||
"s"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
on_click: None,
|
||||
};
|
||||
} else if !failed.is_empty() {
|
||||
}
|
||||
|
||||
if !failed.is_empty() {
|
||||
return Content {
|
||||
icon: Some(WARNING_ICON),
|
||||
message: format!(
|
||||
"Failed to download {} language server{}. Click to show error.",
|
||||
"Failed to download {}. Click to show error.",
|
||||
failed.join(", "),
|
||||
if failed.len() > 1 { "s" } else { "" }
|
||||
),
|
||||
on_click: Some(Arc::new(|this, cx| {
|
||||
this.show_error_message(&Default::default(), cx)
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use call::Room;
|
||||
use client::ChannelId;
|
||||
use gpui::{Model, TestAppContext};
|
||||
@ -15,6 +17,7 @@ mod random_project_collaboration_tests;
|
||||
mod randomized_test_helpers;
|
||||
mod test_server;
|
||||
|
||||
use language::{tree_sitter_rust, Language, LanguageConfig, LanguageMatcher};
|
||||
pub use randomized_test_helpers::{
|
||||
run_randomized_test, save_randomized_test_plan, RandomizedTest, TestError, UserTestPlan,
|
||||
};
|
||||
@ -47,3 +50,17 @@ fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomPartici
|
||||
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
|
||||
cx.read(|cx| room.read(cx).channel_id())
|
||||
}
|
||||
|
||||
fn rust_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
))
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{self, AtomicBool, AtomicUsize},
|
||||
Arc,
|
||||
},
|
||||
use crate::{
|
||||
rpc::RECONNECT_TIMEOUT,
|
||||
tests::{rust_lang, TestServer},
|
||||
};
|
||||
|
||||
use call::ActiveCall;
|
||||
use editor::{
|
||||
actions::{
|
||||
@ -19,16 +15,21 @@ use gpui::{TestAppContext, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher,
|
||||
FakeLspAdapter,
|
||||
};
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{self, AtomicBool, AtomicUsize},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use text::Point;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_host_disconnect(
|
||||
cx_a: &mut TestAppContext,
|
||||
@ -265,20 +266,10 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string()]),
|
||||
@ -288,9 +279,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -455,19 +445,10 @@ async fn test_collaborating_with_code_actions(
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -671,19 +652,10 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
|
||||
prepare_provider: Some(true),
|
||||
@ -692,9 +664,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -858,25 +829,14 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-language-server".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "the-language-server",
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -1152,20 +1112,10 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: ":".to_string(),
|
||||
@ -1174,9 +1124,8 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -1283,20 +1232,10 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: ":".to_string(),
|
||||
@ -1305,9 +1244,8 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -1450,29 +1388,18 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
});
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
client_b.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
client_a.language_registry().add(Arc::clone(&language));
|
||||
client_b.language_registry().add(language);
|
||||
},
|
||||
);
|
||||
|
||||
// Client A opens a project.
|
||||
client_a
|
||||
@ -1723,29 +1650,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
});
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
client_b.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
client_a.language_registry().add(Arc::clone(&language));
|
||||
client_b.language_registry().add(language);
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::{channel_id, room_participants, RoomParticipants, TestClient, TestServer},
|
||||
tests::{channel_id, room_participants, rust_lang, RoomParticipants, TestClient, TestServer},
|
||||
};
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
@ -3785,8 +3785,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
client_a.language_registry().add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -3796,9 +3795,10 @@ async fn test_collaborating_with_diagnostics(
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
)));
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
|
||||
// Share a project as client A
|
||||
client_a
|
||||
@ -4066,26 +4066,15 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
|
||||
disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
},
|
||||
);
|
||||
|
||||
let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
|
||||
client_a
|
||||
@ -4298,20 +4287,10 @@ async fn test_formatting_buffer(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
// Here we insert a fake tree with a directory that exists on disk. This is needed
|
||||
// because later we'll invoke a command, which requires passing a working directory
|
||||
@ -4406,8 +4385,9 @@ async fn test_prettier_formatting_buffer(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
let test_plugin = "test_plugin";
|
||||
|
||||
client_a.language_registry().add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -4418,16 +4398,14 @@ async fn test_prettier_formatting_buffer(
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let test_plugin = "test_plugin";
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
)));
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
prettier_plugins: vec![test_plugin],
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
client_a.language_registry().add(Arc::clone(&language));
|
||||
},
|
||||
);
|
||||
|
||||
// Here we insert a fake tree with a directory that exists on disk. This is needed
|
||||
// because later we'll invoke a command, which requires passing a working directory
|
||||
@ -4525,20 +4503,10 @@ async fn test_definition(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
client_a.language_registry().add(rust_lang());
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -4672,20 +4640,10 @@ async fn test_references(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -4872,20 +4830,10 @@ async fn test_document_highlights(
|
||||
)
|
||||
.await;
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
client_a.language_registry().add(rust_lang());
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
@ -4978,20 +4926,10 @@ async fn test_lsp_hover(
|
||||
)
|
||||
.await;
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
@ -5077,20 +5015,10 @@ async fn test_project_symbols(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@ -5189,20 +5117,10 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
client_a.language_registry().add(Arc::new(language));
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a
|
||||
.language_registry()
|
||||
.register_fake_lsp_adapter("Rust", Default::default());
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
|
@ -1021,7 +1021,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
}
|
||||
|
||||
async fn on_client_added(client: &Rc<TestClient>, _: &mut TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
client.language_registry().add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -1031,9 +1031,10 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
)));
|
||||
client.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-fake-language-server",
|
||||
capabilities: lsp::LanguageServer::full_capabilities(),
|
||||
initializer: Some(Box::new({
|
||||
@ -1132,9 +1133,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
}
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client.app_state.languages.add(Arc::new(language));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async fn on_quiesce(_: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {
|
||||
|
@ -383,8 +383,16 @@ impl Copilot {
|
||||
use lsp::FakeLanguageServer;
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
|
||||
let (server, fake_server) =
|
||||
FakeLanguageServer::new("copilot".into(), Default::default(), cx.to_async());
|
||||
let (server, fake_server) = FakeLanguageServer::new(
|
||||
LanguageServerBinary {
|
||||
path: "path/to/copilot".into(),
|
||||
arguments: vec![],
|
||||
env: None,
|
||||
},
|
||||
"copilot".into(),
|
||||
Default::default(),
|
||||
cx.to_async(),
|
||||
);
|
||||
let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
let this = cx.new_model(|cx| Self {
|
||||
|
@ -5233,32 +5233,24 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
_ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||
.await
|
||||
@ -5355,32 +5347,24 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
_ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||
.await
|
||||
@ -5480,7 +5464,13 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
||||
settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -5493,24 +5483,18 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
)));
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
_ = project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::new(language));
|
||||
});
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||
.await
|
||||
@ -7912,7 +7896,19 @@ async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui
|
||||
async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut language = Language::new(
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { let a = 5; }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -7931,9 +7927,10 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
)));
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: "{".to_string(),
|
||||
@ -7942,20 +7939,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { let a = 5; }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
_ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
@ -8026,8 +8012,25 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { let a = 5; }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let server_restarts = Arc::new(AtomicUsize::new(0));
|
||||
let closure_restarts = Arc::clone(&server_restarts);
|
||||
let language_server_name = "test language server";
|
||||
let language_name: Arc<str> = "Rust".into();
|
||||
let mut language = Language::new(
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::clone(&language_name),
|
||||
matcher: LanguageMatcher {
|
||||
@ -8037,13 +8040,10 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
|
||||
let server_restarts = Arc::new(AtomicUsize::new(0));
|
||||
let closure_restarts = Arc::clone(&server_restarts);
|
||||
let language_server_name = "test language server";
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
)));
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: language_server_name,
|
||||
initialization_options: Some(json!({
|
||||
"testOptionValue": true
|
||||
@ -8056,20 +8056,9 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
});
|
||||
})),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { let a = 5; }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
_ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let _buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
@ -8365,7 +8354,13 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
||||
settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -8376,24 +8371,18 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
)));
|
||||
|
||||
let test_plugin = "test_plugin";
|
||||
let _ = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
let _ = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
prettier_plugins: vec![test_plugin],
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||
let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
|
||||
_ = project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::new(language));
|
||||
});
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||
.await
|
||||
@ -8685,3 +8674,17 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
||||
|
||||
update_test_language_settings(cx, f);
|
||||
}
|
||||
|
||||
pub(crate) fn rust_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
))
|
||||
}
|
||||
|
@ -1553,12 +1553,14 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let mut rs_fake_servers = None;
|
||||
let mut md_fake_servers = None;
|
||||
for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
|
||||
let mut language = Language::new(
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: name.into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -1568,25 +1570,23 @@ pub mod tests {
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
)));
|
||||
let fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
name,
|
||||
FakeLspAdapter {
|
||||
name,
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
},
|
||||
);
|
||||
match name {
|
||||
"Rust" => rs_fake_servers = Some(fake_servers),
|
||||
"Markdown" => md_fake_servers = Some(fake_servers),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::new(language));
|
||||
});
|
||||
}
|
||||
|
||||
let rs_buffer = project
|
||||
@ -2253,26 +2253,6 @@ pub mod tests {
|
||||
})
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
@ -2282,8 +2262,22 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/a/main.rs", cx)
|
||||
@ -2554,27 +2548,6 @@ pub mod tests {
|
||||
})
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
@ -2584,10 +2557,23 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::clone(&language))
|
||||
});
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let language = crate::editor_tests::rust_lang();
|
||||
language_registry.add(language);
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let worktree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees().next().unwrap().read(cx).id()
|
||||
});
|
||||
@ -2911,27 +2897,6 @@ pub mod tests {
|
||||
})
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
@ -2941,10 +2906,22 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::clone(&language))
|
||||
});
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let worktree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees().next().unwrap().read(cx).id()
|
||||
});
|
||||
@ -3149,26 +3126,6 @@ pub mod tests {
|
||||
})
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
@ -3178,8 +3135,22 @@ pub mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/a/main.rs", cx)
|
||||
@ -3396,27 +3367,6 @@ pub mod tests {
|
||||
async fn prepare_test_objects(
|
||||
cx: &mut TestAppContext,
|
||||
) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
@ -3428,7 +3378,30 @@ pub mod tests {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)));
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/a/main.rs", cx)
|
||||
|
@ -32,7 +32,7 @@ pub struct EditorLspTestContext {
|
||||
|
||||
impl EditorLspTestContext {
|
||||
pub async fn new(
|
||||
mut language: Language,
|
||||
language: Language,
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext {
|
||||
@ -53,16 +53,17 @@ impl EditorLspTestContext {
|
||||
.expect("language must have a path suffix for EditorLspTestContext")
|
||||
);
|
||||
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities,
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
language.name().as_ref(),
|
||||
FakeLspAdapter {
|
||||
capabilities,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
language_registry.add(Arc::new(language));
|
||||
|
||||
app_state
|
||||
.fs
|
||||
|
@ -16,13 +16,16 @@ path = "src/extension_json_schemas.rs"
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@ -30,8 +33,12 @@ settings.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
util.workspace = true
|
||||
wasmtime = { workspace = true, features = ["async"] }
|
||||
wasmtime-wasi.workspace = true
|
||||
wasmparser.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
|
90
crates/extension/src/extension_lsp_adapter.rs
Normal file
90
crates/extension/src/extension_lsp_adapter.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::wasm_host::{wit::LanguageServerConfig, WasmExtension};
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::{Future, FutureExt};
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{Language, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{
|
||||
any::Any,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
};
|
||||
use wasmtime_wasi::preview2::WasiView as _;
|
||||
|
||||
pub struct ExtensionLspAdapter {
|
||||
pub(crate) extension: WasmExtension,
|
||||
pub(crate) config: LanguageServerConfig,
|
||||
pub(crate) work_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for ExtensionLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName(self.config.name.clone().into())
|
||||
}
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Arc<Language>,
|
||||
_: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
_: &'a mut AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
|
||||
async move {
|
||||
let command = self
|
||||
.extension
|
||||
.call({
|
||||
let this = self.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table().push(delegate)?;
|
||||
extension
|
||||
.call_language_server_command(store, &this.config, resource)
|
||||
.await
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e))?;
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.work_dir.join(&command.command).into(),
|
||||
arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
|
||||
env: Some(command.env.into_iter().collect()),
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
unreachable!("get_language_server_command is overridden")
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_: Box<dyn 'static + Send + Any>,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
unreachable!("get_language_server_command is overridden")
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
unreachable!("get_language_server_command is overridden")
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
}
|
@ -1,48 +1,118 @@
|
||||
mod extension_lsp_adapter;
|
||||
mod wasm_host;
|
||||
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use collections::{BTreeMap, HashSet};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::channel::mpsc::unbounded;
|
||||
use futures::StreamExt as _;
|
||||
use futures::{io::BufReader, AsyncReadExt as _};
|
||||
use futures::{channel::mpsc::unbounded, io::BufReader, AsyncReadExt as _, StreamExt as _};
|
||||
use gpui::{actions, AppContext, Context, Global, Model, ModelContext, Task};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
|
||||
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, LanguageServerName,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
path::{self, Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use util::http::{AsyncBody, HttpClientWithUrl};
|
||||
use util::TryFutureExt;
|
||||
use util::{http::HttpClient, paths::EXTENSIONS_DIR, ResultExt};
|
||||
use util::{
|
||||
http::{AsyncBody, HttpClient, HttpClientWithUrl},
|
||||
paths::EXTENSIONS_DIR,
|
||||
ResultExt, TryFutureExt,
|
||||
};
|
||||
use wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ExtensionsApiResponse {
|
||||
pub data: Vec<Extension>,
|
||||
pub data: Vec<ExtensionApiResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Extension {
|
||||
pub struct ExtensionApiResponse {
|
||||
pub id: Arc<str>,
|
||||
pub version: Arc<str>,
|
||||
pub name: String,
|
||||
pub version: Arc<str>,
|
||||
pub description: Option<String>,
|
||||
pub authors: Vec<String>,
|
||||
pub repository: String,
|
||||
pub download_count: usize,
|
||||
}
|
||||
|
||||
/// This is the old version of the extension manifest, from when it was `extension.json`.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct OldExtensionManifest {
|
||||
pub name: String,
|
||||
pub version: Arc<str>,
|
||||
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub repository: Option<String>,
|
||||
#[serde(default)]
|
||||
pub authors: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub themes: BTreeMap<Arc<str>, PathBuf>,
|
||||
#[serde(default)]
|
||||
pub languages: BTreeMap<Arc<str>, PathBuf>,
|
||||
#[serde(default)]
|
||||
pub grammars: BTreeMap<Arc<str>, PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionManifest {
|
||||
pub id: Arc<str>,
|
||||
pub name: String,
|
||||
pub version: Arc<str>,
|
||||
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub repository: Option<String>,
|
||||
#[serde(default)]
|
||||
pub authors: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub lib: LibManifestEntry,
|
||||
|
||||
#[serde(default)]
|
||||
pub themes: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub languages: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
|
||||
#[serde(default)]
|
||||
pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct LibManifestEntry {
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct GrammarManifestEntry {
|
||||
repository: String,
|
||||
#[serde(alias = "commit")]
|
||||
rev: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub struct LanguageServerManifestEntry {
|
||||
language: Arc<str>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ExtensionStatus {
|
||||
NotInstalled,
|
||||
@ -67,7 +137,7 @@ impl ExtensionStatus {
|
||||
}
|
||||
|
||||
pub struct ExtensionStore {
|
||||
manifest: Arc<RwLock<Manifest>>,
|
||||
extension_index: ExtensionIndex,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
extensions_dir: PathBuf,
|
||||
@ -76,7 +146,9 @@ pub struct ExtensionStore {
|
||||
manifest_path: PathBuf,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
extension_changes: ExtensionChanges,
|
||||
modified_extensions: HashSet<Arc<str>>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
reload_task: Option<Task<Option<()>>>,
|
||||
needs_reload: bool,
|
||||
_watch_extensions_dir: [Task<()>; 2],
|
||||
@ -86,56 +158,44 @@ struct GlobalExtensionStore(Model<ExtensionStore>);
|
||||
|
||||
impl Global for GlobalExtensionStore {}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
pub struct Manifest {
|
||||
pub extensions: BTreeMap<Arc<str>, Arc<str>>,
|
||||
pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
|
||||
pub languages: BTreeMap<Arc<str>, LanguageManifestEntry>,
|
||||
pub themes: BTreeMap<Arc<str>, ThemeManifestEntry>,
|
||||
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
|
||||
pub struct ExtensionIndex {
|
||||
pub extensions: BTreeMap<Arc<str>, Arc<ExtensionManifest>>,
|
||||
pub themes: BTreeMap<Arc<str>, ExtensionIndexEntry>,
|
||||
pub languages: BTreeMap<Arc<str>, ExtensionIndexLanguageEntry>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
pub struct GrammarManifestEntry {
|
||||
extension: String,
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexEntry {
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct LanguageManifestEntry {
|
||||
extension: String,
|
||||
pub struct ExtensionIndexLanguageEntry {
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
matcher: LanguageMatcher,
|
||||
grammar: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||
pub struct ThemeManifestEntry {
|
||||
extension: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ExtensionChanges {
|
||||
languages: HashSet<Arc<str>>,
|
||||
grammars: HashSet<Arc<str>>,
|
||||
themes: HashSet<Arc<str>>,
|
||||
}
|
||||
|
||||
actions!(zed, [ReloadExtensions]);
|
||||
|
||||
pub fn init(
|
||||
fs: Arc<fs::RealFs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let store = cx.new_model(|cx| {
|
||||
let store = cx.new_model(move |cx| {
|
||||
ExtensionStore::new(
|
||||
EXTENSIONS_DIR.clone(),
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
language_registry.clone(),
|
||||
fs,
|
||||
http_client,
|
||||
node_runtime,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
cx,
|
||||
)
|
||||
@ -158,19 +218,28 @@ impl ExtensionStore {
|
||||
extensions_dir: PathBuf,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
manifest: Default::default(),
|
||||
extension_index: Default::default(),
|
||||
extensions_dir: extensions_dir.join("installed"),
|
||||
manifest_path: extensions_dir.join("manifest.json"),
|
||||
extensions_being_installed: Default::default(),
|
||||
extensions_being_uninstalled: Default::default(),
|
||||
reload_task: None,
|
||||
wasm_host: WasmHost::new(
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
extensions_dir.join("work"),
|
||||
),
|
||||
wasm_extensions: Vec::new(),
|
||||
needs_reload: false,
|
||||
extension_changes: ExtensionChanges::default(),
|
||||
modified_extensions: Default::default(),
|
||||
fs,
|
||||
http_client,
|
||||
language_registry,
|
||||
@ -194,7 +263,8 @@ impl ExtensionStore {
|
||||
|
||||
if let Some(manifest_content) = manifest_content.log_err() {
|
||||
if let Some(manifest) = serde_json::from_str(&manifest_content).log_err() {
|
||||
self.manifest_updated(manifest, cx);
|
||||
// TODO: don't detach
|
||||
self.extensions_updated(manifest, cx).detach();
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,11 +291,15 @@ impl ExtensionStore {
|
||||
return ExtensionStatus::Removing;
|
||||
}
|
||||
|
||||
let installed_version = self.manifest.read().extensions.get(extension_id).cloned();
|
||||
let installed_version = self
|
||||
.extension_index
|
||||
.extensions
|
||||
.get(extension_id)
|
||||
.map(|manifest| manifest.version.clone());
|
||||
let is_installing = self.extensions_being_installed.contains(extension_id);
|
||||
match (installed_version, is_installing) {
|
||||
(Some(_), true) => ExtensionStatus::Upgrading,
|
||||
(Some(version), false) => ExtensionStatus::Installed(version.clone()),
|
||||
(Some(version), false) => ExtensionStatus::Installed(version),
|
||||
(None, true) => ExtensionStatus::Installing,
|
||||
(None, false) => ExtensionStatus::NotInstalled,
|
||||
}
|
||||
@ -235,7 +309,7 @@ impl ExtensionStore {
|
||||
&self,
|
||||
search: Option<&str>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Extension>>> {
|
||||
) -> Task<Result<Vec<ExtensionApiResponse>>> {
|
||||
let url = self.http_client.build_zed_api_url(&format!(
|
||||
"/extensions{query}",
|
||||
query = search
|
||||
@ -335,7 +409,11 @@ impl ExtensionStore {
|
||||
/// no longer in the manifest, or whose files have changed on disk.
|
||||
/// Then it loads any themes, languages, or grammars that are newly
|
||||
/// added to the manifest, or whose files have changed on disk.
|
||||
fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
|
||||
fn extensions_updated(
|
||||
&mut self,
|
||||
new_index: ExtensionIndex,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
fn diff<'a, T, I1, I2>(
|
||||
old_keys: I1,
|
||||
new_keys: I2,
|
||||
@ -379,54 +457,104 @@ impl ExtensionStore {
|
||||
}
|
||||
}
|
||||
|
||||
let old_manifest = self.manifest.read();
|
||||
let (languages_to_remove, languages_to_add) = diff(
|
||||
old_manifest.languages.iter(),
|
||||
manifest.languages.iter(),
|
||||
&self.extension_changes.languages,
|
||||
let old_index = &self.extension_index;
|
||||
let (extensions_to_unload, extensions_to_load) = diff(
|
||||
old_index.extensions.iter(),
|
||||
new_index.extensions.iter(),
|
||||
&self.modified_extensions,
|
||||
);
|
||||
let (grammars_to_remove, grammars_to_add) = diff(
|
||||
old_manifest.grammars.iter(),
|
||||
manifest.grammars.iter(),
|
||||
&self.extension_changes.grammars,
|
||||
);
|
||||
let (themes_to_remove, themes_to_add) = diff(
|
||||
old_manifest.themes.iter(),
|
||||
manifest.themes.iter(),
|
||||
&self.extension_changes.themes,
|
||||
);
|
||||
self.extension_changes.clear();
|
||||
drop(old_manifest);
|
||||
self.modified_extensions.clear();
|
||||
|
||||
let themes_to_remove = &themes_to_remove
|
||||
.into_iter()
|
||||
.map(|theme| theme.into())
|
||||
let themes_to_remove = old_index
|
||||
.themes
|
||||
.iter()
|
||||
.filter_map(|(name, entry)| {
|
||||
if extensions_to_unload.contains(&entry.extension) {
|
||||
Some(name.clone().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let languages_to_remove = old_index
|
||||
.languages
|
||||
.iter()
|
||||
.filter_map(|(name, entry)| {
|
||||
if extensions_to_unload.contains(&entry.extension) {
|
||||
Some(name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let empty = Default::default();
|
||||
let grammars_to_remove = extensions_to_unload
|
||||
.iter()
|
||||
.flat_map(|extension_id| {
|
||||
old_index
|
||||
.extensions
|
||||
.get(extension_id)
|
||||
.map_or(&empty, |extension| &extension.grammars)
|
||||
.keys()
|
||||
.cloned()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.wasm_extensions
|
||||
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
|
||||
|
||||
for extension_id in &extensions_to_unload {
|
||||
if let Some(extension) = old_index.extensions.get(extension_id) {
|
||||
for (language_server_name, config) in extension.language_servers.iter() {
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(config.language.as_ref(), language_server_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.theme_registry.remove_user_themes(&themes_to_remove);
|
||||
self.language_registry
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
|
||||
self.language_registry
|
||||
.register_wasm_grammars(grammars_to_add.iter().map(|grammar_name| {
|
||||
let grammar = manifest.grammars.get(grammar_name).unwrap();
|
||||
let languages_to_add = new_index
|
||||
.languages
|
||||
.iter()
|
||||
.filter(|(_, entry)| extensions_to_load.contains(&entry.extension))
|
||||
.collect::<Vec<_>>();
|
||||
let mut grammars_to_add = Vec::new();
|
||||
let mut themes_to_add = Vec::new();
|
||||
for extension_id in &extensions_to_load {
|
||||
let Some(extension) = new_index.extensions.get(extension_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
grammars_to_add.extend(extension.grammars.keys().map(|grammar_name| {
|
||||
let mut grammar_path = self.extensions_dir.clone();
|
||||
grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
|
||||
grammar_path.extend([extension_id.as_ref(), "grammars"]);
|
||||
grammar_path.push(grammar_name.as_ref());
|
||||
grammar_path.set_extension("wasm");
|
||||
(grammar_name.clone(), grammar_path)
|
||||
}));
|
||||
themes_to_add.extend(extension.themes.iter().map(|theme_path| {
|
||||
let mut path = self.extensions_dir.clone();
|
||||
path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
|
||||
path
|
||||
}));
|
||||
}
|
||||
|
||||
for language_name in &languages_to_add {
|
||||
if language_name.as_ref() == "Swift" {
|
||||
continue;
|
||||
}
|
||||
self.language_registry
|
||||
.register_wasm_grammars(grammars_to_add);
|
||||
|
||||
let language = manifest.languages.get(language_name.as_ref()).unwrap();
|
||||
for (language_name, language) in languages_to_add {
|
||||
let mut language_path = self.extensions_dir.clone();
|
||||
language_path.extend([language.extension.as_ref(), language.path.as_path()]);
|
||||
language_path.extend([
|
||||
Path::new(language.extension.as_ref()),
|
||||
language.path.as_path(),
|
||||
]);
|
||||
self.language_registry.register_language(
|
||||
language_name.clone(),
|
||||
language.grammar.clone(),
|
||||
language.matcher.clone(),
|
||||
vec![],
|
||||
move || {
|
||||
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
||||
let config: LanguageConfig = ::toml::from_str(&config)?;
|
||||
@ -436,107 +564,119 @@ impl ExtensionStore {
|
||||
);
|
||||
}
|
||||
|
||||
let (reload_theme_tx, mut reload_theme_rx) = unbounded();
|
||||
let fs = self.fs.clone();
|
||||
let wasm_host = self.wasm_host.clone();
|
||||
let root_dir = self.extensions_dir.clone();
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
let themes = themes_to_add
|
||||
let extension_manifests = extensions_to_load
|
||||
.iter()
|
||||
.filter_map(|name| manifest.themes.get(name).cloned())
|
||||
.filter_map(|name| new_index.extensions.get(name).cloned())
|
||||
.collect::<Vec<_>>();
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
for theme in &themes {
|
||||
let mut theme_path = root_dir.clone();
|
||||
theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
|
||||
|
||||
theme_registry
|
||||
.load_user_theme(&theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
reload_theme_tx.unbounded_send(()).ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.spawn(|_, cx| async move {
|
||||
while let Some(_) = reload_theme_rx.next().await {
|
||||
if cx
|
||||
.update(|cx| ThemeSettings::reload_current_theme(cx))
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
*self.manifest.write() = manifest;
|
||||
self.extension_index = new_index;
|
||||
cx.notify();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for theme_path in &themes_to_add {
|
||||
theme_registry
|
||||
.load_user_theme(&theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut wasm_extensions = Vec::new();
|
||||
for extension_manifest in extension_manifests {
|
||||
let Some(wasm_path) = &extension_manifest.lib.path else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut path = root_dir.clone();
|
||||
path.extend([
|
||||
Path::new(extension_manifest.id.as_ref()),
|
||||
wasm_path.as_path(),
|
||||
]);
|
||||
let mut wasm_file = fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
let wasm_extension = wasm_host
|
||||
.load_extension(
|
||||
wasm_bytes,
|
||||
extension_manifest.clone(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.context("failed to load wasm extension")?;
|
||||
wasm_extensions.push((extension_manifest.clone(), wasm_extension));
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (manifest, wasm_extension) in &wasm_extensions {
|
||||
for (language_server_name, language_server_config) in &manifest.language_servers
|
||||
{
|
||||
this.language_registry.register_lsp_adapter(
|
||||
language_server_config.language.clone(),
|
||||
Arc::new(ExtensionLspAdapter {
|
||||
extension: wasm_extension.clone(),
|
||||
work_dir: this.wasm_host.work_dir.join(manifest.id.as_ref()),
|
||||
config: wit::LanguageServerConfig {
|
||||
name: language_server_name.0.to_string(),
|
||||
language_name: language_server_config.language.to_string(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
})
|
||||
.ok();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn watch_extensions_dir(&self, cx: &mut ModelContext<Self>) -> [Task<()>; 2] {
|
||||
let manifest = self.manifest.clone();
|
||||
let fs = self.fs.clone();
|
||||
let extensions_dir = self.extensions_dir.clone();
|
||||
|
||||
let (changes_tx, mut changes_rx) = unbounded();
|
||||
let (changed_extensions_tx, mut changed_extensions_rx) = unbounded();
|
||||
|
||||
let events_task = cx.background_executor().spawn(async move {
|
||||
let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await;
|
||||
while let Some(events) = events.next().await {
|
||||
let mut changed_grammars = HashSet::default();
|
||||
let mut changed_languages = HashSet::default();
|
||||
let mut changed_themes = HashSet::default();
|
||||
for event in events {
|
||||
let Ok(event_path) = event.path.strip_prefix(&extensions_dir) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
{
|
||||
let manifest = manifest.read();
|
||||
for event in events {
|
||||
for (grammar_name, grammar) in &manifest.grammars {
|
||||
let mut grammar_path = extensions_dir.clone();
|
||||
grammar_path
|
||||
.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
|
||||
if event.path.starts_with(&grammar_path) || event.path == grammar_path {
|
||||
changed_grammars.insert(grammar_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (language_name, language) in &manifest.languages {
|
||||
let mut language_path = extensions_dir.clone();
|
||||
language_path
|
||||
.extend([language.extension.as_ref(), language.path.as_path()]);
|
||||
if event.path.starts_with(&language_path) || event.path == language_path
|
||||
{
|
||||
changed_languages.insert(language_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (theme_name, theme) in &manifest.themes {
|
||||
let mut theme_path = extensions_dir.clone();
|
||||
theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
|
||||
if event.path.starts_with(&theme_path) || event.path == theme_path {
|
||||
changed_themes.insert(theme_name.clone());
|
||||
}
|
||||
if let Some(path::Component::Normal(extension_dir_name)) =
|
||||
event_path.components().next()
|
||||
{
|
||||
if let Some(extension_id) = extension_dir_name.to_str() {
|
||||
changed_extensions_tx
|
||||
.unbounded_send(Arc::from(extension_id))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changes_tx
|
||||
.unbounded_send(ExtensionChanges {
|
||||
languages: changed_languages,
|
||||
grammars: changed_grammars,
|
||||
themes: changed_themes,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
let reload_task = cx.spawn(|this, mut cx| async move {
|
||||
while let Some(changes) = changes_rx.next().await {
|
||||
while let Some(changed_extension_id) = changed_extensions_rx.next().await {
|
||||
if this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.extension_changes.merge(changes);
|
||||
this.modified_extensions.insert(changed_extension_id);
|
||||
this.reload(cx);
|
||||
})
|
||||
.is_err()
|
||||
@ -556,16 +696,18 @@ impl ExtensionStore {
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
let work_dir = self.wasm_host.work_dir.clone();
|
||||
let extensions_dir = self.extensions_dir.clone();
|
||||
let manifest_path = self.manifest_path.clone();
|
||||
self.needs_reload = false;
|
||||
self.reload_task = Some(cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let manifest = cx
|
||||
let extension_index = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let mut manifest = Manifest::default();
|
||||
let mut index = ExtensionIndex::default();
|
||||
|
||||
fs.create_dir(&work_dir).await.log_err();
|
||||
fs.create_dir(&extensions_dir).await.log_err();
|
||||
|
||||
let extension_paths = fs.read_dir(&extensions_dir).await;
|
||||
@ -574,20 +716,16 @@ impl ExtensionStore {
|
||||
let Ok(extension_dir) = extension_dir else {
|
||||
continue;
|
||||
};
|
||||
Self::add_extension_to_manifest(
|
||||
fs.clone(),
|
||||
extension_dir,
|
||||
&mut manifest,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
Self::add_extension_to_index(fs.clone(), extension_dir, &mut index)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(manifest_json) = serde_json::to_string_pretty(&manifest) {
|
||||
if let Ok(index_json) = serde_json::to_string_pretty(&index) {
|
||||
fs.save(
|
||||
&manifest_path,
|
||||
&manifest_json.as_str().into(),
|
||||
&index_json.as_str().into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@ -595,12 +733,17 @@ impl ExtensionStore {
|
||||
.log_err();
|
||||
}
|
||||
|
||||
manifest
|
||||
index
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Ok(task) = this.update(&mut cx, |this, cx| {
|
||||
this.extensions_updated(extension_index, cx)
|
||||
}) {
|
||||
task.await.log_err();
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.manifest_updated(manifest, cx);
|
||||
this.reload_task.take();
|
||||
if this.needs_reload {
|
||||
this.reload(cx);
|
||||
@ -611,52 +754,65 @@ impl ExtensionStore {
|
||||
}));
|
||||
}
|
||||
|
||||
async fn add_extension_to_manifest(
|
||||
async fn add_extension_to_index(
|
||||
fs: Arc<dyn Fs>,
|
||||
extension_dir: PathBuf,
|
||||
manifest: &mut Manifest,
|
||||
index: &mut ExtensionIndex,
|
||||
) -> Result<()> {
|
||||
let extension_name = extension_dir
|
||||
.file_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or_else(|| anyhow!("invalid extension name"))?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtensionJson {
|
||||
pub version: String,
|
||||
}
|
||||
let mut extension_manifest_path = extension_dir.join("extension.json");
|
||||
let mut extension_manifest;
|
||||
if fs.is_file(&extension_manifest_path).await {
|
||||
let manifest_content = fs
|
||||
.load(&extension_manifest_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to load {extension_name} extension.json"))?;
|
||||
let manifest_json = serde_json::from_str::<OldExtensionManifest>(&manifest_content)
|
||||
.with_context(|| {
|
||||
format!("invalid extension.json for extension {extension_name}")
|
||||
})?;
|
||||
|
||||
let extension_json_path = extension_dir.join("extension.json");
|
||||
let extension_json = fs
|
||||
.load(&extension_json_path)
|
||||
.await
|
||||
.context("failed to load extension.json")?;
|
||||
let extension_json: ExtensionJson =
|
||||
serde_json::from_str(&extension_json).context("invalid extension.json")?;
|
||||
|
||||
manifest
|
||||
.extensions
|
||||
.insert(extension_name.into(), extension_json.version.into());
|
||||
|
||||
if let Ok(mut grammar_paths) = fs.read_dir(&extension_dir.join("grammars")).await {
|
||||
while let Some(grammar_path) = grammar_paths.next().await {
|
||||
let grammar_path = grammar_path?;
|
||||
let Ok(relative_path) = grammar_path.strip_prefix(&extension_dir) else {
|
||||
continue;
|
||||
};
|
||||
let Some(grammar_name) = grammar_path.file_stem().and_then(OsStr::to_str) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
manifest.grammars.insert(
|
||||
grammar_name.into(),
|
||||
GrammarManifestEntry {
|
||||
extension: extension_name.into(),
|
||||
path: relative_path.into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
extension_manifest = ExtensionManifest {
|
||||
id: extension_name.into(),
|
||||
name: manifest_json.name,
|
||||
version: manifest_json.version,
|
||||
description: manifest_json.description,
|
||||
repository: manifest_json.repository,
|
||||
authors: manifest_json.authors,
|
||||
lib: Default::default(),
|
||||
themes: {
|
||||
let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
|
||||
themes.sort();
|
||||
themes.dedup();
|
||||
themes
|
||||
},
|
||||
languages: {
|
||||
let mut languages = manifest_json.languages.into_values().collect::<Vec<_>>();
|
||||
languages.sort();
|
||||
languages.dedup();
|
||||
languages
|
||||
},
|
||||
grammars: manifest_json
|
||||
.grammars
|
||||
.into_iter()
|
||||
.map(|(grammar_name, _)| (grammar_name, Default::default()))
|
||||
.collect(),
|
||||
language_servers: Default::default(),
|
||||
};
|
||||
} else {
|
||||
extension_manifest_path.set_extension("toml");
|
||||
let manifest_content = fs
|
||||
.load(&extension_manifest_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to load {extension_name} extension.toml"))?;
|
||||
extension_manifest = ::toml::from_str(&manifest_content).with_context(|| {
|
||||
format!("invalid extension.json for extension {extension_name}")
|
||||
})?;
|
||||
};
|
||||
|
||||
if let Ok(mut language_paths) = fs.read_dir(&extension_dir.join("languages")).await {
|
||||
while let Some(language_path) = language_paths.next().await {
|
||||
@ -673,11 +829,16 @@ impl ExtensionStore {
|
||||
let config = fs.load(&language_path.join("config.toml")).await?;
|
||||
let config = ::toml::from_str::<LanguageConfig>(&config)?;
|
||||
|
||||
manifest.languages.insert(
|
||||
let relative_path = relative_path.to_path_buf();
|
||||
if !extension_manifest.languages.contains(&relative_path) {
|
||||
extension_manifest.languages.push(relative_path.clone());
|
||||
}
|
||||
|
||||
index.languages.insert(
|
||||
config.name.clone(),
|
||||
LanguageManifestEntry {
|
||||
ExtensionIndexLanguageEntry {
|
||||
extension: extension_name.into(),
|
||||
path: relative_path.into(),
|
||||
path: relative_path,
|
||||
matcher: config.matcher,
|
||||
grammar: config.grammar,
|
||||
},
|
||||
@ -699,35 +860,39 @@ impl ExtensionStore {
|
||||
continue;
|
||||
};
|
||||
|
||||
for theme in theme_family.themes {
|
||||
let location = ThemeManifestEntry {
|
||||
extension: extension_name.into(),
|
||||
path: relative_path.into(),
|
||||
};
|
||||
let relative_path = relative_path.to_path_buf();
|
||||
if !extension_manifest.themes.contains(&relative_path) {
|
||||
extension_manifest.themes.push(relative_path.clone());
|
||||
}
|
||||
|
||||
manifest.themes.insert(theme.name.into(), location);
|
||||
for theme in theme_family.themes {
|
||||
index.themes.insert(
|
||||
theme.name.into(),
|
||||
ExtensionIndexEntry {
|
||||
extension: extension_name.into(),
|
||||
path: relative_path.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let default_extension_wasm_path = extension_dir.join("extension.wasm");
|
||||
if fs.is_file(&default_extension_wasm_path).await {
|
||||
extension_manifest
|
||||
.lib
|
||||
.path
|
||||
.get_or_insert(default_extension_wasm_path);
|
||||
}
|
||||
|
||||
index
|
||||
.extensions
|
||||
.insert(extension_name.into(), Arc::new(extension_manifest));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionChanges {
|
||||
fn clear(&mut self) {
|
||||
self.grammars.clear();
|
||||
self.languages.clear();
|
||||
self.themes.clear();
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: Self) {
|
||||
self.grammars.extend(other.grammars);
|
||||
self.languages.extend(other.languages);
|
||||
self.themes.extend(other.themes);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
|
||||
let mut result = LanguageQueries::default();
|
||||
if let Some(entries) = std::fs::read_dir(root_path).log_err() {
|
||||
|
@ -1,14 +1,27 @@
|
||||
use crate::{
|
||||
ExtensionStore, GrammarManifestEntry, LanguageManifestEntry, Manifest, ThemeManifestEntry,
|
||||
ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry, ExtensionManifest,
|
||||
ExtensionStore, GrammarManifestEntry,
|
||||
};
|
||||
use fs::FakeFs;
|
||||
use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
use fs::{FakeFs, Fs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, TestAppContext};
|
||||
use language::{LanguageMatcher, LanguageRegistry};
|
||||
use language::{
|
||||
Language, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus,
|
||||
LanguageServerName,
|
||||
};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ThemeRegistry;
|
||||
use util::http::FakeHttpClient;
|
||||
use util::http::{FakeHttpClient, Response};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
@ -29,7 +42,13 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
"extension.json": r#"{
|
||||
"id": "zed-monokai",
|
||||
"name": "Zed Monokai",
|
||||
"version": "2.0.0"
|
||||
"version": "2.0.0",
|
||||
"themes": {
|
||||
"Monokai Dark": "themes/monokai.json",
|
||||
"Monokai Light": "themes/monokai.json",
|
||||
"Monokai Pro Dark": "themes/monokai-pro.json",
|
||||
"Monokai Pro Light": "themes/monokai-pro.json"
|
||||
}
|
||||
}"#,
|
||||
"themes": {
|
||||
"monokai.json": r#"{
|
||||
@ -70,7 +89,15 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
"extension.json": r#"{
|
||||
"id": "zed-ruby",
|
||||
"name": "Zed Ruby",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.0",
|
||||
"grammars": {
|
||||
"ruby": "grammars/ruby.wasm",
|
||||
"embedded_template": "grammars/embedded_template.wasm"
|
||||
},
|
||||
"languages": {
|
||||
"ruby": "languages/ruby",
|
||||
"erb": "languages/erb"
|
||||
}
|
||||
}"#,
|
||||
"grammars": {
|
||||
"ruby.wasm": "",
|
||||
@ -100,27 +127,49 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut expected_manifest = Manifest {
|
||||
let mut expected_index = ExtensionIndex {
|
||||
extensions: [
|
||||
("zed-ruby".into(), "1.0.0".into()),
|
||||
("zed-monokai".into(), "2.0.0".into()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
grammars: [
|
||||
(
|
||||
"embedded_template".into(),
|
||||
GrammarManifestEntry {
|
||||
extension: "zed-ruby".into(),
|
||||
path: "grammars/embedded_template.wasm".into(),
|
||||
},
|
||||
"zed-ruby".into(),
|
||||
ExtensionManifest {
|
||||
id: "zed-ruby".into(),
|
||||
name: "Zed Ruby".into(),
|
||||
version: "1.0.0".into(),
|
||||
description: None,
|
||||
authors: Vec::new(),
|
||||
repository: None,
|
||||
themes: Default::default(),
|
||||
lib: Default::default(),
|
||||
languages: vec!["languages/erb".into(), "languages/ruby".into()],
|
||||
grammars: [
|
||||
("embedded_template".into(), GrammarManifestEntry::default()),
|
||||
("ruby".into(), GrammarManifestEntry::default()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
language_servers: BTreeMap::default(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
(
|
||||
"ruby".into(),
|
||||
GrammarManifestEntry {
|
||||
extension: "zed-ruby".into(),
|
||||
path: "grammars/ruby.wasm".into(),
|
||||
},
|
||||
"zed-monokai".into(),
|
||||
ExtensionManifest {
|
||||
id: "zed-monokai".into(),
|
||||
name: "Zed Monokai".into(),
|
||||
version: "2.0.0".into(),
|
||||
description: None,
|
||||
authors: vec![],
|
||||
repository: None,
|
||||
themes: vec![
|
||||
"themes/monokai-pro.json".into(),
|
||||
"themes/monokai.json".into(),
|
||||
],
|
||||
lib: Default::default(),
|
||||
languages: Default::default(),
|
||||
grammars: BTreeMap::default(),
|
||||
language_servers: BTreeMap::default(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
@ -128,7 +177,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
languages: [
|
||||
(
|
||||
"ERB".into(),
|
||||
LanguageManifestEntry {
|
||||
ExtensionIndexLanguageEntry {
|
||||
extension: "zed-ruby".into(),
|
||||
path: "languages/erb".into(),
|
||||
grammar: Some("embedded_template".into()),
|
||||
@ -140,7 +189,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
),
|
||||
(
|
||||
"Ruby".into(),
|
||||
LanguageManifestEntry {
|
||||
ExtensionIndexLanguageEntry {
|
||||
extension: "zed-ruby".into(),
|
||||
path: "languages/ruby".into(),
|
||||
grammar: Some("ruby".into()),
|
||||
@ -156,28 +205,28 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
themes: [
|
||||
(
|
||||
"Monokai Dark".into(),
|
||||
ThemeManifestEntry {
|
||||
ExtensionIndexEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai.json".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Monokai Light".into(),
|
||||
ThemeManifestEntry {
|
||||
ExtensionIndexEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai.json".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Monokai Pro Dark".into(),
|
||||
ThemeManifestEntry {
|
||||
ExtensionIndexEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai-pro.json".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Monokai Pro Light".into(),
|
||||
ThemeManifestEntry {
|
||||
ExtensionIndexEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai-pro.json".into(),
|
||||
},
|
||||
@ -189,12 +238,14 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test());
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
|
||||
let store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
cx,
|
||||
@ -203,10 +254,10 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
store.read_with(cx, |store, _| {
|
||||
let manifest = store.manifest.read();
|
||||
assert_eq!(manifest.grammars, expected_manifest.grammars);
|
||||
assert_eq!(manifest.languages, expected_manifest.languages);
|
||||
assert_eq!(manifest.themes, expected_manifest.themes);
|
||||
let index = &store.extension_index;
|
||||
assert_eq!(index.extensions, expected_index.extensions);
|
||||
assert_eq!(index.languages, expected_index.languages);
|
||||
assert_eq!(index.themes, expected_index.themes);
|
||||
|
||||
assert_eq!(
|
||||
language_registry.language_names(),
|
||||
@ -230,7 +281,10 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
"extension.json": r#"{
|
||||
"id": "zed-gruvbox",
|
||||
"name": "Zed Gruvbox",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.0",
|
||||
"themes": {
|
||||
"Gruvbox": "themes/gruvbox.json"
|
||||
}
|
||||
}"#,
|
||||
"themes": {
|
||||
"gruvbox.json": r#"{
|
||||
@ -249,9 +303,26 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
)
|
||||
.await;
|
||||
|
||||
expected_manifest.themes.insert(
|
||||
expected_index.extensions.insert(
|
||||
"zed-gruvbox".into(),
|
||||
ExtensionManifest {
|
||||
id: "zed-gruvbox".into(),
|
||||
name: "Zed Gruvbox".into(),
|
||||
version: "1.0.0".into(),
|
||||
description: None,
|
||||
authors: vec![],
|
||||
repository: None,
|
||||
themes: vec!["themes/gruvbox.json".into()],
|
||||
lib: Default::default(),
|
||||
languages: Default::default(),
|
||||
grammars: BTreeMap::default(),
|
||||
language_servers: BTreeMap::default(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
expected_index.themes.insert(
|
||||
"Gruvbox".into(),
|
||||
ThemeManifestEntry {
|
||||
ExtensionIndexEntry {
|
||||
extension: "zed-gruvbox".into(),
|
||||
path: "themes/gruvbox.json".into(),
|
||||
},
|
||||
@ -261,10 +332,10 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
store.read_with(cx, |store, _| {
|
||||
let manifest = store.manifest.read();
|
||||
assert_eq!(manifest.grammars, expected_manifest.grammars);
|
||||
assert_eq!(manifest.languages, expected_manifest.languages);
|
||||
assert_eq!(manifest.themes, expected_manifest.themes);
|
||||
let index = &store.extension_index;
|
||||
assert_eq!(index.extensions, expected_index.extensions);
|
||||
assert_eq!(index.languages, expected_index.languages);
|
||||
assert_eq!(index.themes, expected_index.themes);
|
||||
|
||||
assert_eq!(
|
||||
theme_registry.list_names(false),
|
||||
@ -289,6 +360,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
cx,
|
||||
@ -297,11 +369,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
store.read_with(cx, |store, _| {
|
||||
let manifest = store.manifest.read();
|
||||
assert_eq!(manifest.grammars, expected_manifest.grammars);
|
||||
assert_eq!(manifest.languages, expected_manifest.languages);
|
||||
assert_eq!(manifest.themes, expected_manifest.themes);
|
||||
|
||||
assert_eq!(store.extension_index, expected_index);
|
||||
assert_eq!(
|
||||
language_registry.language_names(),
|
||||
["ERB", "Plain Text", "Ruby"]
|
||||
@ -333,19 +401,204 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
expected_manifest.extensions.remove("zed-ruby");
|
||||
expected_manifest.languages.remove("Ruby");
|
||||
expected_manifest.languages.remove("ERB");
|
||||
expected_manifest.grammars.remove("ruby");
|
||||
expected_manifest.grammars.remove("embedded_template");
|
||||
expected_index.extensions.remove("zed-ruby");
|
||||
expected_index.languages.remove("Ruby");
|
||||
expected_index.languages.remove("ERB");
|
||||
|
||||
store.read_with(cx, |store, _| {
|
||||
let manifest = store.manifest.read();
|
||||
assert_eq!(manifest.grammars, expected_manifest.grammars);
|
||||
assert_eq!(manifest.languages, expected_manifest.languages);
|
||||
assert_eq!(manifest.themes, expected_manifest.themes);
|
||||
|
||||
assert_eq!(store.extension_index, expected_index);
|
||||
assert_eq!(language_registry.language_names(), ["Plain Text"]);
|
||||
assert_eq!(language_registry.grammar_names(), []);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let gleam_extension_dir = PathBuf::from_iter([
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"..",
|
||||
"..",
|
||||
"extensions",
|
||||
"gleam",
|
||||
])
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
|
||||
compile_extension("zed_gleam", &gleam_extension_dir);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/the-extension-dir", json!({ "installed": {} }))
|
||||
.await;
|
||||
fs.insert_tree_from_real_fs("/the-extension-dir/installed/gleam", gleam_extension_dir)
|
||||
.await;
|
||||
|
||||
fs.insert_tree(
|
||||
"/the-project-dir",
|
||||
json!({
|
||||
".tool-versions": "rust 1.73.0",
|
||||
"test.gleam": ""
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/the-project-dir".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
|
||||
let mut status_updates = language_registry.language_server_binary_statuses();
|
||||
|
||||
let http_client = FakeHttpClient::create({
|
||||
move |request| async move {
|
||||
match request.uri().to_string().as_str() {
|
||||
"https://api.github.com/repos/gleam-lang/gleam/releases" => Ok(Response::new(
|
||||
json!([
|
||||
{
|
||||
"tag_name": "v1.2.3",
|
||||
"prerelease": false,
|
||||
"tarball_url": "",
|
||||
"zipball_url": "",
|
||||
"assets": [
|
||||
{
|
||||
"name": "gleam-v1.2.3-aarch64-apple-darwin.tar.gz",
|
||||
"browser_download_url": "http://example.com/the-download"
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
.to_string()
|
||||
.into(),
|
||||
)),
|
||||
|
||||
"http://example.com/the-download" => {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
let mut archive = async_tar::Builder::new(&mut bytes);
|
||||
let mut header = async_tar::Header::new_gnu();
|
||||
let content = "the-gleam-binary-contents".as_bytes();
|
||||
header.set_size(content.len() as u64);
|
||||
archive
|
||||
.append_data(&mut header, "gleam", content)
|
||||
.await
|
||||
.unwrap();
|
||||
archive.into_inner().await.unwrap();
|
||||
|
||||
let mut gzipped_bytes = Vec::new();
|
||||
let mut encoder = GzipEncoder::new(BufReader::new(bytes.as_slice()));
|
||||
encoder.read_to_end(&mut gzipped_bytes).await.unwrap();
|
||||
|
||||
Ok(Response::new(gzipped_bytes.into()))
|
||||
}
|
||||
|
||||
_ => Ok(Response::builder().status(404).body("not found".into())?),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let mut fake_servers = language_registry.fake_language_servers("Gleam");
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/the-project-dir/test.gleam", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.set_language_for_buffer(
|
||||
&buffer,
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Gleam".into(),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs.load("/the-extension-dir/work/gleam/gleam-v1.2.3/gleam".as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
"the-gleam-binary-contents"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
fake_server.binary.path,
|
||||
PathBuf::from("/the-extension-dir/work/gleam/gleam-v1.2.3/gleam")
|
||||
);
|
||||
assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
|
||||
|
||||
assert_eq!(
|
||||
[
|
||||
status_updates.next().await.unwrap(),
|
||||
status_updates.next().await.unwrap(),
|
||||
status_updates.next().await.unwrap(),
|
||||
],
|
||||
[
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
),
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
),
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::Downloaded
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
fn compile_extension(name: &str, extension_dir_path: &Path) {
|
||||
let output = std::process::Command::new("cargo")
|
||||
.args(["component", "build", "--target-dir"])
|
||||
.arg(extension_dir_path.join("target"))
|
||||
.current_dir(&extension_dir_path)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"failed to build component {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let mut wasm_path = PathBuf::from(extension_dir_path);
|
||||
wasm_path.extend(["target", "wasm32-wasi", "debug", name]);
|
||||
wasm_path.set_extension("wasm");
|
||||
|
||||
std::fs::rename(wasm_path, extension_dir_path.join("extension.wasm")).unwrap();
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
Project::init_settings(cx);
|
||||
language::init(cx);
|
||||
});
|
||||
}
|
||||
|
405
crates/extension/src/wasm_host.rs
Normal file
405
crates/extension/src/wasm_host.rs
Normal file
@ -0,0 +1,405 @@
|
||||
use crate::ExtensionManifest;
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
channel::{mpsc::UnboundedSender, oneshot},
|
||||
future::BoxFuture,
|
||||
io::BufReader,
|
||||
Future, FutureExt, StreamExt as _,
|
||||
};
|
||||
use gpui::BackgroundExecutor;
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LspAdapterDelegate};
|
||||
use node_runtime::NodeRuntime;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::{http::HttpClient, SemanticVersion};
|
||||
use wasmtime::{
|
||||
component::{Component, Linker, Resource, ResourceTable},
|
||||
Engine, Store,
|
||||
};
|
||||
use wasmtime_wasi::preview2::{command as wasi_command, WasiCtx, WasiCtxBuilder, WasiView};
|
||||
|
||||
pub mod wit {
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
path: "../extension_api/wit",
|
||||
with: {
|
||||
"worktree": super::ExtensionWorktree,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
|
||||
pub(crate) struct WasmHost {
|
||||
engine: Engine,
|
||||
linker: Arc<wasmtime::component::Linker<WasmState>>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
pub(crate) work_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WasmExtension {
|
||||
tx: UnboundedSender<ExtensionCall>,
|
||||
#[allow(unused)]
|
||||
zed_api_version: SemanticVersion,
|
||||
}
|
||||
|
||||
pub(crate) struct WasmState {
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
table: ResourceTable,
|
||||
ctx: WasiCtx,
|
||||
host: Arc<WasmHost>,
|
||||
}
|
||||
|
||||
type ExtensionCall = Box<
|
||||
dyn Send
|
||||
+ for<'a> FnOnce(&'a mut wit::Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, ()>,
|
||||
>;
|
||||
|
||||
static WASM_ENGINE: OnceLock<wasmtime::Engine> = OnceLock::new();
|
||||
|
||||
impl WasmHost {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
work_dir: PathBuf,
|
||||
) -> Arc<Self> {
|
||||
let engine = WASM_ENGINE
|
||||
.get_or_init(|| {
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.wasm_component_model(true);
|
||||
config.async_support(true);
|
||||
wasmtime::Engine::new(&config).unwrap()
|
||||
})
|
||||
.clone();
|
||||
let mut linker = Linker::new(&engine);
|
||||
wasi_command::add_to_linker(&mut linker).unwrap();
|
||||
wit::Extension::add_to_linker(&mut linker, |state: &mut WasmState| state).unwrap();
|
||||
Arc::new(Self {
|
||||
engine,
|
||||
linker: Arc::new(linker),
|
||||
fs,
|
||||
work_dir,
|
||||
http_client,
|
||||
node_runtime,
|
||||
language_registry,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_extension(
|
||||
self: &Arc<Self>,
|
||||
wasm_bytes: Vec<u8>,
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
executor: BackgroundExecutor,
|
||||
) -> impl 'static + Future<Output = Result<WasmExtension>> {
|
||||
let this = self.clone();
|
||||
async move {
|
||||
let component = Component::from_binary(&this.engine, &wasm_bytes)
|
||||
.context("failed to compile wasm component")?;
|
||||
|
||||
let mut zed_api_version = None;
|
||||
for part in wasmparser::Parser::new(0).parse_all(&wasm_bytes) {
|
||||
if let wasmparser::Payload::CustomSection(s) = part? {
|
||||
if s.name() == "zed:api-version" {
|
||||
if s.data().len() != 6 {
|
||||
bail!(
|
||||
"extension {} has invalid zed:api-version section: {:?}",
|
||||
manifest.id,
|
||||
s.data()
|
||||
);
|
||||
}
|
||||
|
||||
let major = u16::from_be_bytes(s.data()[0..2].try_into().unwrap()) as _;
|
||||
let minor = u16::from_be_bytes(s.data()[2..4].try_into().unwrap()) as _;
|
||||
let patch = u16::from_be_bytes(s.data()[4..6].try_into().unwrap()) as _;
|
||||
zed_api_version = Some(SemanticVersion {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(zed_api_version) = zed_api_version else {
|
||||
bail!("extension {} has no zed:api-version section", manifest.id);
|
||||
};
|
||||
|
||||
let mut store = wasmtime::Store::new(
|
||||
&this.engine,
|
||||
WasmState {
|
||||
manifest,
|
||||
table: ResourceTable::new(),
|
||||
ctx: WasiCtxBuilder::new()
|
||||
.inherit_stdio()
|
||||
.env("RUST_BACKTRACE", "1")
|
||||
.build(),
|
||||
host: this.clone(),
|
||||
},
|
||||
);
|
||||
let (mut extension, instance) =
|
||||
wit::Extension::instantiate_async(&mut store, &component, &this.linker)
|
||||
.await
|
||||
.context("failed to instantiate wasm component")?;
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded::<ExtensionCall>();
|
||||
executor
|
||||
.spawn(async move {
|
||||
extension.call_init_extension(&mut store).await.unwrap();
|
||||
|
||||
let _instance = instance;
|
||||
while let Some(call) = rx.next().await {
|
||||
(call)(&mut extension, &mut store).await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Ok(WasmExtension {
|
||||
tx,
|
||||
zed_api_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmExtension {
|
||||
pub async fn call<T, Fn>(&self, f: Fn) -> T
|
||||
where
|
||||
T: 'static + Send,
|
||||
Fn: 'static
|
||||
+ Send
|
||||
+ for<'a> FnOnce(&'a mut wit::Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, T>,
|
||||
{
|
||||
let (return_tx, return_rx) = oneshot::channel();
|
||||
self.tx
|
||||
.clone()
|
||||
.unbounded_send(Box::new(move |extension, store| {
|
||||
async {
|
||||
let result = f(extension, store).await;
|
||||
return_tx.send(result).ok();
|
||||
}
|
||||
.boxed()
|
||||
}))
|
||||
.expect("wasm extension channel should not be closed yet");
|
||||
return_rx.await.expect("wasm extension channel")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl wit::HostWorktree for WasmState {
|
||||
async fn read_text_file(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
let delegate = self.table().get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(path.into())
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<wit::Worktree>) -> Result<()> {
|
||||
// we only ever hand out borrows of worktrees
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl wit::ExtensionImports for WasmState {
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
async fn inner(this: &mut WasmState, package_name: String) -> anyhow::Result<String> {
|
||||
this.host
|
||||
.node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(inner(self, package_name)
|
||||
.await
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: wit::GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<wit::GithubRelease, String>> {
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
repo: String,
|
||||
options: wit::GithubReleaseOptions,
|
||||
) -> anyhow::Result<wit::GithubRelease> {
|
||||
let release = util::github::latest_github_release(
|
||||
&repo,
|
||||
options.require_assets,
|
||||
options.pre_release,
|
||||
this.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(wit::GithubRelease {
|
||||
version: release.tag_name,
|
||||
assets: release
|
||||
.assets
|
||||
.into_iter()
|
||||
.map(|asset| wit::GithubReleaseAsset {
|
||||
name: asset.name,
|
||||
download_url: asset.browser_download_url,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(inner(self, repo, options)
|
||||
.await
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
|
||||
async fn current_platform(&mut self) -> Result<(wit::Os, wit::Architecture)> {
|
||||
Ok((
|
||||
match std::env::consts::OS {
|
||||
"macos" => wit::Os::Mac,
|
||||
"linux" => wit::Os::Linux,
|
||||
"windows" => wit::Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match std::env::consts::ARCH {
|
||||
"aarch64" => wit::Architecture::Aarch64,
|
||||
"x86" => wit::Architecture::X86,
|
||||
"x86_64" => wit::Architecture::X8664,
|
||||
_ => panic!("unsupported architecture"),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
&mut self,
|
||||
server_name: String,
|
||||
status: wit::LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
let status = match status {
|
||||
wit::LanguageServerInstallationStatus::CheckingForUpdate => {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
}
|
||||
wit::LanguageServerInstallationStatus::Downloading => {
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
}
|
||||
wit::LanguageServerInstallationStatus::Downloaded => {
|
||||
LanguageServerBinaryStatus::Downloaded
|
||||
}
|
||||
wit::LanguageServerInstallationStatus::Cached => LanguageServerBinaryStatus::Cached,
|
||||
wit::LanguageServerInstallationStatus::Failed(error) => {
|
||||
LanguageServerBinaryStatus::Failed { error }
|
||||
}
|
||||
};
|
||||
|
||||
self.host
|
||||
.language_registry
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
&mut self,
|
||||
url: String,
|
||||
filename: String,
|
||||
file_type: wit::DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
url: String,
|
||||
filename: String,
|
||||
file_type: wit::DownloadedFileType,
|
||||
) -> anyhow::Result<()> {
|
||||
this.host.fs.create_dir(&this.host.work_dir).await?;
|
||||
let container_dir = this.host.work_dir.join(this.manifest.id.as_ref());
|
||||
let destination_path = container_dir.join(&filename);
|
||||
|
||||
let mut response = this
|
||||
.host
|
||||
.http_client
|
||||
.get(&url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
let body = BufReader::new(response.body_mut());
|
||||
|
||||
match file_type {
|
||||
wit::DownloadedFileType::Uncompressed => {
|
||||
futures::pin_mut!(body);
|
||||
this.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
wit::DownloadedFileType::Gzip => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
this.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
wit::DownloadedFileType::GzipTar => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
this.host
|
||||
.fs
|
||||
.extract_tar_file(&destination_path, Archive::new(body))
|
||||
.await?;
|
||||
}
|
||||
wit::DownloadedFileType::Zip => {
|
||||
let zip_filename = format!("{filename}.zip");
|
||||
let mut zip_path = destination_path.clone();
|
||||
zip_path.set_file_name(zip_filename);
|
||||
futures::pin_mut!(body);
|
||||
this.host.fs.create_file_with(&zip_path, body).await?;
|
||||
|
||||
let unzip_status = std::process::Command::new("unzip")
|
||||
.current_dir(&container_dir)
|
||||
.arg(&zip_path)
|
||||
.output()?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip {filename} archive"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(inner(self, url, filename, file_type)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiView for WasmState {
|
||||
fn table(&mut self) -> &mut ResourceTable {
|
||||
&mut self.table
|
||||
}
|
||||
|
||||
fn ctx(&mut self) -> &mut WasiCtx {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
14
crates/extension_api/Cargo.toml
Normal file
14
crates/extension_api/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "zed_extension_api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lib]
|
||||
path = "src/extension_api.rs"
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = "0.18"
|
||||
|
||||
[package.metadata.component]
|
||||
target = { path = "wit" }
|
1
crates/extension_api/LICENSE-APACHE
Symbolic link
1
crates/extension_api/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
15
crates/extension_api/build.rs
Normal file
15
crates/extension_api/build.rs
Normal file
@ -0,0 +1,15 @@
|
||||
fn main() {
|
||||
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
|
||||
let mut parts = version.split(|c: char| !c.is_digit(10));
|
||||
let major = parts.next().unwrap().parse::<u16>().unwrap().to_be_bytes();
|
||||
let minor = parts.next().unwrap().parse::<u16>().unwrap().to_be_bytes();
|
||||
let patch = parts.next().unwrap().parse::<u16>().unwrap().to_be_bytes();
|
||||
|
||||
std::fs::write(
|
||||
std::path::Path::new(&out_dir).join("version_bytes"),
|
||||
[major[0], major[1], minor[0], minor[1], patch[0], patch[1]],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
62
crates/extension_api/src/extension_api.rs
Normal file
62
crates/extension_api/src/extension_api.rs
Normal file
@ -0,0 +1,62 @@
|
||||
pub struct Guest;
|
||||
pub use wit::*;
|
||||
|
||||
pub type Result<T, E = String> = core::result::Result<T, E>;
|
||||
|
||||
pub trait Extension: Send + Sync {
|
||||
fn new() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
config: wit::LanguageServerConfig,
|
||||
worktree: &wit::Worktree,
|
||||
) -> Result<Command>;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_extension {
|
||||
($extension_type:ty) => {
|
||||
#[export_name = "init-extension"]
|
||||
pub extern "C" fn __init_extension() {
|
||||
zed_extension_api::register_extension(|| {
|
||||
Box::new(<$extension_type as zed_extension_api::Extension>::new())
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
|
||||
unsafe { EXTENSION = Some((build_extension)()) }
|
||||
}
|
||||
|
||||
fn extension() -> &'static mut dyn Extension {
|
||||
unsafe { EXTENSION.as_deref_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut EXTENSION: Option<Box<dyn Extension>> = None;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[link_section = "zed:api-version"]
|
||||
#[doc(hidden)]
|
||||
pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
|
||||
|
||||
mod wit {
|
||||
wit_bindgen::generate!({
|
||||
exports: { world: super::Component },
|
||||
skip: ["init-extension"]
|
||||
});
|
||||
}
|
||||
|
||||
struct Component;
|
||||
|
||||
impl wit::Guest for Component {
|
||||
fn language_server_command(
|
||||
config: wit::LanguageServerConfig,
|
||||
worktree: &wit::Worktree,
|
||||
) -> Result<wit::Command> {
|
||||
extension().language_server_command(config, worktree)
|
||||
}
|
||||
}
|
80
crates/extension_api/wit/extension.wit
Normal file
80
crates/extension_api/wit/extension.wit
Normal file
@ -0,0 +1,80 @@
|
||||
package zed:extension;
|
||||
|
||||
world extension {
|
||||
export init-extension: func();
|
||||
|
||||
record github-release {
|
||||
version: string,
|
||||
assets: list<github-release-asset>,
|
||||
}
|
||||
|
||||
record github-release-asset {
|
||||
name: string,
|
||||
download-url: string,
|
||||
}
|
||||
|
||||
record github-release-options {
|
||||
require-assets: bool,
|
||||
pre-release: bool,
|
||||
}
|
||||
|
||||
enum os {
|
||||
mac,
|
||||
linux,
|
||||
windows,
|
||||
}
|
||||
|
||||
enum architecture {
|
||||
aarch64,
|
||||
x86,
|
||||
x8664,
|
||||
}
|
||||
|
||||
enum downloaded-file-type {
|
||||
gzip,
|
||||
gzip-tar,
|
||||
zip,
|
||||
uncompressed,
|
||||
}
|
||||
|
||||
variant language-server-installation-status {
|
||||
checking-for-update,
|
||||
downloaded,
|
||||
downloading,
|
||||
cached,
|
||||
failed(string),
|
||||
}
|
||||
|
||||
/// Gets the current operating system and architecture
|
||||
import current-platform: func() -> tuple<os, architecture>;
|
||||
|
||||
/// Gets the latest version of the given NPM package.
|
||||
import npm-package-latest-version: func(package-name: string) -> result<string, string>;
|
||||
|
||||
/// Gets the latest release for the given GitHub repository.
|
||||
import latest-github-release: func(repo: string, options: github-release-options) -> result<github-release, string>;
|
||||
|
||||
/// Downloads a file from the given url, and saves it to the given filename within the extension's
|
||||
/// working directory. Extracts the file according to the given file type.
|
||||
import download-file: func(url: string, output-filename: string, file-type: downloaded-file-type) -> result<_, string>;
|
||||
|
||||
/// Updates the installation status for the given language server.
|
||||
import set-language-server-installation-status: func(language-server-name: string, status: language-server-installation-status);
|
||||
|
||||
record command {
|
||||
command: string,
|
||||
args: list<string>,
|
||||
env: list<tuple<string, string>>,
|
||||
}
|
||||
|
||||
resource worktree {
|
||||
read-text-file: func(path: string) -> result<string, string>;
|
||||
}
|
||||
|
||||
record language-server-config {
|
||||
name: string,
|
||||
language-name: string,
|
||||
}
|
||||
|
||||
export language-server-command: func(config: language-server-config, worktree: borrow<worktree>) -> result<command, string>;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use client::telemetry::Telemetry;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use extension::{Extension, ExtensionStatus, ExtensionStore};
|
||||
use extension::{ExtensionApiResponse, ExtensionStatus, ExtensionStore};
|
||||
use gpui::{
|
||||
actions, canvas, uniform_list, AnyElement, AppContext, AvailableSpace, EventEmitter,
|
||||
FocusableView, FontStyle, FontWeight, InteractiveElement, KeyContext, ParentElement, Render,
|
||||
@ -42,7 +42,7 @@ pub struct ExtensionsPage {
|
||||
telemetry: Arc<Telemetry>,
|
||||
is_fetching_extensions: bool,
|
||||
filter: ExtensionFilter,
|
||||
extension_entries: Vec<Extension>,
|
||||
extension_entries: Vec<ExtensionApiResponse>,
|
||||
query_editor: View<Editor>,
|
||||
query_contains_error: bool,
|
||||
_subscription: gpui::Subscription,
|
||||
@ -78,7 +78,7 @@ impl ExtensionsPage {
|
||||
})
|
||||
}
|
||||
|
||||
fn filtered_extension_entries(&self, cx: &mut ViewContext<Self>) -> Vec<Extension> {
|
||||
fn filtered_extension_entries(&self, cx: &mut ViewContext<Self>) -> Vec<ExtensionApiResponse> {
|
||||
let extension_store = ExtensionStore::global(cx).read(cx);
|
||||
|
||||
self.extension_entries
|
||||
@ -154,7 +154,7 @@ impl ExtensionsPage {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_entry(&self, extension: &Extension, cx: &mut ViewContext<Self>) -> Div {
|
||||
fn render_entry(&self, extension: &ExtensionApiResponse, cx: &mut ViewContext<Self>) -> Div {
|
||||
let status = ExtensionStore::global(cx)
|
||||
.read(cx)
|
||||
.extension_status(&extension.id);
|
||||
|
@ -17,6 +17,7 @@ util.workspace = true
|
||||
sum_tree.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
@ -14,7 +14,8 @@ use notify::{Config, EventKind, Watcher};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use futures::{future::BoxFuture, Stream, StreamExt};
|
||||
use async_tar::Archive;
|
||||
use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt};
|
||||
use git2::Repository as LibGitRepository;
|
||||
use parking_lot::Mutex;
|
||||
use repository::GitRepository;
|
||||
@ -43,6 +44,16 @@ use std::ffi::OsStr;
|
||||
pub trait Fs: Send + Sync {
|
||||
async fn create_dir(&self, path: &Path) -> Result<()>;
|
||||
async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
|
||||
async fn create_file_with(
|
||||
&self,
|
||||
path: &Path,
|
||||
content: Pin<&mut (dyn AsyncRead + Send)>,
|
||||
) -> Result<()>;
|
||||
async fn extract_tar_file(
|
||||
&self,
|
||||
path: &Path,
|
||||
content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
|
||||
) -> Result<()>;
|
||||
async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
|
||||
async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
|
||||
async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
||||
@ -125,6 +136,25 @@ impl Fs for RealFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_file_with(
|
||||
&self,
|
||||
path: &Path,
|
||||
content: Pin<&mut (dyn AsyncRead + Send)>,
|
||||
) -> Result<()> {
|
||||
let mut file = smol::fs::File::create(&path).await?;
|
||||
futures::io::copy(content, &mut file).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn extract_tar_file(
|
||||
&self,
|
||||
path: &Path,
|
||||
content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
|
||||
) -> Result<()> {
|
||||
content.unpack(path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
|
||||
if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
|
||||
if options.ignore_if_exists {
|
||||
@ -429,7 +459,7 @@ enum FakeFsEntry {
|
||||
File {
|
||||
inode: u64,
|
||||
mtime: SystemTime,
|
||||
content: String,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
Dir {
|
||||
inode: u64,
|
||||
@ -575,7 +605,7 @@ impl FakeFs {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
|
||||
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
|
||||
self.write_file_internal(path, content).unwrap()
|
||||
}
|
||||
|
||||
@ -598,7 +628,7 @@ impl FakeFs {
|
||||
state.emit_event(&[path]);
|
||||
}
|
||||
|
||||
pub fn write_file_internal(&self, path: impl AsRef<Path>, content: String) -> Result<()> {
|
||||
fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
let path = path.as_ref();
|
||||
let inode = state.next_inode;
|
||||
@ -625,6 +655,16 @@ impl FakeFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_internal(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
|
||||
let path = path.as_ref();
|
||||
let path = normalize_path(path);
|
||||
self.simulate_random_delay().await;
|
||||
let state = self.state.lock();
|
||||
let entry = state.read_path(&path)?;
|
||||
let entry = entry.lock();
|
||||
entry.file_content(&path).cloned()
|
||||
}
|
||||
|
||||
pub fn pause_events(&self) {
|
||||
self.state.lock().events_paused = true;
|
||||
}
|
||||
@ -662,7 +702,7 @@ impl FakeFs {
|
||||
self.create_dir(path).await.unwrap();
|
||||
}
|
||||
String(contents) => {
|
||||
self.insert_file(&path, contents).await;
|
||||
self.insert_file(&path, contents.into_bytes()).await;
|
||||
}
|
||||
_ => {
|
||||
panic!("JSON object must contain only objects, strings, or null");
|
||||
@ -672,6 +712,30 @@ impl FakeFs {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn insert_tree_from_real_fs<'a>(
|
||||
&'a self,
|
||||
path: impl 'a + AsRef<Path> + Send,
|
||||
src_path: impl 'a + AsRef<Path> + Send,
|
||||
) -> futures::future::BoxFuture<'a, ()> {
|
||||
use futures::FutureExt as _;
|
||||
|
||||
async move {
|
||||
let path = path.as_ref();
|
||||
if std::fs::metadata(&src_path).unwrap().is_file() {
|
||||
let contents = std::fs::read(src_path).unwrap();
|
||||
self.insert_file(path, contents).await;
|
||||
} else {
|
||||
self.create_dir(path).await.unwrap();
|
||||
for entry in std::fs::read_dir(&src_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
self.insert_tree_from_real_fs(&path.join(entry.file_name()), &entry.path())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
|
||||
where
|
||||
F: FnOnce(&mut FakeGitRepositoryState),
|
||||
@ -832,7 +896,7 @@ impl FakeFsEntry {
|
||||
matches!(self, Self::Symlink { .. })
|
||||
}
|
||||
|
||||
fn file_content(&self, path: &Path) -> Result<&String> {
|
||||
fn file_content(&self, path: &Path) -> Result<&Vec<u8>> {
|
||||
if let Self::File { content, .. } = self {
|
||||
Ok(content)
|
||||
} else {
|
||||
@ -840,7 +904,7 @@ impl FakeFsEntry {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_file_content(&mut self, path: &Path, new_content: String) -> Result<()> {
|
||||
fn set_file_content(&mut self, path: &Path, new_content: Vec<u8>) -> Result<()> {
|
||||
if let Self::File { content, mtime, .. } = self {
|
||||
*mtime = SystemTime::now();
|
||||
*content = new_content;
|
||||
@ -909,7 +973,7 @@ impl Fs for FakeFs {
|
||||
let file = Arc::new(Mutex::new(FakeFsEntry::File {
|
||||
inode,
|
||||
mtime,
|
||||
content: String::new(),
|
||||
content: Vec::new(),
|
||||
}));
|
||||
state.write_path(path, |entry| {
|
||||
match entry {
|
||||
@ -930,6 +994,36 @@ impl Fs for FakeFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_file_with(
|
||||
&self,
|
||||
path: &Path,
|
||||
mut content: Pin<&mut (dyn AsyncRead + Send)>,
|
||||
) -> Result<()> {
|
||||
let mut bytes = Vec::new();
|
||||
content.read_to_end(&mut bytes).await?;
|
||||
self.write_file_internal(path, bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn extract_tar_file(
|
||||
&self,
|
||||
path: &Path,
|
||||
content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
|
||||
) -> Result<()> {
|
||||
let mut entries = content.entries()?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let mut entry = entry?;
|
||||
if entry.header().entry_type().is_file() {
|
||||
let path = path.join(entry.path()?.as_ref());
|
||||
let mut bytes = Vec::new();
|
||||
entry.read_to_end(&mut bytes).await?;
|
||||
self.create_dir(path.parent().unwrap()).await?;
|
||||
self.write_file_internal(&path, bytes)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
|
||||
self.simulate_random_delay().await;
|
||||
|
||||
@ -1000,7 +1094,7 @@ impl Fs for FakeFs {
|
||||
e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
|
||||
inode,
|
||||
mtime,
|
||||
content: String::new(),
|
||||
content: Vec::new(),
|
||||
})))
|
||||
.clone(),
|
||||
)),
|
||||
@ -1079,35 +1173,30 @@ impl Fs for FakeFs {
|
||||
}
|
||||
|
||||
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
|
||||
let text = self.load(path).await?;
|
||||
Ok(Box::new(io::Cursor::new(text)))
|
||||
let bytes = self.load_internal(path).await?;
|
||||
Ok(Box::new(io::Cursor::new(bytes)))
|
||||
}
|
||||
|
||||
async fn load(&self, path: &Path) -> Result<String> {
|
||||
let path = normalize_path(path);
|
||||
self.simulate_random_delay().await;
|
||||
let state = self.state.lock();
|
||||
let entry = state.read_path(&path)?;
|
||||
let entry = entry.lock();
|
||||
entry.file_content(&path).cloned()
|
||||
let content = self.load_internal(path).await?;
|
||||
Ok(String::from_utf8(content.clone())?)
|
||||
}
|
||||
|
||||
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path.as_path());
|
||||
self.write_file_internal(path, data.to_string())?;
|
||||
|
||||
self.write_file_internal(path, data.into_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path);
|
||||
let content = chunks(text, line_ending).collect();
|
||||
let content = chunks(text, line_ending).collect::<String>();
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
self.write_file_internal(path, content)?;
|
||||
self.write_file_internal(path, content.into_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ pub mod markdown;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::Future;
|
||||
use gpui::{AppContext, AsyncAppContext, Model, Task};
|
||||
pub use highlight_map::HighlightMap;
|
||||
use lazy_static::lazy_static;
|
||||
@ -35,6 +36,7 @@ use schemars::{
|
||||
};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
use smol::future::FutureExt as _;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
@ -44,6 +46,7 @@ use std::{
|
||||
mem,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
str,
|
||||
sync::{
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
|
||||
@ -86,7 +89,9 @@ thread_local! {
|
||||
lazy_static! {
|
||||
static ref NEXT_LANGUAGE_ID: AtomicUsize = Default::default();
|
||||
static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
|
||||
static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
|
||||
static ref WASM_ENGINE: wasmtime::Engine = {
|
||||
wasmtime::Engine::new(&wasmtime::Config::new()).unwrap()
|
||||
};
|
||||
|
||||
/// A shared grammar for plain text, exposed for reuse by downstream crates.
|
||||
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
|
||||
@ -106,10 +111,10 @@ pub trait ToLspPosition {
|
||||
}
|
||||
|
||||
/// A name of a language server.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub struct LanguageServerName(pub Arc<str>);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Location {
|
||||
pub buffer: Model<Buffer>,
|
||||
pub range: Range<Anchor>,
|
||||
@ -120,54 +125,44 @@ pub struct Location {
|
||||
/// once at startup, and caches the results.
|
||||
pub struct CachedLspAdapter {
|
||||
pub name: LanguageServerName,
|
||||
pub short_name: &'static str,
|
||||
pub disk_based_diagnostic_sources: Vec<String>,
|
||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||
pub language_ids: HashMap<String, String>,
|
||||
pub adapter: Arc<dyn LspAdapter>,
|
||||
pub reinstall_attempt_count: AtomicU64,
|
||||
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
|
||||
}
|
||||
|
||||
impl CachedLspAdapter {
|
||||
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
||||
pub fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
||||
let name = adapter.name();
|
||||
let short_name = adapter.short_name();
|
||||
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources();
|
||||
let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token();
|
||||
let language_ids = adapter.language_ids();
|
||||
|
||||
Arc::new(CachedLspAdapter {
|
||||
name,
|
||||
short_name,
|
||||
disk_based_diagnostic_sources,
|
||||
disk_based_diagnostics_progress_token,
|
||||
language_ids,
|
||||
adapter,
|
||||
cached_binary: Default::default(),
|
||||
reinstall_attempt_count: AtomicU64::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
pub async fn get_language_server_command(
|
||||
self: Arc<Self>,
|
||||
language: Arc<Language>,
|
||||
container_dir: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Option<LanguageServerBinary>>> {
|
||||
self.adapter.check_if_user_installed(delegate, cx)
|
||||
}
|
||||
|
||||
pub async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
self.adapter.fetch_latest_server_version(delegate).await
|
||||
}
|
||||
|
||||
pub fn will_fetch_server(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
self.adapter.will_fetch_server(delegate, cx)
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let cached_binary = self.cached_binary.lock().await;
|
||||
self.adapter
|
||||
.clone()
|
||||
.get_language_server_command(language, container_dir, delegate, cached_binary, cx)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn will_start_server(
|
||||
@ -178,27 +173,6 @@ impl CachedLspAdapter {
|
||||
self.adapter.will_start_server(delegate, cx)
|
||||
}
|
||||
|
||||
pub async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
self.adapter
|
||||
.fetch_server_binary(version, container_dir, delegate)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
self.adapter
|
||||
.cached_server_binary(container_dir, delegate)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn can_be_reinstalled(&self) -> bool {
|
||||
self.adapter.can_be_reinstalled()
|
||||
}
|
||||
@ -248,31 +222,124 @@ impl CachedLspAdapter {
|
||||
pub fn prettier_plugins(&self) -> &[&'static str] {
|
||||
self.adapter.prettier_plugins()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> Option<&FakeLspAdapter> {
|
||||
self.adapter.as_fake()
|
||||
}
|
||||
}
|
||||
|
||||
/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
|
||||
// e.g. to display a notification or fetch data from the web.
|
||||
#[async_trait]
|
||||
pub trait LspAdapterDelegate: Send + Sync {
|
||||
fn show_notification(&self, message: &str, cx: &mut AppContext);
|
||||
fn http_client(&self) -> Arc<dyn HttpClient>;
|
||||
fn which_command(
|
||||
&self,
|
||||
command: OsString,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<(PathBuf, HashMap<String, String>)>>;
|
||||
fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus);
|
||||
|
||||
async fn which_command(&self, command: OsString) -> Option<(PathBuf, HashMap<String, String>)>;
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait LspAdapter: 'static + Send + Sync {
|
||||
fn name(&self) -> LanguageServerName;
|
||||
|
||||
fn short_name(&self) -> &'static str;
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
language: Arc<Language>,
|
||||
container_dir: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
cx: &'a mut AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
|
||||
async move {
|
||||
// First we check whether the adapter can give us a user-installed binary.
|
||||
// If so, we do *not* want to cache that, because each worktree might give us a different
|
||||
// binary:
|
||||
//
|
||||
// worktree 1: user-installed at `.bin/gopls`
|
||||
// worktree 2: user-installed at `~/bin/gopls`
|
||||
// worktree 3: no gopls found in PATH -> fallback to Zed installation
|
||||
//
|
||||
// We only want to cache when we fall back to the global one,
|
||||
// because we don't want to download and overwrite our global one
|
||||
// for each worktree we might have open.
|
||||
if let Some(binary) = self.check_if_user_installed(delegate.as_ref()).await {
|
||||
log::info!(
|
||||
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
|
||||
language.name(),
|
||||
binary.path,
|
||||
binary.arguments
|
||||
);
|
||||
return Ok(binary);
|
||||
}
|
||||
|
||||
fn check_if_user_installed(
|
||||
if let Some(cached_binary) = cached_binary.as_ref() {
|
||||
return Ok(cached_binary.clone());
|
||||
}
|
||||
|
||||
if !container_dir.exists() {
|
||||
smol::fs::create_dir_all(&container_dir)
|
||||
.await
|
||||
.context("failed to create container directory")?;
|
||||
}
|
||||
|
||||
if let Some(task) = self.will_fetch_server(&delegate, cx) {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
let name = self.name();
|
||||
log::info!("fetching latest version of language server {:?}", name.0);
|
||||
delegate.update_status(
|
||||
name.clone(),
|
||||
LanguageServerBinaryStatus::CheckingForUpdate,
|
||||
);
|
||||
let version_info = self.fetch_latest_server_version(delegate.as_ref()).await?;
|
||||
|
||||
log::info!("downloading language server {:?}", name.0);
|
||||
delegate.update_status(self.name(), LanguageServerBinaryStatus::Downloading);
|
||||
let mut binary = self
|
||||
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate.as_ref())
|
||||
.await;
|
||||
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded);
|
||||
|
||||
if let Err(error) = binary.as_ref() {
|
||||
if let Some(prev_downloaded_binary) = self
|
||||
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
|
||||
.await
|
||||
{
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::Cached);
|
||||
log::info!(
|
||||
"failed to fetch newest version of language server {:?}. falling back to using {:?}",
|
||||
name.clone(),
|
||||
prev_downloaded_binary.path.display()
|
||||
);
|
||||
binary = Ok(prev_downloaded_binary);
|
||||
} else {
|
||||
delegate.update_status(
|
||||
name.clone(),
|
||||
LanguageServerBinaryStatus::Failed {
|
||||
error: format!("{:?}", error),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(binary) = &binary {
|
||||
*cached_binary = Some(binary.clone());
|
||||
}
|
||||
|
||||
binary
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: &mut AsyncAppContext,
|
||||
) -> Option<Task<Option<LanguageServerBinary>>> {
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
@ -384,6 +451,11 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&[]
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> Option<&FakeLspAdapter> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -578,6 +650,7 @@ pub struct FakeLspAdapter {
|
||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||
pub disk_based_diagnostics_sources: Vec<String>,
|
||||
pub prettier_plugins: Vec<&'static str>,
|
||||
pub language_server_binary: LanguageServerBinary,
|
||||
}
|
||||
|
||||
/// Configuration of handling bracket pairs for a given language.
|
||||
@ -654,13 +727,6 @@ pub struct Language {
|
||||
pub(crate) id: LanguageId,
|
||||
pub(crate) config: LanguageConfig,
|
||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||
pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_adapter: Option<(
|
||||
futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>,
|
||||
Arc<FakeLspAdapter>,
|
||||
)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
@ -775,17 +841,9 @@ impl Language {
|
||||
highlight_map: Default::default(),
|
||||
})
|
||||
}),
|
||||
adapters: Vec::new(),
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_adapter: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lsp_adapters(&self) -> &[Arc<CachedLspAdapter>] {
|
||||
&self.adapters
|
||||
}
|
||||
|
||||
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
|
||||
if let Some(query) = queries.highlights {
|
||||
self = self
|
||||
@ -1077,76 +1135,10 @@ impl Language {
|
||||
Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec<Arc<dyn LspAdapter>>) -> Self {
|
||||
for adapter in lsp_adapters {
|
||||
self.adapters.push(CachedLspAdapter::new(adapter).await);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn set_fake_lsp_adapter(
|
||||
&mut self,
|
||||
fake_lsp_adapter: Arc<FakeLspAdapter>,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
|
||||
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
|
||||
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
|
||||
self.adapters = vec![adapter];
|
||||
servers_rx
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Arc<str> {
|
||||
self.config.name.clone()
|
||||
}
|
||||
|
||||
pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
|
||||
match self.adapters.first().as_ref() {
|
||||
Some(adapter) => &adapter.disk_based_diagnostic_sources,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
|
||||
for adapter in &self.adapters {
|
||||
let token = adapter.disk_based_diagnostics_progress_token.as_deref();
|
||||
if token.is_some() {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
|
||||
for adapter in &self.adapters {
|
||||
adapter.process_completion(completion).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn label_for_completion(
|
||||
self: &Arc<Self>,
|
||||
completion: &lsp::CompletionItem,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapters
|
||||
.first()
|
||||
.as_ref()?
|
||||
.label_for_completion(completion, self)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn label_for_symbol(
|
||||
self: &Arc<Self>,
|
||||
name: &str,
|
||||
kind: lsp::SymbolKind,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapters
|
||||
.first()
|
||||
.as_ref()?
|
||||
.label_for_symbol(name, kind, self)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn highlight_text<'a>(
|
||||
self: &'a Arc<Self>,
|
||||
text: &'a Rope,
|
||||
@ -1404,19 +1396,31 @@ impl Default for FakeLspAdapter {
|
||||
initialization_options: None,
|
||||
disk_based_diagnostics_sources: Vec::new(),
|
||||
prettier_plugins: Vec::new(),
|
||||
language_server_binary: LanguageServerBinary {
|
||||
path: "/the/fake/lsp/path".into(),
|
||||
arguments: vec![],
|
||||
env: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[async_trait]
|
||||
impl LspAdapter for Arc<FakeLspAdapter> {
|
||||
impl LspAdapter for FakeLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName(self.name.into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"FakeLspAdapter"
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Arc<Language>,
|
||||
_: Arc<Path>,
|
||||
_: Arc<dyn LspAdapterDelegate>,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
_: &'a mut AsyncAppContext,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
|
||||
async move { Ok(self.language_server_binary.clone()) }.boxed_local()
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
@ -1464,6 +1468,10 @@ impl LspAdapter for Arc<FakeLspAdapter> {
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&self.prettier_plugins
|
||||
}
|
||||
|
||||
fn as_fake(&self) -> Option<&FakeLspAdapter> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)]) {
|
||||
|
@ -7,9 +7,9 @@ use collections::{hash_map, HashMap};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::Shared,
|
||||
Future, FutureExt as _, TryFutureExt as _,
|
||||
Future, FutureExt as _,
|
||||
};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use lsp::{LanguageServerBinary, LanguageServerId};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
@ -43,14 +43,19 @@ struct LanguageRegistryState {
|
||||
languages: Vec<Arc<Language>>,
|
||||
available_languages: Vec<AvailableLanguage>,
|
||||
grammars: HashMap<Arc<str>, AvailableGrammar>,
|
||||
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
|
||||
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
|
||||
subscription: (watch::Sender<()>, watch::Receiver<()>),
|
||||
theme: Option<Arc<Theme>>,
|
||||
version: usize,
|
||||
reload_count: usize,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_server_txs:
|
||||
HashMap<Arc<str>, Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LanguageServerBinaryStatus {
|
||||
CheckingForUpdate,
|
||||
Downloading,
|
||||
@ -72,7 +77,6 @@ struct AvailableLanguage {
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync>,
|
||||
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||
loaded: bool,
|
||||
}
|
||||
|
||||
@ -112,7 +116,7 @@ pub struct LanguageQueries {
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct LspBinaryStatusSender {
|
||||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
|
||||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, LanguageServerBinaryStatus)>>>>,
|
||||
}
|
||||
|
||||
impl LanguageRegistry {
|
||||
@ -124,10 +128,14 @@ impl LanguageRegistry {
|
||||
available_languages: Default::default(),
|
||||
grammars: Default::default(),
|
||||
loading_languages: Default::default(),
|
||||
lsp_adapters: Default::default(),
|
||||
subscription: watch::channel(),
|
||||
theme: Default::default(),
|
||||
version: 0,
|
||||
reload_count: 0,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_server_txs: Default::default(),
|
||||
}),
|
||||
language_server_download_dir: None,
|
||||
login_shell_env_loaded: login_shell_env_loaded.shared(),
|
||||
@ -139,7 +147,9 @@ impl LanguageRegistry {
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test() -> Self {
|
||||
Self::new(Task::ready(()))
|
||||
let mut this = Self::new(Task::ready(()));
|
||||
this.language_server_download_dir = Some(Path::new("/the-download-dir").into());
|
||||
this
|
||||
}
|
||||
|
||||
pub fn set_executor(&mut self, executor: BackgroundExecutor) {
|
||||
@ -162,24 +172,71 @@ impl LanguageRegistry {
|
||||
.remove_languages(languages_to_remove, grammars_to_remove)
|
||||
}
|
||||
|
||||
pub fn remove_lsp_adapter(&self, language_name: &str, name: &LanguageServerName) {
|
||||
let mut state = self.state.write();
|
||||
if let Some(adapters) = state.lsp_adapters.get_mut(language_name) {
|
||||
adapters.retain(|adapter| &adapter.name != name)
|
||||
}
|
||||
state.version += 1;
|
||||
state.reload_count += 1;
|
||||
*state.subscription.0.borrow_mut() = ();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn register_test_language(&self, config: LanguageConfig) {
|
||||
self.register_language(
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
vec![],
|
||||
move || Ok((config.clone(), Default::default())),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
|
||||
self.state
|
||||
.write()
|
||||
.lsp_adapters
|
||||
.entry(language_name)
|
||||
.or_default()
|
||||
.push(CachedLspAdapter::new(adapter));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn register_fake_lsp_adapter(
|
||||
&self,
|
||||
language_name: &str,
|
||||
adapter: crate::FakeLspAdapter,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
self.state
|
||||
.write()
|
||||
.lsp_adapters
|
||||
.entry(language_name.into())
|
||||
.or_default()
|
||||
.push(CachedLspAdapter::new(Arc::new(adapter)));
|
||||
self.fake_language_servers(language_name)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn fake_language_servers(
|
||||
&self,
|
||||
language_name: &str,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
|
||||
self.state
|
||||
.write()
|
||||
.fake_server_txs
|
||||
.entry(language_name.into())
|
||||
.or_default()
|
||||
.push(servers_tx);
|
||||
servers_rx
|
||||
}
|
||||
|
||||
/// Adds a language to the registry, which can be loaded if needed.
|
||||
pub fn register_language(
|
||||
&self,
|
||||
name: Arc<str>,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||
load: impl Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync,
|
||||
) {
|
||||
let load = Arc::new(load);
|
||||
@ -189,7 +246,6 @@ impl LanguageRegistry {
|
||||
if existing_language.name == name {
|
||||
existing_language.grammar = grammar_name;
|
||||
existing_language.matcher = matcher;
|
||||
existing_language.lsp_adapters = lsp_adapters;
|
||||
existing_language.load = load;
|
||||
return;
|
||||
}
|
||||
@ -201,7 +257,6 @@ impl LanguageRegistry {
|
||||
grammar: grammar_name,
|
||||
matcher,
|
||||
load,
|
||||
lsp_adapters,
|
||||
loaded: false,
|
||||
});
|
||||
state.version += 1;
|
||||
@ -376,10 +431,7 @@ impl LanguageRegistry {
|
||||
None
|
||||
};
|
||||
|
||||
Language::new_with_id(id, config, grammar)
|
||||
.with_lsp_adapters(language.lsp_adapters)
|
||||
.await
|
||||
.with_queries(queries)
|
||||
Language::new_with_id(id, config, grammar).with_queries(queries)
|
||||
}
|
||||
.await;
|
||||
|
||||
@ -492,6 +544,23 @@ impl LanguageRegistry {
|
||||
self.state.read().languages.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn lsp_adapters(&self, language: &Arc<Language>) -> Vec<Arc<CachedLspAdapter>> {
|
||||
self.state
|
||||
.read()
|
||||
.lsp_adapters
|
||||
.get(&language.config.name)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn update_lsp_status(
|
||||
&self,
|
||||
server_name: LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.lsp_binary_status_tx.send(server_name, status);
|
||||
}
|
||||
|
||||
pub fn create_pending_language_server(
|
||||
self: &Arc<Self>,
|
||||
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||
@ -507,100 +576,85 @@ impl LanguageRegistry {
|
||||
adapter.name.0
|
||||
);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if language.fake_adapter.is_some() {
|
||||
let task = cx.spawn(|cx| async move {
|
||||
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
||||
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
||||
fake_adapter.name.to_string(),
|
||||
fake_adapter.capabilities.clone(),
|
||||
cx.clone(),
|
||||
);
|
||||
|
||||
if let Some(initializer) = &fake_adapter.initializer {
|
||||
initializer(&mut fake_server);
|
||||
}
|
||||
|
||||
let servers_tx = servers_tx.clone();
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
if fake_server
|
||||
.try_receive_notification::<lsp::notification::Initialized>()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
servers_tx.unbounded_send(fake_server).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(server)
|
||||
});
|
||||
|
||||
return Some(PendingLanguageServer {
|
||||
server_id,
|
||||
task,
|
||||
container_dir: None,
|
||||
});
|
||||
}
|
||||
|
||||
let download_dir = self
|
||||
.language_server_download_dir
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
|
||||
.log_err()?;
|
||||
let this = self.clone();
|
||||
let language = language.clone();
|
||||
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
|
||||
let root_path = root_path.clone();
|
||||
let adapter = adapter.clone();
|
||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||
let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
|
||||
let this = Arc::downgrade(self);
|
||||
|
||||
let task = {
|
||||
let task = cx.spawn({
|
||||
let container_dir = container_dir.clone();
|
||||
cx.spawn(move |mut cx| async move {
|
||||
// First we check whether the adapter can give us a user-installed binary.
|
||||
// If so, we do *not* want to cache that, because each worktree might give us a different
|
||||
// binary:
|
||||
//
|
||||
// worktree 1: user-installed at `.bin/gopls`
|
||||
// worktree 2: user-installed at `~/bin/gopls`
|
||||
// worktree 3: no gopls found in PATH -> fallback to Zed installation
|
||||
//
|
||||
// We only want to cache when we fall back to the global one,
|
||||
// because we don't want to download and overwrite our global one
|
||||
// for each worktree we might have open.
|
||||
move |mut cx| async move {
|
||||
// If we want to install a binary globally, we need to wait for
|
||||
// the login shell to be set on our process.
|
||||
login_shell_env_loaded.await;
|
||||
|
||||
let user_binary_task = check_user_installed_binary(
|
||||
adapter.clone(),
|
||||
language.clone(),
|
||||
delegate.clone(),
|
||||
&mut cx,
|
||||
);
|
||||
let binary = if let Some(user_binary) = user_binary_task.await {
|
||||
user_binary
|
||||
} else {
|
||||
// If we want to install a binary globally, we need to wait for
|
||||
// the login shell to be set on our process.
|
||||
login_shell_env_loaded.await;
|
||||
|
||||
get_or_install_binary(
|
||||
this,
|
||||
&adapter,
|
||||
language,
|
||||
&delegate,
|
||||
&cx,
|
||||
let binary = adapter
|
||||
.clone()
|
||||
.get_language_server_command(
|
||||
language.clone(),
|
||||
container_dir,
|
||||
lsp_binary_statuses,
|
||||
delegate.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
.await?;
|
||||
|
||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if true {
|
||||
let capabilities = adapter
|
||||
.as_fake()
|
||||
.map(|fake_adapter| fake_adapter.capabilities.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
||||
binary,
|
||||
adapter.name.0.to_string(),
|
||||
capabilities,
|
||||
cx.clone(),
|
||||
);
|
||||
|
||||
if let Some(fake_adapter) = adapter.as_fake() {
|
||||
if let Some(initializer) = &fake_adapter.initializer {
|
||||
initializer(&mut fake_server);
|
||||
}
|
||||
}
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
if fake_server
|
||||
.try_receive_notification::<lsp::notification::Initialized>()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
if let Some(this) = this.upgrade() {
|
||||
if let Some(txs) = this
|
||||
.state
|
||||
.write()
|
||||
.fake_server_txs
|
||||
.get_mut(language.name().as_ref())
|
||||
{
|
||||
for tx in txs {
|
||||
tx.unbounded_send(fake_server.clone()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
return Ok(server);
|
||||
}
|
||||
|
||||
drop(this);
|
||||
lsp::LanguageServer::new(
|
||||
stderr_capture,
|
||||
server_id,
|
||||
@ -609,8 +663,8 @@ impl LanguageRegistry {
|
||||
adapter.code_action_kinds(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Some(PendingLanguageServer {
|
||||
server_id,
|
||||
@ -621,7 +675,7 @@ impl LanguageRegistry {
|
||||
|
||||
pub fn language_server_binary_statuses(
|
||||
&self,
|
||||
) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
||||
) -> mpsc::UnboundedReceiver<(LanguageServerName, LanguageServerBinaryStatus)> {
|
||||
self.lsp_binary_status_tx.subscribe()
|
||||
}
|
||||
|
||||
@ -718,158 +772,16 @@ impl LanguageRegistryState {
|
||||
}
|
||||
|
||||
impl LspBinaryStatusSender {
|
||||
fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
|
||||
fn subscribe(
|
||||
&self,
|
||||
) -> mpsc::UnboundedReceiver<(LanguageServerName, LanguageServerBinaryStatus)> {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
self.txs.lock().push(tx);
|
||||
rx
|
||||
}
|
||||
|
||||
fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
|
||||
fn send(&self, name: LanguageServerName, status: LanguageServerBinaryStatus) {
|
||||
let mut txs = self.txs.lock();
|
||||
txs.retain(|tx| {
|
||||
tx.unbounded_send((language.clone(), status.clone()))
|
||||
.is_ok()
|
||||
});
|
||||
txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_user_installed_binary(
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
language: Arc<Language>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let Some(task) = adapter.check_if_user_installed(&delegate, cx) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
task.await.and_then(|binary| {
|
||||
log::info!(
|
||||
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
|
||||
language.name(),
|
||||
binary.path,
|
||||
binary.arguments
|
||||
);
|
||||
Some(binary)
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_or_install_binary(
|
||||
registry: Arc<LanguageRegistry>,
|
||||
adapter: &Arc<CachedLspAdapter>,
|
||||
language: Arc<Language>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &AsyncAppContext,
|
||||
container_dir: Arc<Path>,
|
||||
lsp_binary_statuses: LspBinaryStatusSender,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let entry = registry
|
||||
.lsp_binary_paths
|
||||
.lock()
|
||||
.entry(adapter.name.clone())
|
||||
.or_insert_with(|| {
|
||||
let adapter = adapter.clone();
|
||||
let language = language.clone();
|
||||
let delegate = delegate.clone();
|
||||
cx.spawn(|cx| {
|
||||
get_binary(
|
||||
adapter,
|
||||
language,
|
||||
delegate,
|
||||
container_dir,
|
||||
lsp_binary_statuses,
|
||||
cx,
|
||||
)
|
||||
.map_err(Arc::new)
|
||||
})
|
||||
.shared()
|
||||
})
|
||||
.clone();
|
||||
|
||||
entry.await.map_err(|err| anyhow!("{:?}", err))
|
||||
}
|
||||
|
||||
async fn get_binary(
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
language: Arc<Language>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
container_dir: Arc<Path>,
|
||||
statuses: LspBinaryStatusSender,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
if !container_dir.exists() {
|
||||
smol::fs::create_dir_all(&container_dir)
|
||||
.await
|
||||
.context("failed to create container directory")?;
|
||||
}
|
||||
|
||||
if let Some(task) = adapter.will_fetch_server(&delegate, &mut cx) {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
let binary = fetch_latest_binary(
|
||||
adapter.clone(),
|
||||
language.clone(),
|
||||
delegate.as_ref(),
|
||||
&container_dir,
|
||||
statuses.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(error) = binary.as_ref() {
|
||||
if let Some(binary) = adapter
|
||||
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
|
||||
.await
|
||||
{
|
||||
statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
|
||||
log::info!(
|
||||
"failed to fetch newest version of language server {:?}. falling back to using {:?}",
|
||||
adapter.name,
|
||||
binary.path.display()
|
||||
);
|
||||
return Ok(binary);
|
||||
}
|
||||
|
||||
statuses.send(
|
||||
language.clone(),
|
||||
LanguageServerBinaryStatus::Failed {
|
||||
error: format!("{:?}", error),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
binary
|
||||
}
|
||||
|
||||
async fn fetch_latest_binary(
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
language: Arc<Language>,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
container_dir: &Path,
|
||||
lsp_binary_statuses_tx: LspBinaryStatusSender,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let container_dir: Arc<Path> = container_dir.into();
|
||||
|
||||
lsp_binary_statuses_tx.send(
|
||||
language.clone(),
|
||||
LanguageServerBinaryStatus::CheckingForUpdate,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"querying GitHub for latest version of language server {:?}",
|
||||
adapter.name.0
|
||||
);
|
||||
let version_info = adapter.fetch_latest_server_version(delegate).await?;
|
||||
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
|
||||
|
||||
log::info!(
|
||||
"checking if Zed already installed or fetching version for language server {:?}",
|
||||
adapter.name.0
|
||||
);
|
||||
let binary = adapter
|
||||
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
|
||||
.await?;
|
||||
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
|
||||
|
||||
Ok(binary)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
|
||||
Language,
|
||||
Language, LanguageRegistry,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clock::ReplicaId;
|
||||
@ -487,6 +487,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completion {
|
||||
pub async fn deserialize_completion(
|
||||
completion: proto::Completion,
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
) -> Result<Completion> {
|
||||
let old_start = completion
|
||||
.old_start
|
||||
@ -500,7 +501,11 @@ pub async fn deserialize_completion(
|
||||
|
||||
let mut label = None;
|
||||
if let Some(language) = language {
|
||||
label = language.label_for_completion(&lsp_completion).await;
|
||||
if let Some(adapter) = language_registry.lsp_adapters(&language).first() {
|
||||
label = adapter
|
||||
.label_for_completion(&lsp_completion, &language)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Completion {
|
||||
|
@ -20,24 +20,6 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
|
||||
|
||||
init_test(cx);
|
||||
|
||||
let mut rust_language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_rust_servers = rust_language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "the-rust-language-server",
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/the-root",
|
||||
@ -47,10 +29,28 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::new(rust_language));
|
||||
});
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)));
|
||||
let mut fake_rust_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-rust-language-server",
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let log_store = cx.new_model(|cx| LogStore::new(cx));
|
||||
log_store.update(cx, |store, cx| store.add_project(&project, cx));
|
||||
|
@ -36,10 +36,6 @@ impl LspAdapter for AstroLspAdapter {
|
||||
LanguageServerName("astro-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"astro"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -20,10 +20,6 @@ impl super::LspAdapter for CLspAdapter {
|
||||
LanguageServerName("clangd".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"clangd"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
@ -296,7 +292,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
});
|
||||
let language = crate::language("c", tree_sitter_c::language(), None).await;
|
||||
let language = crate::language("c", tree_sitter_c::language());
|
||||
|
||||
cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
|
||||
|
@ -18,10 +18,6 @@ impl super::LspAdapter for ClojureLspAdapter {
|
||||
LanguageServerName("clojure-lsp".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"clojure"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -21,10 +21,6 @@ impl super::LspAdapter for OmniSharpAdapter {
|
||||
LanguageServerName("OmniSharp".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"OmniSharp"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -37,10 +37,6 @@ impl LspAdapter for CssLspAdapter {
|
||||
LanguageServerName("vscode-css-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"css"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -19,10 +19,6 @@ impl LspAdapter for DartLanguageServer {
|
||||
LanguageServerName("dart".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"dart"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -62,10 +62,6 @@ impl LspAdapter for DenoLspAdapter {
|
||||
LanguageServerName("deno-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"deno-ts"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -36,10 +36,6 @@ impl LspAdapter for DockerfileLspAdapter {
|
||||
LanguageServerName("docker-langserver".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"dockerfile"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -71,10 +71,6 @@ impl LspAdapter for ElixirLspAdapter {
|
||||
LanguageServerName("elixir-ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"elixir-ls"
|
||||
}
|
||||
|
||||
fn will_start_server(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
@ -302,10 +298,6 @@ impl LspAdapter for NextLspAdapter {
|
||||
LanguageServerName("next-ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"next-ls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
@ -460,10 +452,6 @@ impl LspAdapter for LocalLspAdapter {
|
||||
LanguageServerName("local-ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"local-ls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -40,10 +40,6 @@ impl LspAdapter for ElmLspAdapter {
|
||||
LanguageServerName(SERVER_NAME.into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"elmLS"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -12,10 +12,6 @@ impl LspAdapter for ErlangLspAdapter {
|
||||
LanguageServerName("erlang_ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"erlang_ls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -27,10 +27,6 @@ impl LspAdapter for GleamLspAdapter {
|
||||
LanguageServerName("gleam".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"gleam"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -38,10 +38,6 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
LanguageServerName("gopls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"gopls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
@ -58,23 +54,16 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
fn check_if_user_installed(
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Option<LanguageServerBinary>>> {
|
||||
let delegate = delegate.clone();
|
||||
|
||||
Some(cx.spawn(|cx| async move {
|
||||
match cx.update(|cx| delegate.which_command(OsString::from("gopls"), cx)) {
|
||||
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
env: Some(env),
|
||||
}),
|
||||
Err(_) => None,
|
||||
}
|
||||
}))
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let (path, env) = delegate.which_command(OsString::from("gopls")).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
env: Some(env),
|
||||
})
|
||||
}
|
||||
|
||||
fn will_fetch_server(
|
||||
@ -423,12 +412,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_go_label_for_completion() {
|
||||
let language = language(
|
||||
"go",
|
||||
tree_sitter_go::language(),
|
||||
Some(Arc::new(GoLspAdapter)),
|
||||
)
|
||||
.await;
|
||||
let adapter = Arc::new(GoLspAdapter);
|
||||
let language = language("go", tree_sitter_go::language());
|
||||
|
||||
let theme = SyntaxTheme::new_test([
|
||||
("type", Hsla::default()),
|
||||
@ -446,13 +431,16 @@ mod tests {
|
||||
let highlight_number = grammar.highlight_id_for_name("number").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "Hello".to_string(),
|
||||
detail: Some("func(a B) c.D".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "Hello".to_string(),
|
||||
detail: Some("func(a B) c.D".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "Hello(a B) c.D".to_string(),
|
||||
@ -467,13 +455,16 @@ mod tests {
|
||||
|
||||
// Nested methods
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::METHOD),
|
||||
label: "one.two.Three".to_string(),
|
||||
detail: Some("func() [3]interface{}".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::METHOD),
|
||||
label: "one.two.Three".to_string(),
|
||||
detail: Some("func() [3]interface{}".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "one.two.Three() [3]interface{}".to_string(),
|
||||
@ -488,13 +479,16 @@ mod tests {
|
||||
|
||||
// Nested fields
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FIELD),
|
||||
label: "two.Three".to_string(),
|
||||
detail: Some("a.Bcd".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FIELD),
|
||||
label: "two.Three".to_string(),
|
||||
detail: Some("a.Bcd".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "two.Three a.Bcd".to_string(),
|
||||
|
@ -12,10 +12,6 @@ impl LspAdapter for HaskellLanguageServer {
|
||||
LanguageServerName("hls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"hls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -37,10 +37,6 @@ impl LspAdapter for HtmlLspAdapter {
|
||||
LanguageServerName("vscode-html-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"html"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -90,10 +90,6 @@ impl LspAdapter for JsonLspAdapter {
|
||||
LanguageServerName("json-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"json"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -122,15 +122,17 @@ pub fn init(
|
||||
("dart", tree_sitter_dart::language()),
|
||||
]);
|
||||
|
||||
let language = |asset_dir_name: &'static str, adapters| {
|
||||
let language = |asset_dir_name: &'static str, adapters: Vec<Arc<dyn LspAdapter>>| {
|
||||
let config = load_config(asset_dir_name);
|
||||
for adapter in adapters {
|
||||
languages.register_lsp_adapter(config.name.clone(), adapter);
|
||||
}
|
||||
languages.register_language(
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
adapters,
|
||||
move || Ok((config.clone(), load_queries(asset_dir_name))),
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
language(
|
||||
@ -329,15 +331,9 @@ pub fn init(
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn language(
|
||||
name: &str,
|
||||
grammar: tree_sitter::Language,
|
||||
lsp_adapter: Option<Arc<dyn LspAdapter>>,
|
||||
) -> Arc<Language> {
|
||||
pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
|
||||
Arc::new(
|
||||
Language::new(load_config(name), Some(grammar))
|
||||
.with_lsp_adapters(lsp_adapter.into_iter().collect())
|
||||
.await
|
||||
.with_queries(load_queries(name))
|
||||
.unwrap(),
|
||||
)
|
||||
|
@ -22,10 +22,6 @@ impl super::LspAdapter for LuaLspAdapter {
|
||||
LanguageServerName("lua-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"lua"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -12,10 +12,6 @@ impl LspAdapter for NuLanguageServer {
|
||||
LanguageServerName("nu".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"nu"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -18,10 +18,6 @@ impl LspAdapter for OCamlLspAdapter {
|
||||
LanguageServerName("ocamllsp".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"ocaml"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -40,10 +40,6 @@ impl LspAdapter for IntelephenseLspAdapter {
|
||||
LanguageServerName("intelephense".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"php"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -35,10 +35,6 @@ impl LspAdapter for PrismaLspAdapter {
|
||||
LanguageServerName("prisma-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"prisma-language-server"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -39,10 +39,6 @@ impl LspAdapter for PurescriptLspAdapter {
|
||||
LanguageServerName("purescript-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"purescript"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -34,10 +34,6 @@ impl LspAdapter for PythonLspAdapter {
|
||||
LanguageServerName("pyright".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"pyright"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
@ -188,7 +184,7 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_python_autoindent(cx: &mut TestAppContext) {
|
||||
cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
let language = crate::language("python", tree_sitter_python::language(), None).await;
|
||||
let language = crate::language("python", tree_sitter_python::language());
|
||||
cx.update(|cx| {
|
||||
let test_settings = SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
|
@ -12,10 +12,6 @@ impl LspAdapter for RubyLanguageServer {
|
||||
LanguageServerName("solargraph".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"solargraph"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -23,10 +23,6 @@ impl LspAdapter for RustLspAdapter {
|
||||
LanguageServerName("rust-analyzer".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"rust"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
@ -360,12 +356,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rust_label_for_completion() {
|
||||
let language = language(
|
||||
"rust",
|
||||
tree_sitter_rust::language(),
|
||||
Some(Arc::new(RustLspAdapter)),
|
||||
)
|
||||
.await;
|
||||
let adapter = Arc::new(RustLspAdapter);
|
||||
let language = language("rust", tree_sitter_rust::language());
|
||||
let grammar = language.grammar().unwrap();
|
||||
let theme = SyntaxTheme::new_test([
|
||||
("type", Hsla::default()),
|
||||
@ -382,13 +374,16 @@ mod tests {
|
||||
let highlight_field = grammar.highlight_id_for_name("property").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "hello(…)".to_string(),
|
||||
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "hello(…)".to_string(),
|
||||
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
|
||||
@ -404,13 +399,16 @@ mod tests {
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "hello(…)".to_string(),
|
||||
detail: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "hello(…)".to_string(),
|
||||
detail: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
|
||||
@ -426,13 +424,16 @@ mod tests {
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FIELD),
|
||||
label: "len".to_string(),
|
||||
detail: Some("usize".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FIELD),
|
||||
label: "len".to_string(),
|
||||
detail: Some("usize".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "len: usize".to_string(),
|
||||
@ -442,13 +443,16 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "hello(…)".to_string(),
|
||||
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
adapter
|
||||
.label_for_completion(
|
||||
&lsp::CompletionItem {
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
label: "hello(…)".to_string(),
|
||||
detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
&language
|
||||
)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
|
||||
@ -467,12 +471,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rust_label_for_symbol() {
|
||||
let language = language(
|
||||
"rust",
|
||||
tree_sitter_rust::language(),
|
||||
Some(Arc::new(RustLspAdapter)),
|
||||
)
|
||||
.await;
|
||||
let adapter = Arc::new(RustLspAdapter);
|
||||
let language = language("rust", tree_sitter_rust::language());
|
||||
let grammar = language.grammar().unwrap();
|
||||
let theme = SyntaxTheme::new_test([
|
||||
("type", Hsla::default()),
|
||||
@ -488,8 +488,8 @@ mod tests {
|
||||
let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_symbol("hello", lsp::SymbolKind::FUNCTION)
|
||||
adapter
|
||||
.label_for_symbol("hello", lsp::SymbolKind::FUNCTION, &language)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "fn hello".to_string(),
|
||||
@ -499,8 +499,8 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER)
|
||||
adapter
|
||||
.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER, &language)
|
||||
.await,
|
||||
Some(CodeLabel {
|
||||
text: "type World".to_string(),
|
||||
@ -524,7 +524,7 @@ mod tests {
|
||||
});
|
||||
});
|
||||
|
||||
let language = crate::language("rust", tree_sitter_rust::language(), None).await;
|
||||
let language = crate::language("rust", tree_sitter_rust::language());
|
||||
|
||||
cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
|
||||
|
@ -36,10 +36,6 @@ impl LspAdapter for SvelteLspAdapter {
|
||||
LanguageServerName("svelte-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"svelte"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -38,10 +38,6 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
LanguageServerName("tailwindcss-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"tailwind"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -5,7 +5,7 @@ use futures::StreamExt;
|
||||
pub use language::*;
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, ffi::OsString, path::PathBuf, str};
|
||||
use std::{any::Any, ffi::OsString, path::PathBuf};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
@ -25,10 +25,6 @@ impl LspAdapter for TerraformLspAdapter {
|
||||
LanguageServerName("terraform-ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"terraform-ls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -18,10 +18,6 @@ impl LspAdapter for TaploLspAdapter {
|
||||
LanguageServerName("taplo-ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"taplo-ls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
|
@ -56,10 +56,6 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
LanguageServerName("typescript-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"tsserver"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
@ -283,10 +279,6 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
LanguageServerName(Self::SERVER_NAME.into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"eslint"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
@ -409,12 +401,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_outline(cx: &mut TestAppContext) {
|
||||
let language = crate::language(
|
||||
"typescript",
|
||||
tree_sitter_typescript::language_typescript(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let language = crate::language("typescript", tree_sitter_typescript::language_typescript());
|
||||
|
||||
let text = r#"
|
||||
function a() {
|
||||
|
@ -12,10 +12,6 @@ impl LspAdapter for UiuaLanguageServer {
|
||||
LanguageServerName("uiua".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"uiua"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -44,10 +44,6 @@ impl super::LspAdapter for VueLspAdapter {
|
||||
LanguageServerName("vue-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"vue-language-server"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -39,10 +39,6 @@ impl LspAdapter for YamlLspAdapter {
|
||||
LanguageServerName("yaml-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"yaml"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
|
@ -3,13 +3,11 @@ use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
use gpui::{AsyncAppContext, Task};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use std::env::consts::{ARCH, OS};
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
@ -23,10 +21,6 @@ impl LspAdapter for ZlsAdapter {
|
||||
LanguageServerName("zls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"zls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
@ -47,23 +41,16 @@ impl LspAdapter for ZlsAdapter {
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
fn check_if_user_installed(
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Option<LanguageServerBinary>>> {
|
||||
let delegate = delegate.clone();
|
||||
|
||||
Some(cx.spawn(|cx| async move {
|
||||
match cx.update(|cx| delegate.which_command(OsString::from("zls"), cx)) {
|
||||
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
env: Some(env),
|
||||
}),
|
||||
Err(_) => None,
|
||||
}
|
||||
}))
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let (path, env) = delegate.which_command(OsString::from("zls")).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
env: Some(env),
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
|
@ -616,11 +616,11 @@ impl LanguageServer {
|
||||
uri: root_uri,
|
||||
name: Default::default(),
|
||||
}]),
|
||||
client_info: Some(ClientInfo {
|
||||
name: release_channel::ReleaseChannel::global(cx)
|
||||
.display_name()
|
||||
.to_string(),
|
||||
version: Some(release_channel::AppVersion::global(cx).to_string()),
|
||||
client_info: release_channel::ReleaseChannel::try_global(cx).map(|release_channel| {
|
||||
ClientInfo {
|
||||
name: release_channel.display_name().to_string(),
|
||||
version: Some(release_channel::AppVersion::global(cx).to_string()),
|
||||
}
|
||||
}),
|
||||
locale: None,
|
||||
};
|
||||
@ -1055,6 +1055,7 @@ impl Drop for Subscription {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[derive(Clone)]
|
||||
pub struct FakeLanguageServer {
|
||||
pub binary: LanguageServerBinary,
|
||||
pub server: Arc<LanguageServer>,
|
||||
notifications_rx: channel::Receiver<(String, String)>,
|
||||
}
|
||||
@ -1063,6 +1064,7 @@ pub struct FakeLanguageServer {
|
||||
impl FakeLanguageServer {
|
||||
/// Construct a fake language server.
|
||||
pub fn new(
|
||||
binary: LanguageServerBinary,
|
||||
name: String,
|
||||
capabilities: ServerCapabilities,
|
||||
cx: AsyncAppContext,
|
||||
@ -1084,6 +1086,7 @@ impl FakeLanguageServer {
|
||||
|_| {},
|
||||
);
|
||||
let fake = FakeLanguageServer {
|
||||
binary,
|
||||
server: Arc::new(LanguageServer::new_internal(
|
||||
LanguageServerId(0),
|
||||
stdout_writer,
|
||||
@ -1302,8 +1305,16 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
release_channel::init("0.0.0", cx);
|
||||
});
|
||||
let (server, mut fake) =
|
||||
FakeLanguageServer::new("the-lsp".to_string(), Default::default(), cx.to_async());
|
||||
let (server, mut fake) = FakeLanguageServer::new(
|
||||
LanguageServerBinary {
|
||||
path: "path/to/language-server".into(),
|
||||
arguments: vec![],
|
||||
env: None,
|
||||
},
|
||||
"the-lsp".to_string(),
|
||||
Default::default(),
|
||||
cx.to_async(),
|
||||
);
|
||||
|
||||
let (message_tx, message_rx) = channel::unbounded();
|
||||
let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
|
||||
|
@ -2,7 +2,7 @@ use anyhow::Context;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use gpui::{AsyncAppContext, Model};
|
||||
use language::{language_settings::language_settings, Buffer, Diff};
|
||||
use language::{language_settings::language_settings, Buffer, Diff, LanguageRegistry};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -25,6 +25,7 @@ pub struct RealPrettier {
|
||||
default: bool,
|
||||
prettier_dir: PathBuf,
|
||||
server: Arc<LanguageServer>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@ -155,6 +156,7 @@ impl Prettier {
|
||||
_: LanguageServerId,
|
||||
prettier_dir: PathBuf,
|
||||
_: Arc<dyn NodeRuntime>,
|
||||
_: Arc<LanguageRegistry>,
|
||||
_: AsyncAppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self::Test(TestPrettier {
|
||||
@ -168,6 +170,7 @@ impl Prettier {
|
||||
server_id: LanguageServerId,
|
||||
prettier_dir: PathBuf,
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: AsyncAppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
use lsp::LanguageServerBinary;
|
||||
@ -206,6 +209,7 @@ impl Prettier {
|
||||
Ok(Self::Real(RealPrettier {
|
||||
server,
|
||||
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
|
||||
language_registry,
|
||||
prettier_dir,
|
||||
}))
|
||||
}
|
||||
@ -223,10 +227,12 @@ impl Prettier {
|
||||
let buffer_language = buffer.language();
|
||||
let parser_with_plugins = buffer_language.and_then(|l| {
|
||||
let prettier_parser = l.prettier_parser_name()?;
|
||||
let mut prettier_plugins = l
|
||||
.lsp_adapters()
|
||||
let mut prettier_plugins = local
|
||||
.language_registry
|
||||
.lsp_adapters(l)
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.prettier_plugins())
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
prettier_plugins.dedup();
|
||||
Some((prettier_parser, prettier_plugins))
|
||||
@ -264,7 +270,7 @@ impl Prettier {
|
||||
|
||||
let mut plugins = plugins
|
||||
.into_iter()
|
||||
.filter(|&&plugin_name| {
|
||||
.filter(|&plugin_name| {
|
||||
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
|
||||
add_tailwind_back = true;
|
||||
false
|
||||
|
@ -1472,6 +1472,12 @@ impl LspCommand for GetCompletions {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let language_server_adapter = project
|
||||
.update(&mut cx, |project, _cx| {
|
||||
project.language_server_adapter_for_id(server_id)
|
||||
})?
|
||||
.ok_or_else(|| anyhow!("no such language server"))?;
|
||||
|
||||
let completions = buffer.update(&mut cx, |buffer, cx| {
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let language = buffer.language().cloned();
|
||||
@ -1559,12 +1565,17 @@ impl LspCommand for GetCompletions {
|
||||
|
||||
let language_registry = language_registry.clone();
|
||||
let language = language.clone();
|
||||
let language_server_adapter = language_server_adapter.clone();
|
||||
LineEnding::normalize(&mut new_text);
|
||||
Some(async move {
|
||||
let mut label = None;
|
||||
if let Some(language) = language.as_ref() {
|
||||
language.process_completion(&mut lsp_completion).await;
|
||||
label = language.label_for_completion(&lsp_completion).await;
|
||||
if let Some(language) = &language {
|
||||
language_server_adapter
|
||||
.process_completion(&mut lsp_completion)
|
||||
.await;
|
||||
label = language_server_adapter
|
||||
.label_for_completion(&lsp_completion, language)
|
||||
.await;
|
||||
}
|
||||
|
||||
let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
|
||||
@ -1651,7 +1662,7 @@ impl LspCommand for GetCompletions {
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetCompletionsResponse,
|
||||
_: Model<Project>,
|
||||
project: Model<Project>,
|
||||
buffer: Model<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Completion>> {
|
||||
@ -1662,8 +1673,13 @@ impl LspCommand for GetCompletions {
|
||||
.await?;
|
||||
|
||||
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
|
||||
let language_registry = project.update(&mut cx, |project, _| project.languages.clone())?;
|
||||
let completions = message.completions.into_iter().map(|completion| {
|
||||
language::proto::deserialize_completion(completion, language.clone())
|
||||
language::proto::deserialize_completion(
|
||||
completion,
|
||||
language.clone(),
|
||||
&language_registry,
|
||||
)
|
||||
});
|
||||
future::try_join_all(completions).await
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ use futures::{
|
||||
use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
|
||||
use language::{
|
||||
language_settings::{Formatter, LanguageSettings},
|
||||
Buffer, Language, LanguageServerName, LocalFile,
|
||||
Buffer, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
@ -26,7 +26,8 @@ use crate::{
|
||||
};
|
||||
|
||||
pub fn prettier_plugins_for_language(
|
||||
language: &Language,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
language: &Arc<Language>,
|
||||
language_settings: &LanguageSettings,
|
||||
) -> Option<HashSet<&'static str>> {
|
||||
match &language_settings.formatter {
|
||||
@ -38,8 +39,8 @@ pub fn prettier_plugins_for_language(
|
||||
prettier_plugins
|
||||
.get_or_insert_with(|| HashSet::default())
|
||||
.extend(
|
||||
language
|
||||
.lsp_adapters()
|
||||
language_registry
|
||||
.lsp_adapters(language)
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.prettier_plugins()),
|
||||
)
|
||||
@ -303,15 +304,20 @@ fn start_prettier(
|
||||
) -> PrettierTask {
|
||||
cx.spawn(|project, mut cx| async move {
|
||||
log::info!("Starting prettier at path {prettier_dir:?}");
|
||||
let new_server_id = project.update(&mut cx, |project, _| {
|
||||
project.languages.next_language_server_id()
|
||||
})?;
|
||||
let language_registry = project.update(&mut cx, |project, _| project.languages.clone())?;
|
||||
let new_server_id = language_registry.next_language_server_id();
|
||||
|
||||
let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
|
||||
.await
|
||||
.context("default prettier spawn")
|
||||
.map(Arc::new)
|
||||
.map_err(Arc::new)?;
|
||||
let new_prettier = Prettier::start(
|
||||
new_server_id,
|
||||
prettier_dir,
|
||||
node,
|
||||
language_registry,
|
||||
cx.clone(),
|
||||
)
|
||||
.await
|
||||
.context("default prettier spawn")
|
||||
.map(Arc::new)
|
||||
.map_err(Arc::new)?;
|
||||
register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
|
||||
Ok(new_prettier)
|
||||
})
|
||||
|
@ -10,6 +10,7 @@ pub mod terminals;
|
||||
mod project_tests;
|
||||
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
|
||||
use clock::ReplicaId;
|
||||
use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
|
||||
@ -847,10 +848,12 @@ impl Project {
|
||||
let current_lsp_settings = &self.current_lsp_settings;
|
||||
for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
|
||||
let language = languages.iter().find_map(|l| {
|
||||
let adapter = l
|
||||
.lsp_adapters()
|
||||
let adapter = self
|
||||
.languages
|
||||
.lsp_adapters(l)
|
||||
.iter()
|
||||
.find(|adapter| &adapter.name == started_lsp_name)?;
|
||||
.find(|adapter| &adapter.name == started_lsp_name)?
|
||||
.clone();
|
||||
Some((l, adapter))
|
||||
});
|
||||
if let Some((language, adapter)) = language {
|
||||
@ -889,9 +892,11 @@ impl Project {
|
||||
|
||||
let mut prettier_plugins_by_worktree = HashMap::default();
|
||||
for (worktree, language, settings) in language_formatters_to_check {
|
||||
if let Some(plugins) =
|
||||
prettier_support::prettier_plugins_for_language(&language, &settings)
|
||||
{
|
||||
if let Some(plugins) = prettier_support::prettier_plugins_for_language(
|
||||
&self.languages,
|
||||
&language,
|
||||
&settings,
|
||||
) {
|
||||
prettier_plugins_by_worktree
|
||||
.entry(worktree)
|
||||
.or_insert_with(|| HashSet::default())
|
||||
@ -2047,7 +2052,7 @@ impl Project {
|
||||
}
|
||||
|
||||
if let Some(language) = language {
|
||||
for adapter in language.lsp_adapters() {
|
||||
for adapter in self.languages.lsp_adapters(&language) {
|
||||
let language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
|
||||
let server = self
|
||||
.language_server_ids
|
||||
@ -2118,10 +2123,12 @@ impl Project {
|
||||
let worktree_id = old_file.worktree_id(cx);
|
||||
let ids = &self.language_server_ids;
|
||||
|
||||
let language = buffer.language().cloned();
|
||||
let adapters = language.iter().flat_map(|language| language.lsp_adapters());
|
||||
for &server_id in adapters.flat_map(|a| ids.get(&(worktree_id, a.name.clone()))) {
|
||||
buffer.update_diagnostics(server_id, Default::default(), cx);
|
||||
if let Some(language) = buffer.language().cloned() {
|
||||
for adapter in self.languages.lsp_adapters(&language) {
|
||||
if let Some(server_id) = ids.get(&(worktree_id, adapter.name.clone())) {
|
||||
buffer.update_diagnostics(*server_id, Default::default(), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.buffer_snapshots.remove(&buffer.remote_id());
|
||||
@ -2701,9 +2708,11 @@ impl Project {
|
||||
let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
|
||||
let buffer_file = File::from_dyn(buffer_file.as_ref());
|
||||
let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
|
||||
if let Some(prettier_plugins) =
|
||||
prettier_support::prettier_plugins_for_language(&new_language, &settings)
|
||||
{
|
||||
if let Some(prettier_plugins) = prettier_support::prettier_plugins_for_language(
|
||||
&self.languages,
|
||||
&new_language,
|
||||
&settings,
|
||||
) {
|
||||
self.install_default_prettier(worktree, prettier_plugins, cx);
|
||||
};
|
||||
if let Some(file) = buffer_file {
|
||||
@ -2726,7 +2735,7 @@ impl Project {
|
||||
return;
|
||||
}
|
||||
|
||||
for adapter in language.lsp_adapters() {
|
||||
for adapter in self.languages.clone().lsp_adapters(&language) {
|
||||
self.start_language_server(worktree, adapter.clone(), language.clone(), cx);
|
||||
}
|
||||
}
|
||||
@ -3240,7 +3249,11 @@ impl Project {
|
||||
};
|
||||
|
||||
if file.worktree.read(cx).id() != key.0
|
||||
|| !language.lsp_adapters().iter().any(|a| a.name == key.1)
|
||||
|| !self
|
||||
.languages
|
||||
.lsp_adapters(&language)
|
||||
.iter()
|
||||
.any(|a| a.name == key.1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -3433,8 +3446,10 @@ impl Project {
|
||||
) {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
|
||||
let stop_tasks = language
|
||||
.lsp_adapters()
|
||||
let stop_tasks = self
|
||||
.languages
|
||||
.clone()
|
||||
.lsp_adapters(&language)
|
||||
.iter()
|
||||
.map(|adapter| {
|
||||
let stop_task = self.stop_language_server(worktree_id, adapter.name.clone(), cx);
|
||||
@ -4785,14 +4800,15 @@ impl Project {
|
||||
.languages
|
||||
.language_for_file(&project_path.path, None)
|
||||
.unwrap_or_else(move |_| adapter_language);
|
||||
let language_server_name = adapter.name.clone();
|
||||
let adapter = adapter.clone();
|
||||
Some(async move {
|
||||
let language = language.await;
|
||||
let label =
|
||||
language.label_for_symbol(&symbol_name, symbol_kind).await;
|
||||
let label = adapter
|
||||
.label_for_symbol(&symbol_name, symbol_kind, &language)
|
||||
.await;
|
||||
|
||||
Symbol {
|
||||
language_server_name,
|
||||
language_server_name: adapter.name.clone(),
|
||||
source_worktree_id,
|
||||
path: project_path,
|
||||
label: label.unwrap_or_else(|| {
|
||||
@ -7972,6 +7988,7 @@ impl Project {
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
|
||||
let languages = this.update(&mut cx, |this, _| this.languages.clone())?;
|
||||
let (buffer, completion) = this.update(&mut cx, |this, cx| {
|
||||
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
|
||||
let buffer = this
|
||||
@ -7986,6 +8003,7 @@ impl Project {
|
||||
.completion
|
||||
.ok_or_else(|| anyhow!("invalid completion"))?,
|
||||
language.cloned(),
|
||||
&languages,
|
||||
);
|
||||
Ok::<_, anyhow::Error>((buffer, completion))
|
||||
})??;
|
||||
@ -8713,6 +8731,9 @@ impl Project {
|
||||
.language_for_file(&path.path, None)
|
||||
.await
|
||||
.log_err();
|
||||
let adapter = language
|
||||
.as_ref()
|
||||
.and_then(|language| languages.lsp_adapters(language).first().cloned());
|
||||
Ok(Symbol {
|
||||
language_server_name: LanguageServerName(
|
||||
serialized_symbol.language_server_name.into(),
|
||||
@ -8720,10 +8741,10 @@ impl Project {
|
||||
source_worktree_id,
|
||||
path,
|
||||
label: {
|
||||
match language {
|
||||
Some(language) => {
|
||||
language
|
||||
.label_for_symbol(&serialized_symbol.name, kind)
|
||||
match language.as_ref().zip(adapter.as_ref()) {
|
||||
Some((language, adapter)) => {
|
||||
adapter
|
||||
.label_for_symbol(&serialized_symbol.name, kind, language)
|
||||
.await
|
||||
}
|
||||
None => None,
|
||||
@ -8975,6 +8996,17 @@ impl Project {
|
||||
self.supplementary_language_servers.iter()
|
||||
}
|
||||
|
||||
pub fn language_server_adapter_for_id(
|
||||
&self,
|
||||
id: LanguageServerId,
|
||||
) -> Option<Arc<CachedLspAdapter>> {
|
||||
if let Some(LanguageServerState::Running { adapter, .. }) = self.language_servers.get(&id) {
|
||||
Some(adapter.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
|
||||
if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(&id) {
|
||||
Some(server.clone())
|
||||
@ -9025,8 +9057,8 @@ impl Project {
|
||||
) -> Vec<LanguageServerId> {
|
||||
if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
|
||||
let worktree_id = file.worktree_id(cx);
|
||||
language
|
||||
.lsp_adapters()
|
||||
self.languages
|
||||
.lsp_adapters(&language)
|
||||
.iter()
|
||||
.flat_map(|adapter| {
|
||||
let key = (worktree_id, adapter.name.clone());
|
||||
@ -9190,20 +9222,25 @@ impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
|
||||
|
||||
struct ProjectLspAdapterDelegate {
|
||||
project: Model<Project>,
|
||||
worktree: Model<Worktree>,
|
||||
worktree: worktree::Snapshot,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
}
|
||||
|
||||
impl ProjectLspAdapterDelegate {
|
||||
fn new(project: &Project, worktree: &Model<Worktree>, cx: &ModelContext<Project>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
project: cx.handle(),
|
||||
worktree: worktree.clone(),
|
||||
worktree: worktree.read(cx).snapshot(),
|
||||
fs: project.fs.clone(),
|
||||
http_client: project.client.http_client(),
|
||||
language_registry: project.languages.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapterDelegate for ProjectLspAdapterDelegate {
|
||||
fn show_notification(&self, message: &str, cx: &mut AppContext) {
|
||||
self.project
|
||||
@ -9214,41 +9251,50 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
|
||||
self.http_client.clone()
|
||||
}
|
||||
|
||||
fn which_command(
|
||||
&self,
|
||||
command: OsString,
|
||||
cx: &AppContext,
|
||||
) -> Task<Option<(PathBuf, HashMap<String, String>)>> {
|
||||
let worktree_abs_path = self.worktree.read(cx).abs_path();
|
||||
let command = command.to_owned();
|
||||
async fn which_command(&self, command: OsString) -> Option<(PathBuf, HashMap<String, String>)> {
|
||||
let worktree_abs_path = self.worktree.abs_path();
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let shell_env = load_shell_environment(&worktree_abs_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to determine load login shell environment in {worktree_abs_path:?}"
|
||||
)
|
||||
})
|
||||
.log_err();
|
||||
let shell_env = load_shell_environment(&worktree_abs_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to determine load login shell environment in {worktree_abs_path:?}")
|
||||
})
|
||||
.log_err();
|
||||
|
||||
if let Some(shell_env) = shell_env {
|
||||
let shell_path = shell_env.get("PATH");
|
||||
match which::which_in(&command, shell_path, &worktree_abs_path) {
|
||||
Ok(command_path) => Some((command_path, shell_env)),
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
"failed to determine path for command {:?} in shell PATH {:?}: {error}",
|
||||
command.to_string_lossy(),
|
||||
shell_path.map(String::as_str).unwrap_or("")
|
||||
);
|
||||
None
|
||||
}
|
||||
if let Some(shell_env) = shell_env {
|
||||
let shell_path = shell_env.get("PATH");
|
||||
match which::which_in(&command, shell_path, &worktree_abs_path) {
|
||||
Ok(command_path) => Some((command_path, shell_env)),
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
"failed to determine path for command {:?} in shell PATH {:?}: {error}",
|
||||
command.to_string_lossy(),
|
||||
shell_path.map(String::as_str).unwrap_or("")
|
||||
);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_status(
|
||||
&self,
|
||||
server_name: LanguageServerName,
|
||||
status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(server_name, status);
|
||||
}
|
||||
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
|
||||
if self.worktree.entry_for_path(&path).is_none() {
|
||||
return Err(anyhow!("no such path {path:?}"));
|
||||
}
|
||||
let path = self.worktree.absolutize(path.as_ref())?;
|
||||
let content = self.fs.load(&path).await?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,55 +189,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut rust_language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut json_language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "JSON".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["json".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_rust_servers = rust_language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "the-rust-language-server",
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let mut fake_json_servers = json_language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "the-json-language-server",
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/the-root",
|
||||
@ -251,6 +202,36 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
let mut fake_rust_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-rust-language-server",
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let mut fake_json_servers = language_registry.register_fake_lsp_adapter(
|
||||
"JSON",
|
||||
FakeLspAdapter {
|
||||
name: "the-json-language-server",
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Open a buffer without an associated language server.
|
||||
let toml_buffer = project
|
||||
@ -273,10 +254,8 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
// Now we add the languages to the project, and ensure they get assigned to all
|
||||
// the relevant open buffers.
|
||||
project.update(cx, |project, _| {
|
||||
project.languages.add(Arc::new(json_language));
|
||||
project.languages.add(Arc::new(rust_language));
|
||||
});
|
||||
language_registry.add(json_lang());
|
||||
language_registry.add(rust_lang());
|
||||
cx.executor().run_until_parked();
|
||||
rust_buffer.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
|
||||
@ -581,24 +560,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "the-language-server",
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/the-root",
|
||||
@ -630,9 +591,16 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages.add(Arc::new(language));
|
||||
});
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-language-server",
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// Start the language server by opening a buffer with a compatible file extension.
|
||||
@ -1019,24 +987,6 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let progress_token = "the-progress-token";
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
disk_based_diagnostics_progress_token: Some(progress_token.into()),
|
||||
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
@ -1049,7 +999,18 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
disk_based_diagnostics_progress_token: Some(progress_token.into()),
|
||||
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id());
|
||||
|
||||
// Cause worktree to start the fake language server
|
||||
@ -1155,29 +1116,23 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||
init_test(cx);
|
||||
|
||||
let progress_token = "the-progress-token";
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||
disk_based_diagnostics_progress_token: Some(progress_token.into()),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "the-language-server",
|
||||
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||
disk_based_diagnostics_progress_token: Some(progress_token.into()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
@ -1239,27 +1194,15 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||
async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers =
|
||||
language_registry.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
@ -1331,28 +1274,15 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
|
||||
async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "the-lsp",
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers =
|
||||
language_registry.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
@ -1383,50 +1313,29 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
|
||||
async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut rust = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("Rust"),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_rust_servers = rust
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "rust-lsp",
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let mut js = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("JavaScript"),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_js_servers = js
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
name: "js-lsp",
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages.add(Arc::new(rust));
|
||||
project.languages.add(Arc::new(js));
|
||||
});
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
let mut fake_rust_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "rust-lsp",
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let mut fake_js_servers = language_registry.register_fake_lsp_adapter(
|
||||
"JavaScript",
|
||||
FakeLspAdapter {
|
||||
name: "js-lsp",
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
language_registry.add(rust_lang());
|
||||
language_registry.add(js_lang());
|
||||
|
||||
let _rs_buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
@ -1518,24 +1427,6 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let text = "
|
||||
fn a() { A }
|
||||
fn b() { BB }
|
||||
@ -1547,7 +1438,16 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
disk_based_diagnostics_sources: vec!["disk".into()],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
@ -1932,19 +1832,6 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
|
||||
async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
|
||||
let text = "
|
||||
fn a() {
|
||||
f1();
|
||||
@ -1968,7 +1855,12 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers =
|
||||
language_registry.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
.await
|
||||
@ -2322,19 +2214,6 @@ fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
|
||||
async fn test_definition(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
@ -2346,7 +2225,11 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers =
|
||||
language_registry.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
|
||||
@ -2426,30 +2309,6 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
@ -2460,7 +2319,23 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(typescript_lang());
|
||||
let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
|
||||
"TypeScript",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
||||
.await
|
||||
@ -2526,30 +2401,6 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
@ -2560,7 +2411,23 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(typescript_lang());
|
||||
let mut fake_language_servers = language_registry.register_fake_lsp_adapter(
|
||||
"TypeScript",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
||||
.await
|
||||
@ -2595,19 +2462,6 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
@ -2618,7 +2472,12 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(typescript_lang());
|
||||
let mut fake_language_servers =
|
||||
language_registry.register_fake_lsp_adapter("TypeScript", Default::default());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
||||
.await
|
||||
@ -2904,16 +2763,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
|
||||
let languages = project.update(cx, |project, _| project.languages().clone());
|
||||
languages.register_native_grammars([("rust", tree_sitter_rust::language())]);
|
||||
languages.register_test_language(LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
grammar: Some("rust".into()),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".into()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
languages.add(rust_lang());
|
||||
|
||||
let buffer = project.update(cx, |project, cx| {
|
||||
project.create_buffer("", None, cx).unwrap()
|
||||
@ -3733,30 +3583,6 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
|
||||
prepare_provider: Some(true),
|
||||
work_done_progress_options: Default::default(),
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
@ -3768,7 +3594,23 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
|
||||
prepare_provider: Some(true),
|
||||
work_done_progress_options: Default::default(),
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/dir/one.rs", cx)
|
||||
@ -4475,3 +4317,59 @@ fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn json_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "JSON".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["json".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
fn js_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("JavaScript"),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
fn rust_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
))
|
||||
}
|
||||
|
||||
fn typescript_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
))
|
||||
}
|
||||
|
@ -219,7 +219,7 @@ impl Inventory {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test_inventory {
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
|
@ -271,7 +271,13 @@ mod tests {
|
||||
async fn test_project_symbols(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let mut language = Language::new(
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
@ -281,16 +287,9 @@ mod tests {
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::<FakeLspAdapter>::default())
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
)));
|
||||
let mut fake_servers =
|
||||
language_registry.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
||||
|
||||
let _buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
|
@ -490,9 +490,7 @@ mod test {
|
||||
|
||||
assert_eq!(fs.load(&path).await.unwrap(), "@\n");
|
||||
|
||||
fs.as_fake()
|
||||
.write_file_internal(path, "oops\n".to_string())
|
||||
.unwrap();
|
||||
fs.as_fake().insert_file(path, b"oops\n".to_vec()).await;
|
||||
|
||||
// conflict!
|
||||
cx.simulate_keystrokes(["i", "@", "escape"]);
|
||||
|
@ -178,6 +178,7 @@ fn main() {
|
||||
extension::init(
|
||||
fs.clone(),
|
||||
http.clone(),
|
||||
node_runtime.clone(),
|
||||
languages.clone(),
|
||||
ThemeRegistry::global(cx),
|
||||
cx,
|
||||
|
@ -1594,7 +1594,7 @@ mod tests {
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_file("/root/a.txt", "changed".to_string())
|
||||
.insert_file("/root/a.txt", b"changed".to_vec())
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
13
extensions/gleam/Cargo.toml
Normal file
13
extensions/gleam/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "zed_gleam"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = { path = "../../crates/extension_api" }
|
||||
|
||||
[lib]
|
||||
path = "src/gleam.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
13
extensions/gleam/extension.toml
Normal file
13
extensions/gleam/extension.toml
Normal file
@ -0,0 +1,13 @@
|
||||
id = "gleam"
|
||||
name = "Gleam"
|
||||
description = "Gleam support for Zed"
|
||||
version = "0.0.1"
|
||||
authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
|
||||
|
||||
[language_servers.gleam]
|
||||
name = "Gleam LSP"
|
||||
language = "Gleam"
|
||||
|
||||
[grammars.gleam]
|
||||
repository = "https://github.com/gleam-lang/tree-sitter-gleam"
|
||||
commit = "58b7cac8fc14c92b0677c542610d8738c373fa81"
|
11
extensions/gleam/languages/gleam/config.toml
Normal file
11
extensions/gleam/languages/gleam/config.toml
Normal file
@ -0,0 +1,11 @@
|
||||
name = "Gleam"
|
||||
grammar = "gleam"
|
||||
path_suffixes = ["gleam"]
|
||||
line_comments = ["// ", "/// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
130
extensions/gleam/languages/gleam/highlights.scm
Normal file
130
extensions/gleam/languages/gleam/highlights.scm
Normal file
@ -0,0 +1,130 @@
|
||||
; Comments
|
||||
(module_comment) @comment
|
||||
(statement_comment) @comment
|
||||
(comment) @comment
|
||||
|
||||
; Constants
|
||||
(constant
|
||||
name: (identifier) @constant)
|
||||
|
||||
; Modules
|
||||
(module) @module
|
||||
(import alias: (identifier) @module)
|
||||
(remote_type_identifier
|
||||
module: (identifier) @module)
|
||||
(remote_constructor_name
|
||||
module: (identifier) @module)
|
||||
((field_access
|
||||
record: (identifier) @module
|
||||
field: (label) @function)
|
||||
(#is-not? local))
|
||||
|
||||
; Functions
|
||||
(unqualified_import (identifier) @function)
|
||||
(unqualified_import "type" (type_identifier) @type)
|
||||
(unqualified_import (type_identifier) @constructor)
|
||||
(function
|
||||
name: (identifier) @function)
|
||||
(external_function
|
||||
name: (identifier) @function)
|
||||
(function_parameter
|
||||
name: (identifier) @variable.parameter)
|
||||
((function_call
|
||||
function: (identifier) @function)
|
||||
(#is-not? local))
|
||||
((binary_expression
|
||||
operator: "|>"
|
||||
right: (identifier) @function)
|
||||
(#is-not? local))
|
||||
|
||||
; "Properties"
|
||||
; Assumed to be intended to refer to a name for a field; something that comes
|
||||
; before ":" or after "."
|
||||
; e.g. record field names, tuple indices, names for named arguments, etc
|
||||
(label) @property
|
||||
(tuple_access
|
||||
index: (integer) @property)
|
||||
|
||||
; Attributes
|
||||
(attribute
|
||||
"@" @attribute
|
||||
name: (identifier) @attribute)
|
||||
|
||||
(attribute_value (identifier) @constant)
|
||||
|
||||
; Type names
|
||||
(remote_type_identifier) @type
|
||||
(type_identifier) @type
|
||||
|
||||
; Data constructors
|
||||
(constructor_name) @constructor
|
||||
|
||||
; Literals
|
||||
(string) @string
|
||||
((escape_sequence) @warning
|
||||
; Deprecated in v0.33.0-rc2:
|
||||
(#eq? @warning "\\e"))
|
||||
(escape_sequence) @string.escape
|
||||
(bit_string_segment_option) @function.builtin
|
||||
(integer) @number
|
||||
(float) @number
|
||||
|
||||
; Reserved identifiers
|
||||
; TODO: when tree-sitter supports `#any-of?` in the Rust bindings,
|
||||
; refactor this to use `#any-of?` rather than `#match?`
|
||||
((identifier) @warning
|
||||
(#match? @warning "^(auto|delegate|derive|else|implement|macro|test|echo)$"))
|
||||
|
||||
; Variables
|
||||
(identifier) @variable
|
||||
(discard) @comment.unused
|
||||
|
||||
; Keywords
|
||||
[
|
||||
(visibility_modifier) ; "pub"
|
||||
(opacity_modifier) ; "opaque"
|
||||
"as"
|
||||
"assert"
|
||||
"case"
|
||||
"const"
|
||||
; DEPRECATED: 'external' was removed in v0.30.
|
||||
"external"
|
||||
"fn"
|
||||
"if"
|
||||
"import"
|
||||
"let"
|
||||
"panic"
|
||||
"todo"
|
||||
"type"
|
||||
"use"
|
||||
] @keyword
|
||||
|
||||
; Operators
|
||||
(binary_expression
|
||||
operator: _ @operator)
|
||||
(boolean_negation "!" @operator)
|
||||
(integer_negation "-" @operator)
|
||||
|
||||
; Punctuation
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"<<"
|
||||
">>"
|
||||
] @punctuation.bracket
|
||||
[
|
||||
"."
|
||||
","
|
||||
;; Controversial -- maybe some are operators?
|
||||
":"
|
||||
"#"
|
||||
"="
|
||||
"->"
|
||||
".."
|
||||
"-"
|
||||
"<-"
|
||||
] @punctuation.delimiter
|
3
extensions/gleam/languages/gleam/indents.scm
Normal file
3
extensions/gleam/languages/gleam/indents.scm
Normal file
@ -0,0 +1,3 @@
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
31
extensions/gleam/languages/gleam/outline.scm
Normal file
31
extensions/gleam/languages/gleam/outline.scm
Normal file
@ -0,0 +1,31 @@
|
||||
(external_type
|
||||
(visibility_modifier)? @context
|
||||
"type" @context
|
||||
(type_name) @name) @item
|
||||
|
||||
(type_definition
|
||||
(visibility_modifier)? @context
|
||||
(opacity_modifier)? @context
|
||||
"type" @context
|
||||
(type_name) @name) @item
|
||||
|
||||
(data_constructor
|
||||
(constructor_name) @name) @item
|
||||
|
||||
(data_constructor_argument
|
||||
(label) @name) @item
|
||||
|
||||
(type_alias
|
||||
(visibility_modifier)? @context
|
||||
"type" @context
|
||||
(type_name) @name) @item
|
||||
|
||||
(function
|
||||
(visibility_modifier)? @context
|
||||
"fn" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(constant
|
||||
(visibility_modifier)? @context
|
||||
"const" @context
|
||||
name: (_) @name) @item
|
11
extensions/gleam/src/bindings.rs
Normal file
11
extensions/gleam/src/bindings.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// Generated by `wit-bindgen` 0.16.0. DO NOT EDIT!
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[link_section = "component-type:zed_gleam"]
|
||||
#[doc(hidden)]
|
||||
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 169] = [3, 0, 9, 122, 101, 100, 95, 103, 108, 101, 97, 109, 0, 97, 115, 109, 13, 0, 1, 0, 7, 40, 1, 65, 2, 1, 65, 0, 4, 1, 29, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 122, 101, 100, 95, 103, 108, 101, 97, 109, 47, 122, 101, 100, 95, 103, 108, 101, 97, 109, 4, 0, 11, 15, 1, 0, 9, 122, 101, 100, 95, 103, 108, 101, 97, 109, 3, 0, 0, 0, 16, 12, 112, 97, 99, 107, 97, 103, 101, 45, 100, 111, 99, 115, 0, 123, 125, 0, 70, 9, 112, 114, 111, 100, 117, 99, 101, 114, 115, 1, 12, 112, 114, 111, 99, 101, 115, 115, 101, 100, 45, 98, 121, 2, 13, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, 110, 116, 6, 48, 46, 49, 56, 46, 50, 16, 119, 105, 116, 45, 98, 105, 110, 100, 103, 101, 110, 45, 114, 117, 115, 116, 6, 48, 46, 49, 54, 46, 48];
|
||||
|
||||
#[inline(never)]
|
||||
#[doc(hidden)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn __link_section() {}
|
91
extensions/gleam/src/gleam.rs
Normal file
91
extensions/gleam/src/gleam.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use zed_extension_api::{self as zed, Result};
|
||||
|
||||
struct GleamExtension {
|
||||
cached_binary_path: Option<String>,
|
||||
}
|
||||
|
||||
impl zed::Extension for GleamExtension {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
cached_binary_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
config: zed::LanguageServerConfig,
|
||||
_worktree: &zed::Worktree,
|
||||
) -> Result<zed::Command> {
|
||||
let binary_path = if let Some(path) = &self.cached_binary_path {
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::Cached,
|
||||
);
|
||||
|
||||
path.clone()
|
||||
} else {
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
);
|
||||
let release = zed::latest_github_release(
|
||||
"gleam-lang/gleam",
|
||||
zed::GithubReleaseOptions {
|
||||
require_assets: true,
|
||||
pre_release: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
let (platform, arch) = zed::current_platform();
|
||||
let asset_name = format!(
|
||||
"gleam-{version}-{arch}-{os}.tar.gz",
|
||||
version = release.version,
|
||||
arch = match arch {
|
||||
zed::Architecture::Aarch64 => "aarch64",
|
||||
zed::Architecture::X86 => "x86",
|
||||
zed::Architecture::X8664 => "x86_64",
|
||||
},
|
||||
os = match platform {
|
||||
zed::Os::Mac => "apple-darwin",
|
||||
zed::Os::Linux => "unknown-linux-musl",
|
||||
zed::Os::Windows => "pc-windows-msvc",
|
||||
},
|
||||
);
|
||||
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::Downloading,
|
||||
);
|
||||
let version_dir = format!("gleam-{}", release.version);
|
||||
zed::download_file(
|
||||
&asset.download_url,
|
||||
&version_dir,
|
||||
zed::DownloadedFileType::GzipTar,
|
||||
)
|
||||
.map_err(|e| format!("failed to download file: {e}"))?;
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::Downloaded,
|
||||
);
|
||||
|
||||
let binary_path = format!("{version_dir}/gleam");
|
||||
self.cached_binary_path = Some(binary_path.clone());
|
||||
binary_path
|
||||
};
|
||||
|
||||
Ok(zed::Command {
|
||||
command: binary_path,
|
||||
args: vec!["lsp".to_string()],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
zed::register_extension!(GleamExtension);
|
Loading…
Reference in New Issue
Block a user