Fix issues with extension API that come up when moving Svelte into an extension (#9611)
We're doing it. Svelte support is moving into an extension. This PR fixes some issues that came up along the way. Notes * extensions need to be able to retrieve the path the `node` binary installed by Zed * previously we were silently swallowing any errors that occurred while loading a grammar * npm commands ran by extensions weren't run in the right directory * Tree-sitter's WASM stdlib didn't support a C function (`strncmp`) needed by the Svelte parser's external scanner * the way that LSP installation status was reported was unnecessarily complex Release Notes: - Removed built-in support for the Svelte and Gleam languages, because full support for those languages is now available via extensions. These extensions will be suggested for download when you open a `.svelte` or `.gleam` file. --------- Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
4459eacc98
commit
6ebe599c98
70 changed files with 1278 additions and 1223 deletions
410
Cargo.lock
generated
410
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
@ -1,3 +1,4 @@
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
|
@ -97,6 +98,7 @@ members = [
|
|||
|
||||
"extensions/gleam",
|
||||
"extensions/uiua",
|
||||
"extensions/svelte",
|
||||
|
||||
"tooling/xtask",
|
||||
]
|
||||
|
@ -210,7 +212,7 @@ bitflags = "2.4.2"
|
|||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "61cbd6b2c224791d52b150fe535cee665cc91bb2" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "61cbd6b2c224791d52b150fe535cee665cc91bb2" }
|
||||
blade-rwh = { package = "raw-window-handle", version = "0.5" }
|
||||
cap-std = "2.0"
|
||||
cap-std = "3.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
clickhouse = { version = "0.11.6" }
|
||||
|
@ -293,7 +295,6 @@ tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir"
|
|||
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
tree-sitter-erlang = "0.4.0"
|
||||
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
|
||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" }
|
||||
|
@ -320,7 +321,6 @@ tree-sitter-regex = "0.20.0"
|
|||
tree-sitter-ruby = "0.20.0"
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" }
|
||||
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-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
|
||||
|
@ -330,18 +330,18 @@ unindent = "0.1.7"
|
|||
unicase = "2.6"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmparser = "0.121"
|
||||
wasm-encoder = "0.41"
|
||||
wasmtime = { version = "18.0", default-features = false, features = [
|
||||
wasmparser = "0.201"
|
||||
wasm-encoder = "0.201"
|
||||
wasmtime = { version = "19.0.0", default-features = false, features = [
|
||||
"async",
|
||||
"demangle",
|
||||
"runtime",
|
||||
"cranelift",
|
||||
"component-model",
|
||||
] }
|
||||
wasmtime-wasi = "18.0"
|
||||
wasmtime-wasi = "19.0.0"
|
||||
which = "6.0.0"
|
||||
wit-component = "0.20"
|
||||
wit-component = "0.201"
|
||||
sys-locale = "0.3.1"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
|
@ -375,7 +375,7 @@ features = [
|
|||
]
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "4294e59279205f503eb14348dd5128bd5910c8fb" }
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "05079ae3d1bc44bedc4594eef925b36ba5e317a2" }
|
||||
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
||||
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }
|
||||
|
||||
|
@ -389,6 +389,7 @@ cranelift-codegen = { opt-level = 3 }
|
|||
rustybuzz = { opt-level = 3 }
|
||||
ttf-parser = { opt-level = 3 }
|
||||
wasmtime-cranelift = { opt-level = 3 }
|
||||
wasmtime = { opt-level = 3 }
|
||||
|
||||
[profile.release]
|
||||
debug = "limited"
|
||||
|
|
|
@ -205,7 +205,7 @@ impl ActivityIndicator {
|
|||
}
|
||||
LanguageServerBinaryStatus::Downloading => downloading.push(status.name.0.as_ref()),
|
||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.0.as_ref()),
|
||||
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
|
||||
LanguageServerBinaryStatus::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use rpc::{
|
|||
};
|
||||
use settings::Settings;
|
||||
use std::{mem, sync::Arc, time::Duration};
|
||||
use util::{async_maybe, maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
|
@ -227,7 +227,7 @@ impl ChannelStore {
|
|||
_watch_connection_status: watch_connection_status,
|
||||
disconnect_channel_buffers_task: None,
|
||||
_update_channels: cx.spawn(|this, mut cx| async move {
|
||||
async_maybe!({
|
||||
maybe!(async move {
|
||||
while let Some(update_channels) = update_channels_rx.next().await {
|
||||
if let Some(this) = this.upgrade() {
|
||||
let update_task = this.update(&mut cx, |this, cx| {
|
||||
|
|
|
@ -1228,25 +1228,21 @@ async fn create_room(
|
|||
) -> Result<()> {
|
||||
let live_kit_room = nanoid::nanoid!(30);
|
||||
|
||||
let live_kit_connection_info = {
|
||||
let live_kit_room = live_kit_room.clone();
|
||||
let live_kit_connection_info = util::maybe!(async {
|
||||
let live_kit = session.live_kit_client.as_ref();
|
||||
let live_kit = live_kit?;
|
||||
let user_id = session.user_id().to_string();
|
||||
|
||||
util::async_maybe!({
|
||||
let live_kit = live_kit?;
|
||||
let token = live_kit
|
||||
.room_token(&live_kit_room, &user_id.to_string())
|
||||
.trace_err()?;
|
||||
|
||||
let token = live_kit
|
||||
.room_token(&live_kit_room, &user_id.to_string())
|
||||
.trace_err()?;
|
||||
|
||||
Some(proto::LiveKitConnectionInfo {
|
||||
server_url: live_kit.url().into(),
|
||||
token,
|
||||
can_publish: true,
|
||||
})
|
||||
Some(proto::LiveKitConnectionInfo {
|
||||
server_url: live_kit.url().into(),
|
||||
token,
|
||||
can_publish: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let room = session
|
||||
|
|
|
@ -29,8 +29,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use util::{
|
||||
async_maybe, fs::remove_matching, github::latest_github_release, http::HttpClient, paths,
|
||||
ResultExt,
|
||||
fs::remove_matching, github::latest_github_release, http::HttpClient, maybe, paths, ResultExt,
|
||||
};
|
||||
|
||||
actions!(
|
||||
|
@ -1006,7 +1005,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||
e @ Err(..) => {
|
||||
e.log_err();
|
||||
// Fetch a cached binary, if it exists
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -20,7 +20,7 @@ use sqlez_macros::sql;
|
|||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
|
||||
PRAGMA foreign_keys=TRUE;
|
||||
|
@ -57,7 +57,7 @@ pub async fn open_db<M: Migrator + 'static>(
|
|||
let release_channel_name = release_channel.dev_name();
|
||||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
||||
|
||||
let connection = async_maybe!({
|
||||
let connection = maybe!(async {
|
||||
smol::fs::create_dir_all(&main_db_dir)
|
||||
.await
|
||||
.context("Could not create db directory")
|
||||
|
|
|
@ -85,10 +85,7 @@ impl ExtensionBuilder {
|
|||
|
||||
let cargo_toml_path = extension_dir.join("Cargo.toml");
|
||||
if extension_manifest.lib.kind == Some(ExtensionLibraryKind::Rust)
|
||||
|| fs::metadata(&cargo_toml_path)
|
||||
.ok()
|
||||
.map(|metadata| metadata.is_file())
|
||||
.unwrap_or(false)
|
||||
|| fs::metadata(&cargo_toml_path).map_or(false, |stat| stat.is_file())
|
||||
{
|
||||
log::info!("compiling Rust extension {}", extension_dir.display());
|
||||
self.compile_rust_extension(extension_dir, options)
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
pin::Pin,
|
||||
sync::Arc,
|
||||
};
|
||||
use wasmtime_wasi::preview2::WasiView as _;
|
||||
use wasmtime_wasi::WasiView as _;
|
||||
|
||||
pub struct ExtensionLspAdapter {
|
||||
pub(crate) extension: WasmExtension,
|
||||
|
|
|
@ -39,6 +39,7 @@ use theme::{ThemeRegistry, ThemeSettings};
|
|||
use url::Url;
|
||||
use util::{
|
||||
http::{AsyncBody, HttpClient, HttpClientWithUrl},
|
||||
maybe,
|
||||
paths::EXTENSIONS_DIR,
|
||||
ResultExt,
|
||||
};
|
||||
|
@ -108,6 +109,7 @@ pub enum Event {
|
|||
ExtensionsUpdated,
|
||||
StartedReloading,
|
||||
ExtensionInstalled(Arc<str>),
|
||||
ExtensionFailedToLoad(Arc<str>),
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for ExtensionStore {}
|
||||
|
@ -886,41 +888,38 @@ impl ExtensionStore {
|
|||
continue;
|
||||
};
|
||||
|
||||
let mut path = root_dir.clone();
|
||||
path.extend([extension.manifest.id.as_ref(), "extension.wasm"]);
|
||||
let Some(mut wasm_file) = fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")
|
||||
.log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let wasm_extension = maybe!(async {
|
||||
let mut path = root_dir.clone();
|
||||
path.extend([extension.manifest.clone().id.as_ref(), "extension.wasm"]);
|
||||
let mut wasm_file = fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
if wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")
|
||||
.log_err()
|
||||
.is_none()
|
||||
{
|
||||
continue;
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(
|
||||
wasm_bytes,
|
||||
extension.manifest.clone().clone(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.context("failed to load wasm extension")
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(wasm_extension) = wasm_extension.log_err() {
|
||||
wasm_extensions.push((extension.manifest.clone(), wasm_extension));
|
||||
} else {
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.emit(Event::ExtensionFailedToLoad(extension.manifest.id.clone()))
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
let Some(wasm_extension) = wasm_host
|
||||
.load_extension(
|
||||
wasm_bytes,
|
||||
extension.manifest.clone(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.context("failed to load wasm extension")
|
||||
.log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
wasm_extensions.push((extension.manifest.clone(), wasm_extension));
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry, ExtensionIndexThemeEntry,
|
||||
ExtensionManifest, ExtensionStore, GrammarManifestEntry, RELOAD_DEBOUNCE_DURATION,
|
||||
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
|
||||
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
|
||||
RELOAD_DEBOUNCE_DURATION,
|
||||
};
|
||||
use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
|
@ -558,6 +559,15 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
|||
}
|
||||
});
|
||||
|
||||
extension_store.update(cx, |_, cx| {
|
||||
cx.subscribe(&extension_store, |_, _, event, _| {
|
||||
if matches!(event, Event::ExtensionFailedToLoad(_)) {
|
||||
panic!("extension failed to load");
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
extension_store
|
||||
.update(cx, |store, cx| {
|
||||
store.install_dev_extension(gleam_extension_dir.clone(), cx)
|
||||
|
@ -602,7 +612,7 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
|
|||
),
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::Downloaded
|
||||
LanguageServerBinaryStatus::None
|
||||
)
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
pub(crate) mod wit;
|
||||
|
||||
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::{normalize_path, Fs};
|
||||
use futures::{
|
||||
channel::{
|
||||
|
@ -10,42 +9,28 @@ use futures::{
|
|||
oneshot,
|
||||
},
|
||||
future::BoxFuture,
|
||||
io::BufReader,
|
||||
Future, FutureExt, StreamExt as _,
|
||||
};
|
||||
use gpui::BackgroundExecutor;
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LspAdapterDelegate};
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::NodeRuntime;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::{http::HttpClient, SemanticVersion};
|
||||
use wasmtime::{
|
||||
component::{Component, Linker, Resource, ResourceTable},
|
||||
component::{Component, ResourceTable},
|
||||
Engine, Store,
|
||||
};
|
||||
use wasmtime_wasi::preview2::{self as wasi, WasiCtx};
|
||||
|
||||
pub mod wit {
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
path: "../extension_api/wit",
|
||||
with: {
|
||||
"worktree": super::ExtensionWorktree,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
use wasmtime_wasi as wasi;
|
||||
use wit::Extension;
|
||||
|
||||
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>,
|
||||
pub(crate) language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
pub(crate) work_dir: PathBuf,
|
||||
}
|
||||
|
@ -60,17 +45,27 @@ pub struct WasmExtension {
|
|||
|
||||
pub(crate) struct WasmState {
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
table: ResourceTable,
|
||||
pub(crate) table: ResourceTable,
|
||||
ctx: wasi::WasiCtx,
|
||||
host: Arc<WasmHost>,
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
}
|
||||
|
||||
type ExtensionCall = Box<
|
||||
dyn Send
|
||||
+ for<'a> FnOnce(&'a mut wit::Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, ()>,
|
||||
dyn Send + for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, ()>,
|
||||
>;
|
||||
|
||||
static WASM_ENGINE: OnceLock<wasmtime::Engine> = OnceLock::new();
|
||||
fn wasm_engine() -> wasmtime::Engine {
|
||||
static WASM_ENGINE: OnceLock<wasmtime::Engine> = OnceLock::new();
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
impl WasmHost {
|
||||
pub fn new(
|
||||
|
@ -80,20 +75,8 @@ impl WasmHost {
|
|||
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, wasi_view).unwrap();
|
||||
Arc::new(Self {
|
||||
engine,
|
||||
linker: Arc::new(linker),
|
||||
engine: wasm_engine(),
|
||||
fs,
|
||||
work_dir,
|
||||
http_client,
|
||||
|
@ -144,9 +127,8 @@ impl WasmHost {
|
|||
);
|
||||
|
||||
let (mut extension, instance) =
|
||||
wit::Extension::instantiate_async(&mut store, &component, &this.linker)
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Extension::instantiate_async(&mut store, zed_api_version, &component).await?;
|
||||
|
||||
extension
|
||||
.call_init_extension(&mut store)
|
||||
.await
|
||||
|
@ -170,7 +152,7 @@ impl WasmHost {
|
|||
}
|
||||
}
|
||||
|
||||
async fn build_wasi_ctx(&self, manifest: &Arc<ExtensionManifest>) -> Result<WasiCtx> {
|
||||
async fn build_wasi_ctx(&self, manifest: &Arc<ExtensionManifest>) -> Result<wasi::WasiCtx> {
|
||||
use cap_std::{ambient_authority, fs::Dir};
|
||||
|
||||
let extension_work_dir = self.work_dir.join(manifest.id.as_ref());
|
||||
|
@ -232,7 +214,7 @@ impl WasmExtension {
|
|||
T: 'static + Send,
|
||||
Fn: 'static
|
||||
+ Send
|
||||
+ for<'a> FnOnce(&'a mut wit::Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, T>,
|
||||
+ for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, T>,
|
||||
{
|
||||
let (return_tx, return_rx) = oneshot::channel();
|
||||
self.tx
|
||||
|
@ -249,279 +231,10 @@ impl WasmExtension {
|
|||
}
|
||||
}
|
||||
|
||||
#[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()))
|
||||
impl WasmState {
|
||||
fn work_dir(&self) -> PathBuf {
|
||||
self.host.work_dir.join(self.manifest.id.as_ref())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[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 npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
package_name: String,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
this.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&this.host.work_dir, &package_name)
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(inner(self, package_name)
|
||||
.await
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> anyhow::Result<()> {
|
||||
this.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&this.host.work_dir, &[(&package_name, &version)])
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(inner(self, package_name, version)
|
||||
.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 env::consts::OS {
|
||||
"macos" => wit::Os::Mac,
|
||||
"linux" => wit::Os::Linux,
|
||||
"windows" => wit::Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match 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,
|
||||
path: String,
|
||||
file_type: wit::DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
let path = PathBuf::from(path);
|
||||
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
url: String,
|
||||
path: PathBuf,
|
||||
file_type: wit::DownloadedFileType,
|
||||
) -> anyhow::Result<()> {
|
||||
let extension_work_dir = this.host.work_dir.join(this.manifest.id.as_ref());
|
||||
|
||||
this.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = this
|
||||
.host
|
||||
.writeable_path_from_extension(&this.manifest.id, &path)?;
|
||||
|
||||
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 file_name = destination_path
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("invalid download path"))?
|
||||
.to_string_lossy();
|
||||
let zip_filename = format!("{file_name}.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(&extension_work_dir)
|
||||
.arg(&zip_path)
|
||||
.output()?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip {} archive", path.display()))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(inner(self, url, path, file_type)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn wasi_view(state: &mut WasmState) -> &mut WasmState {
|
||||
state
|
||||
}
|
||||
|
||||
impl wasi::WasiView for WasmState {
|
||||
|
|
103
crates/extension/src/wasm_host/wit.rs
Normal file
103
crates/extension/src/wasm_host/wit.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
mod v0_0_1;
|
||||
mod v0_0_4;
|
||||
|
||||
use super::{wasm_engine, WasmState};
|
||||
use anyhow::{Context, Result};
|
||||
use language::LspAdapterDelegate;
|
||||
use std::sync::Arc;
|
||||
use util::SemanticVersion;
|
||||
use wasmtime::{
|
||||
component::{Component, Instance, Linker, Resource},
|
||||
Store,
|
||||
};
|
||||
|
||||
use v0_0_4 as latest;
|
||||
|
||||
pub use latest::{Command, LanguageServerConfig};
|
||||
|
||||
pub fn new_linker(
|
||||
f: impl Fn(&mut Linker<WasmState>, fn(&mut WasmState) -> &mut WasmState) -> Result<()>,
|
||||
) -> Linker<WasmState> {
|
||||
let mut linker = Linker::new(&wasm_engine());
|
||||
wasmtime_wasi::command::add_to_linker(&mut linker).unwrap();
|
||||
f(&mut linker, wasi_view).unwrap();
|
||||
linker
|
||||
}
|
||||
|
||||
fn wasi_view(state: &mut WasmState) -> &mut WasmState {
|
||||
state
|
||||
}
|
||||
|
||||
pub enum Extension {
|
||||
V004(v0_0_4::Extension),
|
||||
V001(v0_0_1::Extension),
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
pub async fn instantiate_async(
|
||||
store: &mut Store<WasmState>,
|
||||
version: SemanticVersion,
|
||||
component: &Component,
|
||||
) -> Result<(Self, Instance)> {
|
||||
if version < latest::VERSION {
|
||||
let (extension, instance) =
|
||||
v0_0_1::Extension::instantiate_async(store, &component, v0_0_1::linker())
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Ok((Self::V001(extension), instance))
|
||||
} else {
|
||||
let (extension, instance) =
|
||||
v0_0_4::Extension::instantiate_async(store, &component, v0_0_4::linker())
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Ok((Self::V004(extension), instance))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
|
||||
match self {
|
||||
Extension::V004(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V001(ext) => ext.call_init_extension(store).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_language_server_command(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
config: &LanguageServerConfig,
|
||||
resource: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> Result<Result<Command, String>> {
|
||||
match self {
|
||||
Extension::V004(ext) => {
|
||||
ext.call_language_server_command(store, config, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V001(ext) => Ok(ext
|
||||
.call_language_server_command(store, &config.clone().into(), resource)
|
||||
.await?
|
||||
.map(|command| command.into())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_language_server_initialization_options(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
config: &LanguageServerConfig,
|
||||
resource: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V004(ext) => {
|
||||
ext.call_language_server_initialization_options(store, config, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V001(ext) => {
|
||||
ext.call_language_server_initialization_options(
|
||||
store,
|
||||
&config.clone().into(),
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
210
crates/extension/src/wasm_host/wit/v0_0_1.rs
Normal file
210
crates/extension/src/wasm_host/wit/v0_0_1.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
use super::latest;
|
||||
use crate::wasm_host::WasmState;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerBinaryStatus, LspAdapterDelegate};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
path: "../extension_api/wit/0.0.1",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
},
|
||||
});
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
impl From<latest::Os> for Os {
|
||||
fn from(value: latest::Os) -> Self {
|
||||
match value {
|
||||
latest::Os::Mac => Os::Mac,
|
||||
latest::Os::Linux => Os::Linux,
|
||||
latest::Os::Windows => Os::Windows,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::Architecture> for Architecture {
|
||||
fn from(value: latest::Architecture) -> Self {
|
||||
match value {
|
||||
latest::Architecture::Aarch64 => Self::Aarch64,
|
||||
latest::Architecture::X86 => Self::X86,
|
||||
latest::Architecture::X8664 => Self::X8664,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::GithubRelease> for GithubRelease {
|
||||
fn from(value: latest::GithubRelease) -> Self {
|
||||
Self {
|
||||
version: value.version,
|
||||
assets: value.assets.into_iter().map(|asset| asset.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::GithubReleaseAsset> for GithubReleaseAsset {
|
||||
fn from(value: latest::GithubReleaseAsset) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
download_url: value.download_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GithubReleaseOptions> for latest::GithubReleaseOptions {
|
||||
fn from(value: GithubReleaseOptions) -> Self {
|
||||
Self {
|
||||
require_assets: value.require_assets,
|
||||
pre_release: value.pre_release,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DownloadedFileType> for latest::DownloadedFileType {
|
||||
fn from(value: DownloadedFileType) -> Self {
|
||||
match value {
|
||||
DownloadedFileType::Gzip => latest::DownloadedFileType::Gzip,
|
||||
DownloadedFileType::GzipTar => latest::DownloadedFileType::GzipTar,
|
||||
DownloadedFileType::Zip => latest::DownloadedFileType::Zip,
|
||||
DownloadedFileType::Uncompressed => latest::DownloadedFileType::Uncompressed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::LanguageServerConfig> for LanguageServerConfig {
|
||||
fn from(value: latest::LanguageServerConfig) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
language_name: value.language_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Command> for latest::Command {
|
||||
fn from(value: Command) -> Self {
|
||||
Self {
|
||||
command: value.command,
|
||||
args: value.args,
|
||||
env: value.env,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HostWorktree for WasmState {
|
||||
async fn read_text_file(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
latest::HostWorktree::read_text_file(self, delegate, path).await
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> wasmtime::Result<EnvVars> {
|
||||
latest::HostWorktree::shell_env(self, delegate).await
|
||||
}
|
||||
|
||||
async fn which(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
binary_name: String,
|
||||
) -> wasmtime::Result<Option<String>> {
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
}
|
||||
|
||||
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
latest::ExtensionImports::node_binary_path(self).await
|
||||
}
|
||||
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
latest::ExtensionImports::npm_package_latest_version(self, package_name).await
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
latest::ExtensionImports::npm_package_installed_version(self, package_name).await
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
latest::ExtensionImports::npm_install_package(self, package_name, version).await
|
||||
}
|
||||
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<GithubRelease, String>> {
|
||||
Ok(
|
||||
latest::ExtensionImports::latest_github_release(self, repo, options.into())
|
||||
.await?
|
||||
.map(|github| github.into()),
|
||||
)
|
||||
}
|
||||
|
||||
async fn current_platform(&mut self) -> Result<(Os, Architecture)> {
|
||||
latest::ExtensionImports::current_platform(self)
|
||||
.await
|
||||
.map(|(os, arch)| (os.into(), arch.into()))
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
&mut self,
|
||||
server_name: String,
|
||||
status: LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
let status = match status {
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
}
|
||||
LanguageServerInstallationStatus::Downloading => {
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
}
|
||||
LanguageServerInstallationStatus::Cached
|
||||
| LanguageServerInstallationStatus::Downloaded => LanguageServerBinaryStatus::None,
|
||||
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,
|
||||
path: String,
|
||||
file_type: DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
latest::ExtensionImports::download_file(self, url, path, file_type.into()).await
|
||||
}
|
||||
}
|
284
crates/extension/src/wasm_host/wit/v0_0_4.rs
Normal file
284
crates/extension/src/wasm_host/wit/v0_0_4.rs
Normal file
|
@ -0,0 +1,284 @@
|
|||
use crate::wasm_host::WasmState;
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::io::BufReader;
|
||||
use language::{LanguageServerBinaryStatus, LspAdapterDelegate};
|
||||
use std::{
|
||||
env,
|
||||
path::PathBuf,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::{maybe, SemanticVersion};
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
pub const VERSION: SemanticVersion = SemanticVersion {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 4,
|
||||
};
|
||||
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
path: "../extension_api/wit/0.0.4",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
},
|
||||
});
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl 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()))
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> wasmtime::Result<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<Worktree>) -> Result<()> {
|
||||
// we only ever hand out borrows of worktrees
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
convert_result(
|
||||
self.host
|
||||
.node_runtime
|
||||
.binary_path()
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
convert_result(
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
convert_result(
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&self.work_dir(), &package_name)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
convert_result(
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<GithubRelease, String>> {
|
||||
convert_result(
|
||||
maybe!(async {
|
||||
let release = util::github::latest_github_release(
|
||||
&repo,
|
||||
options.require_assets,
|
||||
options.pre_release,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(GithubRelease {
|
||||
version: release.tag_name,
|
||||
assets: release
|
||||
.assets
|
||||
.into_iter()
|
||||
.map(|asset| GithubReleaseAsset {
|
||||
name: asset.name,
|
||||
download_url: asset.browser_download_url,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
async fn current_platform(&mut self) -> Result<(Os, Architecture)> {
|
||||
Ok((
|
||||
match env::consts::OS {
|
||||
"macos" => Os::Mac,
|
||||
"linux" => Os::Linux,
|
||||
"windows" => Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match env::consts::ARCH {
|
||||
"aarch64" => Architecture::Aarch64,
|
||||
"x86" => Architecture::X86,
|
||||
"x86_64" => Architecture::X8664,
|
||||
_ => panic!("unsupported architecture"),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
&mut self,
|
||||
server_name: String,
|
||||
status: LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
let status = match status {
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
}
|
||||
LanguageServerInstallationStatus::Downloading => {
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
}
|
||||
LanguageServerInstallationStatus::None => LanguageServerBinaryStatus::None,
|
||||
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,
|
||||
path: String,
|
||||
file_type: DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
let result = maybe!(async {
|
||||
let path = PathBuf::from(path);
|
||||
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
|
||||
|
||||
self.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, &path)?;
|
||||
|
||||
let mut response = self
|
||||
.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 {
|
||||
DownloadedFileType::Uncompressed => {
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Gzip => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::GzipTar => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.extract_tar_file(&destination_path, Archive::new(body))
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Zip => {
|
||||
let file_name = destination_path
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("invalid download path"))?
|
||||
.to_string_lossy();
|
||||
let zip_filename = format!("{file_name}.zip");
|
||||
let mut zip_path = destination_path.clone();
|
||||
zip_path.set_file_name(zip_filename);
|
||||
|
||||
futures::pin_mut!(body);
|
||||
self.host.fs.create_file_with(&zip_path, body).await?;
|
||||
|
||||
let unzip_status = std::process::Command::new("unzip")
|
||||
.current_dir(&extension_work_dir)
|
||||
.arg(&zip_path)
|
||||
.output()?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip {} archive", path.display()))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
convert_result(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_result<T>(result: Result<T>) -> wasmtime::Result<Result<T, String>> {
|
||||
Ok(result.map_err(|error| error.to_string()))
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "zed_extension_api"
|
||||
version = "0.0.1"
|
||||
version = "0.0.4"
|
||||
description = "APIs for creating Zed extensions in Rust"
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
documentation = "https://docs.rs/zed_extension_api"
|
||||
|
@ -15,7 +15,7 @@ workspace = true
|
|||
path = "src/extension_api.rs"
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = "0.18"
|
||||
wit-bindgen = "0.22"
|
||||
|
||||
[package.metadata.component]
|
||||
target = { path = "wit" }
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
pub struct Guest;
|
||||
pub use wit::*;
|
||||
|
||||
pub type Result<T, E = String> = core::result::Result<T, E>;
|
||||
|
||||
pub trait Extension: Send + Sync {
|
||||
|
@ -10,14 +8,14 @@ pub trait Extension: Send + Sync {
|
|||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
config: wit::LanguageServerConfig,
|
||||
worktree: &wit::Worktree,
|
||||
config: LanguageServerConfig,
|
||||
worktree: &Worktree,
|
||||
) -> Result<Command>;
|
||||
|
||||
fn language_server_initialization_options(
|
||||
&mut self,
|
||||
_config: wit::LanguageServerConfig,
|
||||
_worktree: &wit::Worktree,
|
||||
_config: LanguageServerConfig,
|
||||
_worktree: &Worktree,
|
||||
) -> Result<Option<String>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -54,11 +52,13 @@ pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "
|
|||
|
||||
mod wit {
|
||||
wit_bindgen::generate!({
|
||||
exports: { world: super::Component },
|
||||
skip: ["init-extension"]
|
||||
skip: ["init-extension"],
|
||||
path: "./wit/0.0.4",
|
||||
});
|
||||
}
|
||||
|
||||
wit::export!(Component);
|
||||
|
||||
struct Component;
|
||||
|
||||
impl wit::Guest for Component {
|
||||
|
|
|
@ -48,6 +48,9 @@ world extension {
|
|||
/// Gets the current operating system and architecture
|
||||
import current-platform: func() -> tuple<os, architecture>;
|
||||
|
||||
/// Get the path to the node binary used by Zed.
|
||||
import node-binary-path: func() -> result<string, string>;
|
||||
|
||||
/// Gets the latest version of the given NPM package.
|
||||
import npm-package-latest-version: func(package-name: string) -> result<string, string>;
|
||||
|
93
crates/extension_api/wit/0.0.4/extension.wit
Normal file
93
crates/extension_api/wit/0.0.4/extension.wit
Normal file
|
@ -0,0 +1,93 @@
|
|||
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 {
|
||||
none,
|
||||
downloading,
|
||||
checking-for-update,
|
||||
failed(string),
|
||||
}
|
||||
|
||||
/// Gets the current operating system and architecture
|
||||
import current-platform: func() -> tuple<os, architecture>;
|
||||
|
||||
/// Get the path to the node binary used by Zed.
|
||||
import node-binary-path: func() -> result<string, string>;
|
||||
|
||||
/// Gets the latest version of the given NPM package.
|
||||
import npm-package-latest-version: func(package-name: string) -> result<string, string>;
|
||||
|
||||
/// Returns the installed version of the given NPM package, if it exists.
|
||||
import npm-package-installed-version: func(package-name: string) -> result<option<string>, string>;
|
||||
|
||||
/// Installs the specified NPM package.
|
||||
import npm-install-package: func(package-name: string, version: string) -> result<_, 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);
|
||||
|
||||
type env-vars = list<tuple<string, string>>;
|
||||
|
||||
record command {
|
||||
command: string,
|
||||
args: list<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 {
|
||||
name: string,
|
||||
language-name: string,
|
||||
}
|
||||
|
||||
export language-server-command: func(config: language-server-config, worktree: borrow<worktree>) -> result<command, string>;
|
||||
export language-server-initialization-options: func(config: language-server-config, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
}
|
|
@ -29,6 +29,7 @@ pub fn suggested_extension(file_extension_or_name: &str) -> Option<Arc<str>> {
|
|||
("git-firefly", "MERGE_MSG"),
|
||||
("git-firefly", "NOTES_EDITMSG"),
|
||||
("git-firefly", "TAG_EDITMSG"),
|
||||
("gleam", "gleam"),
|
||||
("graphql", "gql"),
|
||||
("graphql", "graphql"),
|
||||
("java", "java"),
|
||||
|
@ -39,6 +40,7 @@ pub fn suggested_extension(file_extension_or_name: &str) -> Option<Arc<str>> {
|
|||
("r", "r"),
|
||||
("r", "R"),
|
||||
("sql", "sql"),
|
||||
("svelte", "svelte"),
|
||||
("swift", "swift"),
|
||||
("templ", "templ"),
|
||||
("wgsl", "wgsl"),
|
||||
|
|
|
@ -134,11 +134,15 @@ pub struct CachedLspAdapter {
|
|||
pub language_ids: HashMap<String, String>,
|
||||
pub adapter: Arc<dyn LspAdapter>,
|
||||
pub reinstall_attempt_count: AtomicU64,
|
||||
/// Indicates whether this language server is the primary language server
|
||||
/// for a given language. Currently, most LSP-backed features only work
|
||||
/// with one language server, so one server needs to be primary.
|
||||
pub is_primary: bool,
|
||||
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
|
||||
}
|
||||
|
||||
impl CachedLspAdapter {
|
||||
pub fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
|
||||
pub fn new(adapter: Arc<dyn LspAdapter>, is_primary: bool) -> Arc<Self> {
|
||||
let name = adapter.name();
|
||||
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources();
|
||||
let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token();
|
||||
|
@ -150,6 +154,7 @@ impl CachedLspAdapter {
|
|||
disk_based_diagnostics_progress_token,
|
||||
language_ids,
|
||||
adapter,
|
||||
is_primary,
|
||||
cached_binary: Default::default(),
|
||||
reinstall_attempt_count: AtomicU64::new(0),
|
||||
})
|
||||
|
@ -293,7 +298,6 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
|
||||
.await
|
||||
{
|
||||
delegate.update_status(self.name(), LanguageServerBinaryStatus::Cached);
|
||||
log::info!(
|
||||
"failed to fetch newest version of language server {:?}. falling back to using {:?}",
|
||||
self.name(),
|
||||
|
@ -464,7 +468,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
|
|||
.fetch_server_binary(latest_version, container_dir, delegate.as_ref())
|
||||
.await;
|
||||
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded);
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::None);
|
||||
binary
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ use sum_tree::Bias;
|
|||
use text::{Point, Rope};
|
||||
use theme::Theme;
|
||||
use unicase::UniCase;
|
||||
use util::{paths::PathExt, post_inc, ResultExt};
|
||||
use util::{maybe, paths::PathExt, post_inc, ResultExt};
|
||||
|
||||
pub struct LanguageRegistry {
|
||||
state: RwLock<LanguageRegistryState>,
|
||||
|
@ -54,10 +54,9 @@ struct LanguageRegistryState {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LanguageServerBinaryStatus {
|
||||
None,
|
||||
CheckingForUpdate,
|
||||
Downloading,
|
||||
Downloaded,
|
||||
Cached,
|
||||
Failed { error: String },
|
||||
}
|
||||
|
||||
|
@ -91,9 +90,10 @@ enum AvailableGrammar {
|
|||
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
|
||||
Loading(
|
||||
#[allow(unused)] PathBuf,
|
||||
Vec<oneshot::Sender<Result<tree_sitter::Language>>>,
|
||||
Vec<oneshot::Sender<Result<tree_sitter::Language, Arc<anyhow::Error>>>>,
|
||||
),
|
||||
Unloaded(PathBuf),
|
||||
LoadFailed(Arc<anyhow::Error>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -213,7 +213,20 @@ impl LanguageRegistry {
|
|||
.lsp_adapters
|
||||
.entry(language_name)
|
||||
.or_default()
|
||||
.push(CachedLspAdapter::new(adapter));
|
||||
.push(CachedLspAdapter::new(adapter, true));
|
||||
}
|
||||
|
||||
pub fn register_secondary_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, false));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
|
@ -227,7 +240,7 @@ impl LanguageRegistry {
|
|||
.lsp_adapters
|
||||
.entry(language_name.into())
|
||||
.or_default()
|
||||
.push(CachedLspAdapter::new(Arc::new(adapter)));
|
||||
.push(CachedLspAdapter::new(Arc::new(adapter), true));
|
||||
self.fake_language_servers(language_name)
|
||||
}
|
||||
|
||||
|
@ -578,6 +591,9 @@ impl LanguageRegistry {
|
|||
|
||||
if let Some(grammar) = state.grammars.get_mut(name.as_ref()) {
|
||||
match grammar {
|
||||
AvailableGrammar::LoadFailed(error) => {
|
||||
tx.send(Err(error.clone())).ok();
|
||||
}
|
||||
AvailableGrammar::Native(grammar) | AvailableGrammar::Loaded(_, grammar) => {
|
||||
tx.send(Ok(grammar.clone())).ok();
|
||||
}
|
||||
|
@ -586,46 +602,47 @@ impl LanguageRegistry {
|
|||
}
|
||||
AvailableGrammar::Unloaded(wasm_path) => {
|
||||
let this = self.clone();
|
||||
let wasm_path = wasm_path.clone();
|
||||
*grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
|
||||
self.executor
|
||||
.spawn({
|
||||
let wasm_path = wasm_path.clone();
|
||||
async move {
|
||||
.spawn(async move {
|
||||
let grammar_result = maybe!({
|
||||
let wasm_bytes = std::fs::read(&wasm_path)?;
|
||||
let grammar_name = wasm_path
|
||||
.file_stem()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or_else(|| anyhow!("invalid grammar filename"))?;
|
||||
let grammar = PARSER.with(|parser| {
|
||||
anyhow::Ok(PARSER.with(|parser| {
|
||||
let mut parser = parser.borrow_mut();
|
||||
let mut store = parser.take_wasm_store().unwrap();
|
||||
let grammar = store.load_language(&grammar_name, &wasm_bytes);
|
||||
parser.set_wasm_store(store).unwrap();
|
||||
grammar
|
||||
})?;
|
||||
})?)
|
||||
})
|
||||
.map_err(Arc::new);
|
||||
|
||||
if let Some(AvailableGrammar::Loading(_, txs)) =
|
||||
this.state.write().grammars.insert(
|
||||
name,
|
||||
AvailableGrammar::Loaded(wasm_path, grammar.clone()),
|
||||
)
|
||||
{
|
||||
for tx in txs {
|
||||
tx.send(Ok(grammar.clone())).ok();
|
||||
}
|
||||
let value = match &grammar_result {
|
||||
Ok(grammar) => AvailableGrammar::Loaded(wasm_path, grammar.clone()),
|
||||
Err(error) => AvailableGrammar::LoadFailed(error.clone()),
|
||||
};
|
||||
|
||||
let old_value = this.state.write().grammars.insert(name, value);
|
||||
if let Some(AvailableGrammar::Loading(_, txs)) = old_value {
|
||||
for tx in txs {
|
||||
tx.send(grammar_result.clone()).ok();
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
*grammar = AvailableGrammar::Loading(wasm_path.clone(), vec![tx]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tx.send(Err(anyhow!("no such grammar {}", name))).ok();
|
||||
tx.send(Err(Arc::new(anyhow!("no such grammar {}", name))))
|
||||
.ok();
|
||||
}
|
||||
|
||||
async move { rx.await? }
|
||||
async move { rx.await?.map_err(|e| anyhow!(e)) }
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<Arc<Language>> {
|
||||
|
@ -691,7 +708,7 @@ impl LanguageRegistry {
|
|||
// the login shell to be set on our process.
|
||||
login_shell_env_loaded.await;
|
||||
|
||||
let binary = adapter
|
||||
let binary_result = adapter
|
||||
.clone()
|
||||
.get_language_server_command(
|
||||
language.clone(),
|
||||
|
@ -699,8 +716,11 @@ impl LanguageRegistry {
|
|||
delegate.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None);
|
||||
|
||||
let binary = binary_result?;
|
||||
let options = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
|
|
|
@ -49,7 +49,6 @@ tree-sitter-elixir.workspace = true
|
|||
tree-sitter-elm.workspace = true
|
||||
tree-sitter-embedded-template.workspace = true
|
||||
tree-sitter-erlang.workspace = true
|
||||
tree-sitter-gleam.workspace = true
|
||||
tree-sitter-glsl.workspace = true
|
||||
tree-sitter-go.workspace = true
|
||||
tree-sitter-gomod.workspace = true
|
||||
|
@ -75,7 +74,6 @@ tree-sitter-regex.workspace = true
|
|||
tree-sitter-ruby.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-scheme.workspace = true
|
||||
tree-sitter-svelte.workspace = true
|
||||
tree-sitter-toml.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/@astrojs/language-server/bin/nodeServer.js";
|
||||
|
||||
|
@ -107,7 +107,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -7,10 +7,9 @@ use lsp::LanguageServerBinary;
|
|||
use smol::fs::{self, File};
|
||||
use std::{any::Any, env::consts, path::PathBuf, sync::Arc};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
pub struct CLspAdapter;
|
||||
|
@ -264,7 +263,7 @@ impl super::LspAdapter for CLspAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_clangd_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -9,8 +9,7 @@ use smol::fs;
|
|||
use std::env::consts::ARCH;
|
||||
use std::ffi::OsString;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
use util::{github::latest_github_release, maybe};
|
||||
use util::{github::GitHubLspBinaryVersion, ResultExt};
|
||||
|
||||
pub struct OmniSharpAdapter;
|
||||
|
@ -115,7 +114,7 @@ impl super::LspAdapter for OmniSharpAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str =
|
||||
"node_modules/vscode-langservers-extracted/bin/vscode-css-language-server";
|
||||
|
@ -105,7 +105,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -11,10 +11,9 @@ use settings::Settings;
|
|||
use smol::{fs, fs::File};
|
||||
use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
|
@ -207,7 +206,7 @@ impl LspAdapter for DenoLspAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/dockerfile-language-server-nodejs/bin/docker-langserver";
|
||||
|
||||
|
@ -94,7 +94,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -20,10 +20,9 @@ use std::{
|
|||
};
|
||||
use task::static_source::{Definition, TaskDefinitions};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
|
@ -413,7 +412,7 @@ impl LspAdapter for NextLspAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_NAME: &str = "elm-language-server";
|
||||
const SERVER_PATH: &str = "node_modules/@elm-tooling/elm-language-server/out/node/index.js";
|
||||
|
@ -120,7 +120,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
use std::any::Any;
|
||||
use std::env::consts;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::io::BufReader;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use util::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
use util::{async_maybe, ResultExt};
|
||||
|
||||
fn server_binary_arguments() -> Vec<OsString> {
|
||||
vec!["lsp".into()]
|
||||
}
|
||||
|
||||
pub struct GleamLspAdapter;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for GleamLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("gleam".into())
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("gleam-lang/gleam", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!(
|
||||
"gleam-{version}-{arch}-{os}.tar.gz",
|
||||
version = release.tag_name,
|
||||
arch = std::env::consts::ARCH,
|
||||
os = match consts::OS {
|
||||
"macos" => "apple-darwin",
|
||||
"linux" => "unknown-linux-musl",
|
||||
"windows" => "pc-windows-msvc",
|
||||
other => bail!("Running on unsupported os: {other}"),
|
||||
},
|
||||
);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
Ok(Box::new(GitHubLspBinaryVersion {
|
||||
name: release.tag_name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
let binary_path = container_dir.join("gleam");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let archive = Archive::new(decompressed_bytes);
|
||||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--version".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
|
||||
anyhow::Ok(LanguageServerBinary {
|
||||
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
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"] },
|
||||
]
|
|
@ -1,130 +0,0 @@
|
|||
; 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
|
|
@ -1,3 +0,0 @@
|
|||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
|
@ -1,31 +0,0 @@
|
|||
(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
|
|
@ -19,7 +19,7 @@ use std::{
|
|||
Arc,
|
||||
},
|
||||
};
|
||||
use util::{async_maybe, fs::remove_matching, github::latest_github_release, ResultExt};
|
||||
use util::{fs::remove_matching, github::latest_github_release, maybe, ResultExt};
|
||||
|
||||
fn server_binary_arguments() -> Vec<OsString> {
|
||||
vec!["-mode=stdio".into()]
|
||||
|
@ -368,7 +368,7 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str =
|
||||
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
|
||||
|
@ -105,7 +105,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -16,7 +16,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::{async_maybe, paths, ResultExt};
|
||||
use util::{maybe, paths, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
|
||||
|
||||
|
@ -167,7 +167,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -22,7 +22,6 @@ mod dockerfile;
|
|||
mod elixir;
|
||||
mod elm;
|
||||
mod erlang;
|
||||
mod gleam;
|
||||
mod go;
|
||||
mod haskell;
|
||||
mod html;
|
||||
|
@ -36,7 +35,6 @@ mod purescript;
|
|||
mod python;
|
||||
mod ruby;
|
||||
mod rust;
|
||||
mod svelte;
|
||||
mod tailwind;
|
||||
mod terraform;
|
||||
mod toml;
|
||||
|
@ -83,7 +81,6 @@ pub fn init(
|
|||
tree_sitter_embedded_template::language(),
|
||||
),
|
||||
("erlang", tree_sitter_erlang::language()),
|
||||
("gleam", tree_sitter_gleam::language()),
|
||||
("glsl", tree_sitter_glsl::language()),
|
||||
("go", tree_sitter_go::language()),
|
||||
("gomod", tree_sitter_gomod::language()),
|
||||
|
@ -113,7 +110,6 @@ pub fn init(
|
|||
("ruby", tree_sitter_ruby::language()),
|
||||
("rust", tree_sitter_rust::language()),
|
||||
("scheme", tree_sitter_scheme::language()),
|
||||
("svelte", tree_sitter_svelte::language()),
|
||||
("toml", tree_sitter_toml::language()),
|
||||
("tsx", tree_sitter_typescript::language_tsx()),
|
||||
("typescript", tree_sitter_typescript::language_typescript()),
|
||||
|
@ -237,8 +233,6 @@ pub fn init(
|
|||
}
|
||||
}
|
||||
language!("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]);
|
||||
|
||||
language!("gleam", vec![Arc::new(gleam::GleamLspAdapter)]);
|
||||
language!("go", vec![Arc::new(go::GoLspAdapter)]);
|
||||
language!("gomod");
|
||||
language!("gowork");
|
||||
|
@ -346,13 +340,6 @@ pub fn init(
|
|||
"yaml",
|
||||
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))]
|
||||
);
|
||||
language!(
|
||||
"svelte",
|
||||
vec![
|
||||
Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
);
|
||||
language!(
|
||||
"php",
|
||||
vec![
|
||||
|
@ -393,6 +380,11 @@ pub fn init(
|
|||
))]
|
||||
);
|
||||
language!("dart", vec![Arc::new(dart::DartLanguageServer {})]);
|
||||
|
||||
languages.register_secondary_lsp_adapter(
|
||||
"Svelte".into(),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
|
|
@ -8,9 +8,8 @@ use lsp::LanguageServerBinary;
|
|||
use smol::fs;
|
||||
use std::{any::Any, env::consts, path::PathBuf};
|
||||
use util::{
|
||||
async_maybe,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -117,7 +116,7 @@ impl super::LspAdapter for LuaLspAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
fn intelephense_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
|
@ -106,7 +106,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/.bin/prisma-language-server";
|
||||
|
||||
|
@ -94,7 +94,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -13,7 +13,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/.bin/purescript-language-server";
|
||||
|
||||
|
@ -115,7 +115,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -16,10 +16,9 @@ use task::{
|
|||
TaskVariables,
|
||||
};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
pub struct RustLspAdapter;
|
||||
|
@ -397,7 +396,7 @@ impl ContextProvider for RustContextProvider {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde_json::json;
|
||||
use smol::fs;
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/svelte-language-server/bin/server.js";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct SvelteLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
|
||||
impl SvelteLspAdapter {
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
SvelteLspAdapter { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for SvelteLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("svelte-language-server".into())
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version("svelte-language-server")
|
||||
.await?,
|
||||
) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let package_name = "svelte-language-server";
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let config = json!({
|
||||
"inlayHints": {
|
||||
"parameterNames": {
|
||||
"enabled": "all",
|
||||
"suppressWhenArgumentMatchesName": false
|
||||
},
|
||||
"parameterTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"variableTypes": {
|
||||
"enabled": true,
|
||||
"suppressWhenTypeMatchesName": false
|
||||
},
|
||||
"propertyDeclarationTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"functionLikeReturnType": {
|
||||
"enabled": true
|
||||
},
|
||||
"enumMemberValues": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true,
|
||||
"configuration": {
|
||||
"typescript": config,
|
||||
"javascript": config
|
||||
}
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
[
|
||||
(style_element)
|
||||
(script_element)
|
||||
(element)
|
||||
(if_statement)
|
||||
(else_statement)
|
||||
(each_statement)
|
||||
(await_statement)
|
||||
] @fold
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server";
|
||||
|
||||
|
@ -135,7 +135,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -7,10 +7,9 @@ use lsp::{CodeActionKind, LanguageServerBinary};
|
|||
use smol::fs::{self, File};
|
||||
use std::{any::Any, ffi::OsString, path::PathBuf};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
fn terraform_ls_binary_arguments() -> Vec<OsString> {
|
||||
|
@ -154,7 +153,7 @@ fn build_download_url(version: String) -> Result<String> {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -6,8 +6,8 @@ use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
|||
use lsp::LanguageServerBinary;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
use util::maybe;
|
||||
use util::{github::GitHubLspBinaryVersion, ResultExt};
|
||||
|
||||
pub struct TaploLspAdapter;
|
||||
|
@ -108,7 +108,7 @@ impl LspAdapter for TaploLspAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -18,10 +18,9 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{build_tarball_url, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
|
@ -199,7 +198,7 @@ async fn get_cached_ts_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH);
|
||||
let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH);
|
||||
if new_server_path.exists() {
|
||||
|
@ -378,7 +377,7 @@ async fn get_cached_eslint_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
// This is unfortunate but we don't know what the version is to build a path directly
|
||||
let mut dir = fs::read_dir(&container_dir).await?;
|
||||
let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
pub struct VueLspVersion {
|
||||
vue_version: String,
|
||||
|
@ -211,7 +211,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
) -> Option<(LanguageServerBinary, TypescriptPath)> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/yaml-language-server/bin/yaml-language-server";
|
||||
|
||||
|
@ -110,7 +110,7 @@ async fn get_cached_server_binary(
|
|||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -9,8 +9,8 @@ use lsp::LanguageServerBinary;
|
|||
use smol::fs;
|
||||
use std::env::consts::{ARCH, OS};
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
use util::maybe;
|
||||
use util::{github::GitHubLspBinaryVersion, ResultExt};
|
||||
|
||||
pub struct ZlsAdapter;
|
||||
|
@ -113,7 +113,7 @@ impl LspAdapter for ZlsAdapter {
|
|||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
|
|
|
@ -9149,7 +9149,8 @@ impl Project {
|
|||
buffer: &Buffer,
|
||||
cx: &AppContext,
|
||||
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
|
||||
self.language_servers_for_buffer(buffer, cx).next()
|
||||
self.language_servers_for_buffer(buffer, cx)
|
||||
.find(|s| s.0.is_primary)
|
||||
}
|
||||
|
||||
pub fn language_server_for_buffer(
|
||||
|
|
|
@ -426,21 +426,20 @@ pub fn unzip_option<T, U>(option: Option<(T, U)>) -> (Option<T>, Option<U>) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Evaluates to an immediately invoked function expression. Good for using the ? operator
|
||||
/// in functions which do not return an Option or Result
|
||||
/// Expands to an immediately-invoked function expression. Good for using the ? operator
|
||||
/// in functions which do not return an Option or Result.
|
||||
///
|
||||
/// Accepts a normal block, an async block, or an async move block.
|
||||
#[macro_export]
|
||||
macro_rules! maybe {
|
||||
($block:block) => {
|
||||
(|| $block)()
|
||||
};
|
||||
}
|
||||
|
||||
/// Evaluates to an immediately invoked function expression. Good for using the ? operator
|
||||
/// in functions which do not return an Option or Result, but async.
|
||||
#[macro_export]
|
||||
macro_rules! async_maybe {
|
||||
($block:block) => {
|
||||
(|| async move { $block })()
|
||||
(async $block:block) => {
|
||||
(|| async $block)()
|
||||
};
|
||||
(async move $block:block) => {
|
||||
(|| async move $block)()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ impl LanguageServerPrompt {
|
|||
}
|
||||
|
||||
async fn select_option(this: View<Self>, ix: usize, mut cx: AsyncWindowContext) {
|
||||
util::async_maybe!({
|
||||
util::maybe!(async move {
|
||||
let potential_future = this.update(&mut cx, |this, _| {
|
||||
this.request.take().map(|request| request.respond(ix))
|
||||
});
|
||||
|
|
|
@ -47,8 +47,8 @@ use std::{
|
|||
};
|
||||
use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
|
||||
use util::{
|
||||
async_maybe,
|
||||
http::{HttpClient, HttpClientWithUrl},
|
||||
maybe,
|
||||
paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
|
||||
ResultExt, TryFutureExt,
|
||||
};
|
||||
|
@ -455,7 +455,7 @@ async fn installation_id() -> Result<(String, bool)> {
|
|||
}
|
||||
|
||||
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
|
||||
async_maybe!({
|
||||
maybe!(async {
|
||||
if let Some(location) = workspace::last_opened_workspace_paths().await {
|
||||
cx.update(|cx| {
|
||||
workspace::open_paths(
|
||||
|
|
|
@ -8,9 +8,9 @@ license = "Apache-2.0"
|
|||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = { path = "../../crates/extension_api" }
|
||||
|
||||
[lib]
|
||||
path = "src/gleam.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = "0.0.4"
|
||||
|
|
|
@ -9,10 +9,6 @@ impl GleamExtension {
|
|||
fn language_server_binary_path(&mut self, config: zed::LanguageServerConfig) -> Result<String> {
|
||||
if let Some(path) = &self.cached_binary_path {
|
||||
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::Cached,
|
||||
);
|
||||
return Ok(path.clone());
|
||||
}
|
||||
}
|
||||
|
@ -75,11 +71,6 @@ impl GleamExtension {
|
|||
fs::remove_dir_all(&entry.path()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::Downloaded,
|
||||
);
|
||||
}
|
||||
|
||||
self.cached_binary_path = Some(binary_path.clone());
|
||||
|
|
3
extensions/svelte/.gitignore
vendored
Normal file
3
extensions/svelte/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
*.wasm
|
||||
grammars
|
16
extensions/svelte/Cargo.toml
Normal file
16
extensions/svelte/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "zed_svelte"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/svelte.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = "0.0.4"
|
15
extensions/svelte/extension.toml
Normal file
15
extensions/svelte/extension.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
id = "svelte"
|
||||
name = "Svelte"
|
||||
description = "Svelte support"
|
||||
version = "0.0.1"
|
||||
schema_version = 1
|
||||
authors = []
|
||||
repository = "https://github.com/zed-extensions/svelte"
|
||||
|
||||
[language_servers.svelte-language-server]
|
||||
name = "Svelte Language Server"
|
||||
language = "Svelte"
|
||||
|
||||
[grammars.svelte]
|
||||
repository = "https://github.com/Himujjal/tree-sitter-svelte"
|
||||
commit = "ea528fc9985aed8d93c9f438c185644a33d011af"
|
126
extensions/svelte/src/svelte.rs
Normal file
126
extensions/svelte/src/svelte.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use std::{env, fs};
|
||||
use zed_extension_api::{self as zed, Result};
|
||||
|
||||
struct SvelteExtension {
|
||||
did_find_server: bool,
|
||||
}
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/svelte-language-server/bin/server.js";
|
||||
const PACKAGE_NAME: &str = "svelte-language-server";
|
||||
|
||||
impl SvelteExtension {
|
||||
fn server_exists(&self) -> bool {
|
||||
fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
|
||||
}
|
||||
|
||||
fn server_script_path(&mut self, config: zed::LanguageServerConfig) -> Result<String> {
|
||||
let server_exists = self.server_exists();
|
||||
if self.did_find_server && server_exists {
|
||||
return Ok(SERVER_PATH.to_string());
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
);
|
||||
let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
|
||||
|
||||
if !server_exists
|
||||
|| zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
|
||||
{
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::Downloading,
|
||||
);
|
||||
let result = zed::npm_install_package(PACKAGE_NAME, &version);
|
||||
match result {
|
||||
Ok(()) => {
|
||||
if !self.server_exists() {
|
||||
Err(format!(
|
||||
"installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if !self.server_exists() {
|
||||
Err(error)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.did_find_server = true;
|
||||
Ok(SERVER_PATH.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl zed::Extension for SvelteExtension {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
did_find_server: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
config: zed::LanguageServerConfig,
|
||||
_: &zed::Worktree,
|
||||
) -> Result<zed::Command> {
|
||||
let server_path = self.server_script_path(config)?;
|
||||
Ok(zed::Command {
|
||||
command: zed::node_binary_path()?,
|
||||
args: vec![
|
||||
env::current_dir()
|
||||
.unwrap()
|
||||
.join(&server_path)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
"--stdio".to_string(),
|
||||
],
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn language_server_initialization_options(
|
||||
&mut self,
|
||||
_: zed::LanguageServerConfig,
|
||||
_: &zed::Worktree,
|
||||
) -> Result<Option<String>> {
|
||||
let config = r#"{
|
||||
"inlayHints": {
|
||||
"parameterNames": {
|
||||
"enabled": "all",
|
||||
"suppressWhenArgumentMatchesName": false
|
||||
},
|
||||
"parameterTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"variableTypes": {
|
||||
"enabled": true,
|
||||
"suppressWhenTypeMatchesName": false
|
||||
},
|
||||
"propertyDeclarationTypes": {
|
||||
"enabled": true
|
||||
},
|
||||
"functionLikeReturnType": {
|
||||
"enabled": true
|
||||
},
|
||||
"enumMemberValues": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
Ok(Some(format!(
|
||||
r#"{{
|
||||
"provideFormatter": true,
|
||||
"configuration": {{
|
||||
"typescript": {config},
|
||||
"javascript": {config}
|
||||
}}
|
||||
}}"#
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
zed::register_extension!(SvelteExtension);
|
|
@ -8,9 +8,9 @@ license = "Apache-2.0"
|
|||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = { path = "../../crates/extension_api" }
|
||||
|
||||
[lib]
|
||||
path = "src/uiua.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = "0.0.4"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue