mirror of
https://github.com/zed-industries/zed.git
synced 2024-10-06 10:57:40 +03:00
Provide wasm extensions with APIs needed for using pre-installed LSP binaries (#9085)
In this PR, we've added two new methods that LSP extensions can call: * `shell_env()`, for retrieving the environment variables set in the user's default shell in the worktree * `which(command)`, for looking up paths to an executable (accounting for the user's shell env in the worktree) To test this out, we moved the `uiua` language support into an extension. We went ahead and removed the built-in support, since this language is extremely obscure. Sorry @mikayla-maki. To continue coding in Uiua in Zed, for now you can `Add Dev Extension` from the extensions pane, and select the `extensions/uiua` directory in the Zed repo. Very soon, we'll support publishing these extensions so that you'll be able to just install it normally. Release Notes: - N/A --------- Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
5abcc1c3c5
commit
8a6264d933
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -89,9 +89,6 @@ jobs:
|
||||
- name: cargo clippy
|
||||
run: cargo xtask clippy
|
||||
|
||||
- name: Install WASI dependencies
|
||||
run: script/setup-wasm
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
|
163
Cargo.lock
generated
163
Cargo.lock
generated
@ -3011,15 +3011,6 @@ 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"
|
||||
@ -3159,16 +3150,6 @@ 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"
|
||||
@ -3556,6 +3537,7 @@ dependencies = [
|
||||
"theme",
|
||||
"toml 0.8.10",
|
||||
"util",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
@ -4165,28 +4147,6 @@ 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.2",
|
||||
"debugid",
|
||||
"fxhash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@ -5060,26 +5020,6 @@ 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"
|
||||
@ -5385,7 +5325,6 @@ dependencies = [
|
||||
"tree-sitter-svelte",
|
||||
"tree-sitter-toml",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-uiua",
|
||||
"tree-sitter-vue",
|
||||
"tree-sitter-yaml",
|
||||
"tree-sitter-zig",
|
||||
@ -10816,15 +10755,6 @@ dependencies = [
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-uiua"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=21dc2db39494585bf29a3f86d5add6e9d11a22ba#21dc2db39494585bf29a3f86d5add6e9d11a22ba"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-vue"
|
||||
version = "0.0.1"
|
||||
@ -11440,15 +11370,6 @@ dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.20"
|
||||
@ -11461,7 +11382,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
@ -11492,41 +11413,33 @@ version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c843b8bc4dd4f3a76173ba93405c71111d570af0d90ea5f6299c705d0c2add2"
|
||||
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",
|
||||
]
|
||||
|
||||
@ -11563,26 +11476,6 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-cache"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fb4fc2bbf9c790a57875eba65588fa97acf57a7d784dc86d057e648d9a1ed91"
|
||||
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.2"
|
||||
@ -11664,7 +11557,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"target-lexicon",
|
||||
"thiserror",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
"wasmprinter",
|
||||
"wasmtime-component-util",
|
||||
@ -11686,18 +11579,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-jit-debug"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833dae95bc7a4f9177bf93f9497419763535b74e37eb8c37be53937d3281e287"
|
||||
dependencies = [
|
||||
"object",
|
||||
"once_cell",
|
||||
"rustix 0.38.30",
|
||||
"wasmtime-versioned-export-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-jit-icache-coherence"
|
||||
version = "18.0.2"
|
||||
@ -11729,11 +11610,10 @@ dependencies = [
|
||||
"psm",
|
||||
"rustix 0.38.30",
|
||||
"sptr",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasm-encoder",
|
||||
"wasmtime-asm-macros",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-fiber",
|
||||
"wasmtime-jit-debug",
|
||||
"wasmtime-versioned-export-macros",
|
||||
"wasmtime-wmemcheck",
|
||||
"windows-sys 0.52.0",
|
||||
@ -11840,28 +11720,6 @@ dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "201.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"leb128",
|
||||
"memchr",
|
||||
"unicode-width",
|
||||
"wasm-encoder 0.201.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.201.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32"
|
||||
dependencies = [
|
||||
"wast 201.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.3"
|
||||
@ -12515,7 +12373,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser 0.13.2",
|
||||
@ -12534,7 +12392,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder 0.41.2",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser 0.14.0",
|
||||
@ -12584,7 +12442,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"thiserror",
|
||||
"wast 35.0.2",
|
||||
"wast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -13003,6 +12861,13 @@ dependencies = [
|
||||
"zed_extension_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_uiua"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"zed_extension_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.2.3"
|
||||
|
@ -92,7 +92,10 @@ members = [
|
||||
"crates/workspace",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
|
||||
"extensions/gleam",
|
||||
"extensions/uiua",
|
||||
|
||||
"tooling/xtask",
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
@ -308,7 +311,6 @@ tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev =
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "bd60db7d3d06f89b6ec3b287c9a6e9190b5564bd" }
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-uiua = { git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "21dc2db39494585bf29a3f86d5add6e9d11a22ba" }
|
||||
tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
|
||||
@ -317,7 +319,8 @@ unicase = "2.6"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmparser = "0.121"
|
||||
wasmtime = "18.0"
|
||||
wasm-encoder = "0.41"
|
||||
wasmtime = { version = "18.0", default-features = false, features = ["async", "demangle", "runtime", "cranelift", "component-model"] }
|
||||
wasmtime-wasi = "18.0"
|
||||
which = "6.0.0"
|
||||
wit-component = "0.20"
|
||||
|
@ -37,7 +37,8 @@ settings.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
util.workspace = true
|
||||
wasmtime = { workspace = true, features = ["async"] }
|
||||
wasm-encoder.workspace = true
|
||||
wasmtime.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
wasmparser.workspace = true
|
||||
wit-component.workspace = true
|
||||
|
@ -6,13 +6,16 @@ use async_tar::Archive;
|
||||
use futures::io::BufReader;
|
||||
use futures::AsyncReadExt;
|
||||
use serde::Deserialize;
|
||||
use std::mem;
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::http::{AsyncBody, HttpClient};
|
||||
use util::http::{self, AsyncBody, HttpClient};
|
||||
use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _};
|
||||
use wasmparser::Parser;
|
||||
use wit_component::ComponentEncoder;
|
||||
|
||||
/// Currently, we compile with Rust's `wasm32-wasi` target, which works with WASI `preview1`.
|
||||
@ -59,8 +62,11 @@ struct CargoTomlPackage {
|
||||
}
|
||||
|
||||
impl ExtensionBuilder {
|
||||
pub fn new(cache_dir: PathBuf, http: Arc<dyn HttpClient>) -> Self {
|
||||
Self { cache_dir, http }
|
||||
pub fn new(cache_dir: PathBuf) -> Self {
|
||||
Self {
|
||||
cache_dir,
|
||||
http: http::client(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn compile_extension(
|
||||
@ -138,6 +144,10 @@ impl ExtensionBuilder {
|
||||
.encode()
|
||||
.context("failed to encode wasm component")?;
|
||||
|
||||
let component_bytes = self
|
||||
.strip_custom_sections(&component_bytes)
|
||||
.context("failed to strip debug sections from wasm component")?;
|
||||
|
||||
fs::write(extension_dir.join("extension.wasm"), &component_bytes)
|
||||
.context("failed to write extension.wasm")?;
|
||||
|
||||
@ -310,7 +320,7 @@ impl ExtensionBuilder {
|
||||
async fn install_wasi_preview1_adapter_if_needed(&self) -> Result<Vec<u8>> {
|
||||
let cache_path = self.cache_dir.join("wasi_snapshot_preview1.reactor.wasm");
|
||||
if let Ok(content) = fs::read(&cache_path) {
|
||||
if wasmparser::Parser::is_core_wasm(&content) {
|
||||
if Parser::is_core_wasm(&content) {
|
||||
return Ok(content);
|
||||
}
|
||||
}
|
||||
@ -333,7 +343,7 @@ impl ExtensionBuilder {
|
||||
fs::write(&cache_path, &content)
|
||||
.with_context(|| format!("failed to save file {}", cache_path.display()))?;
|
||||
|
||||
if !wasmparser::Parser::is_core_wasm(&content) {
|
||||
if !Parser::is_core_wasm(&content) {
|
||||
bail!("downloaded wasi adapter is invalid");
|
||||
}
|
||||
Ok(content)
|
||||
@ -379,4 +389,68 @@ impl ExtensionBuilder {
|
||||
|
||||
Ok(clang_path)
|
||||
}
|
||||
|
||||
// This was adapted from:
|
||||
// https://github.com/bytecodealliance/wasm-tools/1791a8f139722e9f8679a2bd3d8e423e55132b22/src/bin/wasm-tools/strip.rs
|
||||
fn strip_custom_sections(&self, input: &Vec<u8>) -> Result<Vec<u8>> {
|
||||
use wasmparser::Payload::*;
|
||||
|
||||
let strip_custom_section = |name: &str| name.starts_with(".debug");
|
||||
|
||||
let mut output = Vec::new();
|
||||
let mut stack = Vec::new();
|
||||
|
||||
for payload in Parser::new(0).parse_all(input) {
|
||||
let payload = payload?;
|
||||
|
||||
// Track nesting depth, so that we don't mess with inner producer sections:
|
||||
match payload {
|
||||
Version { encoding, .. } => {
|
||||
output.extend_from_slice(match encoding {
|
||||
wasmparser::Encoding::Component => &wasm_encoder::Component::HEADER,
|
||||
wasmparser::Encoding::Module => &wasm_encoder::Module::HEADER,
|
||||
});
|
||||
}
|
||||
ModuleSection { .. } | ComponentSection { .. } => {
|
||||
stack.push(mem::take(&mut output));
|
||||
continue;
|
||||
}
|
||||
End { .. } => {
|
||||
let mut parent = match stack.pop() {
|
||||
Some(c) => c,
|
||||
None => break,
|
||||
};
|
||||
if output.starts_with(&wasm_encoder::Component::HEADER) {
|
||||
parent.push(ComponentSectionId::Component as u8);
|
||||
output.encode(&mut parent);
|
||||
} else {
|
||||
parent.push(ComponentSectionId::CoreModule as u8);
|
||||
output.encode(&mut parent);
|
||||
}
|
||||
output = parent;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match &payload {
|
||||
CustomSection(c) => {
|
||||
if strip_custom_section(c.name()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some((id, range)) = payload.as_section() {
|
||||
RawSection {
|
||||
id,
|
||||
data: &input[range],
|
||||
}
|
||||
.append_to(&mut output);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ impl ExtensionStore {
|
||||
extension_index: Default::default(),
|
||||
installed_dir,
|
||||
index_path,
|
||||
builder: Arc::new(ExtensionBuilder::new(build_dir, http_client.clone())),
|
||||
builder: Arc::new(ExtensionBuilder::new(build_dir)),
|
||||
outstanding_operations: Default::default(),
|
||||
modified_extensions: Default::default(),
|
||||
reload_complete_senders: Vec::new(),
|
||||
@ -545,7 +545,7 @@ impl ExtensionStore {
|
||||
builder
|
||||
.compile_extension(
|
||||
&extension_source_path,
|
||||
CompileExtensionOptions { release: true },
|
||||
CompileExtensionOptions { release: false },
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
@ -7,10 +7,7 @@ use collections::BTreeMap;
|
||||
use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, TestAppContext};
|
||||
use language::{
|
||||
Language, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus,
|
||||
LanguageServerName,
|
||||
};
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
@ -573,19 +570,6 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
||||
})
|
||||
.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();
|
||||
let expected_server_path = extensions_dir.join("work/gleam/gleam-v1.2.3/gleam");
|
||||
|
@ -3,7 +3,7 @@ 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 fs::{normalize_path, Fs};
|
||||
use futures::{
|
||||
channel::{
|
||||
mpsc::{self, UnboundedSender},
|
||||
@ -72,8 +72,6 @@ type ExtensionCall = Box<
|
||||
|
||||
static WASM_ENGINE: OnceLock<wasmtime::Engine> = OnceLock::new();
|
||||
|
||||
const EXTENSION_WORK_DIR_PATH: &str = "/zed/work";
|
||||
|
||||
impl WasmHost {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
@ -181,11 +179,12 @@ impl WasmHost {
|
||||
.await
|
||||
.context("failed to create extension work dir")?;
|
||||
|
||||
let work_dir_preopen = Dir::open_ambient_dir(extension_work_dir, ambient_authority())
|
||||
let work_dir_preopen = Dir::open_ambient_dir(&extension_work_dir, ambient_authority())
|
||||
.context("failed to preopen extension work directory")?;
|
||||
let current_dir_preopen = work_dir_preopen
|
||||
.try_clone()
|
||||
.context("failed to preopen extension current directory")?;
|
||||
let extension_work_dir = extension_work_dir.to_string_lossy();
|
||||
|
||||
let perms = wasi::FilePerms::all();
|
||||
let dir_perms = wasi::DirPerms::all();
|
||||
@ -193,26 +192,24 @@ impl WasmHost {
|
||||
Ok(wasi::WasiCtxBuilder::new()
|
||||
.inherit_stdio()
|
||||
.preopened_dir(current_dir_preopen, dir_perms, perms, ".")
|
||||
.preopened_dir(work_dir_preopen, dir_perms, perms, EXTENSION_WORK_DIR_PATH)
|
||||
.env("PWD", EXTENSION_WORK_DIR_PATH)
|
||||
.env("RUST_BACKTRACE", "1")
|
||||
.preopened_dir(work_dir_preopen, dir_perms, perms, &extension_work_dir)
|
||||
.env("PWD", &extension_work_dir)
|
||||
.env("RUST_BACKTRACE", "full")
|
||||
.build())
|
||||
}
|
||||
|
||||
pub fn path_from_extension(&self, id: &Arc<str>, path: &Path) -> PathBuf {
|
||||
self.writeable_path_from_extension(id, path)
|
||||
.unwrap_or_else(|| path.to_path_buf())
|
||||
let extension_work_dir = self.work_dir.join(id.as_ref());
|
||||
normalize_path(&extension_work_dir.join(path))
|
||||
}
|
||||
|
||||
pub fn writeable_path_from_extension(&self, id: &Arc<str>, path: &Path) -> Option<PathBuf> {
|
||||
let path = path.strip_prefix(EXTENSION_WORK_DIR_PATH).unwrap_or(path);
|
||||
if path.is_relative() {
|
||||
let mut result = self.work_dir.clone();
|
||||
result.push(id.as_ref());
|
||||
result.extend(path);
|
||||
Some(result)
|
||||
pub fn writeable_path_from_extension(&self, id: &Arc<str>, path: &Path) -> Result<PathBuf> {
|
||||
let extension_work_dir = self.work_dir.join(id.as_ref());
|
||||
let path = normalize_path(&extension_work_dir.join(path));
|
||||
if path.starts_with(&extension_work_dir) {
|
||||
Ok(path)
|
||||
} else {
|
||||
None
|
||||
Err(anyhow!("cannot write to path {}", path.display()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,13 +249,6 @@ impl WasmExtension {
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmState {
|
||||
pub fn writeable_path_from_extension(&self, path: &Path) -> Option<PathBuf> {
|
||||
self.host
|
||||
.writeable_path_from_extension(&self.manifest.id, path)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl wit::HostWorktree for WasmState {
|
||||
async fn read_text_file(
|
||||
@ -273,6 +263,26 @@ impl wit::HostWorktree for WasmState {
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> wasmtime::Result<wit::EnvVars> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.shell_env().await.into_iter().collect())
|
||||
}
|
||||
|
||||
async fn which(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
binary_name: String,
|
||||
) -> wasmtime::Result<Option<String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.which(binary_name.as_ref())
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string()))
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<wit::Worktree>) -> Result<()> {
|
||||
// we only ever hand out borrows of worktrees
|
||||
Ok(())
|
||||
@ -395,8 +405,8 @@ impl wit::ExtensionImports for WasmState {
|
||||
this.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = this
|
||||
.writeable_path_from_extension(&path)
|
||||
.ok_or_else(|| anyhow!("cannot write to path {:?}", path))?;
|
||||
.host
|
||||
.writeable_path_from_extension(&this.manifest.id, &path)?;
|
||||
|
||||
let mut response = this
|
||||
.host
|
||||
|
@ -61,14 +61,18 @@ world extension {
|
||||
/// 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);
|
||||
|
||||
type env-vars = list<tuple<string, string>>;
|
||||
|
||||
record command {
|
||||
command: string,
|
||||
args: list<string>,
|
||||
env: list<tuple<string, string>>,
|
||||
env: env-vars,
|
||||
}
|
||||
|
||||
resource worktree {
|
||||
read-text-file: func(path: string) -> result<string, string>;
|
||||
which: func(binary-name: string) -> option<string>;
|
||||
shell-env: func() -> env-vars;
|
||||
}
|
||||
|
||||
record language-server-config {
|
||||
|
@ -40,7 +40,7 @@ use smol::future::FutureExt as _;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
ffi::OsString,
|
||||
ffi::OsStr,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
mem,
|
||||
@ -277,7 +277,8 @@ pub trait LspAdapterDelegate: Send + Sync {
|
||||
fn http_client(&self) -> Arc<dyn HttpClient>;
|
||||
fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus);
|
||||
|
||||
async fn which_command(&self, command: OsString) -> Option<(PathBuf, HashMap<String, String>)>;
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
|
||||
async fn shell_env(&self) -> HashMap<String, String>;
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String>;
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,6 @@ tree-sitter-scheme.workspace = true
|
||||
tree-sitter-svelte.workspace = true
|
||||
tree-sitter-toml.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-uiua.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
tree-sitter-yaml.workspace = true
|
||||
tree-sitter-zig.workspace = true
|
||||
|
@ -58,7 +58,8 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let (path, env) = delegate.which_command(OsString::from("gopls")).await?;
|
||||
let env = delegate.shell_env().await;
|
||||
let path = delegate.which("gopls".as_ref()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
|
@ -39,7 +39,6 @@ mod tailwind;
|
||||
mod terraform;
|
||||
mod toml;
|
||||
mod typescript;
|
||||
mod uiua;
|
||||
mod vue;
|
||||
mod yaml;
|
||||
mod zig;
|
||||
@ -114,7 +113,6 @@ pub fn init(
|
||||
("toml", tree_sitter_toml::language()),
|
||||
("tsx", tree_sitter_typescript::language_tsx()),
|
||||
("typescript", tree_sitter_typescript::language_typescript()),
|
||||
("uiua", tree_sitter_uiua::language()),
|
||||
("vue", tree_sitter_vue::language()),
|
||||
("yaml", tree_sitter_yaml::language()),
|
||||
("zig", tree_sitter_zig::language()),
|
||||
@ -344,7 +342,6 @@ pub fn init(
|
||||
"vue",
|
||||
vec![Arc::new(vue::VueLspAdapter::new(node_runtime.clone()))]
|
||||
);
|
||||
language!("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]);
|
||||
language!("proto");
|
||||
language!("terraform", vec![Arc::new(terraform::TerraformLspAdapter)]);
|
||||
language!(
|
||||
|
@ -7,7 +7,6 @@ use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use std::env::consts::{ARCH, OS};
|
||||
use std::ffi::OsString;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
@ -45,7 +44,8 @@ impl LspAdapter for ZlsAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let (path, env) = delegate.which_command(OsString::from("zls")).await?;
|
||||
let env = delegate.shell_env().await;
|
||||
let path = delegate.which("zls".as_ref()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
|
@ -72,7 +72,7 @@ use std::{
|
||||
cmp::{self, Ordering},
|
||||
convert::TryInto,
|
||||
env,
|
||||
ffi::OsString,
|
||||
ffi::OsStr,
|
||||
hash::Hash,
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
@ -9390,6 +9390,7 @@ struct ProjectLspAdapterDelegate {
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
shell_env: Mutex<Option<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl ProjectLspAdapterDelegate {
|
||||
@ -9400,8 +9401,21 @@ impl ProjectLspAdapterDelegate {
|
||||
fs: project.fs.clone(),
|
||||
http_client: project.client.http_client(),
|
||||
language_registry: project.languages.clone(),
|
||||
shell_env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn load_shell_env(&self) {
|
||||
let worktree_abs_path = self.worktree.abs_path();
|
||||
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()
|
||||
.unwrap_or_default();
|
||||
*self.shell_env.lock() = Some(shell_env);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -9416,32 +9430,20 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
|
||||
self.http_client.clone()
|
||||
}
|
||||
|
||||
async fn which_command(&self, command: OsString) -> Option<(PathBuf, HashMap<String, String>)> {
|
||||
async fn shell_env(&self) -> HashMap<String, String> {
|
||||
self.load_shell_env().await;
|
||||
self.shell_env.lock().as_ref().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
|
||||
let worktree_abs_path = self.worktree.abs_path();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.load_shell_env().await;
|
||||
let shell_path = self
|
||||
.shell_env
|
||||
.lock()
|
||||
.as_ref()
|
||||
.and_then(|shell_env| shell_env.get("PATH").cloned());
|
||||
which::which_in(command, shell_path.as_ref(), &worktree_abs_path).ok()
|
||||
}
|
||||
|
||||
fn update_status(
|
||||
|
16
extensions/uiua/Cargo.toml
Normal file
16
extensions/uiua/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "zed_uiua"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = { path = "../../crates/extension_api" }
|
||||
|
||||
[lib]
|
||||
path = "src/uiua.rs"
|
||||
crate-type = ["cdylib"]
|
13
extensions/uiua/extension.toml
Normal file
13
extensions/uiua/extension.toml
Normal file
@ -0,0 +1,13 @@
|
||||
id = "uiua"
|
||||
name = "Uiua"
|
||||
description = "Uiua support for Zed"
|
||||
version = "0.0.1"
|
||||
authors = ["Max Brunsfeld <max@zed.dev>"]
|
||||
|
||||
[language_servers.uiua]
|
||||
name = "Uiua LSP"
|
||||
language = "Uiua"
|
||||
|
||||
[grammars.uiua]
|
||||
repository = "https://github.com/shnarazk/tree-sitter-uiua"
|
||||
commit = "21dc2db39494585bf29a3f86d5add6e9d11a22ba"
|
27
extensions/uiua/src/uiua.rs
Normal file
27
extensions/uiua/src/uiua.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use zed_extension_api::{self as zed, Result};
|
||||
|
||||
struct UiuaExtension;
|
||||
|
||||
impl zed::Extension for UiuaExtension {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
_config: zed::LanguageServerConfig,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<zed::Command> {
|
||||
let path = worktree
|
||||
.which("uiua")
|
||||
.ok_or_else(|| "uiua is not installed".to_string())?;
|
||||
|
||||
Ok(zed::Command {
|
||||
command: path,
|
||||
args: vec!["lsp".to_string()],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
zed::register_extension!(UiuaExtension);
|
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
WASI_ADAPTER_URL="https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm"
|
||||
WASI_SDK_URL="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-macos.tar.gz"
|
||||
|
||||
echo "Downloading WASI adapter: $WASI_ADAPTER_URL"
|
||||
curl -L $WASI_ADAPTER_URL -o target/wasi_snapshot_preview1.reactor.wasm
|
||||
|
||||
echo "Downloading WASI SDK: $WASI_SDK_URL"
|
||||
mkdir -p target/wasi-sdk.archive
|
||||
curl -L $WASI_SDK_URL | tar -xz - -C target/wasi-sdk.archive
|
||||
rm -rf target/wasi-sdk/
|
||||
mv -f target/wasi-sdk.archive/wasi-sdk-21.0/ target/wasi-sdk
|
Loading…
Reference in New Issue
Block a user