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:
Max Brunsfeld 2024-03-08 14:18:06 -08:00 committed by GitHub
parent 5abcc1c3c5
commit 8a6264d933
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 235 additions and 256 deletions

View File

@ -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
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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");

View File

@ -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

View File

@ -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 {

View File

@ -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>;
}

View File

@ -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

View File

@ -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(),

View File

@ -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!(

View File

@ -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![],

View File

@ -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(

View 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"]

View 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"

View 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);

View File

@ -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