diff --git a/Cargo.lock b/Cargo.lock index 776f449779..d66856c322 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3718,6 +3718,8 @@ dependencies = [ "anyhow", "bincode", "serde", + "serde_json", + "wasi-common", "wasmtime", "wasmtime-wasi", ] diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 19f08a10b8..a6437deeb0 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -2,7 +2,7 @@ use core::panic; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, ItemFn, Visibility}; +use syn::{parse_macro_input, FnArg, ItemFn, Type, Visibility}; #[proc_macro_attribute] pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { @@ -21,7 +21,17 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let variadic = inner_fn.sig.inputs.len(); let i = (0..variadic).map(syn::Index::from); - let t = (0..variadic).map(|_| quote! { _ }); + let t: Vec = inner_fn + .sig + .inputs + .iter() + .map(|x| match x { + FnArg::Receiver(_) => { + panic!("all arguments must have specified types, no `self` allowed") + } + FnArg::Typed(item) => *item.ty.clone(), + }) + .collect(); // this is cursed... let (args, ty) = if variadic != 1 { @@ -34,7 +44,8 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { }, ) } else { - (quote! { data }, quote! { _ }) + let ty = &t[0]; + (quote! { data }, quote! { #ty }) }; TokenStream::from(quote! { @@ -48,7 +59,14 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let data = unsafe { buffer.to_vec() }; // operation - let data: #ty = ::plugin::bincode::deserialize(&data).unwrap(); + let data: #ty = match ::plugin::bincode::deserialize(&data) { + Ok(d) => d, + Err(e) => { + println!("data: {:?}", data); + println!("error: {}", e); + panic!("Data passed to function not deserializable.") + }, + }; let result = #inner_fn_name(#args); let new_data: Result, _> = ::plugin::bincode::serialize(&result); let new_data = new_data.unwrap(); diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index f37147c00b..b611dfb6a8 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [dependencies] wasmtime = "0.37.0" wasmtime-wasi = "0.37.0" +wasi-common = "0.37.0" anyhow = { version = "1.0", features = ["std"] } serde = "1.0" +serde_json = "1.0" bincode = "1.3" diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index c2747c498a..bb60ffa16d 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -1,10 +1,13 @@ -use std::{collections::HashMap, fs::File, path::Path}; +use std::{fs::File, os::unix::prelude::AsRawFd, path::Path}; use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; -use wasmtime::{Engine, Func, Instance, Linker, Memory, MemoryType, Module, Store, TypedFunc}; -use wasmtime_wasi::{dir, Dir, WasiCtx, WasiCtxBuilder}; +use wasi_common::{dir, file}; +use wasmtime::{Engine, Instance, Linker, Module, Store, TypedFunc}; +use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; + +pub struct WasiResource(u32); pub struct Wasi { engine: Engine, @@ -50,8 +53,13 @@ impl Wasi { pub fn init(plugin: WasiPlugin) -> Result { let engine = Engine::default(); let mut linker = Linker::new(&engine); + + linker.func_wrap("env", "hello", |x: u32| x * 2).unwrap(); + linker.func_wrap("env", "bye", |x: u32| x / 2).unwrap(); + println!("linking"); wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + println!("linked"); let mut store: Store<_> = Store::new(&engine, plugin.wasi_ctx); println!("moduling"); @@ -60,13 +68,13 @@ impl Wasi { linker.module(&mut store, "", &module)?; println!("linked again"); + let instance = linker.instantiate(&mut store, &module)?; println!("instantiated"); let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; println!("can alloc"); - Ok(Wasi { engine, module, @@ -77,20 +85,48 @@ impl Wasi { }) } - pub fn attach_file>(&mut self, path: T) -> Result<(), Error> { + /// Attaches a file or directory the the given system path to the runtime. + /// Note that the resource must be freed by calling `remove_resource` afterwards. + pub fn attach_path>(&mut self, path: T) -> Result { + // grab the WASI context let ctx = self.store.data_mut(); + + // open the file we want, and convert it into the right type + // this is a footgun and a half let file = File::open(&path).unwrap(); let dir = Dir::from_std_file(file); - // this is a footgun and a half. - let dir = dir::Dir::from_cap_std(dir); - ctx.push_preopened_dir(Box::new(dir), path)?; + let dir = Box::new(wasmtime_wasi::dir::Dir::from_cap_std(dir)); + + // grab an empty file descriptor, specify capabilities + let fd = ctx.table().push(Box::new(()))?; + dbg!(fd); + let caps = dir::DirCaps::all(); + let file_caps = file::FileCaps::all(); + + // insert the directory at the given fd, + // return a handle to the resource + ctx.insert_dir(fd, dir, caps, file_caps, path.as_ref().to_path_buf()); + Ok(WasiResource(fd)) + } + + /// Returns `true` if the resource existed and was removed. + pub fn remove_resource(&mut self, resource: WasiResource) -> Result<(), Error> { + self.store + .data_mut() + .table() + .delete(resource.0) + .ok_or_else(|| anyhow!("Resource did not exist, but a valid handle was passed in"))?; Ok(()) } - // pub fn remove_file>(&mut self, path: T) -> Result<(), Error> { - // let ctx = self.store.data_mut(); - // ctx.remove - // Ok(()) + // pub fn with_resource( + // &mut self, + // resource: WasiResource, + // callback: fn(&mut Self) -> Result, + // ) -> Result { + // let result = callback(self); + // self.remove_resource(resource)?; + // return result; // } // So this call function is kinda a dance, I figured it'd be a good idea to document it. @@ -142,6 +178,9 @@ impl Wasi { handle: &str, arg: A, ) -> Result { + dbg!(&handle); + // dbg!(serde_json::to_string(&arg)).unwrap(); + // serialize the argument using bincode let arg = bincode::serialize(&arg)?; let arg_buffer_len = arg.len(); diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index f4c09c189b..8046401ccc 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -2,6 +2,7 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; +use isahc::http::version; use language::{LanguageServerName, LspAdapter}; use parking_lot::{Mutex, RwLock}; use plugin_runtime::{Wasi, WasiPlugin}; @@ -42,7 +43,7 @@ impl LspAdapter for LanguagePluginLspAdapter { } fn server_args<'a>(&'a self) -> Vec { - self.runtime.lock().call("args", ()).unwrap() + self.runtime.lock().call("server_args", ()).unwrap() } fn fetch_latest_server_version( @@ -65,27 +66,38 @@ impl LspAdapter for LanguagePluginLspAdapter { fn fetch_server_binary( &self, - versions: Box, + version: Box, _: Arc, container_dir: PathBuf, ) -> BoxFuture<'static, Result> { - // TODO: async runtime + let version = version.downcast::().unwrap(); let mut runtime = self.runtime.lock(); - let result = runtime.attach_file(&container_dir); - let result = match result { - Ok(_) => runtime.call("fetch_server_binary", container_dir), - Err(e) => Err(e), - }; + + let result: Result = (|| { + let handle = runtime.attach_path(&container_dir)?; + let result = runtime + .call::<_, Option>("fetch_server_binary", container_dir)? + .ok_or_else(|| anyhow!("Could not load cached server binary")); + runtime.remove_resource(handle)?; + result + })(); async move { result }.boxed() } fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { - let result = self - .runtime - .lock() - .call("cached_server_binary", container_dir); - async move { result }.log_err().boxed() + let mut runtime = self.runtime.lock(); + + let result: Option = (|| { + let handle = runtime.attach_path(&container_dir).ok()?; + let result = runtime + .call::<_, Option>("cached_server_binary", container_dir) + .ok()?; + runtime.remove_resource(handle).ok()?; + result + })(); + + async move { result }.boxed() } fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -113,11 +125,13 @@ impl LspAdapter for LanguagePluginLspAdapter { } fn initialization_options(&self) -> Option { - let result = self - .runtime - .lock() - .call("initialization_options", ()) - .unwrap(); - Some(result) + // self.runtime + // .lock() + // .call::<_, Option>("initialization_options", ()) + // .unwrap() + + Some(json!({ + "provideFormatter": true + })) } } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 9687a09e83..ff88593d4b 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -3,11 +3,26 @@ use serde_json::json; use std::fs; use std::path::PathBuf; -// import +// #[import] // fn command(string: String) -> Option; +// TODO: some sort of macro to generate ABI bindings +extern "C" { + pub fn hello(item: u32) -> u32; + pub fn bye(item: u32) -> u32; +} + +// #[bind] +// pub async fn name(u32) -> u32 { + +// } + #[bind] pub fn name() -> &'static str { + let number = unsafe { hello(27) }; + println!("got: {}", number); + let number = unsafe { bye(28) }; + println!("got: {}", number); "vscode-json-languageserver" } @@ -34,65 +49,68 @@ pub fn fetch_latest_server_version() -> Option { Some("1.3.4".into()) } -// #[bind] -// pub fn fetch_server_binary(version: String) -> Option { -// let version_dir = container_dir.join(version.as_str()); -// fs::create_dir_all(&version_dir) -// .await -// .context("failed to create version directory")?; -// let binary_path = version_dir.join(Self::BIN_PATH); +#[bind] +pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Option { + println!("Fetching server binary"); + return None; + // let version_dir = container_dir.join(version.as_str()); + // fs::create_dir_all(&version_dir) + // .await + // .context("failed to create version directory")?; + // let binary_path = version_dir.join(Self::BIN_PATH); -// if fs::metadata(&binary_path).await.is_err() { -// let output = smol::process::Command::new("npm") -// .current_dir(&version_dir) -// .arg("install") -// .arg(format!("vscode-json-languageserver@{}", version)) -// .output() -// .await -// .context("failed to run npm install")?; -// if !output.status.success() { -// Err(anyhow!("failed to install vscode-json-languageserver"))?; -// } + // if fs::metadata(&binary_path).await.is_err() { + // let output = smol::process::Command::new("npm") + // .current_dir(&version_dir) + // .arg("install") + // .arg(format!("vscode-json-languageserver@{}", version)) + // .output() + // .await + // .context("failed to run npm install")?; + // if !output.status.success() { + // Err(anyhow!("failed to install vscode-json-languageserver"))?; + // } -// if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { -// while let Some(entry) = entries.next().await { -// if let Some(entry) = entry.log_err() { -// let entry_path = entry.path(); -// if entry_path.as_path() != version_dir { -// fs::remove_dir_all(&entry_path).await.log_err(); -// } -// } -// } -// } -// } + // if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + // while let Some(entry) = entries.next().await { + // if let Some(entry) = entry.log_err() { + // let entry_path = entry.path(); + // if entry_path.as_path() != version_dir { + // fs::remove_dir_all(&entry_path).await.log_err(); + // } + // } + // } + // } + // } -// Ok(binary_path) -// } + // Ok(binary_path) +} const BIN_PATH: &'static str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; -// #[bind] -// pub fn cached_server_binary(container_dir: PathBuf) -> Option { -// println!("Finding cached server binary..."); -// let mut last_version_dir = None; -// let mut entries = fs::read_dir(&container_dir).ok()?; -// println!("Read Entries..."); -// while let Some(entry) = entries.next() { -// let entry = entry.ok()?; -// if entry.file_type().ok()?.is_dir() { -// last_version_dir = Some(entry.path()); -// } -// } -// let last_version_dir = last_version_dir?; -// let bin_path = last_version_dir.join(BIN_PATH); -// if bin_path.exists() { -// println!("{}", bin_path.display()); -// Some(bin_path) -// } else { -// None -// } -// } +#[bind] +pub fn cached_server_binary(container_dir: PathBuf) -> Option { + println!("Finding cached server binary..."); + let mut last_version_dir = None; + println!("{}", container_dir.exists()); + let mut entries = fs::read_dir(&container_dir).ok()?; + println!("Read Entries..."); + while let Some(entry) = entries.next() { + let entry = entry.ok()?; + if entry.file_type().ok()?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir?; + let bin_path = last_version_dir.join(BIN_PATH); + if bin_path.exists() { + println!("this is the path: {}", bin_path.display()); + Some(bin_path) + } else { + None + } +} #[bind] pub fn initialization_options() -> Option { diff --git a/plugins/json_language/src/sketch.rs b/plugins/json_language/src/sketch.rs new file mode 100644 index 0000000000..def823fcd4 --- /dev/null +++ b/plugins/json_language/src/sketch.rs @@ -0,0 +1,88 @@ + +use zed_plugin::RopeRef; + + +// Host +struct Handles { + items: Vec>, +} + +struct Rope; + +impl Link for Rope { + fn link(linker: &mut Linker) -> Result<()> { + linker.add(|this: &mut Rope| { + + }); + linker.func_wrap("env", "len", |handles, arg| { + let rope = handles.downcast::(arg.0); + let rope = Arc::from_raw(ptr); + let result = rope.len(); + Arc::leak(rope); + result + }); + } + + fn to_handle(self) -> Handle { + todo!() + } +} + +// -- Host + +pub fn edit_rope(&mut self) { + let rope: &mut Rope = self......rope(); + let handle: RopeRef = self.runtime.to_handle(rope); + self.runtime.call("edit_rope", handle); +} + +// Guest + +extern "C" long rope__len(u32 handle); + +struct RopeRef(u32); + +impl RopeRef { + fn len(&self) -> usize { + rope__len(self.0); + } +} + +pub fn edit_rope(rope: RopeRef) { + rope.len() +} + +// Host side --- + +pub struct Rope { .. } + +RopeRef(u32); + +impl Link for RopeRef { + pub fn init(&mut something) { + something.add("length", |rope| ) + } +} + +// --- + +extern "C" { + pub fn length(item: u32) -> u32; +} + +struct RopeRef { + handle: u32, +} + +pub fn length(ref: RopeRef) -> u32 { + ref.length() +} + +// Host side + +#[plugin_interface] +trait LspAdapter { + name() -> &'static str; +} + +// Guest side