From 8293b6971d238bab8320546a13f60a49d407016c Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 1 Jun 2022 11:15:06 +0200 Subject: [PATCH 01/91] Start sketching out runner runtime --- Cargo.lock | 41 ++++++++++++++++++++++++++++++++++++ crates/runner/Cargo.toml | 7 ++++++ crates/runner/src/lib.rs | 37 ++++++++++++++++++++++++++++++++ crates/runner/src/main.rs | 7 ++++++ crates/runner/src/runtime.rs | 22 +++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 crates/runner/Cargo.toml create mode 100644 crates/runner/src/lib.rs create mode 100644 crates/runner/src/main.rs create mode 100644 crates/runner/src/runtime.rs diff --git a/Cargo.lock b/Cargo.lock index 8624921d97..442e10c60a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2627,6 +2627,24 @@ dependencies = [ "url", ] +[[package]] +name = "lua-src" +version = "544.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.3.4+resty073ac54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640b09e99575a442b4da0ef406a78188a1a4313bb9ead7b5b20ec12cc480130f" +dependencies = [ + "cc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2845,6 +2863,22 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "mlua" +version = "0.8.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb054c5769e1aeb137554b95941e997b62d17f5265bef213cb63179d75a6bf6" +dependencies = [ + "bstr", + "cc", + "lua-src", + "luajit-src", + "num-traits", + "once_cell", + "pkg-config", + "rustc-hash", +] + [[package]] name = "multimap" version = "0.8.3" @@ -3971,6 +4005,13 @@ dependencies = [ "zeroize", ] +[[package]] +name = "runner" +version = "0.1.0" +dependencies = [ + "mlua", +] + [[package]] name = "rust-embed" version = "6.4.0" diff --git a/crates/runner/Cargo.toml b/crates/runner/Cargo.toml new file mode 100644 index 0000000000..fe49be4bc9 --- /dev/null +++ b/crates/runner/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "runner" +version = "0.1.0" +edition = "2021" + +[dependencies] +mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored"] } diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs new file mode 100644 index 0000000000..802755c961 --- /dev/null +++ b/crates/runner/src/lib.rs @@ -0,0 +1,37 @@ +use mlua::{Error, FromLua, Lua, ToLua, UserData}; + +pub mod runtime; +pub use runtime::*; + +impl Runtime for Lua { + type Module = String; + // type Error = Error; + type Interface = LuaInterface; + + fn init(module: Self::Module) -> Option { + let lua = Lua::new(); + lua.load(&module).exec().ok()?; + return Some(lua); + } + + fn interface(&self) -> Self::Interface { + todo!() + } + + fn val<'lua, K: ToLua<'lua>, V: FromLua<'lua>>(&'lua self, key: K) -> Option { + self.globals().get(key).ok() + } +} + +pub struct LuaInterface { + funs: Vec, + vals: Vec, +} + +impl Interface for LuaInterface { + type Handle = String; + + fn handles(&self) -> &[Self::Handle] { + todo!() + } +} diff --git a/crates/runner/src/main.rs b/crates/runner/src/main.rs new file mode 100644 index 0000000000..d9ad57ecae --- /dev/null +++ b/crates/runner/src/main.rs @@ -0,0 +1,7 @@ +use mlua::{Lua, Result}; + +use runner::*; + +pub fn main() { + let lua: Lua = Runtime::init("x = 7".to_string()).unwrap(); +} diff --git a/crates/runner/src/runtime.rs b/crates/runner/src/runtime.rs new file mode 100644 index 0000000000..921c274719 --- /dev/null +++ b/crates/runner/src/runtime.rs @@ -0,0 +1,22 @@ +use mlua::{FromLua, ToLua}; + +pub trait FromRuntime: Sized { + fn from_runtime() -> Option; +} + +pub trait Interface { + type Handle; + fn handles(&self) -> &[Self::Handle]; +} + +pub trait Runtime +where + Self: Sized, +{ + type Module; + type Interface: Interface; + + fn init(plugin: Self::Module) -> Option; + fn interface(&self) -> Self::Interface; + fn val<'lua, K: ToLua<'lua>, V: FromLua<'lua>>(&'lua self, key: K) -> Option; +} From 265be4a2fb0700d030d96212bee06748a43f8e21 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 1 Jun 2022 12:43:46 +0200 Subject: [PATCH 02/91] Clean up interface a bit --- crates/runner/src/lib.rs | 32 +++++++++++--------------------- crates/runner/src/main.rs | 10 ++++++++++ crates/runner/src/runtime.rs | 17 ++++++----------- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs index 802755c961..d305f092d5 100644 --- a/crates/runner/src/lib.rs +++ b/crates/runner/src/lib.rs @@ -1,12 +1,12 @@ -use mlua::{Error, FromLua, Lua, ToLua, UserData}; +use std::collections::{HashMap, HashSet}; + +use mlua::{Error, FromLua, Function, Lua, ToLua, UserData, Value}; pub mod runtime; pub use runtime::*; impl Runtime for Lua { type Module = String; - // type Error = Error; - type Interface = LuaInterface; fn init(module: Self::Module) -> Option { let lua = Lua::new(); @@ -14,24 +14,14 @@ impl Runtime for Lua { return Some(lua); } - fn interface(&self) -> Self::Interface { - todo!() - } + fn interface(&self) -> Interface { + let mut globals = HashSet::new(); + for pair in self.globals().pairs::() { + if let Ok((k, _)) = pair { + globals.insert(k); + } + } - fn val<'lua, K: ToLua<'lua>, V: FromLua<'lua>>(&'lua self, key: K) -> Option { - self.globals().get(key).ok() - } -} - -pub struct LuaInterface { - funs: Vec, - vals: Vec, -} - -impl Interface for LuaInterface { - type Handle = String; - - fn handles(&self) -> &[Self::Handle] { - todo!() + globals } } diff --git a/crates/runner/src/main.rs b/crates/runner/src/main.rs index d9ad57ecae..c82e245619 100644 --- a/crates/runner/src/main.rs +++ b/crates/runner/src/main.rs @@ -4,4 +4,14 @@ use runner::*; pub fn main() { let lua: Lua = Runtime::init("x = 7".to_string()).unwrap(); + println!("{:?}", lua.interface()); +} + +struct InterfaceX; + +impl InterfaceX { + pub fn get_x(runtime: T) -> usize { + // runtime.get("x") + todo!() + } } diff --git a/crates/runner/src/runtime.rs b/crates/runner/src/runtime.rs index 921c274719..8436e29da7 100644 --- a/crates/runner/src/runtime.rs +++ b/crates/runner/src/runtime.rs @@ -1,22 +1,17 @@ -use mlua::{FromLua, ToLua}; +use std::collections::HashSet; -pub trait FromRuntime: Sized { - fn from_runtime() -> Option; -} +use mlua::{FromLua, Lua, ToLua, Value}; -pub trait Interface { - type Handle; - fn handles(&self) -> &[Self::Handle]; -} +pub type Interface = HashSet; pub trait Runtime where Self: Sized, { type Module; - type Interface: Interface; fn init(plugin: Self::Module) -> Option; - fn interface(&self) -> Self::Interface; - fn val<'lua, K: ToLua<'lua>, V: FromLua<'lua>>(&'lua self, key: K) -> Option; + fn interface(&self) -> Interface; + // fn val<'a, T>(&'a self, name: String) -> Option; + // fn call<'a, A: MyFromLua<'a>, R>(&'a mut self, name: String, arg: A) -> Option; } From 13e0ad7253b179229496c06888fe9e8e77114983 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 1 Jun 2022 13:42:00 +0200 Subject: [PATCH 03/91] Get Runtime working... --- crates/runner/Cargo.toml | 4 +++- crates/runner/src/lib.rs | 17 +++++++++++++++-- crates/runner/src/main.rs | 22 +++++++++++++++------- crates/runner/src/runtime.rs | 20 ++++++++++++++++---- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/crates/runner/Cargo.toml b/crates/runner/Cargo.toml index fe49be4bc9..eae03bca0c 100644 --- a/crates/runner/Cargo.toml +++ b/crates/runner/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored"] } +mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] } +serde = "1.0" +# bincode = "1.3" diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs index d305f092d5..82aaa62368 100644 --- a/crates/runner/src/lib.rs +++ b/crates/runner/src/lib.rs @@ -1,9 +1,10 @@ use std::collections::{HashMap, HashSet}; -use mlua::{Error, FromLua, Function, Lua, ToLua, UserData, Value}; +use mlua::{Error, FromLua, Function, Lua, LuaSerdeExt, ToLua, UserData, Value}; pub mod runtime; pub use runtime::*; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; impl Runtime for Lua { type Module = String; @@ -14,7 +15,7 @@ impl Runtime for Lua { return Some(lua); } - fn interface(&self) -> Interface { + fn handles(&self) -> Handles { let mut globals = HashSet::new(); for pair in self.globals().pairs::() { if let Ok((k, _)) = pair { @@ -24,4 +25,16 @@ impl Runtime for Lua { globals } + + fn val(&self, name: String) -> Option { + let val: Value = self.globals().get(name).ok()?; + Some(self.from_value(val).ok()?) + } + + fn call(&self, name: String, arg: T) -> Option { + let fun: Function = self.globals().get(name).ok()?; + let arg: Value = self.to_value(&arg).ok()?; + let result = fun.call(arg).ok()?; + Some(self.from_value(result).ok()?) + } } diff --git a/crates/runner/src/main.rs b/crates/runner/src/main.rs index c82e245619..48cdfb07b6 100644 --- a/crates/runner/src/main.rs +++ b/crates/runner/src/main.rs @@ -3,15 +3,23 @@ use mlua::{Lua, Result}; use runner::*; pub fn main() { - let lua: Lua = Runtime::init("x = 7".to_string()).unwrap(); - println!("{:?}", lua.interface()); + let lua: Lua = Runtime::init( + "query = \"Some random tree-sitter query\"\nprint(\"Hello from the Lua test runner!\")" + .to_string(), + ) + .unwrap(); + let runner: TestRunner = lua.as_interface::().unwrap(); + println!("{:#?}", runner); } -struct InterfaceX; +#[derive(Debug)] +struct TestRunner { + query: String, +} -impl InterfaceX { - pub fn get_x(runtime: T) -> usize { - // runtime.get("x") - todo!() +impl Interface for TestRunner { + fn from_runtime(runtime: &T) -> Option { + let query: String = runtime.val("query".to_string())?; + Some(TestRunner { query }) } } diff --git a/crates/runner/src/runtime.rs b/crates/runner/src/runtime.rs index 8436e29da7..da4fd0de30 100644 --- a/crates/runner/src/runtime.rs +++ b/crates/runner/src/runtime.rs @@ -1,8 +1,16 @@ use std::collections::HashSet; use mlua::{FromLua, Lua, ToLua, Value}; +use serde::{de::DeserializeOwned, Serialize}; -pub type Interface = HashSet; +pub type Handles = HashSet; + +pub trait Interface +where + Self: Sized, +{ + fn from_runtime(runtime: &T) -> Option; +} pub trait Runtime where @@ -11,7 +19,11 @@ where type Module; fn init(plugin: Self::Module) -> Option; - fn interface(&self) -> Interface; - // fn val<'a, T>(&'a self, name: String) -> Option; - // fn call<'a, A: MyFromLua<'a>, R>(&'a mut self, name: String, arg: A) -> Option; + fn handles(&self) -> Handles; + fn val(&self, name: String) -> Option; + fn call(&self, name: String, arg: T) -> Option; + + fn as_interface(&self) -> Option { + Interface::from_runtime(self) + } } From 4ff9a6b1b5921fc403d6ad453e0b2ef1336c9a09 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 1 Jun 2022 13:42:25 +0200 Subject: [PATCH 04/91] Update lockfile --- Cargo.lock | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 442e10c60a..ec2078d493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2871,12 +2871,14 @@ checksum = "4bb054c5769e1aeb137554b95941e997b62d17f5265bef213cb63179d75a6bf6" dependencies = [ "bstr", "cc", + "erased-serde", "lua-src", "luajit-src", "num-traits", "once_cell", "pkg-config", "rustc-hash", + "serde", ] [[package]] @@ -4010,6 +4012,7 @@ name = "runner" version = "0.1.0" dependencies = [ "mlua", + "serde", ] [[package]] From 4003037ca87f7aa4127ccbfa3888488203507c2e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 1 Jun 2022 16:39:59 +0200 Subject: [PATCH 05/91] Documented code, got basic example working --- Cargo.lock | 7 +++ crates/runner/Cargo.toml | 1 + crates/runner/plugin/cargo_test.lua | 22 ++++++++++ crates/runner/src/lib.rs | 40 +++-------------- crates/runner/src/lua.rs | 62 ++++++++++++++++++++++++++ crates/runner/src/main.rs | 57 +++++++++++++++++++----- crates/runner/src/runtime.rs | 68 ++++++++++++++++++++++++----- 7 files changed, 200 insertions(+), 57 deletions(-) create mode 100644 crates/runner/plugin/cargo_test.lua create mode 100644 crates/runner/src/lua.rs diff --git a/Cargo.lock b/Cargo.lock index ec2078d493..64fb6d2808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2654,6 +2654,12 @@ dependencies = [ "libc", ] +[[package]] +name = "map-macro" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b0858fc6e216d2d6222d661021d9b184550acd757fbd80a8f86224069422c" + [[package]] name = "matchers" version = "0.1.0" @@ -4011,6 +4017,7 @@ dependencies = [ name = "runner" version = "0.1.0" dependencies = [ + "map-macro", "mlua", "serde", ] diff --git a/crates/runner/Cargo.toml b/crates/runner/Cargo.toml index eae03bca0c..85cebd26f9 100644 --- a/crates/runner/Cargo.toml +++ b/crates/runner/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] } serde = "1.0" +map-macro = "0.2" # bincode = "1.3" diff --git a/crates/runner/plugin/cargo_test.lua b/crates/runner/plugin/cargo_test.lua new file mode 100644 index 0000000000..3921a1b07e --- /dev/null +++ b/crates/runner/plugin/cargo_test.lua @@ -0,0 +1,22 @@ +print("initializing plugin...") + +query = [[( + (attribute_item + (meta_item + (identifier) @test)) @attribute + . + (function_item + name: (identifier) @name) @funciton +)]] + +function run_test(name) + print('running test `' .. name .. '`:') + local command = 'cargo test -- ' .. name + local openPop = assert(io.popen(command, 'r')) + local output = openPop:read('*all') + openPop:close() + print('done running test') + return output +end + +print("done initializing plugin.") \ No newline at end of file diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs index 82aaa62368..4afe88b85a 100644 --- a/crates/runner/src/lib.rs +++ b/crates/runner/src/lib.rs @@ -1,40 +1,10 @@ -use std::collections::{HashMap, HashSet}; +use mlua::{Function, Lua, LuaSerdeExt, Value}; +use serde::{de::DeserializeOwned, Serialize}; -use mlua::{Error, FromLua, Function, Lua, LuaSerdeExt, ToLua, UserData, Value}; +pub use map_macro::{map, set}; pub mod runtime; pub use runtime::*; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -impl Runtime for Lua { - type Module = String; - - fn init(module: Self::Module) -> Option { - let lua = Lua::new(); - lua.load(&module).exec().ok()?; - return Some(lua); - } - - fn handles(&self) -> Handles { - let mut globals = HashSet::new(); - for pair in self.globals().pairs::() { - if let Ok((k, _)) = pair { - globals.insert(k); - } - } - - globals - } - - fn val(&self, name: String) -> Option { - let val: Value = self.globals().get(name).ok()?; - Some(self.from_value(val).ok()?) - } - - fn call(&self, name: String, arg: T) -> Option { - let fun: Function = self.globals().get(name).ok()?; - let arg: Value = self.to_value(&arg).ok()?; - let result = fun.call(arg).ok()?; - Some(self.from_value(result).ok()?) - } -} +pub mod lua; +pub use lua::*; diff --git a/crates/runner/src/lua.rs b/crates/runner/src/lua.rs new file mode 100644 index 0000000000..4e364b9556 --- /dev/null +++ b/crates/runner/src/lua.rs @@ -0,0 +1,62 @@ +use mlua::Result; + +use crate::*; + +impl Runtime for Lua { + type Plugin = LuaPlugin; + type Error = mlua::Error; + + fn init(module: Self::Plugin) -> Result { + let lua = Lua::new(); + + // for action in module.actions { + // action(&mut lua).ok()?; + // } + + lua.load(&module.source).exec()?; + return Ok(lua); + } + + fn constant(&mut self, handle: &Handle) -> Result { + let val: Value = self.globals().get(handle.inner())?; + Ok(self.from_value(val)?) + } + + fn call(&mut self, handle: &Handle, arg: T) -> Result { + let fun: Function = self.globals().get(handle.inner())?; + let arg: Value = self.to_value(&arg)?; + let result = fun.call(arg)?; + Ok(self.from_value(result)?) + } + + fn register_handle>(&mut self, name: T) -> bool { + self.globals() + .contains_key(name.as_ref().to_string()) + .unwrap_or(false) + } +} + +pub struct LuaPlugin { + // name: String, + source: String, + // actions: Vec Result<(), ()>>>, +} + +impl LuaPlugin { + pub fn new( + // name: String, + source: String, + ) -> LuaPlugin { + LuaPlugin { + // name, + source, + // actions: Vec::new(), + } + } + + // pub fn setup(mut self, action: fn(&mut Lua) -> Result<(), ()>) -> LuaPlugin { + // let action = Box::new(action); + // self.actions.push(action); + // self + // } +} diff --git a/crates/runner/src/main.rs b/crates/runner/src/main.rs index 48cdfb07b6..39e8276696 100644 --- a/crates/runner/src/main.rs +++ b/crates/runner/src/main.rs @@ -1,25 +1,58 @@ -use mlua::{Lua, Result}; +use mlua::Lua; use runner::*; -pub fn main() { - let lua: Lua = Runtime::init( - "query = \"Some random tree-sitter query\"\nprint(\"Hello from the Lua test runner!\")" - .to_string(), - ) - .unwrap(); +pub fn main() -> Result<(), mlua::Error> { + let source = include_str!("../plugin/cargo_test.lua").to_string(); + + let module = LuaPlugin::new(source); + // .setup(|runtime| { + // let greet = runtime + // .create_function(|_, name: String| { + // println!("Hello, {}!", name); + // Ok(()) + // }) + // .map_err(|_| ())?; + + // runtime.globals().set("greet", greet).map_err(|_| ())?; + // Ok(()) + // }); + + let mut lua: Lua = Runtime::init(module)?; let runner: TestRunner = lua.as_interface::().unwrap(); - println!("{:#?}", runner); + + println!("extracted interface: {:#?}", &runner); + + let contents = runner.run_test(&mut lua, "it_works".into()); + + println!("test results:{}", contents.unwrap()); + + Ok(()) } +#[allow(dead_code)] #[derive(Debug)] struct TestRunner { - query: String, + pub query: String, + run_test: Handle, } impl Interface for TestRunner { - fn from_runtime(runtime: &T) -> Option { - let query: String = runtime.val("query".to_string())?; - Some(TestRunner { query }) + fn from_runtime(runtime: &mut T) -> Option { + let run_test = runtime.handle_for("run_test")?; + let query = runtime.handle_for("query")?; + let query: String = runtime.constant(&query).ok()?; + Some(TestRunner { query, run_test }) } } + +impl TestRunner { + pub fn run_test(&self, runtime: &mut T, test_name: String) -> Option { + runtime.call(&self.run_test, test_name).ok() + } +} + +#[test] +pub fn it_works() { + panic!("huh, that was surprising..."); +} diff --git a/crates/runner/src/runtime.rs b/crates/runner/src/runtime.rs index da4fd0de30..3ee03e961c 100644 --- a/crates/runner/src/runtime.rs +++ b/crates/runner/src/runtime.rs @@ -1,29 +1,77 @@ -use std::collections::HashSet; +// use std::Error; -use mlua::{FromLua, Lua, ToLua, Value}; use serde::{de::DeserializeOwned, Serialize}; -pub type Handles = HashSet; +/// Represents a handle to a constant or function in the Runtime. +/// Should be constructed by calling [`Runtime::handle_for`]. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Handle(String); +impl Handle { + pub fn inner(&self) -> &str { + &self.0 + } +} + +/// Represents an interface that can be implemented by a plugin. pub trait Interface where Self: Sized, { - fn from_runtime(runtime: &T) -> Option; + /// Create an interface from a given runtime. + /// All handles to be used by the interface should be registered and stored in `Self`. + fn from_runtime(runtime: &mut T) -> Option; } pub trait Runtime where Self: Sized, { - type Module; + /// Represents a plugin to be loaded by the runtime, + /// e.g. some source code + anything else needed to set up. + type Plugin; - fn init(plugin: Self::Module) -> Option; - fn handles(&self) -> Handles; - fn val(&self, name: String) -> Option; - fn call(&self, name: String, arg: T) -> Option; + /// The error type for this module. + /// Ideally should implement the [`std::err::Error`] trait. + type Error; - fn as_interface(&self) -> Option { + /// Initializes a plugin, returning a [`Runtime`] that can be queried. + /// Note that if you have any configuration, + fn init(plugin: Self::Plugin) -> Result; + + /// Returns a top-level constant from the module. + /// This can be used to extract configuration information from the module, for example. + /// Before calling this function, get a handle into the runtime using [`handle_for`]. + fn constant(&mut self, handle: &Handle) -> Result; + + /// Call a function defined in the module. + fn call( + &mut self, + handle: &Handle, + arg: T, + ) -> Result; + + /// Registers a handle with the runtime. + /// This is a mutable item if needed, but generally + /// this should be an immutable operation. + /// Returns whether the handle exists/was successfully registered. + fn register_handle>(&mut self, name: T) -> bool; + + /// Returns the handle for a given name if the handle is defined. + /// Will only return an error if there was an error while trying to register the handle. + /// This function uses [`register_handle`], no need to implement this one. + fn handle_for>(&mut self, name: T) -> Option { + if self.register_handle(&name) { + Some(Handle(name.as_ref().to_string())) + } else { + None + } + } + + /// Creates the given interface from the current module. + /// Returns [`Error`] if the provided plugin does not match the expected interface. + /// Essentially wraps the [`Interface`] trait. + fn as_interface(&mut self) -> Option { Interface::from_runtime(self) } } From f6b6d19041ec0fe4ad208d6b21ac08eed28e6061 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 2 Jun 2022 10:50:26 +0200 Subject: [PATCH 06/91] Add wasmtime and fix zstd version conflict --- crates/rpc/Cargo.toml | 2 +- crates/runner/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 7baba26702..cd959e75a1 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -28,7 +28,7 @@ rsa = "0.4" serde = { version = "1.0", features = ["derive", "rc"] } smol-timeout = "0.6" tracing = { version = "0.1.34", features = ["log"] } -zstd = "0.9" +zstd = "0.11" [build-dependencies] prost-build = "0.9" diff --git a/crates/runner/Cargo.toml b/crates/runner/Cargo.toml index 85cebd26f9..60797899bc 100644 --- a/crates/runner/Cargo.toml +++ b/crates/runner/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] } serde = "1.0" map-macro = "0.2" +wasmtime = "0.37.0" # bincode = "1.3" From feae434684de324931510e16cbf5f4298e4ea891 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 2 Jun 2022 10:51:27 +0200 Subject: [PATCH 07/91] Update lockfile --- Cargo.lock | 830 ++++++++++++++++++++++++++++++++++++++++++----------- Cargo.toml | 2 + 2 files changed, 671 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64fb6d2808..0b0dc2a7bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check", ] @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "arrayref" @@ -420,9 +420,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.6" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2504b827a8bef941ba3dd64bdffe9cf56ca182908a147edd6189c95fbcae7d" +checksum = "c2cc6e8e8c993cb61a005fab8c1e5093a29199b7253b05a6883999312935c1ff" dependencies = [ "async-trait", "axum-core", @@ -455,9 +455,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da31c0ed7b4690e2c78fe4b880d21cd7db04a346ebc658b4270251b695437f17" +checksum = "cf4d047478b986f14a13edad31a009e2e05cb241f9805d0d75e4cba4e129ad4d" dependencies = [ "async-trait", "bytes", @@ -469,17 +469,19 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75330529f6b27544cedc6089108602a056d016df6aa4f2cb24408d840392ef2d" +checksum = "277c75e6c814b061ae4947d02335d9659db9771b9950cca670002ae986372f44" dependencies = [ "axum", "bytes", + "futures-util", "http", "mime", "pin-project-lite 0.2.9", "serde", "serde_json", + "tokio", "tower", "tower-http", "tower-layer", @@ -515,9 +517,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" +checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" [[package]] name = "bincode" @@ -639,9 +641,9 @@ checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "bytemuck" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" +checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a" [[package]] name = "byteorder" @@ -719,7 +721,7 @@ dependencies = [ "postage", "settings", "theme", - "time 0.3.10", + "time 0.3.11", "util", "workspace", ] @@ -733,7 +735,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time 0.1.43", + "time 0.1.44", "winapi 0.3.9", ] @@ -780,16 +782,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "3.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", "indexmap", - "lazy_static", + "once_cell", "strsim 0.10.0", "termcolor", "textwrap 0.15.0", @@ -797,9 +799,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.1.18" +version = "3.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -810,9 +812,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] @@ -822,7 +824,7 @@ name = "cli" version = "0.1.0" dependencies = [ "anyhow", - "clap 3.1.18", + "clap 3.2.8", "core-foundation", "core-services", "dirs 3.0.2", @@ -852,7 +854,7 @@ dependencies = [ "smol", "sum_tree", "thiserror", - "time 0.3.10", + "time 0.3.11", "tiny_http", "url", "util", @@ -913,7 +915,7 @@ dependencies = [ "axum", "axum-extra", "base64 0.13.0", - "clap 3.1.18", + "clap 3.2.8", "client", "collections", "ctor", @@ -942,7 +944,7 @@ dependencies = [ "sha-1 0.9.8", "sqlx", "theme", - "time 0.3.10", + "time 0.3.11", "tokio", "tokio-tungstenite", "toml", @@ -1089,6 +1091,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "cpufeatures" version = "0.2.2" @@ -1098,6 +1109,95 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa7c3188913c2d11a361e0431e135742372a2709a99b103e79758e11a0a797e" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29285f70fd396a8f64455a15a6e1d390322e4a5f5186de513141313211b0a23e" +dependencies = [ + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057eac2f202ec95aebfd8d495e88560ac085f6a415b3c6c28529dc5eb116a141" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d93869efd18874a9341cfd8ad66bcb08164e86357a694a0e939d29e87410b9" + +[[package]] +name = "cranelift-entity" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e34bd7a1fefa902c90a921b36323f17a398b788fa56a75f07a29d83b6e28808" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457018dd2d6ee300953978f63215b5edf3ae42dbdf8c7c038972f10394599f72" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-native" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba027cc41bf1d0eee2ddf16caba2ee1be682d0214520fff0129d2c6557fda89" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b17639ced10b9916c9be120d38c872ea4f9888aa09248568b10056ef0559bfa" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + [[package]] name = "crc" version = "3.0.0" @@ -1134,12 +1234,12 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", ] [[package]] @@ -1150,20 +1250,20 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static", + "crossbeam-utils 0.8.10", "memoffset", + "once_cell", "scopeguard", ] @@ -1174,7 +1274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", ] [[package]] @@ -1190,19 +1290,19 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" dependencies = [ "generic-array", "typenum", @@ -1334,6 +1434,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "3.0.2" @@ -1404,9 +1514,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" +checksum = "140206b78fb2bc3edbcfc9b5ccbd0b30699cfe8d348b8b31b330e47df5291a5a" [[package]] name = "easy-parallel" @@ -1456,9 +1566,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "encoding_rs" @@ -1493,13 +1603,34 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd" +checksum = "81d013529d5574a60caeda29e179e695125448e5de52e3874f7b4c1d7360e18e" dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "etagere" version = "0.2.7" @@ -1535,6 +1666,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "1.7.0" @@ -1544,6 +1681,16 @@ dependencies = [ "instant", ] +[[package]] +name = "file-per-thread-logger" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "file_finder" version = "0.1.0" @@ -1566,9 +1713,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" @@ -1843,6 +1990,15 @@ dependencies = [ "util", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -1866,20 +2022,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "gif" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" dependencies = [ "color_quant", "weezl", @@ -1890,6 +2046,11 @@ name = "gimli" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] [[package]] name = "glob" @@ -1899,9 +2060,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -1968,7 +2129,7 @@ dependencies = [ "smallvec", "smol", "sum_tree", - "time 0.3.10", + "time 0.3.11", "tiny-skia", "tree-sitter", "usvg", @@ -2009,6 +2170,9 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -2235,7 +2399,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" dependencies = [ - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", "globset", "lazy_static", "log", @@ -2268,12 +2432,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", + "hashbrown 0.12.1", + "serde", ] [[package]] @@ -2291,6 +2456,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "io-lifetimes" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" + [[package]] name = "iovec" version = "0.1.4" @@ -2333,7 +2504,7 @@ checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ "async-channel", "castaway", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", "curl", "curl-sys", "encoding_rs", @@ -2367,6 +2538,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "ittapi-rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f712648a1ad72fbfb7adc2772c331e8d90f022f8cf30cbabefba2878dd3172b0" +dependencies = [ + "cc", +] + [[package]] name = "jobserver" version = "0.1.24" @@ -2400,9 +2580,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -2487,6 +2667,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.126" @@ -2521,9 +2707,8 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.6.1+6.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" +version = "0.7.1+7.3.1" +source = "git+https://github.com/rust-rocksdb/rust-rocksdb?rev=39dc822dde743b2a26eb160b660e8fbdab079d49#39dc822dde743b2a26eb160b660e8fbdab079d49" dependencies = [ "bindgen", "bzip2-sys", @@ -2557,9 +2742,15 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" [[package]] name = "lipsum" @@ -2638,13 +2829,22 @@ dependencies = [ [[package]] name = "luajit-src" -version = "210.3.4+resty073ac54" +version = "210.4.0+resty124ff8d" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640b09e99575a442b4da0ef406a78188a1a4313bb9ead7b5b20ec12cc480130f" +checksum = "f76fb2e2c0c7192e18719d321c9a148f7625c4dcbe3df5f4c19e685e4c286f6c" dependencies = [ "cc", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2656,9 +2856,9 @@ dependencies = [ [[package]] name = "map-macro" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b0858fc6e216d2d6222d661021d9b184550acd757fbd80a8f86224069422c" +checksum = "308eec36c376312771da349d880afd5f092533499dbe9beb7bcba8c15eabe2c4" [[package]] name = "matchers" @@ -2702,6 +2902,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memfd" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6627dc657574b49d6ad27105ed671822be56e0d2547d413bfbf3e8d8fa92e7a" +dependencies = [ + "libc", +] + [[package]] name = "memmap2" version = "0.2.3" @@ -2802,9 +3011,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", @@ -2871,9 +3080,9 @@ dependencies = [ [[package]] name = "mlua" -version = "0.8.0-beta.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb054c5769e1aeb137554b95941e997b62d17f5265bef213cb63179d75a6bf6" +checksum = "a82d0b12c7c8d3bdda5933d2aa322c76e4833d822796495f299990ca652bd1bf" dependencies = [ "bstr", "cc", @@ -2887,6 +3096,12 @@ dependencies = [ "serde", ] +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + [[package]] name = "multimap" version = "0.8.3" @@ -3069,14 +3284,17 @@ version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ + "crc32fast", + "hashbrown 0.11.2", + "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -3335,18 +3553,18 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote", @@ -3387,7 +3605,7 @@ dependencies = [ "indexmap", "line-wrap", "serde", - "time 0.3.10", + "time 0.3.11", "xml-rs", ] @@ -3470,9 +3688,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] @@ -3657,6 +3875,15 @@ version = "2.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +[[package]] +name = "psm" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd89aa18fbf9533a581355a22438101fe9c2ed8c9e2f0dcf520552a3afddf2" +dependencies = [ + "cc", +] + [[package]] name = "pulldown-cmark" version = "0.9.1" @@ -3670,9 +3897,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -3764,7 +3991,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -3794,9 +4021,9 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ - "crossbeam-channel 0.5.4", + "crossbeam-channel 0.5.5", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", "num_cpus", ] @@ -3830,16 +4057,28 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "redox_syscall", "thiserror", ] [[package]] -name = "regex" -version = "1.5.6" +name = "regalloc2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "904196c12c9f55d3aea578613219f493ced8e05b3d0c6a42d11cb4142d8b4879" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -3857,9 +4096,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi 0.3.9", +] [[package]] name = "remove_dir_all" @@ -3872,9 +4123,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" dependencies = [ "base64 0.13.0", "bytes", @@ -3899,6 +4150,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -3924,9 +4176,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e74fdc210d8f24a7dbfedc13b04ba5764f5232754ccebfdf5fff1bad791ccbc6" +checksum = "c3b221de559e4a29df3b957eec92bc0de6bc8eaf6ca9cfed43e5e1d67ff65a34" dependencies = [ "bytemuck", ] @@ -3949,8 +4201,7 @@ dependencies = [ [[package]] name = "rocksdb" version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" +source = "git+https://github.com/rust-rocksdb/rust-rocksdb?rev=39dc822dde743b2a26eb160b660e8fbdab079d49#39dc822dde743b2a26eb160b660e8fbdab079d49" dependencies = [ "libc", "librocksdb-sys", @@ -4020,6 +4271,7 @@ dependencies = [ "map-macro", "mlua", "serde", + "wasmtime", ] [[package]] @@ -4078,6 +4330,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.33.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "winapi 0.3.9", +] + [[package]] name = "rustls" version = "0.19.1" @@ -4325,18 +4591,18 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -4365,9 +4631,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "indexmap", "itoa", @@ -4602,6 +4868,12 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + [[package]] name = "sluice" version = "0.5.5" @@ -4615,9 +4887,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smol" @@ -4744,7 +5016,7 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time 0.3.10", + "time 0.3.11", "tokio-stream", "url", "uuid 1.1.2", @@ -4782,6 +5054,12 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -4864,9 +5142,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -4891,6 +5169,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "target-lexicon" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" + [[package]] name = "tempdir" version = "0.3.7" @@ -5061,19 +5345,20 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] [[package]] name = "time" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82501a4c1c0330d640a6e176a3d6a204f5ec5237aca029029d21864a902e27b0" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" dependencies = [ "itoa", "libc", @@ -5139,7 +5424,7 @@ dependencies = [ "bytes", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -5285,9 +5570,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", @@ -5330,15 +5615,15 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log", @@ -5349,9 +5634,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -5360,11 +5645,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -5401,13 +5686,13 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" dependencies = [ "ansi_term", - "lazy_static", "matchers", + "once_cell", "regex", "serde", "serde_json", @@ -5590,9 +5875,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" [[package]] name = "unicase" @@ -5629,15 +5914,15 @@ checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -5760,7 +6045,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -5883,9 +6168,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" @@ -5895,9 +6180,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -5905,9 +6190,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -5920,9 +6205,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -5932,9 +6217,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5942,9 +6227,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -5955,15 +6240,238 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-encoder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77dc97c22bb5ce49a47b745bed8812d30206eff5ef3af31424f2c1820c0974b2" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmtime" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd1101bdfa0414a19018ec0a091951a20b695d4d04f858d49f6c4cc53cd8dd" +dependencies = [ + "anyhow", + "async-trait", + "backtrace", + "bincode", + "cfg-if 1.0.0", + "indexmap", + "lazy_static", + "libc", + "log", + "object", + "once_cell", + "paste", + "psm", + "rayon", + "region", + "serde", + "target-lexicon", + "wasmparser", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "winapi 0.3.9", +] + +[[package]] +name = "wasmtime-cache" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79da81ed0724392948ad7a0fb5088ff1bd15fa937356c8c037c6b1c8b5473cde" +dependencies = [ + "anyhow", + "base64 0.13.0", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix", + "serde", + "sha2 0.9.9", + "toml", + "winapi 0.3.9", + "zstd", +] + +[[package]] +name = "wasmtime-cranelift" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e78edcfb0daa9a9579ac379d00e2d5a5b2a60c0d653c8c95e8412f2166acb9" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "more-asserts", + "object", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4201389132ec467981980549574b33fc70d493b40f2c045c8ce5c7b54fbad97e" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "more-asserts", + "object", + "serde", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba6777a84b44f9a384b5c9d511ae3d86534438b7e25d928b8e8e858ecad5df2" +dependencies = [ + "cc", + "rustix", + "winapi 0.3.9", +] + +[[package]] +name = "wasmtime-jit" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1587ca7752d00862faa540d00fd28e5ccf1ac61ba19756449193f1153cb2b127" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if 1.0.0", + "cpp_demangle", + "gimli", + "ittapi-rs", + "log", + "object", + "region", + "rustc-demangle", + "rustix", + "serde", + "target-lexicon", + "thiserror", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-runtime", + "winapi 0.3.9", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b27233ab6c8934b23171c64f215f902ef19d18c1712b46a0674286d1ef28d5dd" +dependencies = [ + "lazy_static", + "object", + "rustix", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d3b0b8f13db47db59d616e498fe45295819d04a55f9921af29561827bdb816" +dependencies = [ + "anyhow", + "backtrace", + "cc", + "cfg-if 1.0.0", + "indexmap", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "more-asserts", + "rand 0.8.5", + "region", + "rustix", + "thiserror", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "winapi 0.3.9", +] + +[[package]] +name = "wasmtime-types" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1630d9dca185299bec7f557a7e73b28742fe5590caf19df001422282a0a98ad1" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", +] + +[[package]] +name = "wast" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" +dependencies = [ + "wast", +] [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", @@ -6322,18 +6830,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.9.2+zstd.1.5.1" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -6341,9 +6849,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index c4ae77606c..8daf098b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,8 @@ cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } +# TODO - Remove when a new version of RustRocksDB is released +rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "39dc822dde743b2a26eb160b660e8fbdab079d49" } [profile.dev] split-debuginfo = "unpacked" From 6768713de2c971ce1142155fe295ef3bf4f6c568 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 2 Jun 2022 17:49:02 +0200 Subject: [PATCH 08/91] Get basic Wasm runtime running --- Cargo.lock | 2 + crates/runner/Cargo.toml | 3 +- crates/runner/README.md | 6 + crates/runner/build.rs | 24 ++++ crates/runner/plugin/Cargo.lock | 25 ++++ crates/runner/plugin/Cargo.toml | 2 + crates/runner/plugin/cargo_test.lua | 6 - crates/runner/plugin/cargo_test/Cargo.lock | 7 + crates/runner/plugin/cargo_test/Cargo.toml | 7 + crates/runner/plugin/cargo_test/src/main.rs | 63 +++++++++ crates/runner/src/lib.rs | 3 + crates/runner/src/lua.rs | 2 +- crates/runner/src/main.rs | 67 +++++++--- crates/runner/src/runtime.rs | 6 +- crates/runner/src/wasm.rs | 135 ++++++++++++++++++++ 15 files changed, 329 insertions(+), 29 deletions(-) create mode 100644 crates/runner/README.md create mode 100644 crates/runner/build.rs create mode 100644 crates/runner/plugin/Cargo.lock create mode 100644 crates/runner/plugin/Cargo.toml create mode 100644 crates/runner/plugin/cargo_test/Cargo.lock create mode 100644 crates/runner/plugin/cargo_test/Cargo.toml create mode 100644 crates/runner/plugin/cargo_test/src/main.rs create mode 100644 crates/runner/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 0b0dc2a7bc..70cf05fab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4268,6 +4268,8 @@ dependencies = [ name = "runner" version = "0.1.0" dependencies = [ + "anyhow", + "bincode", "map-macro", "mlua", "serde", diff --git a/crates/runner/Cargo.toml b/crates/runner/Cargo.toml index 60797899bc..49b95360a8 100644 --- a/crates/runner/Cargo.toml +++ b/crates/runner/Cargo.toml @@ -8,4 +8,5 @@ mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] serde = "1.0" map-macro = "0.2" wasmtime = "0.37.0" -# bincode = "1.3" +anyhow = { version = "1.0", features = ["std"] } +bincode = "1.3" diff --git a/crates/runner/README.md b/crates/runner/README.md new file mode 100644 index 0000000000..1ee7de139b --- /dev/null +++ b/crates/runner/README.md @@ -0,0 +1,6 @@ +# Zed's Plugin Runner +This crate contains a fairly generic interface through which plugins may be added to extend the editor. Currently the intention of this plugin runtime is language server definitions. + +Anything that implements the `Runtime` trait may be used as a plugin. Plugin interfaces are declared by implementing the `Interface` trait. + +Wasm plugins can be run through `wasmtime`. We plan to add wasi support eventually. We also plan to add macros to generate bindings between Rust plugins compiled to Wasm and the host runtime. \ No newline at end of file diff --git a/crates/runner/build.rs b/crates/runner/build.rs new file mode 100644 index 0000000000..00a8297f79 --- /dev/null +++ b/crates/runner/build.rs @@ -0,0 +1,24 @@ +use std::env; +use std::process::Command; + +fn main() { + let cwd = std::env::current_dir().unwrap(); + let plugin_workspace = cwd.join("plugin").join("Cargo.toml"); + Command::new("cargo") + .args(&["clean", "--manifest-path"]) + .arg(&plugin_workspace) + .status() + .unwrap(); + Command::new("cargo") + .args(&[ + "build", + "--release", + "--target", + "wasm32-unknown-unknown", + "--manifest-path", + ]) + .arg(&plugin_workspace) + .status() + .unwrap(); + println!("cargo:warning=recompiling plugins") +} diff --git a/crates/runner/plugin/Cargo.lock b/crates/runner/plugin/Cargo.lock new file mode 100644 index 0000000000..d027f6bd0f --- /dev/null +++ b/crates/runner/plugin/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_test" +version = "0.1.0" +dependencies = [ + "bincode", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" diff --git a/crates/runner/plugin/Cargo.toml b/crates/runner/plugin/Cargo.toml new file mode 100644 index 0000000000..83bca541cf --- /dev/null +++ b/crates/runner/plugin/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["cargo_test"] \ No newline at end of file diff --git a/crates/runner/plugin/cargo_test.lua b/crates/runner/plugin/cargo_test.lua index 3921a1b07e..bdae0401b5 100644 --- a/crates/runner/plugin/cargo_test.lua +++ b/crates/runner/plugin/cargo_test.lua @@ -1,5 +1,3 @@ -print("initializing plugin...") - query = [[( (attribute_item (meta_item @@ -10,13 +8,9 @@ query = [[( )]] function run_test(name) - print('running test `' .. name .. '`:') local command = 'cargo test -- ' .. name local openPop = assert(io.popen(command, 'r')) local output = openPop:read('*all') openPop:close() - print('done running test') return output end - -print("done initializing plugin.") \ No newline at end of file diff --git a/crates/runner/plugin/cargo_test/Cargo.lock b/crates/runner/plugin/cargo_test/Cargo.lock new file mode 100644 index 0000000000..db8b4f0b9a --- /dev/null +++ b/crates/runner/plugin/cargo_test/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cargo_test" +version = "0.1.0" diff --git a/crates/runner/plugin/cargo_test/Cargo.toml b/crates/runner/plugin/cargo_test/Cargo.toml new file mode 100644 index 0000000000..9d13904251 --- /dev/null +++ b/crates/runner/plugin/cargo_test/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "cargo_test" +version = "0.1.0" +edition = "2021" + +[dependencies] +bincode = "1.3" \ No newline at end of file diff --git a/crates/runner/plugin/cargo_test/src/main.rs b/crates/runner/plugin/cargo_test/src/main.rs new file mode 100644 index 0000000000..a97a194ffc --- /dev/null +++ b/crates/runner/plugin/cargo_test/src/main.rs @@ -0,0 +1,63 @@ +use core::slice; + +#[repr(C)] +pub struct Buffer { + ptr: *const u8, + len: usize, +} + +/// Allocates a buffer with an exact size. +/// We don't return the size because it has to be passed in anyway. +#[no_mangle] +pub extern "C" fn __alloc_buffer(len: usize) -> *const u8 { + let vec = vec![0; len]; + let buffer = unsafe { Buffer::from_vec(vec) }; + return buffer.ptr; +} + +/// Frees a given buffer, requires the size. +#[no_mangle] +pub extern "C" fn __free_buffer(ptr: *const u8, len: usize) { + let buffer = Buffer { ptr, len }; + let vec = unsafe { buffer.to_vec() }; + std::mem::drop(vec); +} + +impl Buffer { + pub unsafe fn to_vec(&self) -> Vec { + slice::from_raw_parts(self.ptr, self.len).to_vec() + } + + pub unsafe fn from_vec(mut vec: Vec) -> Buffer { + vec.shrink_to(0); + let ptr = vec.as_ptr(); + let len = vec.len(); + std::mem::forget(vec); + Buffer { ptr, len } + } + + pub fn leak_to_heap(self) -> *const Buffer { + let boxed = Box::new(self); + let ptr = Box::::into_raw(boxed) as *const Buffer; + return ptr; + } +} + +#[no_mangle] +pub extern "C" fn banana(ptr: *const u8, len: usize) -> *const Buffer { + // setup + let buffer = Buffer { ptr, len }; + let data = unsafe { buffer.to_vec() }; + // operation + // let reversed: Vec = data.into_iter().rev().collect(); + let number: f64 = bincode::deserialize(&data).unwrap(); + let new_number = number * 2.0; + let new_data = bincode::serialize(&new_number).unwrap(); + // teardown + let new_buffer = unsafe { Buffer::from_vec(new_data) }; + return new_buffer.leak_to_heap(); +} + +pub fn main() -> () { + () +} diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs index 4afe88b85a..de6544f6fa 100644 --- a/crates/runner/src/lib.rs +++ b/crates/runner/src/lib.rs @@ -8,3 +8,6 @@ pub use runtime::*; pub mod lua; pub use lua::*; + +pub mod wasm; +pub use wasm::*; diff --git a/crates/runner/src/lua.rs b/crates/runner/src/lua.rs index 4e364b9556..1c20580d95 100644 --- a/crates/runner/src/lua.rs +++ b/crates/runner/src/lua.rs @@ -22,7 +22,7 @@ impl Runtime for Lua { Ok(self.from_value(val)?) } - fn call(&mut self, handle: &Handle, arg: T) -> Result { + fn call(&mut self, handle: &Handle, arg: A) -> Result { let fun: Function = self.globals().get(handle.inner())?; let arg: Value = self.to_value(&arg)?; let result = fun.call(arg)?; diff --git a/crates/runner/src/main.rs b/crates/runner/src/main.rs index 39e8276696..9f78835eb6 100644 --- a/crates/runner/src/main.rs +++ b/crates/runner/src/main.rs @@ -2,34 +2,65 @@ use mlua::Lua; use runner::*; -pub fn main() -> Result<(), mlua::Error> { - let source = include_str!("../plugin/cargo_test.lua").to_string(); +// pub fn main() -> Result<(), mlua::Error> { +// let source = include_str!("../plugin/cargo_test.lua").to_string(); - let module = LuaPlugin::new(source); - // .setup(|runtime| { - // let greet = runtime - // .create_function(|_, name: String| { - // println!("Hello, {}!", name); - // Ok(()) - // }) - // .map_err(|_| ())?; +// let module = LuaPlugin::new(source); +// let mut lua: Lua = Runtime::init(module)?; +// let runner: TestRunner = lua.as_interface::().unwrap(); - // runtime.globals().set("greet", greet).map_err(|_| ())?; - // Ok(()) - // }); +// println!("extracted interface: {:#?}", &runner); - let mut lua: Lua = Runtime::init(module)?; - let runner: TestRunner = lua.as_interface::().unwrap(); +// let contents = runner.run_test(&mut lua, "it_works".into()); - println!("extracted interface: {:#?}", &runner); +// println!("test results:{}", contents.unwrap()); - let contents = runner.run_test(&mut lua, "it_works".into()); +// Ok(()) +// } - println!("test results:{}", contents.unwrap()); +// pub fn main() -> mlua::Result<()> { +// let module = LuaPlugin::new(include_str!("../plugin/cargo_test.lua").to_string()); +// let mut lua: Lua = Runtime::init(module)?; +// let runner = lua.as_interface::().unwrap(); +// let test_results = runner.run_test(&mut lua, "it_works".into()); +// Ok(()) +// } + +pub fn main() -> anyhow::Result<()> { + let plugin = WasmPlugin { + source_bytes: include_bytes!( + "../plugin/target/wasm32-unknown-unknown/release/cargo_test.wasm" + ) + .to_vec(), + store_data: (), + }; + + let mut wasm: Wasm<()> = Runtime::init(plugin)?; + let banana = wasm.as_interface::().unwrap(); + let result = banana.banana(&mut wasm, 420.69); + + dbg!("{}", result); Ok(()) } +struct Banana { + banana: Handle, +} + +impl Interface for Banana { + fn from_runtime(runtime: &mut T) -> Option { + let banana = runtime.handle_for("banana")?; + Some(Banana { banana }) + } +} + +impl Banana { + fn banana(&self, runtime: &mut T, number: f64) -> Option { + runtime.call(&self.banana, number).ok() + } +} + #[allow(dead_code)] #[derive(Debug)] struct TestRunner { diff --git a/crates/runner/src/runtime.rs b/crates/runner/src/runtime.rs index 3ee03e961c..44b118024f 100644 --- a/crates/runner/src/runtime.rs +++ b/crates/runner/src/runtime.rs @@ -45,11 +45,11 @@ where fn constant(&mut self, handle: &Handle) -> Result; /// Call a function defined in the module. - fn call( + fn call( &mut self, handle: &Handle, - arg: T, - ) -> Result; + arg: A, + ) -> Result; /// Registers a handle with the runtime. /// This is a mutable item if needed, but generally diff --git a/crates/runner/src/wasm.rs b/crates/runner/src/wasm.rs new file mode 100644 index 0000000000..06d539d3cb --- /dev/null +++ b/crates/runner/src/wasm.rs @@ -0,0 +1,135 @@ +use std::collections::HashMap; + +use anyhow::anyhow; + +use wasmtime::{Engine, Func, Instance, Memory, MemoryType, Module, Store, TypedFunc}; + +use crate::*; + +pub struct Wasm { + engine: Engine, + module: Module, + store: Store, + instance: Instance, + alloc_buffer: TypedFunc, + free_buffer: TypedFunc<(i32, i32), ()>, +} + +pub struct WasmPlugin { + pub source_bytes: Vec, + pub store_data: T, +} + +impl Wasm { + pub fn dump_memory(data: &[u8]) { + for (i, byte) in data.iter().enumerate() { + if i % 32 == 0 { + println!(); + } + if i % 4 == 0 { + print!("|"); + } + if *byte == 0 { + print!("__") + } else { + print!("{:02x}", byte); + } + } + println!(); + } +} + +impl Runtime for Wasm { + type Plugin = WasmPlugin; + type Error = anyhow::Error; + + fn init(plugin: WasmPlugin) -> Result { + let engine = Engine::default(); + let module = Module::new(&engine, plugin.source_bytes)?; + let mut store: Store = Store::new(&engine, plugin.store_data); + let instance = Instance::new(&mut store, &module, &[])?; + + let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; + let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; + + Ok(Wasm { + engine, + module, + store, + instance, + alloc_buffer, + free_buffer, + }) + } + + fn constant(&mut self, handle: &Handle) -> Result { + let export = self + .instance + .get_export(&mut self.store, handle.inner()) + .ok_or_else(|| anyhow!("Could not get export"))?; + + todo!() + } + + // TODO: dont' use as for conversions + fn call( + &mut self, + handle: &Handle, + arg: A, + ) -> Result { + // serialize the argument using bincode + let arg = bincode::serialize(&arg)?; + let arg_buffer_len = arg.len(); + + // allocate a buffer and write the argument to that buffer + let arg_buffer_ptr = self + .alloc_buffer + .call(&mut self.store, arg_buffer_len as i32)?; + let plugin_memory = self + .instance + .get_memory(&mut self.store, "memory") + .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; + plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; + + // get the webassembly function we want to actually call + let fun = self + .instance + .get_typed_func::<(i32, i32), i32, _>(&mut self.store, handle.inner())?; + + // call the function, passing in the buffer and its length + // this should return a pointer to a (ptr, lentgh) pair + let result_buffer = fun.call(&mut self.store, (arg_buffer_ptr, arg_buffer_len as i32))?; + dbg!(result_buffer); + + // panic!(); + // dbg!() + + // create a buffer to read the (ptr, length) pair into + // this is a total of 4 + 4 = 8 bytes. + let buffer = &mut [0; 8]; + plugin_memory.read(&mut self.store, result_buffer as usize, buffer)?; + + // use these bytes (wasm stores things little-endian) + // to get a pointer to the buffer and its length + let b = buffer; + let result_buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; + let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; + let result_buffer_end = result_buffer_ptr + result_buffer_len; + + dbg!(result_buffer_ptr); + dbg!(result_buffer_len); + + // read the buffer at this point into a byte array + let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; + + // deserialize the byte array into the provided serde type + let result = bincode::deserialize(result)?; + return Ok(result); + } + + fn register_handle>(&mut self, name: T) -> bool { + self.instance + .get_export(&mut self.store, name.as_ref()) + .is_some() + } +} From dda6dcb3b88b26d7154c6a5fb651c34a8273f24d Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 2 Jun 2022 18:00:01 +0200 Subject: [PATCH 09/91] Quick documentation pass --- crates/runner/src/wasm.rs | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/crates/runner/src/wasm.rs b/crates/runner/src/wasm.rs index 06d539d3cb..d196bfe42a 100644 --- a/crates/runner/src/wasm.rs +++ b/crates/runner/src/wasm.rs @@ -71,6 +71,49 @@ impl Runtime for Wasm { todo!() } + // So this call function is kinda a dance, I figured it'd be a good idea to document it. + // the high level is we take a serde type, serialize it to a byte array, + // (we're doing this using bincode for now) + // then toss that byte array into webassembly. + // webassembly grabs that byte array, does some magic, + // and serializes the result into yet another byte array. + // we then grab *that* result byte array and deserialize it into a result. + // + // phew... + // + // now the problem is, webassambly doesn't support buffers. + // only really like i32s, that's it (yeah, it's sad. Not even unsigned!) + // (ok, I'm exaggerating a bit). + // + // the Wasm function that this calls must have a very specific signature: + // + // fn(pointer to byte array: i32, length of byte array: i32) + // -> pointer to ( + // pointer to byte_array: i32, + // length of byte array: i32, + // ): i32 + // + // This pair `(pointer to byte array, length of byte array)` is called a `Buffer` + // and can be found in the cargo_test plugin. + // + // so on the wasm side, we grab the two parameters to the function, + // stuff them into a `Buffer`, + // and then pray to the `unsafe` Rust gods above that a valid byte array pops out. + // + // On the flip side, when returning from a wasm function, + // we convert whatever serialized result we get into byte array, + // which we stuff into a Buffer and allocate on the heap, + // which pointer to we then return. + // Note the double indirection! + // + // So when returning from a function, we actually leak memory *twice*: + // + // 1) once when we leak the byte array + // 2) again when we leak the allocated `Buffer` + // + // This isn't a problem because Wasm stops executing after the function returns, + // so the heap is still valid for our inspection when we want to pull things out. + // TODO: dont' use as for conversions fn call( &mut self, From f6a9558c5c8f60d6b5ef28c33f6032c63f735db8 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 3 Jun 2022 10:33:11 +0200 Subject: [PATCH 10/91] Work on macro binding generation, some cleanup needed, rename runner to plugin --- .gitignore | 2 +- Cargo.lock | 20 ++ crates/plugin/Cargo.toml | 9 + .../src/main.rs => plugin/src/lib.rs} | 40 ++-- crates/plugin_macros/Cargo.toml | 14 ++ crates/plugin_macros/src/lib.rs | 45 +++++ crates/{runner => plugin_runtime}/Cargo.toml | 0 crates/{runner => plugin_runtime}/README.md | 0 crates/{runner => plugin_runtime}/build.rs | 0 crates/plugin_runtime/heck.txt | 0 crates/plugin_runtime/plugin/Cargo.lock | 180 ++++++++++++++++++ .../plugin/Cargo.toml | 0 .../plugin/cargo_test.lua | 0 .../plugin/cargo_test/Cargo.lock | 0 .../plugin/cargo_test/Cargo.toml | 4 +- .../plugin/cargo_test/src/main.rs | 34 ++++ crates/{runner => plugin_runtime}/src/lib.rs | 0 crates/{runner => plugin_runtime}/src/lua.rs | 0 crates/{runner => plugin_runtime}/src/main.rs | 0 .../{runner => plugin_runtime}/src/runtime.rs | 0 crates/{runner => plugin_runtime}/src/wasm.rs | 16 +- crates/runner/plugin/Cargo.lock | 25 --- 22 files changed, 330 insertions(+), 59 deletions(-) create mode 100644 crates/plugin/Cargo.toml rename crates/{runner/plugin/cargo_test/src/main.rs => plugin/src/lib.rs} (52%) create mode 100644 crates/plugin_macros/Cargo.toml create mode 100644 crates/plugin_macros/src/lib.rs rename crates/{runner => plugin_runtime}/Cargo.toml (100%) rename crates/{runner => plugin_runtime}/README.md (100%) rename crates/{runner => plugin_runtime}/build.rs (100%) create mode 100644 crates/plugin_runtime/heck.txt create mode 100644 crates/plugin_runtime/plugin/Cargo.lock rename crates/{runner => plugin_runtime}/plugin/Cargo.toml (100%) rename crates/{runner => plugin_runtime}/plugin/cargo_test.lua (100%) rename crates/{runner => plugin_runtime}/plugin/cargo_test/Cargo.lock (100%) rename crates/{runner => plugin_runtime}/plugin/cargo_test/Cargo.toml (54%) create mode 100644 crates/plugin_runtime/plugin/cargo_test/src/main.rs rename crates/{runner => plugin_runtime}/src/lib.rs (100%) rename crates/{runner => plugin_runtime}/src/lua.rs (100%) rename crates/{runner => plugin_runtime}/src/main.rs (100%) rename crates/{runner => plugin_runtime}/src/runtime.rs (100%) rename crates/{runner => plugin_runtime}/src/wasm.rs (93%) delete mode 100644 crates/runner/plugin/Cargo.lock diff --git a/.gitignore b/.gitignore index f280b60475..1372cd1c37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +**/target /zed.xcworkspace .DS_Store /script/node_modules diff --git a/Cargo.lock b/Cargo.lock index 70cf05fab0..fa9eb96708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3609,6 +3609,15 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "plugin" +version = "0.1.0" +dependencies = [ + "bincode", + "rust_plugin_macros", + "serde", +] + [[package]] name = "png" version = "0.16.8" @@ -4311,6 +4320,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust_plugin_macros" +version = "0.1.0" +dependencies = [ + "bincode", + "proc-macro2", + "quote", + "serde", + "syn", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml new file mode 100644 index 0000000000..28b5be9984 --- /dev/null +++ b/crates/plugin/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "plugin" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "1.0" +bincode = "1.3" +rust_plugin_macros = { path = "../plugin_macros" } \ No newline at end of file diff --git a/crates/runner/plugin/cargo_test/src/main.rs b/crates/plugin/src/lib.rs similarity index 52% rename from crates/runner/plugin/cargo_test/src/main.rs rename to crates/plugin/src/lib.rs index a97a194ffc..40d5e17012 100644 --- a/crates/runner/plugin/cargo_test/src/main.rs +++ b/crates/plugin/src/lib.rs @@ -1,5 +1,3 @@ -use core::slice; - #[repr(C)] pub struct Buffer { ptr: *const u8, @@ -15,19 +13,21 @@ pub extern "C" fn __alloc_buffer(len: usize) -> *const u8 { return buffer.ptr; } -/// Frees a given buffer, requires the size. -#[no_mangle] -pub extern "C" fn __free_buffer(ptr: *const u8, len: usize) { - let buffer = Buffer { ptr, len }; - let vec = unsafe { buffer.to_vec() }; - std::mem::drop(vec); -} +// /// Frees a given buffer, requires the size. +// #[no_mangle] +// pub extern "C" fn __free_buffer(ptr: *const u8, len: usize) { +// let buffer = Buffer { ptr, len }; +// let vec = unsafe { buffer.to_vec() }; +// std::mem::drop(vec); +// } impl Buffer { + #[inline(always)] pub unsafe fn to_vec(&self) -> Vec { slice::from_raw_parts(self.ptr, self.len).to_vec() } + #[inline(always)] pub unsafe fn from_vec(mut vec: Vec) -> Buffer { vec.shrink_to(0); let ptr = vec.as_ptr(); @@ -36,6 +36,7 @@ impl Buffer { Buffer { ptr, len } } + #[inline(always)] pub fn leak_to_heap(self) -> *const Buffer { let boxed = Box::new(self); let ptr = Box::::into_raw(boxed) as *const Buffer; @@ -43,21 +44,8 @@ impl Buffer { } } -#[no_mangle] -pub extern "C" fn banana(ptr: *const u8, len: usize) -> *const Buffer { - // setup - let buffer = Buffer { ptr, len }; - let data = unsafe { buffer.to_vec() }; - // operation - // let reversed: Vec = data.into_iter().rev().collect(); - let number: f64 = bincode::deserialize(&data).unwrap(); - let new_number = number * 2.0; - let new_data = bincode::serialize(&new_number).unwrap(); - // teardown - let new_buffer = unsafe { Buffer::from_vec(new_data) }; - return new_buffer.leak_to_heap(); -} - -pub fn main() -> () { - () +pub mod prelude { + pub use super::{Buffer, __alloc_buffer}; + #[macro_use] + pub use plugin_macros::bind; } diff --git a/crates/plugin_macros/Cargo.toml b/crates/plugin_macros/Cargo.toml new file mode 100644 index 0000000000..2d106105ab --- /dev/null +++ b/crates/plugin_macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "plugin_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" +serde = "1.0" +bincode = "1.3" \ No newline at end of file diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs new file mode 100644 index 0000000000..c8d0f1f93f --- /dev/null +++ b/crates/plugin_macros/src/lib.rs @@ -0,0 +1,45 @@ +use core::panic; + +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, ItemFn, VisPublic, Visibility}; + +#[proc_macro_attribute] +pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { + if !args.is_empty() { + panic!("The bind attribute does not take any arguments"); + } + + let inner_fn = parse_macro_input!(function as ItemFn); + if let Visibility::Public(_) = inner_fn.vis { + } else { + panic!("The bind attribute only works for public functions"); + } + + let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); + let outer_fn_name = format_ident!("__{}", inner_fn_name); + + TokenStream::from(quote! { + use serde; + + #[no_mangle] + #inner_fn + + #[no_mangle] + pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const Buffer { + // setup + let buffer = Buffer { ptr, len }; + let data = unsafe { buffer.to_vec() }; + + // operation + let argument = bincode::deserialize(&data).unwrap(); + let result = #inner_fn_name(argument); + let new_data: Result, _> = bincode::serialize(&result); + let new_data = new_data.unwrap(); + + // teardown + let new_buffer = unsafe { Buffer::from_vec(new_data) }; + return new_buffer.leak_to_heap(); + } + }) +} diff --git a/crates/runner/Cargo.toml b/crates/plugin_runtime/Cargo.toml similarity index 100% rename from crates/runner/Cargo.toml rename to crates/plugin_runtime/Cargo.toml diff --git a/crates/runner/README.md b/crates/plugin_runtime/README.md similarity index 100% rename from crates/runner/README.md rename to crates/plugin_runtime/README.md diff --git a/crates/runner/build.rs b/crates/plugin_runtime/build.rs similarity index 100% rename from crates/runner/build.rs rename to crates/plugin_runtime/build.rs diff --git a/crates/plugin_runtime/heck.txt b/crates/plugin_runtime/heck.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/plugin_runtime/plugin/Cargo.lock b/crates/plugin_runtime/plugin/Cargo.lock new file mode 100644 index 0000000000..8cc9507c21 --- /dev/null +++ b/crates/plugin_runtime/plugin/Cargo.lock @@ -0,0 +1,180 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "cargo_test" +version = "0.1.0" +dependencies = [ + "bincode", + "rust_plugin_macros", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust_plugin_macros" +version = "0.1.0" +dependencies = [ + "bincode", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" diff --git a/crates/runner/plugin/Cargo.toml b/crates/plugin_runtime/plugin/Cargo.toml similarity index 100% rename from crates/runner/plugin/Cargo.toml rename to crates/plugin_runtime/plugin/Cargo.toml diff --git a/crates/runner/plugin/cargo_test.lua b/crates/plugin_runtime/plugin/cargo_test.lua similarity index 100% rename from crates/runner/plugin/cargo_test.lua rename to crates/plugin_runtime/plugin/cargo_test.lua diff --git a/crates/runner/plugin/cargo_test/Cargo.lock b/crates/plugin_runtime/plugin/cargo_test/Cargo.lock similarity index 100% rename from crates/runner/plugin/cargo_test/Cargo.lock rename to crates/plugin_runtime/plugin/cargo_test/Cargo.lock diff --git a/crates/runner/plugin/cargo_test/Cargo.toml b/crates/plugin_runtime/plugin/cargo_test/Cargo.toml similarity index 54% rename from crates/runner/plugin/cargo_test/Cargo.toml rename to crates/plugin_runtime/plugin/cargo_test/Cargo.toml index 9d13904251..9a94cad78d 100644 --- a/crates/runner/plugin/cargo_test/Cargo.toml +++ b/crates/plugin_runtime/plugin/cargo_test/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -bincode = "1.3" \ No newline at end of file +serde = "1.0" +bincode = "1.3" +plugin = { path = "../../../plugin" } diff --git a/crates/plugin_runtime/plugin/cargo_test/src/main.rs b/crates/plugin_runtime/plugin/cargo_test/src/main.rs new file mode 100644 index 0000000000..ba4fd20d3a --- /dev/null +++ b/crates/plugin_runtime/plugin/cargo_test/src/main.rs @@ -0,0 +1,34 @@ +use core::slice; +use plugin::prelude::*; + +#[no_mangle] +pub extern "C" fn banana(ptr: *const u8, len: usize) -> *const Buffer { + // setup + let buffer = Buffer { ptr, len }; + let data = unsafe { buffer.to_vec() }; + // operation + // let reversed: Vec = data.into_iter().rev().collect(); + let number: f64 = bincode::deserialize(&data).unwrap(); + let new_number = number * 2.0; + let new_data = bincode::serialize(&new_number).unwrap(); + // teardown + let new_buffer = unsafe { Buffer::from_vec(new_data) }; + return new_buffer.leak_to_heap(); +} + +pub fn banana2(number: f64) -> f64 { + number * 2.0 +} + +#[bind] +pub fn sum_lengths(strings: Vec) -> usize { + let mut total = 0; + for string in strings { + total += string.len(); + } + return total; +} + +pub fn main() -> () { + () +} diff --git a/crates/runner/src/lib.rs b/crates/plugin_runtime/src/lib.rs similarity index 100% rename from crates/runner/src/lib.rs rename to crates/plugin_runtime/src/lib.rs diff --git a/crates/runner/src/lua.rs b/crates/plugin_runtime/src/lua.rs similarity index 100% rename from crates/runner/src/lua.rs rename to crates/plugin_runtime/src/lua.rs diff --git a/crates/runner/src/main.rs b/crates/plugin_runtime/src/main.rs similarity index 100% rename from crates/runner/src/main.rs rename to crates/plugin_runtime/src/main.rs diff --git a/crates/runner/src/runtime.rs b/crates/plugin_runtime/src/runtime.rs similarity index 100% rename from crates/runner/src/runtime.rs rename to crates/plugin_runtime/src/runtime.rs diff --git a/crates/runner/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs similarity index 93% rename from crates/runner/src/wasm.rs rename to crates/plugin_runtime/src/wasm.rs index d196bfe42a..6e2506cb58 100644 --- a/crates/runner/src/wasm.rs +++ b/crates/plugin_runtime/src/wasm.rs @@ -12,7 +12,7 @@ pub struct Wasm { store: Store, instance: Instance, alloc_buffer: TypedFunc, - free_buffer: TypedFunc<(i32, i32), ()>, + // free_buffer: TypedFunc<(i32, i32), ()>, } pub struct WasmPlugin { @@ -50,7 +50,7 @@ impl Runtime for Wasm { let instance = Instance::new(&mut store, &module, &[])?; let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; - let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; + // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; Ok(Wasm { engine, @@ -58,7 +58,7 @@ impl Runtime for Wasm { store, instance, alloc_buffer, - free_buffer, + // free_buffer, }) } @@ -141,7 +141,8 @@ impl Runtime for Wasm { // call the function, passing in the buffer and its length // this should return a pointer to a (ptr, lentgh) pair - let result_buffer = fun.call(&mut self.store, (arg_buffer_ptr, arg_buffer_len as i32))?; + let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); + let result_buffer = fun.call(&mut self.store, arg_buffer)?; dbg!(result_buffer); // panic!(); @@ -163,10 +164,13 @@ impl Runtime for Wasm { dbg!(result_buffer_len); // read the buffer at this point into a byte array - let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; - // deserialize the byte array into the provided serde type + let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; let result = bincode::deserialize(result)?; + + // // deallocate the argument buffer + // self.free_buffer.call(&mut self.store, arg_buffer); + return Ok(result); } diff --git a/crates/runner/plugin/Cargo.lock b/crates/runner/plugin/Cargo.lock deleted file mode 100644 index d027f6bd0f..0000000000 --- a/crates/runner/plugin/Cargo.lock +++ /dev/null @@ -1,25 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_test" -version = "0.1.0" -dependencies = [ - "bincode", -] - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" From 0cf64d6fbad07c5d4ba11f119f1d0003101d67c7 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 3 Jun 2022 11:18:13 +0200 Subject: [PATCH 11/91] Clean up impl a bit --- crates/plugin_runtime/plugin/Cargo.lock | 132 +++--------------- .../plugin/cargo_test/src/main.rs | 20 --- crates/plugin_runtime/src/main.rs | 97 ++++--------- crates/plugin_runtime/src/wasm.rs | 12 +- 4 files changed, 51 insertions(+), 210 deletions(-) diff --git a/crates/plugin_runtime/plugin/Cargo.lock b/crates/plugin_runtime/plugin/Cargo.lock index 8cc9507c21..e40b59046a 100644 --- a/crates/plugin_runtime/plugin/Cargo.lock +++ b/crates/plugin_runtime/plugin/Cargo.lock @@ -11,47 +11,33 @@ dependencies = [ "serde", ] -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - [[package]] name = "cargo_test" version = "0.1.0" dependencies = [ "bincode", - "rust_plugin_macros", + "plugin", "serde", - "wasm-bindgen", ] [[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +name = "plugin" +version = "0.1.0" dependencies = [ - "cfg-if", + "bincode", + "plugin_macros", + "serde", +] + +[[package]] +name = "plugin_macros" +version = "0.1.0" +dependencies = [ + "bincode", + "proc-macro2", + "quote", + "serde", + "syn", ] [[package]] @@ -72,40 +58,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rust_plugin_macros" -version = "0.1.0" -dependencies = [ - "bincode", - "proc-macro2", - "quote", - "serde", - "syn", -] - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - [[package]] name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" -[[package]] -name = "serde_json" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - [[package]] name = "syn" version = "1.0.96" @@ -122,59 +80,3 @@ name = "unicode-ident" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[package]] -name = "wasm-bindgen" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" -dependencies = [ - "cfg-if", - "serde", - "serde_json", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" diff --git a/crates/plugin_runtime/plugin/cargo_test/src/main.rs b/crates/plugin_runtime/plugin/cargo_test/src/main.rs index ba4fd20d3a..65abd38399 100644 --- a/crates/plugin_runtime/plugin/cargo_test/src/main.rs +++ b/crates/plugin_runtime/plugin/cargo_test/src/main.rs @@ -1,25 +1,5 @@ -use core::slice; use plugin::prelude::*; -#[no_mangle] -pub extern "C" fn banana(ptr: *const u8, len: usize) -> *const Buffer { - // setup - let buffer = Buffer { ptr, len }; - let data = unsafe { buffer.to_vec() }; - // operation - // let reversed: Vec = data.into_iter().rev().collect(); - let number: f64 = bincode::deserialize(&data).unwrap(); - let new_number = number * 2.0; - let new_data = bincode::serialize(&new_number).unwrap(); - // teardown - let new_buffer = unsafe { Buffer::from_vec(new_data) }; - return new_buffer.leak_to_heap(); -} - -pub fn banana2(number: f64) -> f64 { - number * 2.0 -} - #[bind] pub fn sum_lengths(strings: Vec) -> usize { let mut total = 0; diff --git a/crates/plugin_runtime/src/main.rs b/crates/plugin_runtime/src/main.rs index 9f78835eb6..64f869370b 100644 --- a/crates/plugin_runtime/src/main.rs +++ b/crates/plugin_runtime/src/main.rs @@ -2,30 +2,6 @@ use mlua::Lua; use runner::*; -// pub fn main() -> Result<(), mlua::Error> { -// let source = include_str!("../plugin/cargo_test.lua").to_string(); - -// let module = LuaPlugin::new(source); -// let mut lua: Lua = Runtime::init(module)?; -// let runner: TestRunner = lua.as_interface::().unwrap(); - -// println!("extracted interface: {:#?}", &runner); - -// let contents = runner.run_test(&mut lua, "it_works".into()); - -// println!("test results:{}", contents.unwrap()); - -// Ok(()) -// } - -// pub fn main() -> mlua::Result<()> { -// let module = LuaPlugin::new(include_str!("../plugin/cargo_test.lua").to_string()); -// let mut lua: Lua = Runtime::init(module)?; -// let runner = lua.as_interface::().unwrap(); -// let test_results = runner.run_test(&mut lua, "it_works".into()); -// Ok(()) -// } - pub fn main() -> anyhow::Result<()> { let plugin = WasmPlugin { source_bytes: include_bytes!( @@ -35,55 +11,42 @@ pub fn main() -> anyhow::Result<()> { store_data: (), }; - let mut wasm: Wasm<()> = Runtime::init(plugin)?; - let banana = wasm.as_interface::().unwrap(); - let result = banana.banana(&mut wasm, 420.69); + let mut sum = Wasm::init(plugin)?; + let strings = "I hope you have a nice day".split(" ").iter().collect(); + let result = sum.sum_lengths(strings); - dbg!("{}", result); + dbg!(result); Ok(()) } -struct Banana { - banana: Handle, +// struct SumLengths { +// sum_lengths: Handle, +// } + +// impl Interface for SumLengths { +// fn from_runtime(runtime: &mut T) -> Option { +// Some(SumLengths { +// sum_lengths: runtime.handle_for("sum_lengths")?, +// }) +// } +// } + +// impl SumLengths { +// fn sum_lengths(&self, runtime: &mut T, strings: Vec) -> Option { +// runtime.call(&self.sum_lengths, strings).ok() +// } +// } + +// #[plugin::interface] +trait SumLengths { + fn sum_lengths(&mut self, strings: Vec) -> usize; } -impl Interface for Banana { - fn from_runtime(runtime: &mut T) -> Option { - let banana = runtime.handle_for("banana")?; - Some(Banana { banana }) +impl SumLengths for T { + fn sum_lengths(&mut self, strings: Vec) -> usize { + let handle = self.handle_for("sum_lengths").unwrap(); + let result = self.call(&handle, strings).ok().unwrap(); + return result; } } - -impl Banana { - fn banana(&self, runtime: &mut T, number: f64) -> Option { - runtime.call(&self.banana, number).ok() - } -} - -#[allow(dead_code)] -#[derive(Debug)] -struct TestRunner { - pub query: String, - run_test: Handle, -} - -impl Interface for TestRunner { - fn from_runtime(runtime: &mut T) -> Option { - let run_test = runtime.handle_for("run_test")?; - let query = runtime.handle_for("query")?; - let query: String = runtime.constant(&query).ok()?; - Some(TestRunner { query, run_test }) - } -} - -impl TestRunner { - pub fn run_test(&self, runtime: &mut T, test_name: String) -> Option { - runtime.call(&self.run_test, test_name).ok() - } -} - -#[test] -pub fn it_works() { - panic!("huh, that was surprising..."); -} diff --git a/crates/plugin_runtime/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs index 6e2506cb58..b0014ac27a 100644 --- a/crates/plugin_runtime/src/wasm.rs +++ b/crates/plugin_runtime/src/wasm.rs @@ -135,18 +135,16 @@ impl Runtime for Wasm { plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; // get the webassembly function we want to actually call + // TODO: precompute handle + let fun_name = format!("__{}", handle.inner()); let fun = self .instance - .get_typed_func::<(i32, i32), i32, _>(&mut self.store, handle.inner())?; + .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; // call the function, passing in the buffer and its length // this should return a pointer to a (ptr, lentgh) pair let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); let result_buffer = fun.call(&mut self.store, arg_buffer)?; - dbg!(result_buffer); - - // panic!(); - // dbg!() // create a buffer to read the (ptr, length) pair into // this is a total of 4 + 4 = 8 bytes. @@ -160,14 +158,12 @@ impl Runtime for Wasm { let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; let result_buffer_end = result_buffer_ptr + result_buffer_len; - dbg!(result_buffer_ptr); - dbg!(result_buffer_len); - // read the buffer at this point into a byte array // deserialize the byte array into the provided serde type let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; let result = bincode::deserialize(result)?; + // TODO: this is handled wasm-side, but I'd like to double-check // // deallocate the argument buffer // self.free_buffer.call(&mut self.store, arg_buffer); From 35b2eff29ca1a55fb13a34f513c1cb785a88916e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 3 Jun 2022 11:18:54 +0200 Subject: [PATCH 12/91] Update plugin dependencies --- Cargo.lock | 24 ++++++++++++------------ crates/plugin/Cargo.toml | 2 +- crates/plugin/src/lib.rs | 23 +++++++++++------------ crates/plugin_macros/src/lib.rs | 14 ++++++-------- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa9eb96708..b9dd80470a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3614,10 +3614,21 @@ name = "plugin" version = "0.1.0" dependencies = [ "bincode", - "rust_plugin_macros", + "plugin_macros", "serde", ] +[[package]] +name = "plugin_macros" +version = "0.1.0" +dependencies = [ + "bincode", + "proc-macro2", + "quote", + "serde", + "syn", +] + [[package]] name = "png" version = "0.16.8" @@ -4320,17 +4331,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rust_plugin_macros" -version = "0.1.0" -dependencies = [ - "bincode", - "proc-macro2", - "quote", - "serde", - "syn", -] - [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml index 28b5be9984..c4f22c94d3 100644 --- a/crates/plugin/Cargo.toml +++ b/crates/plugin/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" [dependencies] serde = "1.0" bincode = "1.3" -rust_plugin_macros = { path = "../plugin_macros" } \ No newline at end of file +plugin_macros = { path = "../plugin_macros" } \ No newline at end of file diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs index 40d5e17012..42ed97c0d4 100644 --- a/crates/plugin/src/lib.rs +++ b/crates/plugin/src/lib.rs @@ -1,7 +1,7 @@ #[repr(C)] -pub struct Buffer { - ptr: *const u8, - len: usize, +pub struct __Buffer { + pub ptr: *const u8, + pub len: usize, } /// Allocates a buffer with an exact size. @@ -9,7 +9,7 @@ pub struct Buffer { #[no_mangle] pub extern "C" fn __alloc_buffer(len: usize) -> *const u8 { let vec = vec![0; len]; - let buffer = unsafe { Buffer::from_vec(vec) }; + let buffer = unsafe { __Buffer::from_vec(vec) }; return buffer.ptr; } @@ -21,31 +21,30 @@ pub extern "C" fn __alloc_buffer(len: usize) -> *const u8 { // std::mem::drop(vec); // } -impl Buffer { +impl __Buffer { #[inline(always)] pub unsafe fn to_vec(&self) -> Vec { - slice::from_raw_parts(self.ptr, self.len).to_vec() + core::slice::from_raw_parts(self.ptr, self.len).to_vec() } #[inline(always)] - pub unsafe fn from_vec(mut vec: Vec) -> Buffer { + pub unsafe fn from_vec(mut vec: Vec) -> __Buffer { vec.shrink_to(0); let ptr = vec.as_ptr(); let len = vec.len(); std::mem::forget(vec); - Buffer { ptr, len } + __Buffer { ptr, len } } #[inline(always)] - pub fn leak_to_heap(self) -> *const Buffer { + pub fn leak_to_heap(self) -> *const __Buffer { let boxed = Box::new(self); - let ptr = Box::::into_raw(boxed) as *const Buffer; + let ptr = Box::<__Buffer>::into_raw(boxed) as *const __Buffer; return ptr; } } pub mod prelude { - pub use super::{Buffer, __alloc_buffer}; - #[macro_use] + pub use super::{__Buffer, __alloc_buffer}; pub use plugin_macros::bind; } diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index c8d0f1f93f..3d762c6a98 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, VisPublic, Visibility}; +use syn::{parse_macro_input, ItemFn, Visibility}; #[proc_macro_attribute] pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { @@ -20,25 +20,23 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let outer_fn_name = format_ident!("__{}", inner_fn_name); TokenStream::from(quote! { - use serde; - #[no_mangle] #inner_fn #[no_mangle] - pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const Buffer { + pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { // setup - let buffer = Buffer { ptr, len }; + let buffer = ::plugin::__Buffer { ptr, len }; let data = unsafe { buffer.to_vec() }; // operation - let argument = bincode::deserialize(&data).unwrap(); + let argument = ::bincode::deserialize(&data).unwrap(); let result = #inner_fn_name(argument); - let new_data: Result, _> = bincode::serialize(&result); + let new_data: Result, _> = ::bincode::serialize(&result); let new_data = new_data.unwrap(); // teardown - let new_buffer = unsafe { Buffer::from_vec(new_data) }; + let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }; return new_buffer.leak_to_heap(); } }) From 7dd3114a7a443a123b7e154344472440a18413f6 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 3 Jun 2022 14:42:50 +0200 Subject: [PATCH 13/91] Start switching JSON LSP adapter to plugin (take 2) --- .gitignore | 1 + Cargo.lock | 26 +++-- crates/language/src/language.rs | 6 +- crates/lsp/src/lsp.rs | 4 +- crates/plugin/src/lib.rs | 3 + crates/plugin_macros/src/lib.rs | 4 +- crates/plugin_runtime/Cargo.toml | 2 +- crates/plugin_runtime/build.rs | 1 - crates/plugin_runtime/src/lua.rs | 22 ++-- crates/plugin_runtime/src/main.rs | 10 +- crates/plugin_runtime/src/runtime.rs | 88 +++++++-------- crates/plugin_runtime/src/wasm.rs | 28 ++--- crates/zed/Cargo.toml | 2 + crates/zed/src/languages.rs | 5 +- crates/zed/src/languages/json.rs | 4 +- crates/zed/src/languages/language_plugin.rs | 116 ++++++++++++++++++++ crates/zed/src/languages/typescript.rs | 7 +- plugins/Cargo.lock | 80 ++++++++++++++ plugins/Cargo.toml | 2 + plugins/json_language/Cargo.toml | 10 ++ plugins/json_language/src/lib.rs | 6 + script/build-plugins | 45 ++++++++ 22 files changed, 372 insertions(+), 100 deletions(-) create mode 100644 crates/zed/src/languages/language_plugin.rs create mode 100644 plugins/Cargo.lock create mode 100644 plugins/Cargo.toml create mode 100644 plugins/json_language/Cargo.toml create mode 100644 plugins/json_language/src/lib.rs create mode 100755 script/build-plugins diff --git a/.gitignore b/.gitignore index 1372cd1c37..d3d0634a40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/target /zed.xcworkspace .DS_Store +/plugins/bin /script/node_modules /styles/node_modules /crates/collab/.env.toml diff --git a/Cargo.lock b/Cargo.lock index b9dd80470a..b774889ee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3629,6 +3629,18 @@ dependencies = [ "syn", ] +[[package]] +name = "plugin_runtime" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "map-macro", + "mlua", + "serde", + "wasmtime", +] + [[package]] name = "png" version = "0.16.8" @@ -4284,18 +4296,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "runner" -version = "0.1.0" -dependencies = [ - "anyhow", - "bincode", - "map-macro", - "mlua", - "serde", - "wasmtime", -] - [[package]] name = "rust-embed" version = "6.4.0" @@ -6786,6 +6786,8 @@ dependencies = [ "num_cpus", "outline", "parking_lot 0.11.2", + "plugin", + "plugin_runtime", "postage", "project", "project_panel", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2bdafddb1b..23758c07ca 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -88,8 +88,8 @@ pub trait LspAdapter: 'static + Send + Sync { None } - fn server_args(&self) -> &[&str] { - &[] + fn server_args(&self) -> Vec { + Vec::new() } fn initialization_options(&self) -> Option { @@ -366,7 +366,7 @@ impl LanguageRegistry { let server = lsp::LanguageServer::new( server_id, &server_binary_path, - server_args, + &server_args, &root_path, cx, )?; diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 8282f1c8ff..bb6562629f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -101,10 +101,10 @@ struct Error { } impl LanguageServer { - pub fn new( + pub fn new>( server_id: usize, binary_path: &Path, - args: &[&str], + args: &[T], root_path: &Path, cx: AsyncAppContext, ) -> Result { diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs index 42ed97c0d4..3c7a09b415 100644 --- a/crates/plugin/src/lib.rs +++ b/crates/plugin/src/lib.rs @@ -1,3 +1,6 @@ +pub use bincode; +pub use serde; + #[repr(C)] pub struct __Buffer { pub ptr: *const u8, diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 3d762c6a98..75c78182e4 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -30,9 +30,9 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let data = unsafe { buffer.to_vec() }; // operation - let argument = ::bincode::deserialize(&data).unwrap(); + let argument = ::plugin::bincode::deserialize(&data).unwrap(); let result = #inner_fn_name(argument); - let new_data: Result, _> = ::bincode::serialize(&result); + let new_data: Result, _> = ::plugin::bincode::serialize(&result); let new_data = new_data.unwrap(); // teardown diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index 49b95360a8..5637610070 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "runner" +name = "plugin_runtime" version = "0.1.0" edition = "2021" diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 00a8297f79..4748d2d23a 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -1,4 +1,3 @@ -use std::env; use std::process::Command; fn main() { diff --git a/crates/plugin_runtime/src/lua.rs b/crates/plugin_runtime/src/lua.rs index 1c20580d95..f2324c7ee4 100644 --- a/crates/plugin_runtime/src/lua.rs +++ b/crates/plugin_runtime/src/lua.rs @@ -17,23 +17,23 @@ impl Runtime for Lua { return Ok(lua); } - fn constant(&mut self, handle: &Handle) -> Result { - let val: Value = self.globals().get(handle.inner())?; - Ok(self.from_value(val)?) - } + // fn constant(&mut self, handle: &Handle) -> Result { + // let val: Value = self.globals().get(handle.inner())?; + // Ok(self.from_value(val)?) + // } - fn call(&mut self, handle: &Handle, arg: A) -> Result { - let fun: Function = self.globals().get(handle.inner())?; + fn call(&mut self, handle: &str, arg: A) -> Result { + let fun: Function = self.globals().get(handle.to_string())?; let arg: Value = self.to_value(&arg)?; let result = fun.call(arg)?; Ok(self.from_value(result)?) } - fn register_handle>(&mut self, name: T) -> bool { - self.globals() - .contains_key(name.as_ref().to_string()) - .unwrap_or(false) - } + // fn register_handle>(&mut self, name: T) -> bool { + // self.globals() + // .contains_key(name.as_ref().to_string()) + // .unwrap_or(false) + // } } pub struct LuaPlugin { diff --git a/crates/plugin_runtime/src/main.rs b/crates/plugin_runtime/src/main.rs index 64f869370b..002e4cb08e 100644 --- a/crates/plugin_runtime/src/main.rs +++ b/crates/plugin_runtime/src/main.rs @@ -1,6 +1,6 @@ use mlua::Lua; -use runner::*; +use plugin_runtime::*; pub fn main() -> anyhow::Result<()> { let plugin = WasmPlugin { @@ -12,7 +12,10 @@ pub fn main() -> anyhow::Result<()> { }; let mut sum = Wasm::init(plugin)?; - let strings = "I hope you have a nice day".split(" ").iter().collect(); + let strings = "I hope you have a nice day" + .split(" ") + .map(|x| x.to_string()) + .collect(); let result = sum.sum_lengths(strings); dbg!(result); @@ -45,8 +48,7 @@ trait SumLengths { impl SumLengths for T { fn sum_lengths(&mut self, strings: Vec) -> usize { - let handle = self.handle_for("sum_lengths").unwrap(); - let result = self.call(&handle, strings).ok().unwrap(); + let result = self.call("sum_lengths", strings).ok().unwrap(); return result; } } diff --git a/crates/plugin_runtime/src/runtime.rs b/crates/plugin_runtime/src/runtime.rs index 44b118024f..89305a309f 100644 --- a/crates/plugin_runtime/src/runtime.rs +++ b/crates/plugin_runtime/src/runtime.rs @@ -2,26 +2,26 @@ use serde::{de::DeserializeOwned, Serialize}; -/// Represents a handle to a constant or function in the Runtime. -/// Should be constructed by calling [`Runtime::handle_for`]. -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct Handle(String); +// /// Represents a handle to a constant or function in the Runtime. +// /// Should be constructed by calling [`Runtime::handle_for`]. +// #[derive(Debug, Clone, Hash, PartialEq, Eq)] +// pub struct Handle(String); -impl Handle { - pub fn inner(&self) -> &str { - &self.0 - } -} +// impl Handle { +// pub fn inner(&self) -> &str { +// &self.0 +// } +// } -/// Represents an interface that can be implemented by a plugin. -pub trait Interface -where - Self: Sized, -{ - /// Create an interface from a given runtime. - /// All handles to be used by the interface should be registered and stored in `Self`. - fn from_runtime(runtime: &mut T) -> Option; -} +// /// Represents an interface that can be implemented by a plugin. +// pub trait Interface +// where +// Self: Sized, +// { +// /// Create an interface from a given runtime. +// /// All handles to be used by the interface should be registered and stored in `Self`. +// fn from_runtime(runtime: &mut T) -> Option; +// } pub trait Runtime where @@ -39,39 +39,39 @@ where /// Note that if you have any configuration, fn init(plugin: Self::Plugin) -> Result; - /// Returns a top-level constant from the module. - /// This can be used to extract configuration information from the module, for example. - /// Before calling this function, get a handle into the runtime using [`handle_for`]. - fn constant(&mut self, handle: &Handle) -> Result; + // /// Returns a top-level constant from the module. + // /// This can be used to extract configuration information from the module, for example. + // /// Before calling this function, get a handle into the runtime using [`handle_for`]. + // fn constant(&mut self, handle: &Handle) -> Result; /// Call a function defined in the module. fn call( &mut self, - handle: &Handle, + handle: &str, arg: A, ) -> Result; - /// Registers a handle with the runtime. - /// This is a mutable item if needed, but generally - /// this should be an immutable operation. - /// Returns whether the handle exists/was successfully registered. - fn register_handle>(&mut self, name: T) -> bool; + // /// Registers a handle with the runtime. + // /// This is a mutable item if needed, but generally + // /// this should be an immutable operation. + // /// Returns whether the handle exists/was successfully registered. + // fn register_handle>(&mut self, name: T) -> bool; - /// Returns the handle for a given name if the handle is defined. - /// Will only return an error if there was an error while trying to register the handle. - /// This function uses [`register_handle`], no need to implement this one. - fn handle_for>(&mut self, name: T) -> Option { - if self.register_handle(&name) { - Some(Handle(name.as_ref().to_string())) - } else { - None - } - } + // /// Returns the handle for a given name if the handle is defined. + // /// Will only return an error if there was an error while trying to register the handle. + // /// This function uses [`register_handle`], no need to implement this one. + // fn handle_for>(&mut self, name: T) -> Option { + // if self.register_handle(&name) { + // Some(Handle(name.as_ref().to_string())) + // } else { + // None + // } + // } - /// Creates the given interface from the current module. - /// Returns [`Error`] if the provided plugin does not match the expected interface. - /// Essentially wraps the [`Interface`] trait. - fn as_interface(&mut self) -> Option { - Interface::from_runtime(self) - } + // /// Creates the given interface from the current module. + // /// Returns [`Error`] if the provided plugin does not match the expected interface. + // /// Essentially wraps the [`Interface`] trait. + // fn as_interface(&mut self) -> Option { + // Interface::from_runtime(self) + // } } diff --git a/crates/plugin_runtime/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs index b0014ac27a..dc5541d113 100644 --- a/crates/plugin_runtime/src/wasm.rs +++ b/crates/plugin_runtime/src/wasm.rs @@ -62,14 +62,14 @@ impl Runtime for Wasm { }) } - fn constant(&mut self, handle: &Handle) -> Result { - let export = self - .instance - .get_export(&mut self.store, handle.inner()) - .ok_or_else(|| anyhow!("Could not get export"))?; + // fn constant(&mut self, handle: &Handle) -> Result { + // let export = self + // .instance + // .get_export(&mut self.store, handle.inner()) + // .ok_or_else(|| anyhow!("Could not get export"))?; - todo!() - } + // todo!() + // } // So this call function is kinda a dance, I figured it'd be a good idea to document it. // the high level is we take a serde type, serialize it to a byte array, @@ -117,7 +117,7 @@ impl Runtime for Wasm { // TODO: dont' use as for conversions fn call( &mut self, - handle: &Handle, + handle: &str, arg: A, ) -> Result { // serialize the argument using bincode @@ -136,7 +136,7 @@ impl Runtime for Wasm { // get the webassembly function we want to actually call // TODO: precompute handle - let fun_name = format!("__{}", handle.inner()); + let fun_name = format!("__{}", handle); let fun = self .instance .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; @@ -170,9 +170,9 @@ impl Runtime for Wasm { return Ok(result); } - fn register_handle>(&mut self, name: T) -> bool { - self.instance - .get_export(&mut self.store, name.as_ref()) - .is_some() - } + // fn register_handle>(&mut self, name: T) -> bool { + // self.instance + // .get_export(&mut self.store, name.as_ref()) + // .is_some() + // } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 88acfdb14d..433c436003 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -39,6 +39,8 @@ journal = { path = "../journal" } language = { path = "../language" } lsp = { path = "../lsp" } outline = { path = "../outline" } +plugin = { path = "../plugin" } +plugin_runtime = { path = "../plugin_runtime" } project = { path = "../project" } project_panel = { path = "../project_panel" } project_symbols = { path = "../project_symbols" } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index d792660d20..ac9c44d68a 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -6,10 +6,11 @@ use std::{borrow::Cow, str, sync::Arc}; mod c; mod go; mod installation; -mod json; mod python; +mod language_plugin; mod rust; mod typescript; +// mod json; #[derive(RustEmbed)] #[folder = "src/languages"] @@ -37,7 +38,7 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi ( "json", tree_sitter_json::language(), - Some(Arc::new(json::JsonLspAdapter)), + Some(Arc::new(language_plugin::new_json())), ), ( "markdown", diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 28a130d080..7c500da682 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -24,8 +24,8 @@ impl LspAdapter for JsonLspAdapter { LanguageServerName("vscode-json-languageserver".into()) } - fn server_args(&self) -> &[&str] { - &["--stdio"] + fn server_args(&self) -> Vec { + vec!["--stdio".into()] } fn fetch_latest_server_version( diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs new file mode 100644 index 0000000000..c5e521dcb7 --- /dev/null +++ b/crates/zed/src/languages/language_plugin.rs @@ -0,0 +1,116 @@ +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 language::{LanguageServerName, LspAdapter}; +use parking_lot::{Mutex, RwLock}; +use plugin_runtime::{Runtime, Wasm, WasmPlugin}; +use serde_json::json; +use smol::fs; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::{ResultExt, TryFutureExt}; + +pub fn new_json() {} + +pub struct LanguagePluginLspAdapter { + runtime: Mutex>, +} + +impl LanguagePluginLspAdapter { + pub fn new(plugin: WasmPlugin<()>) -> Self { + Self { + runtime: Mutex::new(Wasm::init(plugin).unwrap()), + } + } +} + +struct Versions { + language_version: String, + server_version: String, +} + +impl LspAdapter for LanguagePluginLspAdapter { + fn name(&self) -> LanguageServerName { + let name: String = self.runtime.lock().call("name", ()).unwrap(); + LanguageServerName(name.into()) + } + + fn server_args<'a>(&'a self) -> Vec { + self.runtime.lock().call("args", ()).unwrap() + } + + fn fetch_latest_server_version( + &self, + _: Arc, + ) -> BoxFuture<'static, Result>> { + let versions: Result<(String, String)> = + self.runtime.lock().call("fetch_latest_server_version", ()); + + async move { + if let Ok((language_version, server_version)) = versions { + Ok(Box::new(Versions { + language_version, + server_version, + }) as Box<_>) + } else { + panic!() + } + } + .boxed() + } + + fn fetch_server_binary( + &self, + versions: Box, + _: Arc, + container_dir: PathBuf, + ) -> BoxFuture<'static, Result> { + // TODO: async runtime + let result = self + .runtime + .lock() + .call("fetch_server_binary", container_dir); + 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() + } + + fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + + fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &language::Language, + ) -> Option { + use lsp::CompletionItemKind as Kind; + let len = item.label.len(); + let grammar = language.grammar()?; + let kind = format!("{:?}", item.kind?); + let name: String = self + .runtime + .lock() + .call("label_for_completion", kind) + .ok()?; + let highlight_id = grammar.highlight_id_for_name(&name)?; + Some(language::CodeLabel { + text: item.label.clone(), + runs: vec![(0..len, highlight_id)], + filter_range: 0..len, + }) + } + + fn initialization_options(&self) -> Option { + let result = self + .runtime + .lock() + .call("initialization_options", ()) + .unwrap(); + Some(result) + } +} diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 7b436ad79f..cd5309761d 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -28,8 +28,11 @@ impl LspAdapter for TypeScriptLspAdapter { LanguageServerName("typescript-language-server".into()) } - fn server_args(&self) -> &[&str] { - &["--stdio", "--tsserver-path", "node_modules/typescript/lib"] + fn server_args(&self) -> Vec { + ["--stdio", "--tsserver-path", "node_modules/typescript/lib"] + .into_iter() + .map(str::to_string) + .collect() } fn fetch_latest_server_version( diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock new file mode 100644 index 0000000000..e0a9bd2439 --- /dev/null +++ b/plugins/Cargo.lock @@ -0,0 +1,80 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "json_language" +version = "0.1.0" +dependencies = [ + "plugin", +] + +[[package]] +name = "plugin" +version = "0.1.0" +dependencies = [ + "bincode", + "plugin_macros", + "serde", +] + +[[package]] +name = "plugin_macros" +version = "0.1.0" +dependencies = [ + "bincode", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml new file mode 100644 index 0000000000..7fcc42a745 --- /dev/null +++ b/plugins/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["./json_language"] diff --git a/plugins/json_language/Cargo.toml b/plugins/json_language/Cargo.toml new file mode 100644 index 0000000000..ccb2511960 --- /dev/null +++ b/plugins/json_language/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "json_language" +version = "0.1.0" +edition = "2021" + +[dependencies] +plugin = { path = "../../crates/plugin" } + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs new file mode 100644 index 0000000000..22b55633cf --- /dev/null +++ b/plugins/json_language/src/lib.rs @@ -0,0 +1,6 @@ +use plugin::prelude::*; + +#[bind] +pub fn add(a: (f64, f64)) -> f64 { + a.0 + a.1 +} diff --git a/script/build-plugins b/script/build-plugins new file mode 100755 index 0000000000..ee5baa93c3 --- /dev/null +++ b/script/build-plugins @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e + +echo "Clearing cached plugins..." +cargo clean --manifest-path plugins/Cargo.toml + +echo "Building Wasm plugins..." +cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml + +echo +echo "Extracting binaries..." +rm -rf plugins/bin +mkdir plugins/bin + +for f in plugins/target/wasm32-unknown-unknown/release/*.wasm +do + name=$(basename $f) + cp $f plugins/bin/$name + echo "- Extracted plugin $name" +done + +echo +echo "Creating .wat versions (for human inspection)..." + +for f in plugins/bin/*.wasm +do + name=$(basename $f) + base=$(echo $name | sed "s/\..*//") + wasm2wat $f --output plugins/bin/$base.wat + echo "- Converted $base.wasm -> $base.wat" +done + +echo +echo "Optimizing plugins using wasm-opt..." + +for f in plugins/bin/*.wasm +do + name=$(basename $f) + wasm-opt -Oz $f --output $f + echo "- Optimized $name" +done + +echo +echo "Done!" \ No newline at end of file From b84948711c731b5a4d219914df22b8cd567b7b49 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 3 Jun 2022 15:03:30 +0200 Subject: [PATCH 14/91] Start moving code from Zed to plugin --- crates/zed/src/languages/language_plugin.rs | 18 +++++++++++------- plugins/json_language/src/lib.rs | 9 +++++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index c5e521dcb7..0a9cb5592b 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -10,7 +10,13 @@ use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; -pub fn new_json() {} +pub fn new_json() -> LanguagePluginLspAdapter { + let plugin = WasmPlugin { + source_bytes: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), + store_data: (), + }; + LanguagePluginLspAdapter::new(plugin) +} pub struct LanguagePluginLspAdapter { runtime: Mutex>, @@ -47,14 +53,12 @@ impl LspAdapter for LanguagePluginLspAdapter { self.runtime.lock().call("fetch_latest_server_version", ()); async move { - if let Ok((language_version, server_version)) = versions { - Ok(Box::new(Versions { + versions.map(|(language_version, server_version)| { + Box::new(Versions { language_version, server_version, - }) as Box<_>) - } else { - panic!() - } + }) as Box<_> + }) } .boxed() } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 22b55633cf..f8796907be 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,6 +1,11 @@ use plugin::prelude::*; #[bind] -pub fn add(a: (f64, f64)) -> f64 { - a.0 + a.1 +pub fn name(_: ()) -> &'static str { + "vscode-json-languageserver" +} + +#[bind] +pub fn server_args(_: ()) -> Vec { + vec!["--stdio".into()] } From 17d15b2f08b01956cc0af108271e707d4d1acde0 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 6 Jun 2022 08:40:56 +0200 Subject: [PATCH 15/91] Get Wasi working --- Cargo.lock | 287 +++++++++++++++++++- crates/plugin_runtime/Cargo.toml | 1 + crates/plugin_runtime/src/lib.rs | 3 + crates/plugin_runtime/src/main.rs | 1 - crates/plugin_runtime/src/wasi.rs | 194 +++++++++++++ crates/plugin_runtime/src/wasm.rs | 17 +- crates/zed/src/languages/language_plugin.rs | 11 +- plugins/json_language/src/lib.rs | 99 +++++++ script/build-plugins | 5 +- 9 files changed, 597 insertions(+), 21 deletions(-) create mode 100644 crates/plugin_runtime/src/wasi.rs diff --git a/Cargo.lock b/Cargo.lock index b774889ee2..35e88625f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ambient-authority" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" + [[package]] name = "ansi_term" version = "0.12.1" @@ -376,7 +382,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -674,6 +680,72 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "cap-fs-ext" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54b86398b5852ddd45784b1d9b196b98beb39171821bad4b8b44534a1e87927" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "winapi 0.3.9", +] + +[[package]] +name = "cap-primitives" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8fca3e81fae1d91a36e9784ca22a39ef623702b5f7904d89dc31f10184a178" +dependencies = [ + "ambient-authority", + "errno", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix", + "winapi 0.3.9", + "winapi-util", + "winx", +] + +[[package]] +name = "cap-rand" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3b27294116983d706f4c8168f6d10c84f9f5daed0c28bc7d0296cf16bcf971" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2247568946095c7765ad2b441a56caffc08027734c634a6d5edda648f04e32eb" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "ipnet", + "rustix", +] + +[[package]] +name = "cap-time-ext" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50472b6ebc302af0401fa3fb939694cd8ff00e0d4c9182001e434fc822ab83a" +dependencies = [ + "cap-primitives", + "once_cell", + "rustix", + "winx", +] + [[package]] name = "castaway" version = "0.1.2" @@ -1826,6 +1898,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fs-set-times" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df62ee66ee2d532ea8d567b5a3f0d03ecd64636b98bad5be1e93dcc918b92aa" +dependencies = [ + "io-lifetimes", + "rustix", + "winapi 0.3.9", +] + [[package]] name = "fsevent" version = "2.0.2" @@ -2244,6 +2327,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37fb7dc756218a0559bfc21e4381f03cbb696cdaf959e7e95e927496f0564cd" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -2456,11 +2548,25 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "io-extras" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c937cc9891c12eaa8c63ad347e4a288364b1328b924886970b47a14ab8f8f8" +dependencies = [ + "io-lifetimes", + "winapi 0.3.9", +] + [[package]] name = "io-lifetimes" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" +dependencies = [ + "libc", + "winapi 0.3.9", +] [[package]] name = "iovec" @@ -2496,6 +2602,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "is-terminal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c89a757e762896bdbdfadf2860d0f8b0cea5e363d8cf3e7bdfeb63d1d976352" +dependencies = [ + "hermit-abi 0.2.3", + "io-lifetimes", + "rustix", + "winapi 0.3.9", +] + [[package]] name = "isahc" version = "1.7.2" @@ -2881,6 +2999,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3246,7 +3370,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -3639,6 +3763,7 @@ dependencies = [ "mlua", "serde", "wasmtime", + "wasmtime-wasi", ] [[package]] @@ -4361,8 +4486,10 @@ dependencies = [ "bitflags", "errno", "io-lifetimes", + "itoa", "libc", "linux-raw-sys", + "once_cell", "winapi 0.3.9", ] @@ -4803,6 +4930,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + [[package]] name = "shlex" version = "1.1.0" @@ -5191,6 +5327,22 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-interface" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e09bb3fb4e02ec4b87e182ea9718fadbc0fa3e50085b40a9af9690572b67f9e" +dependencies = [ + "atty", + "bitflags", + "cap-fs-ext", + "cap-std", + "io-lifetimes", + "rustix", + "winapi 0.3.9", + "winx", +] + [[package]] name = "target-lexicon" version = "0.12.4" @@ -6200,6 +6352,48 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi-cap-std-sync" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1029972e08194fe0ca67a83221945fff9d6d1b0dd8b752c6073b45d0254ac71b" +dependencies = [ + "anyhow", + "async-trait", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes", + "is-terminal", + "lazy_static", + "rustix", + "system-interface", + "tracing", + "wasi-common", + "winapi 0.3.9", +] + +[[package]] +name = "wasi-common" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396cc8d920f924474f589f6a2202ad9783579ef12f96e6b675d0b2f3917c7126" +dependencies = [ + "anyhow", + "bitflags", + "cap-rand", + "cap-std", + "io-extras", + "rustix", + "thiserror", + "tracing", + "wiggle", + "winapi 0.3.9", +] + [[package]] name = "wasm-bindgen" version = "0.2.81" @@ -6468,6 +6662,28 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasmtime-wasi" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d507b07263a4923440da662c611affc2e671714bb6e5f68f6b068a5736bcd7f" +dependencies = [ + "anyhow", + "wasi-cap-std-sync", + "wasi-common", + "wasmtime", + "wiggle", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + [[package]] name = "wast" version = "43.0.0" @@ -6486,7 +6702,7 @@ version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" dependencies = [ - "wast", + "wast 43.0.0", ] [[package]] @@ -6573,6 +6789,48 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wiggle" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e618e90d9f3d6d76943d9f2903c609b62f2ab5d16dedcff4816d38db726dd" +dependencies = [ + "anyhow", + "async-trait", + "bitflags", + "thiserror", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534d70579cd9aca243e94eeb14a348dbc9ae87e7758ffebfdd4bb4a98d59b9b0" +dependencies = [ + "anyhow", + "heck 0.4.0", + "proc-macro2", + "quote", + "shellexpand", + "syn", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87216b1f58eeee44a6385de39e8c03190c209942cfa34344c9ba69426f146d8d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", +] + [[package]] name = "winapi" version = "0.2.8" @@ -6668,6 +6926,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "winx" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" +dependencies = [ + "bitflags", + "io-lifetimes", + "winapi 0.3.9", +] + [[package]] name = "wio" version = "0.2.2" @@ -6677,6 +6946,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror", + "wast 35.0.2", +] + [[package]] name = "workspace" version = "0.1.0" diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index 5637610070..c3a99ca007 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -8,5 +8,6 @@ mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] serde = "1.0" map-macro = "0.2" wasmtime = "0.37.0" +wasmtime-wasi = "0.37.0" anyhow = { version = "1.0", features = ["std"] } bincode = "1.3" diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index de6544f6fa..cac99797bf 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -11,3 +11,6 @@ pub use lua::*; pub mod wasm; pub use wasm::*; + +pub mod wasi; +pub use wasi::*; diff --git a/crates/plugin_runtime/src/main.rs b/crates/plugin_runtime/src/main.rs index 002e4cb08e..4819c1420a 100644 --- a/crates/plugin_runtime/src/main.rs +++ b/crates/plugin_runtime/src/main.rs @@ -8,7 +8,6 @@ pub fn main() -> anyhow::Result<()> { "../plugin/target/wasm32-unknown-unknown/release/cargo_test.wasm" ) .to_vec(), - store_data: (), }; let mut sum = Wasm::init(plugin)?; diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs new file mode 100644 index 0000000000..745b6364fa --- /dev/null +++ b/crates/plugin_runtime/src/wasi.rs @@ -0,0 +1,194 @@ +use std::collections::HashMap; + +use anyhow::anyhow; + +use wasmtime::{Engine, Func, Instance, Linker, Memory, MemoryType, Module, Store, TypedFunc}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; + +use crate::*; + +pub struct Wasi { + engine: Engine, + module: Module, + store: Store, + instance: Instance, + alloc_buffer: TypedFunc, + // free_buffer: TypedFunc<(i32, i32), ()>, +} + +pub struct WasiPlugin { + pub source_bytes: Vec, +} + +impl Wasi { + pub fn dump_memory(data: &[u8]) { + for (i, byte) in data.iter().enumerate() { + if i % 32 == 0 { + println!(); + } + if i % 4 == 0 { + print!("|"); + } + if *byte == 0 { + print!("__") + } else { + print!("{:02x}", byte); + } + } + println!(); + } +} + +impl Runtime for Wasi { + type Plugin = WasiPlugin; + type Error = anyhow::Error; + + fn init(plugin: WasiPlugin) -> Result { + let engine = Engine::default(); + let mut linker = Linker::new(&engine); + println!("linking"); + wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + println!("linked"); + let wasi = WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build(); + + let mut store: Store<_> = Store::new(&engine, wasi); + println!("moduling"); + let module = Module::new(&engine, plugin.source_bytes)?; + println!("moduled"); + + 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, + store, + instance, + alloc_buffer, + // free_buffer, + }) + } + + // fn constant(&mut self, handle: &Handle) -> Result { + // let export = self + // .instance + // .get_export(&mut self.store, handle.inner()) + // .ok_or_else(|| anyhow!("Could not get export"))?; + + // todo!() + // } + + // So this call function is kinda a dance, I figured it'd be a good idea to document it. + // the high level is we take a serde type, serialize it to a byte array, + // (we're doing this using bincode for now) + // then toss that byte array into webassembly. + // webassembly grabs that byte array, does some magic, + // and serializes the result into yet another byte array. + // we then grab *that* result byte array and deserialize it into a result. + // + // phew... + // + // now the problem is, webassambly doesn't support buffers. + // only really like i32s, that's it (yeah, it's sad. Not even unsigned!) + // (ok, I'm exaggerating a bit). + // + // the Wasm function that this calls must have a very specific signature: + // + // fn(pointer to byte array: i32, length of byte array: i32) + // -> pointer to ( + // pointer to byte_array: i32, + // length of byte array: i32, + // ): i32 + // + // This pair `(pointer to byte array, length of byte array)` is called a `Buffer` + // and can be found in the cargo_test plugin. + // + // so on the wasm side, we grab the two parameters to the function, + // stuff them into a `Buffer`, + // and then pray to the `unsafe` Rust gods above that a valid byte array pops out. + // + // On the flip side, when returning from a wasm function, + // we convert whatever serialized result we get into byte array, + // which we stuff into a Buffer and allocate on the heap, + // which pointer to we then return. + // Note the double indirection! + // + // So when returning from a function, we actually leak memory *twice*: + // + // 1) once when we leak the byte array + // 2) again when we leak the allocated `Buffer` + // + // This isn't a problem because Wasm stops executing after the function returns, + // so the heap is still valid for our inspection when we want to pull things out. + + // TODO: dont' use as for conversions + fn call( + &mut self, + handle: &str, + arg: A, + ) -> Result { + // serialize the argument using bincode + let arg = bincode::serialize(&arg)?; + let arg_buffer_len = arg.len(); + + // allocate a buffer and write the argument to that buffer + let arg_buffer_ptr = self + .alloc_buffer + .call(&mut self.store, arg_buffer_len as i32)?; + let plugin_memory = self + .instance + .get_memory(&mut self.store, "memory") + .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; + plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; + + // get the webassembly function we want to actually call + // TODO: precompute handle + let fun_name = format!("__{}", handle); + let fun = self + .instance + .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; + + // call the function, passing in the buffer and its length + // this should return a pointer to a (ptr, lentgh) pair + let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); + let result_buffer = fun.call(&mut self.store, arg_buffer)?; + + // create a buffer to read the (ptr, length) pair into + // this is a total of 4 + 4 = 8 bytes. + let buffer = &mut [0; 8]; + plugin_memory.read(&mut self.store, result_buffer as usize, buffer)?; + + // use these bytes (wasm stores things little-endian) + // to get a pointer to the buffer and its length + let b = buffer; + let result_buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; + let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; + let result_buffer_end = result_buffer_ptr + result_buffer_len; + + // read the buffer at this point into a byte array + // deserialize the byte array into the provided serde type + let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; + let result = bincode::deserialize(result)?; + + // TODO: this is handled wasm-side, but I'd like to double-check + // // deallocate the argument buffer + // self.free_buffer.call(&mut self.store, arg_buffer); + + return Ok(result); + } + + // fn register_handle>(&mut self, name: T) -> bool { + // self.instance + // .get_export(&mut self.store, name.as_ref()) + // .is_some() + // } +} diff --git a/crates/plugin_runtime/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs index dc5541d113..34064d975e 100644 --- a/crates/plugin_runtime/src/wasm.rs +++ b/crates/plugin_runtime/src/wasm.rs @@ -6,21 +6,20 @@ use wasmtime::{Engine, Func, Instance, Memory, MemoryType, Module, Store, TypedF use crate::*; -pub struct Wasm { +pub struct Wasm { engine: Engine, module: Module, - store: Store, + store: Store<()>, instance: Instance, alloc_buffer: TypedFunc, // free_buffer: TypedFunc<(i32, i32), ()>, } -pub struct WasmPlugin { +pub struct WasmPlugin { pub source_bytes: Vec, - pub store_data: T, } -impl Wasm { +impl Wasm { pub fn dump_memory(data: &[u8]) { for (i, byte) in data.iter().enumerate() { if i % 32 == 0 { @@ -39,14 +38,14 @@ impl Wasm { } } -impl Runtime for Wasm { - type Plugin = WasmPlugin; +impl Runtime for Wasm { + type Plugin = WasmPlugin; type Error = anyhow::Error; - fn init(plugin: WasmPlugin) -> Result { + fn init(plugin: WasmPlugin) -> Result { let engine = Engine::default(); let module = Module::new(&engine, plugin.source_bytes)?; - let mut store: Store = Store::new(&engine, plugin.store_data); + let mut store: Store<()> = Store::new(&engine, ()); let instance = Instance::new(&mut store, &module, &[])?; let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 0a9cb5592b..9d25954487 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,28 +4,27 @@ use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; use parking_lot::{Mutex, RwLock}; -use plugin_runtime::{Runtime, Wasm, WasmPlugin}; +use plugin_runtime::{Runtime, Wasi, WasiPlugin}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; pub fn new_json() -> LanguagePluginLspAdapter { - let plugin = WasmPlugin { + let plugin = WasiPlugin { source_bytes: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), - store_data: (), }; LanguagePluginLspAdapter::new(plugin) } pub struct LanguagePluginLspAdapter { - runtime: Mutex>, + runtime: Mutex, } impl LanguagePluginLspAdapter { - pub fn new(plugin: WasmPlugin<()>) -> Self { + pub fn new(plugin: WasiPlugin) -> Self { Self { - runtime: Mutex::new(Wasm::init(plugin).unwrap()), + runtime: Mutex::new(Wasi::init(plugin).unwrap()), } } } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index f8796907be..7801b81b70 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,7 +1,17 @@ use plugin::prelude::*; +use std::fs; + +#[import] +fn command(string: String) -> Option; #[bind] pub fn name(_: ()) -> &'static str { + println!("huh, let me see..."); + Command::new("sh") + .arg("-c") + .arg("echo hello") + .output() + .expect("failed to execute process"); "vscode-json-languageserver" } @@ -9,3 +19,92 @@ pub fn name(_: ()) -> &'static str { pub fn server_args(_: ()) -> Vec { vec!["--stdio".into()] } + +#[bind] +fn fetch_latest_server_version() -> Option { + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } + + let output = command("npm info vscode-json-languageserver --json")?; + if !output.status.success() { + return None; + } + + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + info.versions.pop() +} + +#[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); + + 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(); + } + } + } + } + } + + Ok(binary_path) +} + +#[bind] +pub fn cached_server_binary(container_dir: PathBuf) -> Option { + 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 bin_path = last_version_dir.join(Self::BIN_PATH); + if bin_path.exists() { + Ok(bin_path) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } +} + +#[bind] +pub fn initialization_options(_: ()) -> Option { + Some(json!({ + "provideFormatter": true + })) +} + +#[bind] +fn id_for_language(name: String) -> Option { + if name == "JSON" { + Some("jsonc".into()) + } else { + None + } +} diff --git a/script/build-plugins b/script/build-plugins index ee5baa93c3..8417828c1f 100755 --- a/script/build-plugins +++ b/script/build-plugins @@ -6,14 +6,15 @@ echo "Clearing cached plugins..." cargo clean --manifest-path plugins/Cargo.toml echo "Building Wasm plugins..." -cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml +# cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml +cargo build --release --target wasm32-wasi --manifest-path plugins/Cargo.toml echo echo "Extracting binaries..." rm -rf plugins/bin mkdir plugins/bin -for f in plugins/target/wasm32-unknown-unknown/release/*.wasm +for f in plugins/target/wasm32-wasi/release/*.wasm do name=$(basename $f) cp $f plugins/bin/$name From 805c86b781f18e70b4a380d0c66471ff33d517dd Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 6 Jun 2022 09:18:40 +0200 Subject: [PATCH 16/91] Add support for variadic functions --- crates/plugin_macros/src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 75c78182e4..6cc4b6ea08 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -19,6 +19,17 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); let outer_fn_name = format_ident!("__{}", inner_fn_name); + let variadic = inner_fn.sig.inputs.len(); + let mut args = vec![]; + for i in 0..variadic { + let ident = format_ident!("__{}_arg_{}", inner_fn_name, i); + args.push(ident); + } + + let args = quote! { + ( #(args ,) )* + }; + TokenStream::from(quote! { #[no_mangle] #inner_fn @@ -30,8 +41,8 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let data = unsafe { buffer.to_vec() }; // operation - let argument = ::plugin::bincode::deserialize(&data).unwrap(); - let result = #inner_fn_name(argument); + let #args = ::plugin::bincode::deserialize(&data).unwrap(); + let result = #inner_fn_name #args; let new_data: Result, _> = ::plugin::bincode::serialize(&result); let new_data = new_data.unwrap(); From 38d73215115f949f3f983dc0184b8d3a0c26bf8e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 6 Jun 2022 11:00:05 +0200 Subject: [PATCH 17/91] Remove non-WASI code --- Cargo.lock | 44 ----- crates/plugin_macros/src/lib.rs | 25 ++- crates/plugin_runtime/Cargo.toml | 4 +- crates/plugin_runtime/README.md | 48 ++++- crates/plugin_runtime/build.rs | 23 --- crates/plugin_runtime/heck.txt | 0 crates/plugin_runtime/plugin/Cargo.lock | 82 -------- crates/plugin_runtime/plugin/Cargo.toml | 2 - crates/plugin_runtime/plugin/cargo_test.lua | 16 -- .../plugin/cargo_test/Cargo.lock | 7 - .../plugin/cargo_test/Cargo.toml | 9 - .../plugin/cargo_test/src/main.rs | 14 -- crates/plugin_runtime/src/lib.rs | 14 -- crates/plugin_runtime/src/lua.rs | 62 ------ crates/plugin_runtime/src/main.rs | 53 ------ crates/plugin_runtime/src/runtime.rs | 77 -------- crates/plugin_runtime/src/wasi.rs | 62 +++--- crates/plugin_runtime/src/wasm.rs | 177 ------------------ crates/zed/src/languages/language_plugin.rs | 18 +- plugins/Cargo.lock | 25 +++ plugins/json_language/Cargo.toml | 4 +- plugins/json_language/src/lib.rs | 153 +++++++-------- script/build-plugins | 4 +- 23 files changed, 211 insertions(+), 712 deletions(-) delete mode 100644 crates/plugin_runtime/build.rs delete mode 100644 crates/plugin_runtime/heck.txt delete mode 100644 crates/plugin_runtime/plugin/Cargo.lock delete mode 100644 crates/plugin_runtime/plugin/Cargo.toml delete mode 100644 crates/plugin_runtime/plugin/cargo_test.lua delete mode 100644 crates/plugin_runtime/plugin/cargo_test/Cargo.lock delete mode 100644 crates/plugin_runtime/plugin/cargo_test/Cargo.toml delete mode 100644 crates/plugin_runtime/plugin/cargo_test/src/main.rs delete mode 100644 crates/plugin_runtime/src/lua.rs delete mode 100644 crates/plugin_runtime/src/main.rs delete mode 100644 crates/plugin_runtime/src/runtime.rs delete mode 100644 crates/plugin_runtime/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 35e88625f3..776f449779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2936,24 +2936,6 @@ dependencies = [ "url", ] -[[package]] -name = "lua-src" -version = "544.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c" -dependencies = [ - "cc", -] - -[[package]] -name = "luajit-src" -version = "210.4.0+resty124ff8d" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fb2e2c0c7192e18719d321c9a148f7625c4dcbe3df5f4c19e685e4c286f6c" -dependencies = [ - "cc", -] - [[package]] name = "mach" version = "0.3.2" @@ -2972,12 +2954,6 @@ dependencies = [ "libc", ] -[[package]] -name = "map-macro" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308eec36c376312771da349d880afd5f092533499dbe9beb7bcba8c15eabe2c4" - [[package]] name = "matchers" version = "0.1.0" @@ -3202,24 +3178,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "mlua" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82d0b12c7c8d3bdda5933d2aa322c76e4833d822796495f299990ca652bd1bf" -dependencies = [ - "bstr", - "cc", - "erased-serde", - "lua-src", - "luajit-src", - "num-traits", - "once_cell", - "pkg-config", - "rustc-hash", - "serde", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -3759,8 +3717,6 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", - "map-macro", - "mlua", "serde", "wasmtime", "wasmtime-wasi", diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 6cc4b6ea08..19f08a10b8 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -20,14 +20,21 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let outer_fn_name = format_ident!("__{}", inner_fn_name); let variadic = inner_fn.sig.inputs.len(); - let mut args = vec![]; - for i in 0..variadic { - let ident = format_ident!("__{}_arg_{}", inner_fn_name, i); - args.push(ident); - } + let i = (0..variadic).map(syn::Index::from); + let t = (0..variadic).map(|_| quote! { _ }); - let args = quote! { - ( #(args ,) )* + // this is cursed... + let (args, ty) = if variadic != 1 { + ( + quote! { + #( data.#i ),* + }, + quote! { + ( #( #t ),* ) + }, + ) + } else { + (quote! { data }, quote! { _ }) }; TokenStream::from(quote! { @@ -41,8 +48,8 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let data = unsafe { buffer.to_vec() }; // operation - let #args = ::plugin::bincode::deserialize(&data).unwrap(); - let result = #inner_fn_name #args; + let data: #ty = ::plugin::bincode::deserialize(&data).unwrap(); + 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 c3a99ca007..f37147c00b 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] } -serde = "1.0" -map-macro = "0.2" wasmtime = "0.37.0" wasmtime-wasi = "0.37.0" anyhow = { version = "1.0", features = ["std"] } +serde = "1.0" bincode = "1.3" diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 1ee7de139b..4e6693db4c 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -1,6 +1,48 @@ # Zed's Plugin Runner -This crate contains a fairly generic interface through which plugins may be added to extend the editor. Currently the intention of this plugin runtime is language server definitions. +Wasm plugins can be run through `wasmtime`, with supported for sandboxed system integration through WASI. There are three `plugin` crates that implement different things: -Anything that implements the `Runtime` trait may be used as a plugin. Plugin interfaces are declared by implementing the `Interface` trait. +1. `plugin_runtime` loads and runs compiled `Wasm` plugins, and handles setting up system bindings. -Wasm plugins can be run through `wasmtime`. We plan to add wasi support eventually. We also plan to add macros to generate bindings between Rust plugins compiled to Wasm and the host runtime. \ No newline at end of file +2. `plugin` is the crate that Rust Wasm plugins should depend on. It re-exports some required crates (e.g. `serde`, `bincode`) and provides some necessary macros for generating bindings that `plugin_runtime` can hook into. + +3. `plugin_macros` implements the proc macros required by `plugin`, like the `#[bind]` attribute macro. + +## ABI +The interface between the host Rust runtime ('Runtime') and plugins implemented in Wasm ('Plugin') is pretty simple. + +`Buffer` is a pair of two 4-byte (`u32`) fields: + +``` +struct Buffer { + ptr: u32, + len: u32, +} +``` + +All functions that Plugin exports must have the following properties: + +- Have the signature `fn(ptr: u32, len: u32) -> u32`, where the return type is a pointer to a `Buffer`, and the arguments are the component parts of a `Buffer`. + + - The input `Buffer` will contain the input arguments serialized to `bincode`. + - The output `Buffer` will contain the output arguments serialized to `bincode`. + +- Have a name starting with two underscores. + +Additionally, Plugin must export an: + +- `__alloc_buffer` function that, given a `u32` length, returns a `u32` pointer to a buffer of that length. + +Note that all of these requirements are automatically fullfilled for any Rust Wasm plugin that uses the `plugin` crate, and imports the `prelude`. + +Here's an example Rust Wasm plugin that doubles the value of every float in a `Vec` passed into it: + +```rust +use plugin::prelude::*; + +#[bind] +pub fn double(mut x: Vec) -> Vec { + x.into_iter().map(|x| x * 2.0).collect() +} +``` + +All the serialization code is automatically generated by `#[bind]`. \ No newline at end of file diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs deleted file mode 100644 index 4748d2d23a..0000000000 --- a/crates/plugin_runtime/build.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::process::Command; - -fn main() { - let cwd = std::env::current_dir().unwrap(); - let plugin_workspace = cwd.join("plugin").join("Cargo.toml"); - Command::new("cargo") - .args(&["clean", "--manifest-path"]) - .arg(&plugin_workspace) - .status() - .unwrap(); - Command::new("cargo") - .args(&[ - "build", - "--release", - "--target", - "wasm32-unknown-unknown", - "--manifest-path", - ]) - .arg(&plugin_workspace) - .status() - .unwrap(); - println!("cargo:warning=recompiling plugins") -} diff --git a/crates/plugin_runtime/heck.txt b/crates/plugin_runtime/heck.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/plugin_runtime/plugin/Cargo.lock b/crates/plugin_runtime/plugin/Cargo.lock deleted file mode 100644 index e40b59046a..0000000000 --- a/crates/plugin_runtime/plugin/Cargo.lock +++ /dev/null @@ -1,82 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_test" -version = "0.1.0" -dependencies = [ - "bincode", - "plugin", - "serde", -] - -[[package]] -name = "plugin" -version = "0.1.0" -dependencies = [ - "bincode", - "plugin_macros", - "serde", -] - -[[package]] -name = "plugin_macros" -version = "0.1.0" -dependencies = [ - "bincode", - "proc-macro2", - "quote", - "serde", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" - -[[package]] -name = "syn" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" diff --git a/crates/plugin_runtime/plugin/Cargo.toml b/crates/plugin_runtime/plugin/Cargo.toml deleted file mode 100644 index 83bca541cf..0000000000 --- a/crates/plugin_runtime/plugin/Cargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[workspace] -members = ["cargo_test"] \ No newline at end of file diff --git a/crates/plugin_runtime/plugin/cargo_test.lua b/crates/plugin_runtime/plugin/cargo_test.lua deleted file mode 100644 index bdae0401b5..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test.lua +++ /dev/null @@ -1,16 +0,0 @@ -query = [[( - (attribute_item - (meta_item - (identifier) @test)) @attribute - . - (function_item - name: (identifier) @name) @funciton -)]] - -function run_test(name) - local command = 'cargo test -- ' .. name - local openPop = assert(io.popen(command, 'r')) - local output = openPop:read('*all') - openPop:close() - return output -end diff --git a/crates/plugin_runtime/plugin/cargo_test/Cargo.lock b/crates/plugin_runtime/plugin/cargo_test/Cargo.lock deleted file mode 100644 index db8b4f0b9a..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "cargo_test" -version = "0.1.0" diff --git a/crates/plugin_runtime/plugin/cargo_test/Cargo.toml b/crates/plugin_runtime/plugin/cargo_test/Cargo.toml deleted file mode 100644 index 9a94cad78d..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "cargo_test" -version = "0.1.0" -edition = "2021" - -[dependencies] -serde = "1.0" -bincode = "1.3" -plugin = { path = "../../../plugin" } diff --git a/crates/plugin_runtime/plugin/cargo_test/src/main.rs b/crates/plugin_runtime/plugin/cargo_test/src/main.rs deleted file mode 100644 index 65abd38399..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test/src/main.rs +++ /dev/null @@ -1,14 +0,0 @@ -use plugin::prelude::*; - -#[bind] -pub fn sum_lengths(strings: Vec) -> usize { - let mut total = 0; - for string in strings { - total += string.len(); - } - return total; -} - -pub fn main() -> () { - () -} diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index cac99797bf..460e824da3 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -1,16 +1,2 @@ -use mlua::{Function, Lua, LuaSerdeExt, Value}; -use serde::{de::DeserializeOwned, Serialize}; - -pub use map_macro::{map, set}; - -pub mod runtime; -pub use runtime::*; - -pub mod lua; -pub use lua::*; - -pub mod wasm; -pub use wasm::*; - pub mod wasi; pub use wasi::*; diff --git a/crates/plugin_runtime/src/lua.rs b/crates/plugin_runtime/src/lua.rs deleted file mode 100644 index f2324c7ee4..0000000000 --- a/crates/plugin_runtime/src/lua.rs +++ /dev/null @@ -1,62 +0,0 @@ -use mlua::Result; - -use crate::*; - -impl Runtime for Lua { - type Plugin = LuaPlugin; - type Error = mlua::Error; - - fn init(module: Self::Plugin) -> Result { - let lua = Lua::new(); - - // for action in module.actions { - // action(&mut lua).ok()?; - // } - - lua.load(&module.source).exec()?; - return Ok(lua); - } - - // fn constant(&mut self, handle: &Handle) -> Result { - // let val: Value = self.globals().get(handle.inner())?; - // Ok(self.from_value(val)?) - // } - - fn call(&mut self, handle: &str, arg: A) -> Result { - let fun: Function = self.globals().get(handle.to_string())?; - let arg: Value = self.to_value(&arg)?; - let result = fun.call(arg)?; - Ok(self.from_value(result)?) - } - - // fn register_handle>(&mut self, name: T) -> bool { - // self.globals() - // .contains_key(name.as_ref().to_string()) - // .unwrap_or(false) - // } -} - -pub struct LuaPlugin { - // name: String, - source: String, - // actions: Vec Result<(), ()>>>, -} - -impl LuaPlugin { - pub fn new( - // name: String, - source: String, - ) -> LuaPlugin { - LuaPlugin { - // name, - source, - // actions: Vec::new(), - } - } - - // pub fn setup(mut self, action: fn(&mut Lua) -> Result<(), ()>) -> LuaPlugin { - // let action = Box::new(action); - // self.actions.push(action); - // self - // } -} diff --git a/crates/plugin_runtime/src/main.rs b/crates/plugin_runtime/src/main.rs deleted file mode 100644 index 4819c1420a..0000000000 --- a/crates/plugin_runtime/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -use mlua::Lua; - -use plugin_runtime::*; - -pub fn main() -> anyhow::Result<()> { - let plugin = WasmPlugin { - source_bytes: include_bytes!( - "../plugin/target/wasm32-unknown-unknown/release/cargo_test.wasm" - ) - .to_vec(), - }; - - let mut sum = Wasm::init(plugin)?; - let strings = "I hope you have a nice day" - .split(" ") - .map(|x| x.to_string()) - .collect(); - let result = sum.sum_lengths(strings); - - dbg!(result); - - Ok(()) -} - -// struct SumLengths { -// sum_lengths: Handle, -// } - -// impl Interface for SumLengths { -// fn from_runtime(runtime: &mut T) -> Option { -// Some(SumLengths { -// sum_lengths: runtime.handle_for("sum_lengths")?, -// }) -// } -// } - -// impl SumLengths { -// fn sum_lengths(&self, runtime: &mut T, strings: Vec) -> Option { -// runtime.call(&self.sum_lengths, strings).ok() -// } -// } - -// #[plugin::interface] -trait SumLengths { - fn sum_lengths(&mut self, strings: Vec) -> usize; -} - -impl SumLengths for T { - fn sum_lengths(&mut self, strings: Vec) -> usize { - let result = self.call("sum_lengths", strings).ok().unwrap(); - return result; - } -} diff --git a/crates/plugin_runtime/src/runtime.rs b/crates/plugin_runtime/src/runtime.rs deleted file mode 100644 index 89305a309f..0000000000 --- a/crates/plugin_runtime/src/runtime.rs +++ /dev/null @@ -1,77 +0,0 @@ -// use std::Error; - -use serde::{de::DeserializeOwned, Serialize}; - -// /// Represents a handle to a constant or function in the Runtime. -// /// Should be constructed by calling [`Runtime::handle_for`]. -// #[derive(Debug, Clone, Hash, PartialEq, Eq)] -// pub struct Handle(String); - -// impl Handle { -// pub fn inner(&self) -> &str { -// &self.0 -// } -// } - -// /// Represents an interface that can be implemented by a plugin. -// pub trait Interface -// where -// Self: Sized, -// { -// /// Create an interface from a given runtime. -// /// All handles to be used by the interface should be registered and stored in `Self`. -// fn from_runtime(runtime: &mut T) -> Option; -// } - -pub trait Runtime -where - Self: Sized, -{ - /// Represents a plugin to be loaded by the runtime, - /// e.g. some source code + anything else needed to set up. - type Plugin; - - /// The error type for this module. - /// Ideally should implement the [`std::err::Error`] trait. - type Error; - - /// Initializes a plugin, returning a [`Runtime`] that can be queried. - /// Note that if you have any configuration, - fn init(plugin: Self::Plugin) -> Result; - - // /// Returns a top-level constant from the module. - // /// This can be used to extract configuration information from the module, for example. - // /// Before calling this function, get a handle into the runtime using [`handle_for`]. - // fn constant(&mut self, handle: &Handle) -> Result; - - /// Call a function defined in the module. - fn call( - &mut self, - handle: &str, - arg: A, - ) -> Result; - - // /// Registers a handle with the runtime. - // /// This is a mutable item if needed, but generally - // /// this should be an immutable operation. - // /// Returns whether the handle exists/was successfully registered. - // fn register_handle>(&mut self, name: T) -> bool; - - // /// Returns the handle for a given name if the handle is defined. - // /// Will only return an error if there was an error while trying to register the handle. - // /// This function uses [`register_handle`], no need to implement this one. - // fn handle_for>(&mut self, name: T) -> Option { - // if self.register_handle(&name) { - // Some(Handle(name.as_ref().to_string())) - // } else { - // None - // } - // } - - // /// Creates the given interface from the current module. - // /// Returns [`Error`] if the provided plugin does not match the expected interface. - // /// Essentially wraps the [`Interface`] trait. - // fn as_interface(&mut self) -> Option { - // Interface::from_runtime(self) - // } -} diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 745b6364fa..c2747c498a 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -1,11 +1,10 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fs::File, path::Path}; -use anyhow::anyhow; +use anyhow::{anyhow, Error}; +use serde::{de::DeserializeOwned, Serialize}; use wasmtime::{Engine, Func, Instance, Linker, Memory, MemoryType, Module, Store, TypedFunc}; -use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; - -use crate::*; +use wasmtime_wasi::{dir, Dir, WasiCtx, WasiCtxBuilder}; pub struct Wasi { engine: Engine, @@ -17,7 +16,8 @@ pub struct Wasi { } pub struct WasiPlugin { - pub source_bytes: Vec, + pub module: Vec, + pub wasi_ctx: WasiCtx, } impl Wasi { @@ -39,24 +39,23 @@ impl Wasi { } } -impl Runtime for Wasi { - type Plugin = WasiPlugin; - type Error = anyhow::Error; +impl Wasi { + pub fn default_ctx() -> WasiCtx { + WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build() + } - fn init(plugin: WasiPlugin) -> Result { + pub fn init(plugin: WasiPlugin) -> Result { let engine = Engine::default(); let mut linker = Linker::new(&engine); println!("linking"); wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; println!("linked"); - let wasi = WasiCtxBuilder::new() - .inherit_stdout() - .inherit_stderr() - .build(); - - let mut store: Store<_> = Store::new(&engine, wasi); + let mut store: Store<_> = Store::new(&engine, plugin.wasi_ctx); println!("moduling"); - let module = Module::new(&engine, plugin.source_bytes)?; + let module = Module::new(&engine, plugin.module)?; println!("moduled"); linker.module(&mut store, "", &module)?; @@ -78,13 +77,20 @@ impl Runtime for Wasi { }) } - // fn constant(&mut self, handle: &Handle) -> Result { - // let export = self - // .instance - // .get_export(&mut self.store, handle.inner()) - // .ok_or_else(|| anyhow!("Could not get export"))?; + pub fn attach_file>(&mut self, path: T) -> Result<(), Error> { + let ctx = self.store.data_mut(); + 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)?; + Ok(()) + } - // todo!() + // pub fn remove_file>(&mut self, path: T) -> Result<(), Error> { + // let ctx = self.store.data_mut(); + // ctx.remove + // Ok(()) // } // So this call function is kinda a dance, I figured it'd be a good idea to document it. @@ -131,11 +137,11 @@ impl Runtime for Wasi { // so the heap is still valid for our inspection when we want to pull things out. // TODO: dont' use as for conversions - fn call( + pub fn call( &mut self, handle: &str, arg: A, - ) -> Result { + ) -> Result { // serialize the argument using bincode let arg = bincode::serialize(&arg)?; let arg_buffer_len = arg.len(); @@ -185,10 +191,4 @@ impl Runtime for Wasi { return Ok(result); } - - // fn register_handle>(&mut self, name: T) -> bool { - // self.instance - // .get_export(&mut self.store, name.as_ref()) - // .is_some() - // } } diff --git a/crates/plugin_runtime/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs deleted file mode 100644 index 34064d975e..0000000000 --- a/crates/plugin_runtime/src/wasm.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::collections::HashMap; - -use anyhow::anyhow; - -use wasmtime::{Engine, Func, Instance, Memory, MemoryType, Module, Store, TypedFunc}; - -use crate::*; - -pub struct Wasm { - engine: Engine, - module: Module, - store: Store<()>, - instance: Instance, - alloc_buffer: TypedFunc, - // free_buffer: TypedFunc<(i32, i32), ()>, -} - -pub struct WasmPlugin { - pub source_bytes: Vec, -} - -impl Wasm { - pub fn dump_memory(data: &[u8]) { - for (i, byte) in data.iter().enumerate() { - if i % 32 == 0 { - println!(); - } - if i % 4 == 0 { - print!("|"); - } - if *byte == 0 { - print!("__") - } else { - print!("{:02x}", byte); - } - } - println!(); - } -} - -impl Runtime for Wasm { - type Plugin = WasmPlugin; - type Error = anyhow::Error; - - fn init(plugin: WasmPlugin) -> Result { - let engine = Engine::default(); - let module = Module::new(&engine, plugin.source_bytes)?; - let mut store: Store<()> = Store::new(&engine, ()); - let instance = Instance::new(&mut store, &module, &[])?; - - let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; - // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; - - Ok(Wasm { - engine, - module, - store, - instance, - alloc_buffer, - // free_buffer, - }) - } - - // fn constant(&mut self, handle: &Handle) -> Result { - // let export = self - // .instance - // .get_export(&mut self.store, handle.inner()) - // .ok_or_else(|| anyhow!("Could not get export"))?; - - // todo!() - // } - - // So this call function is kinda a dance, I figured it'd be a good idea to document it. - // the high level is we take a serde type, serialize it to a byte array, - // (we're doing this using bincode for now) - // then toss that byte array into webassembly. - // webassembly grabs that byte array, does some magic, - // and serializes the result into yet another byte array. - // we then grab *that* result byte array and deserialize it into a result. - // - // phew... - // - // now the problem is, webassambly doesn't support buffers. - // only really like i32s, that's it (yeah, it's sad. Not even unsigned!) - // (ok, I'm exaggerating a bit). - // - // the Wasm function that this calls must have a very specific signature: - // - // fn(pointer to byte array: i32, length of byte array: i32) - // -> pointer to ( - // pointer to byte_array: i32, - // length of byte array: i32, - // ): i32 - // - // This pair `(pointer to byte array, length of byte array)` is called a `Buffer` - // and can be found in the cargo_test plugin. - // - // so on the wasm side, we grab the two parameters to the function, - // stuff them into a `Buffer`, - // and then pray to the `unsafe` Rust gods above that a valid byte array pops out. - // - // On the flip side, when returning from a wasm function, - // we convert whatever serialized result we get into byte array, - // which we stuff into a Buffer and allocate on the heap, - // which pointer to we then return. - // Note the double indirection! - // - // So when returning from a function, we actually leak memory *twice*: - // - // 1) once when we leak the byte array - // 2) again when we leak the allocated `Buffer` - // - // This isn't a problem because Wasm stops executing after the function returns, - // so the heap is still valid for our inspection when we want to pull things out. - - // TODO: dont' use as for conversions - fn call( - &mut self, - handle: &str, - arg: A, - ) -> Result { - // serialize the argument using bincode - let arg = bincode::serialize(&arg)?; - let arg_buffer_len = arg.len(); - - // allocate a buffer and write the argument to that buffer - let arg_buffer_ptr = self - .alloc_buffer - .call(&mut self.store, arg_buffer_len as i32)?; - let plugin_memory = self - .instance - .get_memory(&mut self.store, "memory") - .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; - plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; - - // get the webassembly function we want to actually call - // TODO: precompute handle - let fun_name = format!("__{}", handle); - let fun = self - .instance - .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; - - // call the function, passing in the buffer and its length - // this should return a pointer to a (ptr, lentgh) pair - let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); - let result_buffer = fun.call(&mut self.store, arg_buffer)?; - - // create a buffer to read the (ptr, length) pair into - // this is a total of 4 + 4 = 8 bytes. - let buffer = &mut [0; 8]; - plugin_memory.read(&mut self.store, result_buffer as usize, buffer)?; - - // use these bytes (wasm stores things little-endian) - // to get a pointer to the buffer and its length - let b = buffer; - let result_buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; - let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; - let result_buffer_end = result_buffer_ptr + result_buffer_len; - - // read the buffer at this point into a byte array - // deserialize the byte array into the provided serde type - let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; - let result = bincode::deserialize(result)?; - - // TODO: this is handled wasm-side, but I'd like to double-check - // // deallocate the argument buffer - // self.free_buffer.call(&mut self.store, arg_buffer); - - return Ok(result); - } - - // fn register_handle>(&mut self, name: T) -> bool { - // self.instance - // .get_export(&mut self.store, name.as_ref()) - // .is_some() - // } -} diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9d25954487..f4c09c189b 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,15 +4,16 @@ use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; use parking_lot::{Mutex, RwLock}; -use plugin_runtime::{Runtime, Wasi, WasiPlugin}; +use plugin_runtime::{Wasi, WasiPlugin}; use serde_json::json; -use smol::fs; +use std::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; pub fn new_json() -> LanguagePluginLspAdapter { let plugin = WasiPlugin { - source_bytes: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), + module: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), + wasi_ctx: Wasi::default_ctx(), }; LanguagePluginLspAdapter::new(plugin) } @@ -69,10 +70,13 @@ impl LspAdapter for LanguagePluginLspAdapter { container_dir: PathBuf, ) -> BoxFuture<'static, Result> { // TODO: async runtime - let result = self - .runtime - .lock() - .call("fetch_server_binary", container_dir); + 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), + }; + async move { result }.boxed() } diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index e0a9bd2439..a6d421e386 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -11,11 +11,19 @@ dependencies = [ "serde", ] +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + [[package]] name = "json_language" version = "0.1.0" dependencies = [ "plugin", + "serde", + "serde_json", ] [[package]] @@ -56,12 +64,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + [[package]] name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "syn" version = "1.0.96" diff --git a/plugins/json_language/Cargo.toml b/plugins/json_language/Cargo.toml index ccb2511960..33415db798 100644 --- a/plugins/json_language/Cargo.toml +++ b/plugins/json_language/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" [dependencies] plugin = { path = "../../crates/plugin" } +serde = "1.0" +serde_json = "1.0" [lib] -crate-type = ["cdylib"] \ No newline at end of file +crate-type = ["cdylib"] diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 7801b81b70..9687a09e83 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,107 +1,108 @@ use plugin::prelude::*; +use serde_json::json; use std::fs; +use std::path::PathBuf; -#[import] -fn command(string: String) -> Option; +// import +// fn command(string: String) -> Option; #[bind] -pub fn name(_: ()) -> &'static str { - println!("huh, let me see..."); - Command::new("sh") - .arg("-c") - .arg("echo hello") - .output() - .expect("failed to execute process"); +pub fn name() -> &'static str { "vscode-json-languageserver" } #[bind] -pub fn server_args(_: ()) -> Vec { +pub fn server_args() -> Vec { vec!["--stdio".into()] } #[bind] -fn fetch_latest_server_version() -> Option { - #[derive(Deserialize)] - struct NpmInfo { - versions: Vec, - } +pub fn fetch_latest_server_version() -> Option { + // #[derive(Deserialize)] + // struct NpmInfo { + // versions: Vec, + // } - let output = command("npm info vscode-json-languageserver --json")?; - if !output.status.success() { - return None; - } + // let output = command("npm info vscode-json-languageserver --json")?; + // if !output.status.success() { + // return None; + // } - let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; - info.versions.pop() + // let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + // info.versions.pop() + println!("fetching server version"); + 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(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); - 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 { - 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 bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { - Ok(bin_path) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } -} - -#[bind] -pub fn initialization_options(_: ()) -> Option { +pub fn initialization_options() -> Option { Some(json!({ "provideFormatter": true })) } #[bind] -fn id_for_language(name: String) -> Option { +pub fn id_for_language(name: String) -> Option { if name == "JSON" { Some("jsonc".into()) } else { diff --git a/script/build-plugins b/script/build-plugins index 8417828c1f..32f9cedc14 100755 --- a/script/build-plugins +++ b/script/build-plugins @@ -2,8 +2,8 @@ set -e -echo "Clearing cached plugins..." -cargo clean --manifest-path plugins/Cargo.toml +# echo "Clearing cached plugins..." +# cargo clean --manifest-path plugins/Cargo.toml echo "Building Wasm plugins..." # cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml From fbaff615a34e1353572fcc6297e07b8564d21a2e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 6 Jun 2022 16:02:54 +0200 Subject: [PATCH 18/91] Get JSON LSP running, still work to be done --- Cargo.lock | 2 + crates/plugin_macros/src/lib.rs | 26 +++- crates/plugin_runtime/Cargo.toml | 2 + crates/plugin_runtime/src/wasi.rs | 63 ++++++++-- crates/zed/src/languages/language_plugin.rs | 52 +++++--- plugins/json_language/src/lib.rs | 124 +++++++++++--------- plugins/json_language/src/sketch.rs | 88 ++++++++++++++ 7 files changed, 269 insertions(+), 88 deletions(-) create mode 100644 plugins/json_language/src/sketch.rs 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 From e9b87f3dc3b546dac506a02fdfa11df8cec6c1b7 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 7 Jun 2022 10:10:03 +0200 Subject: [PATCH 19/91] Factor out buffer code --- crates/plugin_macros/src/lib.rs | 6 +- crates/plugin_runtime/src/wasi.rs | 104 +++++++++++--------- crates/zed/src/languages/language_plugin.rs | 13 ++- plugins/json_language/src/lib.rs | 104 +++++++++----------- 4 files changed, 109 insertions(+), 118 deletions(-) diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index a6437deeb0..eb3cc71cc5 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -61,11 +61,7 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { // operation 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.") - }, + Err(e) => panic!("Data passed to function not deserializable."), }; let result = #inner_fn_name(#args); let new_data: Result, _> = ::plugin::bincode::serialize(&result); diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index bb60ffa16d..f9415c2761 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -14,8 +14,8 @@ pub struct Wasi { module: Module, store: Store, instance: Instance, - alloc_buffer: TypedFunc, - // free_buffer: TypedFunc<(i32, i32), ()>, + alloc_buffer: TypedFunc, + // free_buffer: TypedFunc<(u32, u32), ()>, } pub struct WasiPlugin { @@ -54,27 +54,20 @@ impl Wasi { 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(); + 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"); let module = Module::new(&engine, plugin.module)?; - println!("moduled"); 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, @@ -99,7 +92,6 @@ impl Wasi { // 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(); @@ -172,62 +164,78 @@ impl Wasi { // This isn't a problem because Wasm stops executing after the function returns, // so the heap is still valid for our inspection when we want to pull things out. - // TODO: dont' use as for conversions - pub fn call( - &mut self, - handle: &str, - arg: A, - ) -> Result { - dbg!(&handle); - // dbg!(serde_json::to_string(&arg)).unwrap(); - + /// Takes an item, allocates a buffer, serializes the argument to that buffer, + /// and returns a (ptr, len) pair to that buffer. + fn serialize_to_buffer(&mut self, item: T) -> Result<(u32, u32), Error> { // serialize the argument using bincode - let arg = bincode::serialize(&arg)?; - let arg_buffer_len = arg.len(); + let item = bincode::serialize(&item)?; + let buffer_len = item.len() as u32; // allocate a buffer and write the argument to that buffer - let arg_buffer_ptr = self - .alloc_buffer - .call(&mut self.store, arg_buffer_len as i32)?; + let buffer_ptr = self.alloc_buffer.call(&mut self.store, buffer_len)?; let plugin_memory = self .instance .get_memory(&mut self.store, "memory") .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; - plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; - - // get the webassembly function we want to actually call - // TODO: precompute handle - let fun_name = format!("__{}", handle); - let fun = self - .instance - .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; - - // call the function, passing in the buffer and its length - // this should return a pointer to a (ptr, lentgh) pair - let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); - let result_buffer = fun.call(&mut self.store, arg_buffer)?; + plugin_memory.write(&mut self.store, buffer_ptr as usize, &item)?; + Ok((buffer_ptr, buffer_len)) + } + /// Takes a ptr to a (ptr, len) pair and returns the corresponding deserialized buffer + fn deserialize_from_buffer(&mut self, buffer: u32) -> Result { // create a buffer to read the (ptr, length) pair into // this is a total of 4 + 4 = 8 bytes. - let buffer = &mut [0; 8]; - plugin_memory.read(&mut self.store, result_buffer as usize, buffer)?; + let raw_buffer = &mut [0; 8]; + let plugin_memory = self + .instance + .get_memory(&mut self.store, "memory") + .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; + plugin_memory.read(&mut self.store, buffer as usize, raw_buffer)?; // use these bytes (wasm stores things little-endian) // to get a pointer to the buffer and its length - let b = buffer; - let result_buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; - let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; - let result_buffer_end = result_buffer_ptr + result_buffer_len; + let b = raw_buffer; + let buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; + let buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; + let buffer_end = buffer_ptr + buffer_len; // read the buffer at this point into a byte array // deserialize the byte array into the provided serde type - let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; + let result = &plugin_memory.data(&mut self.store)[buffer_ptr..buffer_end]; let result = bincode::deserialize(result)?; // TODO: this is handled wasm-side, but I'd like to double-check // // deallocate the argument buffer // self.free_buffer.call(&mut self.store, arg_buffer); - return Ok(result); + Ok(result) + } + + // TODO: dont' use as for conversions + pub fn call( + &mut self, + handle: &str, + arg: A, + ) -> Result { + let start = std::time::Instant::now(); + dbg!(&handle); + // dbg!(serde_json::to_string(&arg)).unwrap(); + + // write the argument to linear memory + // this returns a (ptr, lentgh) pair + let arg_buffer = self.serialize_to_buffer(arg)?; + + // get the webassembly function we want to actually call + // TODO: precompute handle + let fun_name = format!("__{}", handle); + let fun = self + .instance + .get_typed_func::<(u32, u32), u32, _>(&mut self.store, &fun_name)?; + + // call the function, passing in the buffer and its length + // this returns a ptr to a (ptr, lentgh) pair + let result_buffer = fun.call(&mut self.store, arg_buffer)?; + + self.deserialize_from_buffer(result_buffer) } } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 8046401ccc..9b4cda9c20 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -125,13 +125,12 @@ impl LspAdapter for LanguagePluginLspAdapter { } fn initialization_options(&self) -> Option { - // self.runtime - // .lock() - // .call::<_, Option>("initialization_options", ()) - // .unwrap() + let string = self + .runtime + .lock() + .call::<_, Option>("initialization_options", ()) + .unwrap()?; - Some(json!({ - "provideFormatter": true - })) + serde_json::from_str(&string).ok() } } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index ff88593d4b..8bd2d78a36 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -17,12 +17,15 @@ extern "C" { // } +const BIN_PATH: &'static str = + "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; + #[bind] pub fn name() -> &'static str { - let number = unsafe { hello(27) }; - println!("got: {}", number); - let number = unsafe { bye(28) }; - println!("got: {}", number); + // let number = unsafe { hello(27) }; + // println!("got: {}", number); + // let number = unsafe { bye(28) }; + // println!("got: {}", number); "vscode-json-languageserver" } @@ -33,79 +36,66 @@ pub fn server_args() -> Vec { #[bind] pub fn fetch_latest_server_version() -> Option { - // #[derive(Deserialize)] - // struct NpmInfo { - // versions: Vec, - // } + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } - // let output = command("npm info vscode-json-languageserver --json")?; - // if !output.status.success() { - // return None; - // } + let output = command("npm info vscode-json-languageserver --json")?; + if !output.status.success() { + return None; + } - // let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; - // info.versions.pop() - println!("fetching server version"); - Some("1.3.4".into()) + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + info.versions.pop() } #[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); +pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result { + let version_dir = container_dir.join(version.as_str()); + fs::create_dir_all(&version_dir) + .or_or_else(|| "failed to create version directory".to_string())?; + 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 = command(format!( + "npm install vscode-json-languageserver@{}", + version + )); + 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; - 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 @@ -113,10 +103,8 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option { } #[bind] -pub fn initialization_options() -> Option { - Some(json!({ - "provideFormatter": true - })) +pub fn initialization_options() -> Option { + Some("{ \"provideFormatter\": true }".to_string()) } #[bind] From 8bce35d1e9ecc4cda59ac337bb45e7975484885b Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 7 Jun 2022 17:05:55 +0200 Subject: [PATCH 20/91] Move Wasi to async, validate timeslicing, using async in traits still WIP --- crates/plugin_runtime/src/wasi.rs | 26 ++-- crates/zed/src/languages.rs | 15 ++- crates/zed/src/languages/language_plugin.rs | 125 +++++++++++--------- crates/zed/src/main.rs | 6 +- plugins/json_language/src/lib.rs | 87 +++++++------- 5 files changed, 144 insertions(+), 115 deletions(-) diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index f9415c2761..9d6340a11f 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; use wasi_common::{dir, file}; -use wasmtime::{Engine, Instance, Linker, Module, Store, TypedFunc}; +use wasmtime::{Config, Engine, Instance, Linker, Module, Store, TypedFunc}; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; pub struct WasiResource(u32); @@ -50,8 +50,10 @@ impl Wasi { .build() } - pub fn init(plugin: WasiPlugin) -> Result { - let engine = Engine::default(); + pub async fn init(plugin: WasiPlugin) -> Result { + let mut config = Config::default(); + config.async_support(true); + let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); linker.func_wrap("env", "__hello", |x: u32| x * 2).unwrap(); @@ -62,8 +64,8 @@ impl Wasi { let mut store: Store<_> = Store::new(&engine, plugin.wasi_ctx); let module = Module::new(&engine, plugin.module)?; - linker.module(&mut store, "", &module)?; - let instance = linker.instantiate(&mut store, &module)?; + linker.module_async(&mut store, "", &module).await?; + let instance = linker.instantiate_async(&mut store, &module).await?; let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; @@ -166,13 +168,16 @@ impl Wasi { /// Takes an item, allocates a buffer, serializes the argument to that buffer, /// and returns a (ptr, len) pair to that buffer. - fn serialize_to_buffer(&mut self, item: T) -> Result<(u32, u32), Error> { + async fn serialize_to_buffer(&mut self, item: T) -> Result<(u32, u32), Error> { // serialize the argument using bincode let item = bincode::serialize(&item)?; let buffer_len = item.len() as u32; // allocate a buffer and write the argument to that buffer - let buffer_ptr = self.alloc_buffer.call(&mut self.store, buffer_len)?; + let buffer_ptr = self + .alloc_buffer + .call_async(&mut self.store, buffer_len) + .await?; let plugin_memory = self .instance .get_memory(&mut self.store, "memory") @@ -212,18 +217,17 @@ impl Wasi { } // TODO: dont' use as for conversions - pub fn call( + pub async fn call( &mut self, handle: &str, arg: A, ) -> Result { - let start = std::time::Instant::now(); dbg!(&handle); // dbg!(serde_json::to_string(&arg)).unwrap(); // write the argument to linear memory // this returns a (ptr, lentgh) pair - let arg_buffer = self.serialize_to_buffer(arg)?; + let arg_buffer = self.serialize_to_buffer(arg).await?; // get the webassembly function we want to actually call // TODO: precompute handle @@ -234,7 +238,7 @@ impl Wasi { // call the function, passing in the buffer and its length // this returns a ptr to a (ptr, lentgh) pair - let result_buffer = fun.call(&mut self.store, arg_buffer)?; + let result_buffer = fun.call_async(&mut self.store, arg_buffer).await?; self.deserialize_from_buffer(result_buffer) } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index ac9c44d68a..bd003fa8b2 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,7 +1,11 @@ -use gpui::Task; +use gpui::{ + executor::{self, Background}, + Task, +}; pub use language::*; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; +use util::ResultExt; mod c; mod go; @@ -17,8 +21,7 @@ mod typescript; #[exclude = "*.rs"] struct LanguageDir; -pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry { - let languages = LanguageRegistry::new(login_shell_env_loaded); +pub async fn init(languages: Arc, executor: Arc) { for (name, grammar, lsp_adapter) in [ ( "c", @@ -38,7 +41,10 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi ( "json", tree_sitter_json::language(), - Some(Arc::new(language_plugin::new_json())), + language_plugin::new_json(executor) + .await + .log_err() + .map(|lang| Arc::new(lang) as Arc<_>), ), ( "markdown", @@ -78,7 +84,6 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi ] { languages.add(Arc::new(language(name, grammar, lsp_adapter))); } - languages } pub(crate) fn language( diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9b4cda9c20..45eff32316 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 gpui::executor::{self, Background}; use isahc::http::version; use language::{LanguageServerName, LspAdapter}; use parking_lot::{Mutex, RwLock}; @@ -11,23 +12,25 @@ use std::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; -pub fn new_json() -> LanguagePluginLspAdapter { +pub async fn new_json(executor: Arc) -> Result { let plugin = WasiPlugin { module: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), wasi_ctx: Wasi::default_ctx(), }; - LanguagePluginLspAdapter::new(plugin) + PluginLspAdapter::new(plugin, executor).await } -pub struct LanguagePluginLspAdapter { - runtime: Mutex, +pub struct PluginLspAdapter { + runtime: Arc>, + executor: Arc, } -impl LanguagePluginLspAdapter { - pub fn new(plugin: WasiPlugin) -> Self { - Self { - runtime: Mutex::new(Wasi::init(plugin).unwrap()), - } +impl PluginLspAdapter { + pub async fn new(plugin: WasiPlugin, executor: Arc) -> Result { + Ok(Self { + runtime: Arc::new(Mutex::new(Wasi::init(plugin).await?)), + executor, + }) } } @@ -36,32 +39,43 @@ struct Versions { server_version: String, } -impl LspAdapter for LanguagePluginLspAdapter { +macro_rules! call_block { + ($self:ident, $name:expr, $arg:expr) => { + $self + .executor + .block(async { $self.runtime.lock().call($name, $arg).await }) + }; +} + +impl LspAdapter for PluginLspAdapter { fn name(&self) -> LanguageServerName { - let name: String = self.runtime.lock().call("name", ()).unwrap(); + let name: String = call_block!(self, "name", ()).unwrap(); LanguageServerName(name.into()) } fn server_args<'a>(&'a self) -> Vec { - self.runtime.lock().call("server_args", ()).unwrap() + call_block!(self, "server_args", ()).unwrap() } fn fetch_latest_server_version( &self, _: Arc, ) -> BoxFuture<'static, Result>> { - let versions: Result<(String, String)> = - self.runtime.lock().call("fetch_latest_server_version", ()); - - async move { - versions.map(|(language_version, server_version)| { - Box::new(Versions { - language_version, - server_version, - }) as Box<_> - }) - } - .boxed() + todo!() + // async move { + // let versions: Result = self + // .runtime + // .lock() + // .call::<_, Option>("fetch_latest_server_version", ()) + // .await?; + // versions.map(|(language_version, server_version)| { + // Box::new(Versions { + // language_version, + // server_version, + // }) as Box<_> + // }) + // } + // .boxed() } fn fetch_server_binary( @@ -70,34 +84,37 @@ impl LspAdapter for LanguagePluginLspAdapter { _: Arc, container_dir: PathBuf, ) -> BoxFuture<'static, Result> { - let version = version.downcast::().unwrap(); - let mut runtime = self.runtime.lock(); + todo!() + // let version = version.downcast::().unwrap(); - 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() + // async move { + // let runtime = self.runtime.clone(); + // let handle = runtime.lock().attach_path(&container_dir).unwrap(); + // let result = runtime + // .lock() + // .call::<_, Option>("fetch_server_binary", container_dir) + // .await + // .unwrap() + // .ok_or_else(|| anyhow!("Could not load cached server binary")); + // // runtime.remove_resource(handle).ok(); + // result + // } + // .boxed() } fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { - 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() + todo!() + // let runtime = self.runtime.clone(); + // async move { + // let handle = runtime.lock().attach_path(&container_dir).ok()?; + // let result = runtime + // .lock() + // .call::<_, Option>("cached_server_binary", container_dir); + // let result = result.await; + // runtime.lock().remove_resource(handle).ok()?; + // result.ok()? + // } + // .boxed() } fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -111,11 +128,7 @@ impl LspAdapter for LanguagePluginLspAdapter { let len = item.label.len(); let grammar = language.grammar()?; let kind = format!("{:?}", item.kind?); - let name: String = self - .runtime - .lock() - .call("label_for_completion", kind) - .ok()?; + let name: String = call_block!(self, "label_for_completion", kind).log_err()?; let highlight_id = grammar.highlight_id_for_name(&name)?; Some(language::CodeLabel { text: item.label.clone(), @@ -125,11 +138,7 @@ impl LspAdapter for LanguagePluginLspAdapter { } fn initialization_options(&self) -> Option { - let string = self - .runtime - .lock() - .call::<_, Option>("initialization_options", ()) - .unwrap()?; + let string: String = call_block!(self, "initialization_options", ()).log_err()?; serde_json::from_str(&string).ok() } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c7a7e24c5a..0e8e350276 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -21,6 +21,7 @@ use futures::{ }; use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task}; use isahc::{config::Configurable, AsyncBody, Request}; +use language::LanguageRegistry; use log::LevelFilter; use parking_lot::Mutex; use project::{Fs, ProjectStore}; @@ -163,7 +164,7 @@ fn main() { app.run(move |cx| { let client = client::Client::new(http.clone()); - let mut languages = languages::build_language_registry(login_shell_env_loaded); + let mut languages = LanguageRegistry::new(login_shell_env_loaded); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); context_menu::init(cx); @@ -210,6 +211,9 @@ fn main() { languages.set_language_server_download_dir(zed::ROOT_PATH.clone()); let languages = Arc::new(languages); + cx.background() + .spawn(languages::init(languages.clone(), cx.background().clone())) + .detach(); cx.observe_global::({ let languages = languages.clone(); diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 8bd2d78a36..f0ffc74598 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -17,6 +17,13 @@ extern "C" { // } +// #[no_mangle] +// pub extern "C" fn very_unique_name_of_course() -> impl std::future::Future { +// async move { +// std::fs::read_to_string("heck.txt").unwrap().len() as u32 +// } +// } + const BIN_PATH: &'static str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; @@ -34,52 +41,52 @@ pub fn server_args() -> Vec { vec!["--stdio".into()] } -#[bind] -pub fn fetch_latest_server_version() -> Option { - #[derive(Deserialize)] - struct NpmInfo { - versions: Vec, - } +// #[bind] +// pub fn fetch_latest_server_version() -> Option { +// #[derive(Deserialize)] +// struct NpmInfo { +// versions: Vec, +// } - let output = command("npm info vscode-json-languageserver --json")?; - if !output.status.success() { - return None; - } +// let output = command("npm info vscode-json-languageserver --json")?; +// if !output.status.success() { +// return None; +// } - let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; - info.versions.pop() -} +// let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; +// info.versions.pop() +// } -#[bind] -pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result { - let version_dir = container_dir.join(version.as_str()); - fs::create_dir_all(&version_dir) - .or_or_else(|| "failed to create version directory".to_string())?; - let binary_path = version_dir.join(Self::BIN_PATH); +// #[bind] +// pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result { +// let version_dir = container_dir.join(version.as_str()); +// fs::create_dir_all(&version_dir) +// .or_or_else(|| "failed to create version directory".to_string())?; +// let binary_path = version_dir.join(Self::BIN_PATH); - if fs::metadata(&binary_path).await.is_err() { - let output = command(format!( - "npm install vscode-json-languageserver@{}", - version - )); - if !output.status.success() { - Err(anyhow!("failed to install vscode-json-languageserver"))?; - } +// if fs::metadata(&binary_path).await.is_err() { +// let output = command(format!( +// "npm install vscode-json-languageserver@{}", +// version +// )); +// 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) +// } #[bind] pub fn cached_server_binary(container_dir: PathBuf) -> Option { From d7b97b25b8ff6e8f2f4227b3d73cf5548b009f8b Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 8 Jun 2022 09:49:25 +0200 Subject: [PATCH 21/91] Async runtime working but is blocking --- crates/zed/src/languages/language_plugin.rs | 76 +++++++++------------ plugins/json_language/src/lib.rs | 28 ++++---- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 45eff32316..4def96beb5 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -61,21 +61,20 @@ impl LspAdapter for PluginLspAdapter { &self, _: Arc, ) -> BoxFuture<'static, Result>> { - todo!() - // async move { - // let versions: Result = self - // .runtime - // .lock() - // .call::<_, Option>("fetch_latest_server_version", ()) - // .await?; - // versions.map(|(language_version, server_version)| { - // Box::new(Versions { - // language_version, - // server_version, - // }) as Box<_> - // }) - // } - // .boxed() + let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); + async move { + // let versions: Result> = self + // .runtime + // .lock() + // .call::<_, Option>("fetch_latest_server_version", ()) + // .await; + + versions + .map_err(|e| anyhow!("{}", e))? + .ok_or_else(|| anyhow!("Could not fetch latest server version")) + .map(|v| Box::new(v) as Box<_>) + } + .boxed() } fn fetch_server_binary( @@ -84,37 +83,30 @@ impl LspAdapter for PluginLspAdapter { _: Arc, container_dir: PathBuf, ) -> BoxFuture<'static, Result> { - todo!() - // let version = version.downcast::().unwrap(); + let version = version.downcast::().unwrap(); + let mut runtime = self.runtime.lock(); + let result = (|| { + let handle = runtime.attach_path(&container_dir)?; + let result: Option = + call_block!(self, "fetch_server_binary", (container_dir, version))?; + runtime.remove_resource(handle)?; + result.ok_or_else(|| anyhow!("Could not load cached server binary")) + })(); - // async move { - // let runtime = self.runtime.clone(); - // let handle = runtime.lock().attach_path(&container_dir).unwrap(); - // let result = runtime - // .lock() - // .call::<_, Option>("fetch_server_binary", container_dir) - // .await - // .unwrap() - // .ok_or_else(|| anyhow!("Could not load cached server binary")); - // // runtime.remove_resource(handle).ok(); - // result - // } - // .boxed() + async move { result }.boxed() } fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { - todo!() - // let runtime = self.runtime.clone(); - // async move { - // let handle = runtime.lock().attach_path(&container_dir).ok()?; - // let result = runtime - // .lock() - // .call::<_, Option>("cached_server_binary", container_dir); - // let result = result.await; - // runtime.lock().remove_resource(handle).ok()?; - // result.ok()? - // } - // .boxed() + let mut runtime = self.runtime.lock(); + let result: Option = (|| { + let handle = runtime.attach_path(&container_dir).ok()?; + let result: Option = + call_block!(self, "cached_server_binary", container_dir).ok()?; + runtime.remove_resource(handle).ok()?; + result + })(); + + async move { result }.boxed() } fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index f0ffc74598..8828d3c566 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -41,24 +41,24 @@ pub fn server_args() -> Vec { vec!["--stdio".into()] } -// #[bind] -// pub fn fetch_latest_server_version() -> Option { -// #[derive(Deserialize)] -// struct NpmInfo { -// versions: Vec, -// } +#[bind] +pub fn fetch_latest_server_version() -> Option { + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } -// let output = command("npm info vscode-json-languageserver --json")?; -// if !output.status.success() { -// return None; -// } + let output = command("npm info vscode-json-languageserver --json")?; + if !output.status.success() { + return None; + } -// let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; -// info.versions.pop() -// } + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + info.versions.pop() +} // #[bind] -// pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result { +// pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Option { // let version_dir = container_dir.join(version.as_str()); // fs::create_dir_all(&version_dir) // .or_or_else(|| "failed to create version directory".to_string())?; From 923f093aca6ec2a8a267866b4e3b4387da9ab52e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 8 Jun 2022 10:32:19 +0200 Subject: [PATCH 22/91] First pass of plugin side of things complete --- crates/zed/src/languages/language_plugin.rs | 50 ++++++++------- plugins/Cargo.lock | 14 +++++ plugins/json_language/Cargo.toml | 2 +- plugins/json_language/src/lib.rs | 68 +++++++++++---------- 4 files changed, 78 insertions(+), 56 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 4def96beb5..76557ccedf 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -1,11 +1,11 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; use client::http::HttpClient; +use futures::lock::Mutex; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::executor::{self, Background}; use isahc::http::version; use language::{LanguageServerName, LspAdapter}; -use parking_lot::{Mutex, RwLock}; use plugin_runtime::{Wasi, WasiPlugin}; use serde_json::json; use std::fs; @@ -43,7 +43,7 @@ macro_rules! call_block { ($self:ident, $name:expr, $arg:expr) => { $self .executor - .block(async { $self.runtime.lock().call($name, $arg).await }) + .block(async { $self.runtime.lock().await.call($name, $arg).await }) }; } @@ -61,14 +61,13 @@ impl LspAdapter for PluginLspAdapter { &self, _: Arc, ) -> BoxFuture<'static, Result>> { - let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); + // let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); + let runtime = self.runtime.clone(); async move { - // let versions: Result> = self - // .runtime - // .lock() - // .call::<_, Option>("fetch_latest_server_version", ()) - // .await; - + let mut runtime = runtime.lock().await; + let versions: Result> = runtime + .call::<_, Option>("fetch_latest_server_version", ()) + .await; versions .map_err(|e| anyhow!("{}", e))? .ok_or_else(|| anyhow!("Could not fetch latest server version")) @@ -84,29 +83,34 @@ impl LspAdapter for PluginLspAdapter { container_dir: PathBuf, ) -> BoxFuture<'static, Result> { let version = version.downcast::().unwrap(); - let mut runtime = self.runtime.lock(); - let result = (|| { + let runtime = self.runtime.clone(); + + async move { + let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir)?; - let result: Option = - call_block!(self, "fetch_server_binary", (container_dir, version))?; + let result: Option = runtime + .call("fetch_server_binary", (container_dir, version)) + .await?; runtime.remove_resource(handle)?; result.ok_or_else(|| anyhow!("Could not load cached server binary")) - })(); - - async move { result }.boxed() + } + .boxed() } fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { - let mut runtime = self.runtime.lock(); - let result: Option = (|| { + let runtime = self.runtime.clone(); + + async move { + let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir).ok()?; - let result: Option = - call_block!(self, "cached_server_binary", container_dir).ok()?; + let result: Option = runtime + .call("cached_server_binary", container_dir) + .await + .ok()?; runtime.remove_resource(handle).ok()?; result - })(); - - async move { result }.boxed() + } + .boxed() } fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index a6d421e386..8ef3e49d08 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -75,6 +75,20 @@ name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" diff --git a/plugins/json_language/Cargo.toml b/plugins/json_language/Cargo.toml index 33415db798..7e01b9793f 100644 --- a/plugins/json_language/Cargo.toml +++ b/plugins/json_language/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] plugin = { path = "../../crates/plugin" } -serde = "1.0" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" [lib] diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 8828d3c566..83d2283970 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,10 +1,13 @@ use plugin::prelude::*; +use serde::Deserialize; use serde_json::json; use std::fs; use std::path::PathBuf; // #[import] -// fn command(string: String) -> Option; +fn command(string: &str) -> Option { + todo!() +} // TODO: some sort of macro to generate ABI bindings extern "C" { @@ -48,45 +51,46 @@ pub fn fetch_latest_server_version() -> Option { versions: Vec, } + // TODO: command returns error code let output = command("npm info vscode-json-languageserver --json")?; - if !output.status.success() { - return None; - } + // if !output.is_ok() { + // return None; + // } - let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + let mut info: NpmInfo = serde_json::from_str(&output).ok()?; info.versions.pop() } -// #[bind] -// pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Option { -// let version_dir = container_dir.join(version.as_str()); -// fs::create_dir_all(&version_dir) -// .or_or_else(|| "failed to create version directory".to_string())?; -// let binary_path = version_dir.join(Self::BIN_PATH); +#[bind] +pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result { + let version_dir = container_dir.join(version.as_str()); + fs::create_dir_all(&version_dir) + .map_err(|_| "failed to create version directory".to_string())?; + let binary_path = version_dir.join(BIN_PATH); -// if fs::metadata(&binary_path).await.is_err() { -// let output = command(format!( -// "npm install vscode-json-languageserver@{}", -// version -// )); -// if !output.status.success() { -// Err(anyhow!("failed to install vscode-json-languageserver"))?; -// } + if fs::metadata(&binary_path).is_err() { + let output = command(&format!( + "npm install vscode-json-languageserver@{}", + version + )); + if output.is_none() { + return Err("failed to install vscode-json-languageserver".to_string()); + } -// 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).ok() { + while let Some(entry) = entries.next() { + if let Some(entry) = entry.ok() { + let entry_path = entry.path(); + if entry_path.as_path() != version_dir { + fs::remove_dir_all(&entry_path).ok(); + } + } + } + } + } -// Ok(binary_path) -// } + Ok(binary_path) +} #[bind] pub fn cached_server_binary(container_dir: PathBuf) -> Option { From 71e0555763c88d9200f9a97d4358350320a896f4 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 8 Jun 2022 11:34:20 +0200 Subject: [PATCH 23/91] Add JSON LSP plugin --- crates/language/src/language.rs | 2 +- crates/plugin_runtime/src/wasi.rs | 52 ++++++++++++++++++--- crates/zed/src/languages.rs | 4 +- crates/zed/src/languages/language_plugin.rs | 50 ++++++++++++-------- crates/zed/src/main.rs | 19 +++++--- plugins/json_language/src/lib.rs | 7 ++- 6 files changed, 98 insertions(+), 36 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 23758c07ca..8b8184b174 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -286,7 +286,7 @@ impl LanguageRegistry { .config .path_suffixes .iter() - .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) + .any(|suffix| dbg!(path_suffixes.contains(&Some(dbg!(suffix.as_str()))))) }) .cloned() } diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 9d6340a11f..df7e7a2ed7 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -1,4 +1,4 @@ -use std::{fs::File, os::unix::prelude::AsRawFd, path::Path}; +use std::{fs::File, marker::PhantomData, path::Path}; use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; @@ -9,6 +9,29 @@ use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; pub struct WasiResource(u32); +pub struct WasiFn { + function: TypedFunc<(u32, u32), u32>, + _function_type: PhantomData R>, +} + +impl Copy for WasiFn {} + +impl Clone for WasiFn { + fn clone(&self) -> Self { + Self { + function: self.function, + _function_type: PhantomData, + } + } +} + +// impl WasiFn { +// #[inline(always)] +// pub async fn call(&self, runtime: &mut Wasi, arg: A) -> Result { +// runtime.call(self, arg).await +// } +// } + pub struct Wasi { engine: Engine, module: Module, @@ -216,13 +239,27 @@ impl Wasi { Ok(result) } + pub fn function>( + &mut self, + name: T, + ) -> Result, Error> { + let fun_name = format!("__{}", name.as_ref()); + let fun = self + .instance + .get_typed_func::<(u32, u32), u32, _>(&mut self.store, &fun_name)?; + Ok(WasiFn { + function: fun, + _function_type: PhantomData, + }) + } + // TODO: dont' use as for conversions pub async fn call( &mut self, - handle: &str, + handle: &WasiFn, arg: A, ) -> Result { - dbg!(&handle); + // dbg!(&handle.name); // dbg!(serde_json::to_string(&arg)).unwrap(); // write the argument to linear memory @@ -231,10 +268,11 @@ impl Wasi { // get the webassembly function we want to actually call // TODO: precompute handle - let fun_name = format!("__{}", handle); - let fun = self - .instance - .get_typed_func::<(u32, u32), u32, _>(&mut self.store, &fun_name)?; + // let fun_name = format!("__{}", handle); + // let fun = self + // .instance + // .get_typed_func::<(u32, u32), u32, _>(&mut self.store, &fun_name)?; + let fun = handle.function; // call the function, passing in the buffer and its length // this returns a ptr to a (ptr, lentgh) pair diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index bd003fa8b2..75bf8212f8 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -10,11 +10,10 @@ use util::ResultExt; mod c; mod go; mod installation; -mod python; mod language_plugin; +mod python; mod rust; mod typescript; -// mod json; #[derive(RustEmbed)] #[folder = "src/languages"] @@ -41,6 +40,7 @@ pub async fn init(languages: Arc, executor: Arc) { ( "json", tree_sitter_json::language(), + // Some(Arc::new(json::JsonLspAdapter)), language_plugin::new_json(executor) .await .log_err() diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 76557ccedf..8bc41a5e59 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -6,7 +6,7 @@ use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::executor::{self, Background}; use isahc::http::version; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Wasi, WasiPlugin}; +use plugin_runtime::{Wasi, WasiFn, WasiPlugin}; use serde_json::json; use std::fs; use std::{any::Any, path::PathBuf, sync::Arc}; @@ -21,15 +21,30 @@ pub async fn new_json(executor: Arc) -> Result { } pub struct PluginLspAdapter { - runtime: Arc>, + name: WasiFn<(), String>, + server_args: WasiFn<(), Vec>, + fetch_latest_server_version: WasiFn<(), Option>, + fetch_server_binary: WasiFn<(PathBuf, String), Option>, + cached_server_binary: WasiFn>, + label_for_completion: WasiFn>, + initialization_options: WasiFn<(), String>, executor: Arc, + runtime: Arc>, } impl PluginLspAdapter { pub async fn new(plugin: WasiPlugin, executor: Arc) -> Result { + let mut plugin = Wasi::init(plugin).await?; Ok(Self { - runtime: Arc::new(Mutex::new(Wasi::init(plugin).await?)), + name: plugin.function("name")?, + server_args: plugin.function("server_args")?, + fetch_latest_server_version: plugin.function("fetch_latest_server_version")?, + fetch_server_binary: plugin.function("fetch_server_binary")?, + cached_server_binary: plugin.function("cached_server_binary")?, + label_for_completion: plugin.function("label_for_completion")?, + initialization_options: plugin.function("initialization_options")?, executor, + runtime: Arc::new(Mutex::new(plugin)), }) } } @@ -49,12 +64,12 @@ macro_rules! call_block { impl LspAdapter for PluginLspAdapter { fn name(&self) -> LanguageServerName { - let name: String = call_block!(self, "name", ()).unwrap(); + let name: String = call_block!(self, &self.name, ()).unwrap(); LanguageServerName(name.into()) } fn server_args<'a>(&'a self) -> Vec { - call_block!(self, "server_args", ()).unwrap() + call_block!(self, &self.server_args, ()).unwrap() } fn fetch_latest_server_version( @@ -63,11 +78,11 @@ impl LspAdapter for PluginLspAdapter { ) -> BoxFuture<'static, Result>> { // let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); let runtime = self.runtime.clone(); + let function = self.fetch_latest_server_version; async move { let mut runtime = runtime.lock().await; - let versions: Result> = runtime - .call::<_, Option>("fetch_latest_server_version", ()) - .await; + let versions: Result> = + runtime.call::<_, Option>(&function, ()).await; versions .map_err(|e| anyhow!("{}", e))? .ok_or_else(|| anyhow!("Could not fetch latest server version")) @@ -82,15 +97,13 @@ impl LspAdapter for PluginLspAdapter { _: Arc, container_dir: PathBuf, ) -> BoxFuture<'static, Result> { - let version = version.downcast::().unwrap(); + let version = *version.downcast::().unwrap(); let runtime = self.runtime.clone(); - + let function = self.fetch_server_binary; async move { let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir)?; - let result: Option = runtime - .call("fetch_server_binary", (container_dir, version)) - .await?; + let result: Option = runtime.call(&function, (container_dir, version)).await?; runtime.remove_resource(handle)?; result.ok_or_else(|| anyhow!("Could not load cached server binary")) } @@ -99,14 +112,12 @@ impl LspAdapter for PluginLspAdapter { fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { let runtime = self.runtime.clone(); + let function = self.cached_server_binary; async move { let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir).ok()?; - let result: Option = runtime - .call("cached_server_binary", container_dir) - .await - .ok()?; + let result: Option = runtime.call(&function, container_dir).await.ok()?; runtime.remove_resource(handle).ok()?; result } @@ -120,11 +131,12 @@ impl LspAdapter for PluginLspAdapter { item: &lsp::CompletionItem, language: &language::Language, ) -> Option { + // TODO: Push more of this method down into the plugin. use lsp::CompletionItemKind as Kind; let len = item.label.len(); let grammar = language.grammar()?; let kind = format!("{:?}", item.kind?); - let name: String = call_block!(self, "label_for_completion", kind).log_err()?; + let name: String = call_block!(self, &self.label_for_completion, kind).log_err()??; let highlight_id = grammar.highlight_id_for_name(&name)?; Some(language::CodeLabel { text: item.label.clone(), @@ -134,7 +146,7 @@ impl LspAdapter for PluginLspAdapter { } fn initialization_options(&self) -> Option { - let string: String = call_block!(self, "initialization_options", ()).log_err()?; + let string: String = call_block!(self, &self.initialization_options, ()).log_err()?; serde_json::from_str(&string).ok() } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 0e8e350276..4fceaed2d8 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -165,6 +165,11 @@ fn main() { app.run(move |cx| { let client = client::Client::new(http.clone()); let mut languages = LanguageRegistry::new(login_shell_env_loaded); + languages.set_language_server_download_dir(zed::ROOT_PATH.clone()); + let languages = Arc::new(languages); + let init_languages = cx + .background() + .spawn(languages::init(languages.clone(), cx.background().clone())); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); context_menu::init(cx); @@ -209,12 +214,6 @@ fn main() { }) .detach(); - languages.set_language_server_download_dir(zed::ROOT_PATH.clone()); - let languages = Arc::new(languages); - cx.background() - .spawn(languages::init(languages.clone(), cx.background().clone())) - .detach(); - cx.observe_global::({ let languages = languages.clone(); move |cx| { @@ -223,6 +222,14 @@ fn main() { }) .detach(); cx.set_global(settings); + cx.spawn({ + let languages = languages.clone(); + |cx| async move { + init_languages.await; + cx.read(|cx| languages.set_theme(&cx.global::().theme.editor.syntax)); + } + }) + .detach(); let project_store = cx.add_model(|_| ProjectStore::new(db.clone())); let app_state = Arc::new(AppState { diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 83d2283970..a60bfd2ad2 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; // #[import] fn command(string: &str) -> Option { - todo!() + None } // TODO: some sort of macro to generate ABI bindings @@ -113,6 +113,11 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option { } } +#[bind] +pub fn label_for_completion(label: String) -> Option { + None +} + #[bind] pub fn initialization_options() -> Option { Some("{ \"provideFormatter\": true }".to_string()) From 53e56f128462864d9502ac0c8fd25d07f13e2f61 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 8 Jun 2022 18:17:38 +0200 Subject: [PATCH 24/91] Start working on host-side functions --- crates/plugin/src/lib.rs | 2 +- crates/plugin_macros/src/lib.rs | 74 +++++++++++++- crates/plugin_runtime/src/wasi.rs | 103 ++++++++++++++++++-- crates/zed/src/languages/language_plugin.rs | 17 ++-- plugins/json_language/src/lib.rs | 66 +++++++++++-- 5 files changed, 232 insertions(+), 30 deletions(-) diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs index 3c7a09b415..ec685bc490 100644 --- a/crates/plugin/src/lib.rs +++ b/crates/plugin/src/lib.rs @@ -49,5 +49,5 @@ impl __Buffer { pub mod prelude { pub use super::{__Buffer, __alloc_buffer}; - pub use plugin_macros::bind; + pub use plugin_macros::{export, import}; } diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index eb3cc71cc5..6455d99957 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -5,15 +5,15 @@ use quote::{format_ident, quote}; use syn::{parse_macro_input, FnArg, ItemFn, Type, Visibility}; #[proc_macro_attribute] -pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { +pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { if !args.is_empty() { - panic!("The bind attribute does not take any arguments"); + panic!("The export attribute does not take any arguments"); } let inner_fn = parse_macro_input!(function as ItemFn); if let Visibility::Public(_) = inner_fn.vis { } else { - panic!("The bind attribute only works for public functions"); + panic!("The export attribute only works for public functions"); } let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); @@ -53,6 +53,7 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { #inner_fn #[no_mangle] + // TODO: switch len from usize to u32? pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { // setup let buffer = ::plugin::__Buffer { ptr, len }; @@ -73,3 +74,70 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { } }) } + +#[proc_macro_attribute] +pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { + todo!() + // if !args.is_empty() { + // panic!("The import attribute does not take any arguments"); + // } + + // let inner_fn = parse_macro_input!(function as ItemFn); + + // let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); + // // let outer_fn_name = format_ident!("__{}", inner_fn_name); + + // let variadic = inner_fn.sig.inputs.len(); + // let i = (0..variadic).map(syn::Index::from); + // 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 { + // ( + // quote! { + // #( data.#i ),* + // }, + // quote! { + // ( #( #t ),* ) + // }, + // ) + // } else { + // let ty = &t[0]; + // (quote! { data }, quote! { #ty }) + // }; + + // TokenStream::from(quote! { + // #[no_mangle] + // #inner_fn + + // #[no_mangle] + // pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { + // // setup + // let buffer = ::plugin::__Buffer { ptr, len }; + // let data = unsafe { buffer.to_vec() }; + + // // operation + // let data: #ty = match ::plugin::bincode::deserialize(&data) { + // Ok(d) => d, + // Err(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(); + + // // teardown + // let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }; + // return new_buffer.leak_to_heap(); + // } + // }) +} diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index df7e7a2ed7..ed1e2149fe 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -1,10 +1,13 @@ -use std::{fs::File, marker::PhantomData, path::Path}; +use std::{ + collections::HashMap, fs::File, future::Future, marker::PhantomData, path::Path, pin::Pin, +}; use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; use wasi_common::{dir, file}; -use wasmtime::{Config, Engine, Instance, Linker, Module, Store, TypedFunc}; +use wasmtime::IntoFunc; +use wasmtime::{Caller, Config, Engine, Instance, Linker, Module, Store, TypedFunc}; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; pub struct WasiResource(u32); @@ -41,9 +44,93 @@ pub struct Wasi { // free_buffer: TypedFunc<(u32, u32), ()>, } +// type signature derived from: +// https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html#method.func_wrap2_async +// macro_rules! dynHostFunction { +// () => { +// Box< +// dyn for<'a> Fn(Caller<'a, WasiCtx>, u32, u32) +// -> Box + Send + 'a> +// + Send +// + Sync +// + 'static +// > +// }; +// } + +// macro_rules! implHostFunction { +// () => { +// impl for<'a> Fn(Caller<'a, WasiCtx>, u32, u32) +// -> Box + Send + 'a> +// + Send +// + Sync +// + 'static +// }; +// } + +// This type signature goodness gracious +pub type HostFunction = Box>; + +pub struct WasiPluginBuilder { + host_functions: HashMap, + wasi_ctx_builder: WasiCtxBuilder, +} + +impl WasiPluginBuilder { + pub fn new() -> Self { + WasiPluginBuilder { + host_functions: HashMap::new(), + wasi_ctx_builder: WasiCtxBuilder::new(), + } + } + + pub fn new_with_default_ctx() -> WasiPluginBuilder { + let mut this = Self::new(); + this.wasi_ctx_builder = this.wasi_ctx_builder.inherit_stdin().inherit_stderr(); + this + } + + fn wrap_host_function( + function: impl Fn(A) -> R + Send + Sync + 'static, + ) -> HostFunction { + Box::new(move |ptr, len| { + function(todo!()); + todo!() + }) + } + + pub fn host_function( + mut self, + name: &str, + function: impl Fn(A) -> R + Send + Sync + 'static, + ) -> Self { + self.host_functions + .insert(name.to_string(), Self::wrap_host_function(function)); + self + } + + pub fn wasi_ctx(mut self, config: impl FnOnce(WasiCtxBuilder) -> WasiCtxBuilder) -> Self { + self.wasi_ctx_builder = config(self.wasi_ctx_builder); + self + } + + pub async fn init>(self, module: T) -> Result { + let plugin = WasiPlugin { + module: module.as_ref().to_vec(), + wasi_ctx: self.wasi_ctx_builder.build(), + host_functions: self.host_functions, + }; + + Wasi::init(plugin).await + } +} + +/// Represents a to-be-initialized plugin. +/// Please use [`WasiPluginBuilder`], don't use this directly. pub struct WasiPlugin { pub module: Vec, pub wasi_ctx: WasiCtx, + pub host_functions: HashMap, } impl Wasi { @@ -66,19 +153,15 @@ impl Wasi { } impl Wasi { - pub fn default_ctx() -> WasiCtx { - WasiCtxBuilder::new() - .inherit_stdout() - .inherit_stderr() - .build() - } - - pub async fn init(plugin: WasiPlugin) -> Result { + async fn init(plugin: WasiPlugin) -> Result { let mut config = Config::default(); config.async_support(true); let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); + linker + .func_wrap("env", "__command", |x: u32, y: u32| x + y) + .unwrap(); linker.func_wrap("env", "__hello", |x: u32| x * 2).unwrap(); linker.func_wrap("env", "__bye", |x: u32| x / 2).unwrap(); diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 8bc41a5e59..deb5cb5d32 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -6,17 +6,21 @@ use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::executor::{self, Background}; use isahc::http::version; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Wasi, WasiFn, WasiPlugin}; +use plugin_runtime::{Wasi, WasiFn, WasiPlugin, WasiPluginBuilder}; use serde_json::json; use std::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; pub async fn new_json(executor: Arc) -> Result { - let plugin = WasiPlugin { - module: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), - wasi_ctx: Wasi::default_ctx(), - }; + let plugin = WasiPluginBuilder::new_with_default_ctx() + .host_function("command", |command: String| { + // TODO: actual thing + std::process::Command::new(command).output().unwrap(); + Some("Hello".to_string()) + }) + .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) + .await?; PluginLspAdapter::new(plugin, executor).await } @@ -33,8 +37,7 @@ pub struct PluginLspAdapter { } impl PluginLspAdapter { - pub async fn new(plugin: WasiPlugin, executor: Arc) -> Result { - let mut plugin = Wasi::init(plugin).await?; + pub async fn new(mut plugin: Wasi, executor: Arc) -> Result { Ok(Self { name: plugin.function("name")?, server_args: plugin.function("server_args")?, diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index a60bfd2ad2..3f2329f5d1 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -5,8 +5,56 @@ use std::fs; use std::path::PathBuf; // #[import] +// fn command(string: &str) -> Option; + +extern "C" { + #[no_mangle] + fn __command(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer; +} + +// #[no_mangle] +// // TODO: switch len from usize to u32? +// pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { +// // setup +// let buffer = ::plugin::__Buffer { ptr, len }; +// let data = unsafe { buffer.to_vec() }; + +// // operation +// let data: #ty = match ::plugin::bincode::deserialize(&data) { +// Ok(d) => d, +// Err(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(); + +// // teardown +// let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }; +// return new_buffer.leak_to_heap(); +// } + +#[no_mangle] fn command(string: &str) -> Option { - None + println!("executing command: {}", string); + // serialize data + let data = string; + let data = ::plugin::bincode::serialize(&data).unwrap(); + let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; + let ptr = buffer.ptr; + let len = buffer.len; + // leak data to heap + buffer.leak_to_heap(); + // call extern function + let result = unsafe { __command(ptr, len) }; + // get result + let result = todo!(); // convert into box + + // deserialize data + let data: Option = match ::plugin::bincode::deserialize(&data) { + Ok(d) => d, + Err(e) => panic!("Data passed to function not deserializable."), + }; + return data; } // TODO: some sort of macro to generate ABI bindings @@ -30,7 +78,7 @@ extern "C" { const BIN_PATH: &'static str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; -#[bind] +#[export] pub fn name() -> &'static str { // let number = unsafe { hello(27) }; // println!("got: {}", number); @@ -39,12 +87,12 @@ pub fn name() -> &'static str { "vscode-json-languageserver" } -#[bind] +#[export] pub fn server_args() -> Vec { vec!["--stdio".into()] } -#[bind] +#[export] pub fn fetch_latest_server_version() -> Option { #[derive(Deserialize)] struct NpmInfo { @@ -61,7 +109,7 @@ pub fn fetch_latest_server_version() -> Option { info.versions.pop() } -#[bind] +#[export] pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result { let version_dir = container_dir.join(version.as_str()); fs::create_dir_all(&version_dir) @@ -92,7 +140,7 @@ pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result Option { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).ok()?; @@ -113,17 +161,17 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option { } } -#[bind] +#[export] pub fn label_for_completion(label: String) -> Option { None } -#[bind] +#[export] pub fn initialization_options() -> Option { Some("{ \"provideFormatter\": true }".to_string()) } -#[bind] +#[export] pub fn id_for_language(name: String) -> Option { if name == "JSON" { Some("jsonc".into()) From 96c2559d2ca64bf58dd23b4900b5576d66cf5228 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 9 Jun 2022 10:08:02 +0200 Subject: [PATCH 25/91] Work on plugin builder --- crates/plugin_macros/Cargo.toml | 3 +- crates/plugin_macros/src/lib.rs | 82 ++++++++++++++++++++----------- crates/plugin_runtime/src/lib.rs | 12 +++++ crates/plugin_runtime/src/wasi.rs | 33 +++++++------ plugins/json_language/src/lib.rs | 23 +++++---- 5 files changed, 97 insertions(+), 56 deletions(-) diff --git a/crates/plugin_macros/Cargo.toml b/crates/plugin_macros/Cargo.toml index 2d106105ab..1ba3c64831 100644 --- a/crates/plugin_macros/Cargo.toml +++ b/crates/plugin_macros/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" proc-macro = true [dependencies] -syn = { version = "1.0", features = ["full"] } +# TODO: remove "extra-traits" +syn = { version = "1.0", features = ["full", "extra-traits"] } quote = "1.0" proc-macro2 = "1.0" serde = "1.0" diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 6455d99957..154b2f2a83 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, FnArg, ItemFn, Type, Visibility}; +use syn::{parse_macro_input, FnArg, ForeignItemFn, ItemFn, Type, Visibility}; #[proc_macro_attribute] pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { @@ -11,6 +11,11 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { } let inner_fn = parse_macro_input!(function as ItemFn); + + if !inner_fn.sig.generics.params.is_empty() { + panic!("Exported functions can not take generic parameters"); + } + if let Visibility::Public(_) = inner_fn.vis { } else { panic!("The export attribute only works for public functions"); @@ -77,15 +82,27 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { - todo!() - // if !args.is_empty() { - // panic!("The import attribute does not take any arguments"); - // } + if !args.is_empty() { + panic!("The import attribute does not take any arguments"); + } - // let inner_fn = parse_macro_input!(function as ItemFn); + let fn_declare = parse_macro_input!(function as ForeignItemFn); - // let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); - // // let outer_fn_name = format_ident!("__{}", inner_fn_name); + if !fn_declare.sig.generics.params.is_empty() { + panic!("Exported functions can not take generic parameters"); + } + + dbg!(&fn_declare.sig); + + // let inner_fn = ItemFn { + // attrs: fn_declare.attrs, + // vis: fn_declare.vis, + // sig: fn_declare.sig, + // block: todo!(), + // }; + + // let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); + // let outer_fn_name = format_ident!("__{}", inner_fn_name); // let variadic = inner_fn.sig.inputs.len(); // let i = (0..variadic).map(syn::Index::from); @@ -116,28 +133,35 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { // (quote! { data }, quote! { #ty }) // }; - // TokenStream::from(quote! { + // TokenStream::from(quote! { + // extern "C" { // #[no_mangle] - // #inner_fn + // fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer; + // } - // #[no_mangle] - // pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { - // // setup - // let buffer = ::plugin::__Buffer { ptr, len }; - // let data = unsafe { buffer.to_vec() }; + // #[no_mangle] + // fn #inner_fn_name #params -> #output { + // println!("executing command: {}", string); + // // serialize data + // let data = #collect_params; + // let data = ::plugin::bincode::serialize(&data).unwrap(); + // let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; + // let ptr = buffer.ptr; + // let len = buffer.len; + // // leak data to heap + // buffer.leak_to_heap(); + // // call extern function + // let result = unsafe { __command(ptr, len) }; + // // get result + // let result = todo!(); // convert into box - // // operation - // let data: #ty = match ::plugin::bincode::deserialize(&data) { - // Ok(d) => d, - // Err(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(); - - // // teardown - // let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }; - // return new_buffer.leak_to_heap(); - // } - // }) + // // deserialize data + // let data: Option = match ::plugin::bincode::deserialize(&data) { + // Ok(d) => d, + // Err(e) => panic!("Data passed to function not deserializable."), + // }; + // return data; + // } + // }) + todo!() } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 460e824da3..109eb9d4e2 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -1,2 +1,14 @@ pub mod wasi; pub use wasi::*; + +// #[cfg(test)] +// mod tests { +// use super::*; + +// pub fn init_wasi() { +// let plugin = WasiPluginBuilder::new().init(todo!()).unwrap(); +// let handle: WasiFn = plugin.function("hello").unwrap(); +// let result = plugin.call(handle, 27).unwrap(); +// assert_eq!(result, "world 27"); +// } +// } diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index ed1e2149fe..ec4761c80a 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -72,7 +72,7 @@ pub struct Wasi { pub type HostFunction = Box>; pub struct WasiPluginBuilder { - host_functions: HashMap, + host_functions: HashMap) -> Result<(), Error>>>, wasi_ctx_builder: WasiCtxBuilder, } @@ -90,22 +90,22 @@ impl WasiPluginBuilder { this } - fn wrap_host_function( - function: impl Fn(A) -> R + Send + Sync + 'static, - ) -> HostFunction { - Box::new(move |ptr, len| { - function(todo!()); - todo!() - }) - } - pub fn host_function( mut self, name: &str, - function: impl Fn(A) -> R + Send + Sync + 'static, + function: &dyn Fn(A) -> R + Send + Sync + 'static, ) -> Self { - self.host_functions - .insert(name.to_string(), Self::wrap_host_function(function)); + let name = name.to_string(); + self.host_functions.insert( + name, + Box::new(move |name: &str, linker: &mut Linker| { + linker.func_wrap("env", name, |ptr: u32, len: u32| { + function(todo!()); + 7u32 + })?; + Ok(()) + }), + ); self } @@ -130,7 +130,8 @@ impl WasiPluginBuilder { pub struct WasiPlugin { pub module: Vec, pub wasi_ctx: WasiCtx, - pub host_functions: HashMap, + pub host_functions: + HashMap) -> Result<(), Error>>>, } impl Wasi { @@ -159,6 +160,10 @@ impl Wasi { let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); + for (name, add_to_linker) in plugin.host_functions.into_iter() { + add_to_linker(&name, &mut linker)?; + } + linker .func_wrap("env", "__command", |x: u32, y: u32| x + y) .unwrap(); diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 3f2329f5d1..b611579340 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -5,12 +5,7 @@ use std::fs; use std::path::PathBuf; // #[import] -// fn command(string: &str) -> Option; - -extern "C" { - #[no_mangle] - fn __command(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer; -} +// fn my_command(string: &str) -> Option; // #[no_mangle] // // TODO: switch len from usize to u32? @@ -33,9 +28,13 @@ extern "C" { // return new_buffer.leak_to_heap(); // } +extern "C" { + fn __command(ptr: *const u8, len: usize) -> *mut ::plugin::__Buffer; +} + #[no_mangle] fn command(string: &str) -> Option { - println!("executing command: {}", string); + dbg!("executing command: {}", string); // serialize data let data = string; let data = ::plugin::bincode::serialize(&data).unwrap(); @@ -47,14 +46,15 @@ fn command(string: &str) -> Option { // call extern function let result = unsafe { __command(ptr, len) }; // get result - let result = todo!(); // convert into box + let new_buffer = unsafe { Box::from_raw(result) }; // convert into box + let new_data = unsafe { new_buffer.to_vec() }; // deserialize data - let data: Option = match ::plugin::bincode::deserialize(&data) { + let new_data: Option = match ::plugin::bincode::deserialize(&new_data) { Ok(d) => d, - Err(e) => panic!("Data passed to function not deserializable."), + Err(e) => panic!("Data returned from function not deserializable."), }; - return data; + return new_data; } // TODO: some sort of macro to generate ABI bindings @@ -81,7 +81,6 @@ const BIN_PATH: &'static str = #[export] pub fn name() -> &'static str { // let number = unsafe { hello(27) }; - // println!("got: {}", number); // let number = unsafe { bye(28) }; // println!("got: {}", number); "vscode-json-languageserver" From 7266dff5370073aa8b1218db8aa4c5474da58e9a Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 9 Jun 2022 10:22:53 +0200 Subject: [PATCH 26/91] Fix issue with host function binding --- crates/plugin_runtime/src/wasi.rs | 87 +++++++++------------ crates/zed/src/languages/language_plugin.rs | 4 +- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index ec4761c80a..9aec7fc29a 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -72,59 +72,55 @@ pub struct Wasi { pub type HostFunction = Box>; pub struct WasiPluginBuilder { - host_functions: HashMap) -> Result<(), Error>>>, - wasi_ctx_builder: WasiCtxBuilder, + // host_functions: HashMap) -> Result<(), Error>>>, + wasi_ctx: WasiCtx, + engine: Engine, + linker: Linker, } impl WasiPluginBuilder { - pub fn new() -> Self { - WasiPluginBuilder { - host_functions: HashMap::new(), - wasi_ctx_builder: WasiCtxBuilder::new(), - } + pub fn new(wasi_ctx: WasiCtx) -> Result { + let mut config = Config::default(); + config.async_support(true); + let engine = Engine::new(&config)?; + let mut linker = Linker::new(&engine); + + Ok(WasiPluginBuilder { + // host_functions: HashMap::new(), + wasi_ctx, + engine, + linker, + }) } - pub fn new_with_default_ctx() -> WasiPluginBuilder { - let mut this = Self::new(); - this.wasi_ctx_builder = this.wasi_ctx_builder.inherit_stdin().inherit_stderr(); - this + pub fn new_with_default_ctx() -> Result { + let wasi_ctx = WasiCtxBuilder::new() + .inherit_stdin() + .inherit_stderr() + .build(); + Self::new(wasi_ctx) } pub fn host_function( mut self, name: &str, - function: &dyn Fn(A) -> R + Send + Sync + 'static, - ) -> Self { - let name = name.to_string(); - self.host_functions.insert( - name, - Box::new(move |name: &str, linker: &mut Linker| { - linker.func_wrap("env", name, |ptr: u32, len: u32| { - function(todo!()); - 7u32 - })?; - Ok(()) - }), - ); - self - } - - pub fn wasi_ctx(mut self, config: impl FnOnce(WasiCtxBuilder) -> WasiCtxBuilder) -> Self { - self.wasi_ctx_builder = config(self.wasi_ctx_builder); - self + function: impl Fn(A) -> R + Send + Sync + 'static, + ) -> Result { + self.linker + .func_wrap("env", name, move |ptr: u32, len: u32| { + // TODO: insert serialization code + function(todo!()); + 7u32 + })?; + Ok(self) } pub async fn init>(self, module: T) -> Result { - let plugin = WasiPlugin { - module: module.as_ref().to_vec(), - wasi_ctx: self.wasi_ctx_builder.build(), - host_functions: self.host_functions, - }; - - Wasi::init(plugin).await + Wasi::init(module.as_ref().to_vec(), self).await } } +// TODO: remove /// Represents a to-be-initialized plugin. /// Please use [`WasiPluginBuilder`], don't use this directly. pub struct WasiPlugin { @@ -154,26 +150,17 @@ impl Wasi { } impl Wasi { - async fn init(plugin: WasiPlugin) -> Result { - let mut config = Config::default(); - config.async_support(true); - let engine = Engine::new(&config)?; - let mut linker = Linker::new(&engine); + async fn init(module: Vec, plugin: WasiPluginBuilder) -> Result { + let engine = plugin.engine; + let mut linker = plugin.linker; - for (name, add_to_linker) in plugin.host_functions.into_iter() { - add_to_linker(&name, &mut linker)?; - } - - linker - .func_wrap("env", "__command", |x: u32, y: u32| x + y) - .unwrap(); linker.func_wrap("env", "__hello", |x: u32| x * 2).unwrap(); linker.func_wrap("env", "__bye", |x: u32| x / 2).unwrap(); wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; let mut store: Store<_> = Store::new(&engine, plugin.wasi_ctx); - let module = Module::new(&engine, plugin.module)?; + let module = Module::new(&engine, module)?; linker.module_async(&mut store, "", &module).await?; let instance = linker.instantiate_async(&mut store, &module).await?; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index deb5cb5d32..39287fe0da 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -13,12 +13,12 @@ use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; pub async fn new_json(executor: Arc) -> Result { - let plugin = WasiPluginBuilder::new_with_default_ctx() + let plugin = WasiPluginBuilder::new_with_default_ctx()? .host_function("command", |command: String| { // TODO: actual thing std::process::Command::new(command).output().unwrap(); Some("Hello".to_string()) - }) + })? .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) .await?; PluginLspAdapter::new(plugin, executor).await From 47520f0ca1e8f87752aaeaf6d21f338d3459de33 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 9 Jun 2022 10:40:46 +0200 Subject: [PATCH 27/91] Remove dependency on self in call-related functions --- crates/plugin_runtime/src/wasi.rs | 93 +++++++++++++++++++------------ 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 9aec7fc29a..e32249b4f5 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -6,8 +6,8 @@ use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; use wasi_common::{dir, file}; -use wasmtime::IntoFunc; use wasmtime::{Caller, Config, Engine, Instance, Linker, Module, Store, TypedFunc}; +use wasmtime::{IntoFunc, Memory}; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; pub struct WasiResource(u32); @@ -106,12 +106,15 @@ impl WasiPluginBuilder { name: &str, function: impl Fn(A) -> R + Send + Sync + 'static, ) -> Result { - self.linker - .func_wrap("env", name, move |ptr: u32, len: u32| { + self.linker.func_wrap( + "env", + name, + move |ctx: Caller<'_, WasiCtx>, ptr: u32, len: u32| { // TODO: insert serialization code function(todo!()); 7u32 - })?; + }, + )?; Ok(self) } @@ -266,45 +269,56 @@ impl Wasi { /// Takes an item, allocates a buffer, serializes the argument to that buffer, /// and returns a (ptr, len) pair to that buffer. - async fn serialize_to_buffer(&mut self, item: T) -> Result<(u32, u32), Error> { + async fn serialize_to_buffer( + alloc_buffer: TypedFunc, + plugin_memory: &mut Memory, + mut store: &mut Store, + item: T, + ) -> Result<(u32, u32), Error> { // serialize the argument using bincode let item = bincode::serialize(&item)?; let buffer_len = item.len() as u32; // allocate a buffer and write the argument to that buffer - let buffer_ptr = self - .alloc_buffer - .call_async(&mut self.store, buffer_len) - .await?; - let plugin_memory = self - .instance - .get_memory(&mut self.store, "memory") - .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; - plugin_memory.write(&mut self.store, buffer_ptr as usize, &item)?; + let buffer_ptr = alloc_buffer.call_async(&mut store, buffer_len).await?; + plugin_memory.write(&mut store, buffer_ptr as usize, &item)?; Ok((buffer_ptr, buffer_len)) } - /// Takes a ptr to a (ptr, len) pair and returns the corresponding deserialized buffer - fn deserialize_from_buffer(&mut self, buffer: u32) -> Result { + /// Takes `ptr to a `(ptr, len)` pair, and returns `(ptr, len)`. + fn deref_buffer( + plugin_memory: &mut Memory, + store: &mut Store, + buffer: u32, + ) -> Result<(u32, u32), Error> { // create a buffer to read the (ptr, length) pair into // this is a total of 4 + 4 = 8 bytes. let raw_buffer = &mut [0; 8]; - let plugin_memory = self - .instance - .get_memory(&mut self.store, "memory") - .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; - plugin_memory.read(&mut self.store, buffer as usize, raw_buffer)?; + plugin_memory.read(store, buffer as usize, raw_buffer)?; // use these bytes (wasm stores things little-endian) // to get a pointer to the buffer and its length let b = raw_buffer; - let buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; - let buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; + let buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]); + let buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]); + + return Ok((buffer_ptr, buffer_len)); + } + + /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. + fn deserialize_from_buffer( + plugin_memory: &mut Memory, + store: &mut Store, + buffer_ptr: u32, + buffer_len: u32, + ) -> Result { + let buffer_ptr = buffer_ptr as usize; + let buffer_len = buffer_len as usize; let buffer_end = buffer_ptr + buffer_len; // read the buffer at this point into a byte array // deserialize the byte array into the provided serde type - let result = &plugin_memory.data(&mut self.store)[buffer_ptr..buffer_end]; + let result = &plugin_memory.data(store)[buffer_ptr..buffer_end]; let result = bincode::deserialize(result)?; // TODO: this is handled wasm-side, but I'd like to double-check @@ -337,22 +351,31 @@ impl Wasi { // dbg!(&handle.name); // dbg!(serde_json::to_string(&arg)).unwrap(); + let mut plugin_memory = self + .instance + .get_memory(&mut self.store, "memory") + .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; + // write the argument to linear memory // this returns a (ptr, lentgh) pair - let arg_buffer = self.serialize_to_buffer(arg).await?; - - // get the webassembly function we want to actually call - // TODO: precompute handle - // let fun_name = format!("__{}", handle); - // let fun = self - // .instance - // .get_typed_func::<(u32, u32), u32, _>(&mut self.store, &fun_name)?; - let fun = handle.function; + let arg_buffer = + Self::serialize_to_buffer(self.alloc_buffer, &mut plugin_memory, &mut self.store, arg) + .await?; // call the function, passing in the buffer and its length // this returns a ptr to a (ptr, lentgh) pair - let result_buffer = fun.call_async(&mut self.store, arg_buffer).await?; + let result_buffer = handle + .function + .call_async(&mut self.store, arg_buffer) + .await?; + let (result_buffer_ptr, result_buffer_len) = + Self::deref_buffer(&mut plugin_memory, &mut self.store, result_buffer)?; - self.deserialize_from_buffer(result_buffer) + Self::deserialize_from_buffer( + &mut plugin_memory, + &mut self.store, + result_buffer_ptr, + result_buffer_len, + ) } } From 1f5903d16dacf112fa47aaeed0f700cf9f68036e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 9 Jun 2022 12:44:10 +0200 Subject: [PATCH 28/91] Add allocator to store so that it can be used by host functions --- crates/plugin_runtime/src/wasi.rs | 153 +++++++++++++------- crates/zed/src/languages/language_plugin.rs | 14 +- 2 files changed, 109 insertions(+), 58 deletions(-) diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index e32249b4f5..63a07788f4 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -6,7 +6,10 @@ use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; use wasi_common::{dir, file}; -use wasmtime::{Caller, Config, Engine, Instance, Linker, Module, Store, TypedFunc}; +use wasmtime::{ + AsContext, AsContextMut, Caller, Config, Engine, Extern, Instance, Linker, Module, Store, + StoreContext, StoreContextMut, Trap, TypedFunc, +}; use wasmtime::{IntoFunc, Memory}; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; @@ -35,15 +38,6 @@ impl Clone for WasiFn { // } // } -pub struct Wasi { - engine: Engine, - module: Module, - store: Store, - instance: Instance, - alloc_buffer: TypedFunc, - // free_buffer: TypedFunc<(u32, u32), ()>, -} - // type signature derived from: // https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html#method.func_wrap2_async // macro_rules! dynHostFunction { @@ -68,14 +62,10 @@ pub struct Wasi { // }; // } -// This type signature goodness gracious -pub type HostFunction = Box>; - pub struct WasiPluginBuilder { - // host_functions: HashMap) -> Result<(), Error>>>, wasi_ctx: WasiCtx, engine: Engine, - linker: Linker, + linker: Linker, } impl WasiPluginBuilder { @@ -101,7 +91,7 @@ impl WasiPluginBuilder { Self::new(wasi_ctx) } - pub fn host_function( + pub fn host_function( mut self, name: &str, function: impl Fn(A) -> R + Send + Sync + 'static, @@ -109,10 +99,23 @@ impl WasiPluginBuilder { self.linker.func_wrap( "env", name, - move |ctx: Caller<'_, WasiCtx>, ptr: u32, len: u32| { - // TODO: insert serialization code - function(todo!()); - 7u32 + move |mut caller: Caller<'_, WasiCtxAlloc>, ptr: u32, len: u32| { + let mut plugin_memory = match caller.get_export("memory") { + Some(Extern::Memory(mem)) => mem, + _ => return Err(Trap::new("Could not grab slice of plugin memory")), + }; + let args = Wasi::deserialize_from_buffer(&mut plugin_memory, &caller, ptr, len)?; + + let result = function(args); + let buffer = Wasi::serialize_to_buffer( + caller.data().alloc_buffer(), + &mut plugin_memory, + &mut caller, + result, + ) + .await; + + Ok(7u32) }, )?; Ok(self) @@ -123,14 +126,50 @@ impl WasiPluginBuilder { } } -// TODO: remove -/// Represents a to-be-initialized plugin. -/// Please use [`WasiPluginBuilder`], don't use this directly. -pub struct WasiPlugin { - pub module: Vec, - pub wasi_ctx: WasiCtx, - pub host_functions: - HashMap) -> Result<(), Error>>>, +// // TODO: remove +// /// Represents a to-be-initialized plugin. +// /// Please use [`WasiPluginBuilder`], don't use this directly. +// pub struct WasiPlugin { +// pub module: Vec, +// pub wasi_ctx: WasiCtx, +// pub host_functions: +// HashMap) -> Result<(), Error>>>, +// } + +#[derive(Copy, Clone)] +struct WasiAlloc { + alloc_buffer: TypedFunc, + free_buffer: TypedFunc, +} + +struct WasiCtxAlloc { + wasi_ctx: WasiCtx, + alloc: Option, +} + +impl WasiCtxAlloc { + fn alloc_buffer(&self) -> TypedFunc { + self.alloc + .expect("allocator has been not initialized, cannot allocate buffer!") + .alloc_buffer + } + + fn free_buffer(&self) -> TypedFunc { + self.alloc + .expect("allocator has been not initialized, cannot free buffer!") + .free_buffer + } + + fn init_alloc(&mut self, alloc: WasiAlloc) { + self.alloc = Some(alloc) + } +} + +pub struct Wasi { + engine: Engine, + module: Module, + store: Store, + instance: Instance, } impl Wasi { @@ -154,30 +193,40 @@ impl Wasi { impl Wasi { async fn init(module: Vec, plugin: WasiPluginBuilder) -> Result { + // initialize the WebAssembly System Interface context let engine = plugin.engine; let mut linker = plugin.linker; + wasmtime_wasi::add_to_linker(&mut linker, |s| &mut s.wasi_ctx)?; - linker.func_wrap("env", "__hello", |x: u32| x * 2).unwrap(); - linker.func_wrap("env", "__bye", |x: u32| x / 2).unwrap(); - - wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; - - let mut store: Store<_> = Store::new(&engine, plugin.wasi_ctx); + // create a store, note that we can't initialize the allocator, + // because we can't grab the functions until initialized. + let mut store: Store = Store::new( + &engine, + WasiCtxAlloc { + wasi_ctx: plugin.wasi_ctx, + alloc: None, + }, + ); let module = Module::new(&engine, module)?; + // load the provided module into the asynchronous runtime linker.module_async(&mut store, "", &module).await?; let instance = linker.instantiate_async(&mut store, &module).await?; + // now that the module is initialized, + // we can initialize the store's allocator let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; - // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; + let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; + store.data_mut().init_alloc(WasiAlloc { + alloc_buffer, + free_buffer, + }); Ok(Wasi { engine, module, store, instance, - alloc_buffer, - // free_buffer, }) } @@ -194,13 +243,14 @@ impl Wasi { 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(()))?; + let fd = ctx.wasi_ctx.table().push(Box::new(()))?; 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()); + ctx.wasi_ctx + .insert_dir(fd, dir, caps, file_caps, path.as_ref().to_path_buf()); Ok(WasiResource(fd)) } @@ -208,6 +258,7 @@ impl Wasi { pub fn remove_resource(&mut self, resource: WasiResource) -> Result<(), Error> { self.store .data_mut() + .wasi_ctx .table() .delete(resource.0) .ok_or_else(|| anyhow!("Resource did not exist, but a valid handle was passed in"))?; @@ -269,11 +320,11 @@ impl Wasi { /// Takes an item, allocates a buffer, serializes the argument to that buffer, /// and returns a (ptr, len) pair to that buffer. - async fn serialize_to_buffer( + async fn serialize_to_buffer( alloc_buffer: TypedFunc, plugin_memory: &mut Memory, - mut store: &mut Store, - item: T, + mut store: impl AsContextMut, + item: A, ) -> Result<(u32, u32), Error> { // serialize the argument using bincode let item = bincode::serialize(&item)?; @@ -288,7 +339,7 @@ impl Wasi { /// Takes `ptr to a `(ptr, len)` pair, and returns `(ptr, len)`. fn deref_buffer( plugin_memory: &mut Memory, - store: &mut Store, + store: impl AsContext, buffer: u32, ) -> Result<(u32, u32), Error> { // create a buffer to read the (ptr, length) pair into @@ -308,7 +359,7 @@ impl Wasi { /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. fn deserialize_from_buffer( plugin_memory: &mut Memory, - store: &mut Store, + store: impl AsContext, buffer_ptr: u32, buffer_len: u32, ) -> Result { @@ -318,10 +369,10 @@ impl Wasi { // read the buffer at this point into a byte array // deserialize the byte array into the provided serde type - let result = &plugin_memory.data(store)[buffer_ptr..buffer_end]; + let result = &plugin_memory.data(store.as_context())[buffer_ptr..buffer_end]; let result = bincode::deserialize(result)?; - // TODO: this is handled wasm-side, but I'd like to double-check + // TODO: this is handled wasm-side // // deallocate the argument buffer // self.free_buffer.call(&mut self.store, arg_buffer); @@ -358,9 +409,13 @@ impl Wasi { // write the argument to linear memory // this returns a (ptr, lentgh) pair - let arg_buffer = - Self::serialize_to_buffer(self.alloc_buffer, &mut plugin_memory, &mut self.store, arg) - .await?; + let arg_buffer = Self::serialize_to_buffer( + self.store.data().alloc_buffer(), + &mut plugin_memory, + &mut self.store, + arg, + ) + .await?; // call the function, passing in the buffer and its length // this returns a ptr to a (ptr, lentgh) pair diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 39287fe0da..64eac21611 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -1,16 +1,12 @@ -use super::installation::{npm_install_packages, npm_package_latest_version}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use client::http::HttpClient; use futures::lock::Mutex; -use futures::{future::BoxFuture, FutureExt, StreamExt}; -use gpui::executor::{self, Background}; -use isahc::http::version; +use futures::{future::BoxFuture, FutureExt}; +use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Wasi, WasiFn, WasiPlugin, WasiPluginBuilder}; -use serde_json::json; -use std::fs; +use plugin_runtime::{Wasi, WasiFn, WasiPluginBuilder}; use std::{any::Any, path::PathBuf, sync::Arc}; -use util::{ResultExt, TryFutureExt}; +use util::ResultExt; pub async fn new_json(executor: Arc) -> Result { let plugin = WasiPluginBuilder::new_with_default_ctx()? From 7edcf7c42320460f8b271490562bf806e64ff564 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 9 Jun 2022 13:12:35 +0200 Subject: [PATCH 29/91] Factor out serialization code --- crates/plugin_runtime/src/wasi.rs | 69 ++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 63a07788f4..feb7281265 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -91,31 +91,51 @@ impl WasiPluginBuilder { Self::new(wasi_ctx) } - pub fn host_function( + pub fn host_function( mut self, name: &str, function: impl Fn(A) -> R + Send + Sync + 'static, ) -> Result { - self.linker.func_wrap( + self.linker.func_wrap2_async( "env", name, move |mut caller: Caller<'_, WasiCtxAlloc>, ptr: u32, len: u32| { - let mut plugin_memory = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::new("Could not grab slice of plugin memory")), - }; - let args = Wasi::deserialize_from_buffer(&mut plugin_memory, &caller, ptr, len)?; + // TODO: use try block once avaliable + let result: Result<(Memory, Vec), Trap> = (|| { + // grab a handle to the memory + let mut plugin_memory = match caller.get_export("memory") { + Some(Extern::Memory(mem)) => mem, + _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, + }; - let result = function(args); - let buffer = Wasi::serialize_to_buffer( - caller.data().alloc_buffer(), - &mut plugin_memory, - &mut caller, - result, - ) - .await; + // get the args passed from Guest + let args = + Wasi::deserialize_from_buffer(&mut plugin_memory, &caller, ptr, len)?; - Ok(7u32) + // Call the Host-side function + let result: R = function(args); + + // Serialize the result back to guest + let result = Wasi::serialize(result).map_err(|_| { + Trap::new("Could not serialize value returned from function") + })?; + Ok((plugin_memory, result)) + })(); + + Box::new(async move { + let (mut plugin_memory, result) = result?; + + // todo!(); + let (ptr, len) = Wasi::serialize_to_buffer( + caller.data().alloc_buffer(), + &mut plugin_memory, + &mut caller, + result, + ) + .await?; + + Ok(7u32) + }) }, )?; Ok(self) @@ -318,19 +338,22 @@ impl Wasi { // This isn't a problem because Wasm stops executing after the function returns, // so the heap is still valid for our inspection when we want to pull things out. + fn serialize(item: A) -> Result, Error> { + // serialize the argument using bincode + let item = bincode::serialize(&item)?; + Ok(item) + } + /// Takes an item, allocates a buffer, serializes the argument to that buffer, /// and returns a (ptr, len) pair to that buffer. - async fn serialize_to_buffer( + async fn serialize_to_buffer( alloc_buffer: TypedFunc, plugin_memory: &mut Memory, mut store: impl AsContextMut, - item: A, + item: Vec, ) -> Result<(u32, u32), Error> { - // serialize the argument using bincode - let item = bincode::serialize(&item)?; - let buffer_len = item.len() as u32; - // allocate a buffer and write the argument to that buffer + let buffer_len = item.len() as u32; let buffer_ptr = alloc_buffer.call_async(&mut store, buffer_len).await?; plugin_memory.write(&mut store, buffer_ptr as usize, &item)?; Ok((buffer_ptr, buffer_len)) @@ -413,7 +436,7 @@ impl Wasi { self.store.data().alloc_buffer(), &mut plugin_memory, &mut self.store, - arg, + Self::serialize(arg)?, ) .await?; From 5b40734f8031a500ef63f2b8fa8f514415060a2b Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 9 Jun 2022 15:49:55 +0200 Subject: [PATCH 30/91] Change ABI from pair of u32s to single u64 --- crates/plugin/src/lib.rs | 49 ++++--- crates/plugin_macros/src/lib.rs | 45 +++--- crates/plugin_runtime/src/wasi.rs | 143 +++++++++----------- crates/zed/src/languages/language_plugin.rs | 17 ++- plugins/json_language/src/lib.rs | 49 ++++--- 5 files changed, 152 insertions(+), 151 deletions(-) diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs index ec685bc490..06ece26cdb 100644 --- a/crates/plugin/src/lib.rs +++ b/crates/plugin/src/lib.rs @@ -1,50 +1,55 @@ pub use bincode; pub use serde; -#[repr(C)] +// TODO: move the implementation to one place? pub struct __Buffer { - pub ptr: *const u8, - pub len: usize, + pub ptr: u32, // *const u8, + pub len: u32, // usize, +} + +impl __Buffer { + pub fn into_u64(self) -> u64 { + ((self.ptr as u64) << 32) | (self.len as u64) + } + + pub fn from_u64(packed: u64) -> Self { + __Buffer { + ptr: (packed >> 32) as u32, + len: packed as u32, + } + } } /// Allocates a buffer with an exact size. /// We don't return the size because it has to be passed in anyway. #[no_mangle] -pub extern "C" fn __alloc_buffer(len: usize) -> *const u8 { - let vec = vec![0; len]; +pub extern "C" fn __alloc_buffer(len: u32) -> u32 { + let vec = vec![0; len as usize]; let buffer = unsafe { __Buffer::from_vec(vec) }; return buffer.ptr; } -// /// Frees a given buffer, requires the size. -// #[no_mangle] -// pub extern "C" fn __free_buffer(ptr: *const u8, len: usize) { -// let buffer = Buffer { ptr, len }; -// let vec = unsafe { buffer.to_vec() }; -// std::mem::drop(vec); -// } +/// Frees a given buffer, requires the size. +#[no_mangle] +pub extern "C" fn __free_buffer(buffer: u64) { + let vec = unsafe { __Buffer::from_u64(buffer).to_vec() }; + std::mem::drop(vec); +} impl __Buffer { #[inline(always)] pub unsafe fn to_vec(&self) -> Vec { - core::slice::from_raw_parts(self.ptr, self.len).to_vec() + core::slice::from_raw_parts(self.ptr as *const u8, self.len as usize).to_vec() } #[inline(always)] pub unsafe fn from_vec(mut vec: Vec) -> __Buffer { vec.shrink_to(0); - let ptr = vec.as_ptr(); - let len = vec.len(); + let ptr = vec.as_ptr() as u32; + let len = vec.len() as u32; std::mem::forget(vec); __Buffer { ptr, len } } - - #[inline(always)] - pub fn leak_to_heap(self) -> *const __Buffer { - let boxed = Box::new(self); - let ptr = Box::<__Buffer>::into_raw(boxed) as *const __Buffer; - return ptr; - } } pub mod prelude { diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 154b2f2a83..0389d0b56a 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -59,10 +59,9 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { #[no_mangle] // TODO: switch len from usize to u32? - pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { + pub extern "C" fn #outer_fn_name(packed_buffer: u64) -> u64 { // setup - let buffer = ::plugin::__Buffer { ptr, len }; - let data = unsafe { buffer.to_vec() }; + let data = unsafe { ::plugin::__Buffer::from_u64(packed_buffer).to_vec() }; // operation let data: #ty = match ::plugin::bincode::deserialize(&data) { @@ -74,8 +73,8 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { let new_data = new_data.unwrap(); // teardown - let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }; - return new_buffer.leak_to_heap(); + let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }.into_u64(); + return new_buffer; } }) } @@ -101,8 +100,8 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { // block: todo!(), // }; - // let inner_fn_name = format_ident!("{}", inner_fn.sig.ident); - // let outer_fn_name = format_ident!("__{}", inner_fn_name); + let outer_fn_name = format_ident!("{}", fn_declare.sig.ident); + let inner_fn_name = format_ident!("__{}", outer_fn_name); // let variadic = inner_fn.sig.inputs.len(); // let i = (0..variadic).map(syn::Index::from); @@ -135,32 +134,26 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { // TokenStream::from(quote! { // extern "C" { - // #[no_mangle] - // fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer; + // fn #inner_fn_name(buffer: u64) -> u64; // } // #[no_mangle] - // fn #inner_fn_name #params -> #output { - // println!("executing command: {}", string); - // // serialize data - // let data = #collect_params; + // fn #outer_fn_name #args /* (string: &str) */ -> #return_type /* Option> */ { + // dbg!("executing command: {}", string); + // // setup + // let data = #args_collect; // let data = ::plugin::bincode::serialize(&data).unwrap(); // let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; - // let ptr = buffer.ptr; - // let len = buffer.len; - // // leak data to heap - // buffer.leak_to_heap(); - // // call extern function - // let result = unsafe { __command(ptr, len) }; - // // get result - // let result = todo!(); // convert into box - // // deserialize data - // let data: Option = match ::plugin::bincode::deserialize(&data) { + // // operation + // let new_buffer = unsafe { #inner_fn_name(buffer.into_u64()) }; + // let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; + + // // teardown + // match ::plugin::bincode::deserialize(&new_data) { // Ok(d) => d, - // Err(e) => panic!("Data passed to function not deserializable."), - // }; - // return data; + // Err(e) => panic!("Data returned from function not deserializable."), + // } // } // }) todo!() diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index feb7281265..5ea74a143c 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -8,15 +8,34 @@ use serde::{de::DeserializeOwned, Serialize}; use wasi_common::{dir, file}; use wasmtime::{ AsContext, AsContextMut, Caller, Config, Engine, Extern, Instance, Linker, Module, Store, - StoreContext, StoreContextMut, Trap, TypedFunc, + StoreContext, StoreContextMut, Trap, TypedFunc, WasmParams, }; use wasmtime::{IntoFunc, Memory}; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; pub struct WasiResource(u32); +#[repr(C)] +struct WasiBuffer { + ptr: u32, + len: u32, +} + +impl WasiBuffer { + pub fn into_u64(self) -> u64 { + ((self.ptr as u64) << 32) | (self.len as u64) + } + + pub fn from_u64(packed: u64) -> Self { + WasiBuffer { + ptr: (packed >> 32) as u32, + len: packed as u32, + } + } +} + pub struct WasiFn { - function: TypedFunc<(u32, u32), u32>, + function: TypedFunc, _function_type: PhantomData R>, } @@ -31,37 +50,6 @@ impl Clone for WasiFn { } } -// impl WasiFn { -// #[inline(always)] -// pub async fn call(&self, runtime: &mut Wasi, arg: A) -> Result { -// runtime.call(self, arg).await -// } -// } - -// type signature derived from: -// https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html#method.func_wrap2_async -// macro_rules! dynHostFunction { -// () => { -// Box< -// dyn for<'a> Fn(Caller<'a, WasiCtx>, u32, u32) -// -> Box + Send + 'a> -// + Send -// + Sync -// + 'static -// > -// }; -// } - -// macro_rules! implHostFunction { -// () => { -// impl for<'a> Fn(Caller<'a, WasiCtx>, u32, u32) -// -> Box + Send + 'a> -// + Send -// + Sync -// + 'static -// }; -// } - pub struct WasiPluginBuilder { wasi_ctx: WasiCtx, engine: Engine, @@ -73,7 +61,7 @@ impl WasiPluginBuilder { let mut config = Config::default(); config.async_support(true); let engine = Engine::new(&config)?; - let mut linker = Linker::new(&engine); + let linker = Linker::new(&engine); Ok(WasiPluginBuilder { // host_functions: HashMap::new(), @@ -96,10 +84,10 @@ impl WasiPluginBuilder { name: &str, function: impl Fn(A) -> R + Send + Sync + 'static, ) -> Result { - self.linker.func_wrap2_async( + self.linker.func_wrap1_async( "env", - name, - move |mut caller: Caller<'_, WasiCtxAlloc>, ptr: u32, len: u32| { + &format!("__{}", name), + move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { // TODO: use try block once avaliable let result: Result<(Memory, Vec), Trap> = (|| { // grab a handle to the memory @@ -109,8 +97,11 @@ impl WasiPluginBuilder { }; // get the args passed from Guest - let args = - Wasi::deserialize_from_buffer(&mut plugin_memory, &caller, ptr, len)?; + let args = Wasi::deserialize_from_buffer( + &mut plugin_memory, + &caller, + WasiBuffer::from_u64(packed_buffer), + )?; // Call the Host-side function let result: R = function(args); @@ -125,8 +116,7 @@ impl WasiPluginBuilder { Box::new(async move { let (mut plugin_memory, result) = result?; - // todo!(); - let (ptr, len) = Wasi::serialize_to_buffer( + let buffer = Wasi::serialize_to_buffer( caller.data().alloc_buffer(), &mut plugin_memory, &mut caller, @@ -134,7 +124,7 @@ impl WasiPluginBuilder { ) .await?; - Ok(7u32) + Ok(buffer.into_u64()) }) }, )?; @@ -159,7 +149,7 @@ impl WasiPluginBuilder { #[derive(Copy, Clone)] struct WasiAlloc { alloc_buffer: TypedFunc, - free_buffer: TypedFunc, + free_buffer: TypedFunc, } struct WasiCtxAlloc { @@ -174,7 +164,7 @@ impl WasiCtxAlloc { .alloc_buffer } - fn free_buffer(&self) -> TypedFunc { + fn free_buffer(&self) -> TypedFunc { self.alloc .expect("allocator has been not initialized, cannot free buffer!") .free_buffer @@ -351,48 +341,46 @@ impl Wasi { plugin_memory: &mut Memory, mut store: impl AsContextMut, item: Vec, - ) -> Result<(u32, u32), Error> { + ) -> Result { // allocate a buffer and write the argument to that buffer - let buffer_len = item.len() as u32; - let buffer_ptr = alloc_buffer.call_async(&mut store, buffer_len).await?; - plugin_memory.write(&mut store, buffer_ptr as usize, &item)?; - Ok((buffer_ptr, buffer_len)) + let len = item.len() as u32; + let ptr = alloc_buffer.call_async(&mut store, len).await?; + plugin_memory.write(&mut store, ptr as usize, &item)?; + Ok(WasiBuffer { ptr, len }) } - /// Takes `ptr to a `(ptr, len)` pair, and returns `(ptr, len)`. - fn deref_buffer( - plugin_memory: &mut Memory, - store: impl AsContext, - buffer: u32, - ) -> Result<(u32, u32), Error> { - // create a buffer to read the (ptr, length) pair into - // this is a total of 4 + 4 = 8 bytes. - let raw_buffer = &mut [0; 8]; - plugin_memory.read(store, buffer as usize, raw_buffer)?; + // /// Takes `ptr to a `(ptr, len)` pair, and returns `(ptr, len)`. + // fn deref_buffer( + // plugin_memory: &mut Memory, + // store: impl AsContext, + // buffer: u32, + // ) -> Result<(u32, u32), Error> { + // // create a buffer to read the (ptr, length) pair into + // // this is a total of 4 + 4 = 8 bytes. + // let raw_buffer = &mut [0; 8]; + // plugin_memory.read(store, buffer as usize, raw_buffer)?; - // use these bytes (wasm stores things little-endian) - // to get a pointer to the buffer and its length - let b = raw_buffer; - let buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]); - let buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]); + // // use these bytes (wasm stores things little-endian) + // // to get a pointer to the buffer and its length + // let b = raw_buffer; + // let buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]); + // let buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]); - return Ok((buffer_ptr, buffer_len)); - } + // return Ok((buffer_ptr, buffer_len)); + // } /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. fn deserialize_from_buffer( plugin_memory: &mut Memory, store: impl AsContext, - buffer_ptr: u32, - buffer_len: u32, + buffer: WasiBuffer, ) -> Result { - let buffer_ptr = buffer_ptr as usize; - let buffer_len = buffer_len as usize; - let buffer_end = buffer_ptr + buffer_len; + let buffer_start = buffer.ptr as usize; + let buffer_end = buffer_start + buffer.len as usize; // read the buffer at this point into a byte array // deserialize the byte array into the provided serde type - let result = &plugin_memory.data(store.as_context())[buffer_ptr..buffer_end]; + let result = &plugin_memory.data(store.as_context())[buffer_start..buffer_end]; let result = bincode::deserialize(result)?; // TODO: this is handled wasm-side @@ -402,6 +390,7 @@ impl Wasi { Ok(result) } + /// Retrieves the handle to a function of a given type. pub fn function>( &mut self, name: T, @@ -409,7 +398,7 @@ impl Wasi { let fun_name = format!("__{}", name.as_ref()); let fun = self .instance - .get_typed_func::<(u32, u32), u32, _>(&mut self.store, &fun_name)?; + .get_typed_func::(&mut self.store, &fun_name)?; Ok(WasiFn { function: fun, _function_type: PhantomData, @@ -417,6 +406,7 @@ impl Wasi { } // TODO: dont' use as for conversions + /// Asynchronously calls a function defined Guest-side. pub async fn call( &mut self, handle: &WasiFn, @@ -444,16 +434,13 @@ impl Wasi { // this returns a ptr to a (ptr, lentgh) pair let result_buffer = handle .function - .call_async(&mut self.store, arg_buffer) + .call_async(&mut self.store, arg_buffer.into_u64()) .await?; - let (result_buffer_ptr, result_buffer_len) = - Self::deref_buffer(&mut plugin_memory, &mut self.store, result_buffer)?; Self::deserialize_from_buffer( &mut plugin_memory, &mut self.store, - result_buffer_ptr, - result_buffer_len, + WasiBuffer::from_u64(result_buffer), ) } } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 64eac21611..60ec2eb68d 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -12,8 +12,14 @@ pub async fn new_json(executor: Arc) -> Result { let plugin = WasiPluginBuilder::new_with_default_ctx()? .host_function("command", |command: String| { // TODO: actual thing - std::process::Command::new(command).output().unwrap(); - Some("Hello".to_string()) + dbg!(&command); + let mut args = command.split(' '); + let command = args.next().unwrap(); + std::process::Command::new(command) + .args(args) + .output() + .log_err() + .map(|output| dbg!(output.stdout)) })? .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) .await?; @@ -24,7 +30,7 @@ pub struct PluginLspAdapter { name: WasiFn<(), String>, server_args: WasiFn<(), Vec>, fetch_latest_server_version: WasiFn<(), Option>, - fetch_server_binary: WasiFn<(PathBuf, String), Option>, + fetch_server_binary: WasiFn<(PathBuf, String), Result>, cached_server_binary: WasiFn>, label_for_completion: WasiFn>, initialization_options: WasiFn<(), String>, @@ -102,9 +108,10 @@ impl LspAdapter for PluginLspAdapter { async move { let mut runtime = runtime.lock().await; let handle = runtime.attach_path(&container_dir)?; - let result: Option = runtime.call(&function, (container_dir, version)).await?; + let result: Result = + runtime.call(&function, (container_dir, version)).await?; runtime.remove_resource(handle)?; - result.ok_or_else(|| anyhow!("Could not load cached server binary")) + result.map_err(|e| anyhow!("{}", e)) } .boxed() } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index b611579340..f70d620ddb 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -5,7 +5,7 @@ use std::fs; use std::path::PathBuf; // #[import] -// fn my_command(string: &str) -> Option; +// fn command(string: &str) -> Option; // #[no_mangle] // // TODO: switch len from usize to u32? @@ -29,39 +29,34 @@ use std::path::PathBuf; // } extern "C" { - fn __command(ptr: *const u8, len: usize) -> *mut ::plugin::__Buffer; + fn __command(buffer: u64) -> u64; } #[no_mangle] -fn command(string: &str) -> Option { +fn command(string: &str) -> Option> { dbg!("executing command: {}", string); - // serialize data + // setup let data = string; let data = ::plugin::bincode::serialize(&data).unwrap(); let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; - let ptr = buffer.ptr; - let len = buffer.len; - // leak data to heap - buffer.leak_to_heap(); - // call extern function - let result = unsafe { __command(ptr, len) }; - // get result - let new_buffer = unsafe { Box::from_raw(result) }; // convert into box - let new_data = unsafe { new_buffer.to_vec() }; - // deserialize data - let new_data: Option = match ::plugin::bincode::deserialize(&new_data) { + // operation + let new_buffer = unsafe { __command(buffer.into_u64()) }; + let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; + let new_data: Option> = match ::plugin::bincode::deserialize(&new_data) { Ok(d) => d, Err(e) => panic!("Data returned from function not deserializable."), }; + + // teardown return new_data; } // TODO: some sort of macro to generate ABI bindings -extern "C" { - pub fn hello(item: u32) -> u32; - pub fn bye(item: u32) -> u32; -} +// extern "C" { +// pub fn hello(item: u32) -> u32; +// pub fn bye(item: u32) -> u32; +// } // #[bind] // pub async fn name(u32) -> u32 { @@ -99,11 +94,15 @@ pub fn fetch_latest_server_version() -> Option { } // TODO: command returns error code - let output = command("npm info vscode-json-languageserver --json")?; + let output = + command("npm info vscode-json-languageserver --json").expect("could not run command"); // if !output.is_ok() { // return None; // } + let output = String::from_utf8(output).unwrap(); + dbg!(&output); + let mut info: NpmInfo = serde_json::from_str(&output).ok()?; info.versions.pop() } @@ -120,6 +119,8 @@ pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result Result Option { let mut last_version_dir = None; + println!("reading directory"); let mut entries = fs::read_dir(&container_dir).ok()?; while let Some(entry) = entries.next() { + println!("looking at entries"); let entry = entry.ok()?; + println!("some more stuff"); if entry.file_type().ok()?.is_dir() { + println!("this is it!"); + last_version_dir = Some(entry.path()); } } let last_version_dir = last_version_dir?; + println!("here we go"); let bin_path = last_version_dir.join(BIN_PATH); if bin_path.exists() { + dbg!(&bin_path); Some(bin_path) } else { + println!("no binary found"); None } } From 8aef8ab259763415cc443e58f627c075ebd7838e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 10 Jun 2022 18:41:43 +0200 Subject: [PATCH 31/91] Add build.rs to rebuild plugins, and a test plugin --- Cargo.lock | 1 + crates/plugin_runtime/Cargo.toml | 1 + crates/plugin_runtime/build.rs | 47 ++++++++++++++++++++++++ crates/plugin_runtime/src/lib.rs | 62 ++++++++++++++++++++++++++------ plugins/Cargo.lock | 7 ++++ plugins/Cargo.toml | 2 +- plugins/test_plugin/Cargo.toml | 10 ++++++ plugins/test_plugin/src/lib.rs | 44 +++++++++++++++++++++++ 8 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 crates/plugin_runtime/build.rs create mode 100644 plugins/test_plugin/Cargo.toml create mode 100644 plugins/test_plugin/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d66856c322..68f3132b9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3717,6 +3717,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", + "pollster", "serde", "serde_json", "wasi-common", diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index b611dfb6a8..492b7e14c9 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -11,3 +11,4 @@ anyhow = { version = "1.0", features = ["std"] } serde = "1.0" serde_json = "1.0" bincode = "1.3" +pollster = "0.2.5" \ No newline at end of file diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs new file mode 100644 index 0000000000..1cc5b859c6 --- /dev/null +++ b/crates/plugin_runtime/build.rs @@ -0,0 +1,47 @@ +use std::path::Path; + +fn main() { + let base = Path::new("../../plugins"); + + // println!("cargo:rerun-if-changed=../../plugins/*"); + println!("cargo:warning=Rebuilding plugins..."); + + let _ = std::fs::remove_dir_all(base.join("bin")); + let _ = + std::fs::create_dir_all(base.join("bin")).expect("Could not make plugins bin directory"); + + std::process::Command::new("cargo") + .args([ + "build", + "--release", + "--target", + "wasm32-wasi", + "--manifest-path", + base.join("Cargo.toml").to_str().unwrap(), + ]) + .status() + .expect("Could not build plugins"); + + let binaries = std::fs::read_dir(base.join("target/wasm32-wasi/release")) + .expect("Could not find compiled plugins in target"); + println!("cargo:warning={:?}", binaries); + + for file in binaries { + let is_wasm = || { + let path = file.ok()?.path(); + if path.extension()? == "wasm" { + Some(path) + } else { + None + } + }; + + if let Some(path) = is_wasm() { + std::fs::copy(&path, base.join("bin").join(path.file_name().unwrap())) + .expect("Could not copy compiled plugin to bin"); + } + } + + // TODO: create .wat versions + // TODO: optimize with wasm-opt +} diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 109eb9d4e2..7276edc907 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -1,14 +1,56 @@ pub mod wasi; +use pollster::FutureExt as _; pub use wasi::*; -// #[cfg(test)] -// mod tests { -// use super::*; +#[cfg(test)] +mod tests { + use super::*; -// pub fn init_wasi() { -// let plugin = WasiPluginBuilder::new().init(todo!()).unwrap(); -// let handle: WasiFn = plugin.function("hello").unwrap(); -// let result = plugin.call(handle, 27).unwrap(); -// assert_eq!(result, "world 27"); -// } -// } + #[test] + pub fn test_plugin() { + pub struct TestPlugin { + noop: WasiFn<(), ()>, + constant: WasiFn<(), u32>, + identity: WasiFn, + add: WasiFn<(u32, u32), u32>, + swap: WasiFn<(u32, u32), (u32, u32)>, + sort: WasiFn, Vec>, + print: WasiFn, + // and_back: WasiFn, + } + + async { + let mut runtime = WasiPluginBuilder::new_with_default_ctx() + .unwrap() + .host_function("mystery_number", |input: u32| input + 7) + .unwrap() + .init(include_bytes!("../../../plugins/bin/test_plugin.wasm")) + .await + .unwrap(); + + let plugin = TestPlugin { + noop: runtime.function("noop").unwrap(), + constant: runtime.function("constant").unwrap(), + identity: runtime.function("identity").unwrap(), + add: runtime.function("add").unwrap(), + swap: runtime.function("swap").unwrap(), + sort: runtime.function("sort").unwrap(), + print: runtime.function("print").unwrap(), + // and_back: runtime.function("and_back").unwrap(), + }; + + let unsorted = vec![1, 3, 4, 2, 5]; + let sorted = vec![1, 2, 3, 4, 5]; + + assert_eq!(runtime.call(&plugin.noop, ()).await.unwrap(), ()); + assert_eq!(runtime.call(&plugin.constant, ()).await.unwrap(), 27); + assert_eq!(runtime.call(&plugin.identity, 58).await.unwrap(), 58); + assert_eq!(runtime.call(&plugin.add, (3, 4)).await.unwrap(), 7); + assert_eq!(runtime.call(&plugin.swap, (1, 2)).await.unwrap(), (2, 1)); + assert_eq!(runtime.call(&plugin.sort, unsorted).await.unwrap(), sorted); + assert_eq!(runtime.call(&plugin.print, "Hi!".into()).await.unwrap(), ()); + // assert_eq!(runtime.call(&plugin.and_back, 1).await.unwrap(), 8); + } + .block_on() + } +} diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index 8ef3e49d08..8b6fccd276 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -112,6 +112,13 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test_plugin" +version = "0.1.0" +dependencies = [ + "plugin", +] + [[package]] name = "unicode-ident" version = "1.0.0" diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml index 7fcc42a745..912ad8eea2 100644 --- a/plugins/Cargo.toml +++ b/plugins/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["./json_language"] +members = ["./json_language", "./test_plugin"] diff --git a/plugins/test_plugin/Cargo.toml b/plugins/test_plugin/Cargo.toml new file mode 100644 index 0000000000..850b4a7401 --- /dev/null +++ b/plugins/test_plugin/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test_plugin" +version = "0.1.0" +edition = "2021" + +[dependencies] +plugin = { path = "../../crates/plugin" } + +[lib] +crate-type = ["cdylib"] diff --git a/plugins/test_plugin/src/lib.rs b/plugins/test_plugin/src/lib.rs new file mode 100644 index 0000000000..fcb0a29e22 --- /dev/null +++ b/plugins/test_plugin/src/lib.rs @@ -0,0 +1,44 @@ +use plugin::prelude::*; + +#[export] +pub fn noop() {} + +#[export] +pub fn constant() -> u32 { + 27 +} + +#[export] +pub fn identity(i: u32) -> u32 { + i +} + +#[export] +pub fn add(a: u32, b: u32) -> u32 { + a + b +} + +#[export] +pub fn swap(a: u32, b: u32) -> (u32, u32) { + (b, a) +} + +#[export] +pub fn sort(mut list: Vec) -> Vec { + list.sort(); + list +} + +#[export] +pub fn print(string: String) { + println!("to stdout: {}", string); + eprintln!("to stderr: {}", string); +} + +// #[import] +// fn mystery_number(input: u32) -> u32; + +// #[export] +// pub fn and_back(secret: u32) -> u32 { +// mystery_number(secret) +// } From 28f071e50dddd82ed9a7353303ad862de6d80630 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 10 Jun 2022 22:05:06 +0200 Subject: [PATCH 32/91] Split out lifecycle of serialization, buffer is freed now --- crates/plugin_runtime/src/wasi.rs | 93 ++++++++++++++++--------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 5ea74a143c..19acf7a6b6 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -89,34 +89,35 @@ impl WasiPluginBuilder { &format!("__{}", name), move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { // TODO: use try block once avaliable - let result: Result<(Memory, Vec), Trap> = (|| { + let result: Result<(WasiBuffer, Memory, Vec), Trap> = (|| { // grab a handle to the memory let mut plugin_memory = match caller.get_export("memory") { Some(Extern::Memory(mem)) => mem, _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, }; + let buffer = WasiBuffer::from_u64(packed_buffer); + // get the args passed from Guest - let args = Wasi::deserialize_from_buffer( - &mut plugin_memory, - &caller, - WasiBuffer::from_u64(packed_buffer), - )?; + let args = Wasi::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?; // Call the Host-side function let result: R = function(args); // Serialize the result back to guest - let result = Wasi::serialize(result).map_err(|_| { + let result = Wasi::serialize_to_bytes(result).map_err(|_| { Trap::new("Could not serialize value returned from function") })?; - Ok((plugin_memory, result)) + + Ok((buffer, plugin_memory, result)) })(); Box::new(async move { - let (mut plugin_memory, result) = result?; + let (buffer, mut plugin_memory, result) = result?; - let buffer = Wasi::serialize_to_buffer( + Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; + + let buffer = Wasi::bytes_to_buffer( caller.data().alloc_buffer(), &mut plugin_memory, &mut caller, @@ -328,15 +329,30 @@ impl Wasi { // This isn't a problem because Wasm stops executing after the function returns, // so the heap is still valid for our inspection when we want to pull things out. - fn serialize(item: A) -> Result, Error> { + fn serialize_to_bytes(item: A) -> Result, Error> { // serialize the argument using bincode - let item = bincode::serialize(&item)?; - Ok(item) + let bytes = bincode::serialize(&item)?; + Ok(bytes) } + // fn deserialize( + // plugin_memory: &mut Memory, + // mut store: impl AsContextMut, + // buffer: WasiBuffer, + // ) -> Result { + // let buffer_start = buffer.ptr as usize; + // let buffer_end = buffer_start + buffer.len as usize; + + // // read the buffer at this point into a byte array + // // deserialize the byte array into the provided serde type + // let item = &plugin_memory.data(store.as_context())[buffer_start..buffer_end]; + // let item = bincode::deserialize(bytes)?; + // Ok(item) + // } + /// Takes an item, allocates a buffer, serializes the argument to that buffer, /// and returns a (ptr, len) pair to that buffer. - async fn serialize_to_buffer( + async fn bytes_to_buffer( alloc_buffer: TypedFunc, plugin_memory: &mut Memory, mut store: impl AsContextMut, @@ -349,31 +365,11 @@ impl Wasi { Ok(WasiBuffer { ptr, len }) } - // /// Takes `ptr to a `(ptr, len)` pair, and returns `(ptr, len)`. - // fn deref_buffer( - // plugin_memory: &mut Memory, - // store: impl AsContext, - // buffer: u32, - // ) -> Result<(u32, u32), Error> { - // // create a buffer to read the (ptr, length) pair into - // // this is a total of 4 + 4 = 8 bytes. - // let raw_buffer = &mut [0; 8]; - // plugin_memory.read(store, buffer as usize, raw_buffer)?; - - // // use these bytes (wasm stores things little-endian) - // // to get a pointer to the buffer and its length - // let b = raw_buffer; - // let buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]); - // let buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]); - - // return Ok((buffer_ptr, buffer_len)); - // } - /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. - fn deserialize_from_buffer( - plugin_memory: &mut Memory, + fn buffer_to_type( + plugin_memory: &Memory, store: impl AsContext, - buffer: WasiBuffer, + buffer: &WasiBuffer, ) -> Result { let buffer_start = buffer.ptr as usize; let buffer_end = buffer_start + buffer.len as usize; @@ -383,13 +379,20 @@ impl Wasi { let result = &plugin_memory.data(store.as_context())[buffer_start..buffer_end]; let result = bincode::deserialize(result)?; - // TODO: this is handled wasm-side - // // deallocate the argument buffer - // self.free_buffer.call(&mut self.store, arg_buffer); - Ok(result) } + async fn buffer_to_free( + free_buffer: TypedFunc, + mut store: impl AsContextMut, + buffer: WasiBuffer, + ) -> Result<(), Error> { + // deallocate the argument buffer + Ok(free_buffer + .call_async(&mut store, buffer.into_u64()) + .await?) + } + /// Retrieves the handle to a function of a given type. pub fn function>( &mut self, @@ -422,11 +425,11 @@ impl Wasi { // write the argument to linear memory // this returns a (ptr, lentgh) pair - let arg_buffer = Self::serialize_to_buffer( + let arg_buffer = Self::bytes_to_buffer( self.store.data().alloc_buffer(), &mut plugin_memory, &mut self.store, - Self::serialize(arg)?, + Self::serialize_to_bytes(arg)?, ) .await?; @@ -437,10 +440,10 @@ impl Wasi { .call_async(&mut self.store, arg_buffer.into_u64()) .await?; - Self::deserialize_from_buffer( + Self::buffer_to_type( &mut plugin_memory, &mut self.store, - WasiBuffer::from_u64(result_buffer), + &WasiBuffer::from_u64(result_buffer), ) } } From f110945fd662c9db56e2fba4fd67b0ae90204b7b Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 10:24:33 +0200 Subject: [PATCH 33/91] Add functions with multiple arguments to import macro, add test cases --- crates/plugin_macros/src/lib.rs | 121 ++++++++++++++++--------------- crates/plugin_runtime/src/lib.rs | 17 ++++- plugins/test_plugin/src/lib.rs | 31 ++++++-- 3 files changed, 100 insertions(+), 69 deletions(-) diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 0389d0b56a..1d86b6ddaf 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -2,7 +2,9 @@ use core::panic; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, FnArg, ForeignItemFn, ItemFn, Type, Visibility}; +use syn::{ + parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatIdent, Type, Visibility, +}; #[proc_macro_attribute] pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { @@ -32,7 +34,7 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { .iter() .map(|x| match x { FnArg::Receiver(_) => { - panic!("all arguments must have specified types, no `self` allowed") + panic!("All arguments must have specified types, no `self` allowed") } FnArg::Typed(item) => *item.ty.clone(), }) @@ -91,70 +93,69 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { panic!("Exported functions can not take generic parameters"); } - dbg!(&fn_declare.sig); + // let inner_fn_name = format_ident!("{}", fn_declare.sig.ident); + let extern_fn_name = format_ident!("__{}", fn_declare.sig.ident); - // let inner_fn = ItemFn { - // attrs: fn_declare.attrs, - // vis: fn_declare.vis, - // sig: fn_declare.sig, - // block: todo!(), - // }; + let (args, tys): (Vec, Vec) = fn_declare + .sig + .inputs + .clone() + .into_iter() + .map(|x| match x { + FnArg::Receiver(_) => { + panic!("All arguments must have specified types, no `self` allowed") + } + FnArg::Typed(t) => { + if let Pat::Ident(i) = *t.pat { + (i.ident, *t.ty) + } else { + panic!("All function arguments must be identifiers"); + } + } + }) + .unzip(); - let outer_fn_name = format_ident!("{}", fn_declare.sig.ident); - let inner_fn_name = format_ident!("__{}", outer_fn_name); + dbg!("hello"); - // let variadic = inner_fn.sig.inputs.len(); - // let i = (0..variadic).map(syn::Index::from); - // 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(); + let body = TokenStream::from(quote! { + { + // dbg!("executing imported function"); + // setup + let data: (#( #tys ),*) = (#( #args ),*); + let data = ::plugin::bincode::serialize(&data).unwrap(); + let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; - // // this is cursed... - // let (args, ty) = if variadic != 1 { - // ( - // quote! { - // #( data.#i ),* - // }, - // quote! { - // ( #( #t ),* ) - // }, - // ) - // } else { - // let ty = &t[0]; - // (quote! { data }, quote! { #ty }) - // }; + // operation + let new_buffer = unsafe { #extern_fn_name(buffer.into_u64()) }; + let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; - // TokenStream::from(quote! { - // extern "C" { - // fn #inner_fn_name(buffer: u64) -> u64; - // } + // teardown + match ::plugin::bincode::deserialize(&new_data) { + Ok(d) => d, + Err(e) => panic!("Data returned from function not deserializable."), + } + } + }); - // #[no_mangle] - // fn #outer_fn_name #args /* (string: &str) */ -> #return_type /* Option> */ { - // dbg!("executing command: {}", string); - // // setup - // let data = #args_collect; - // let data = ::plugin::bincode::serialize(&data).unwrap(); - // let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; + dbg!("hello2"); - // // operation - // let new_buffer = unsafe { #inner_fn_name(buffer.into_u64()) }; - // let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; + let block = parse_macro_input!(body as Block); - // // teardown - // match ::plugin::bincode::deserialize(&new_data) { - // Ok(d) => d, - // Err(e) => panic!("Data returned from function not deserializable."), - // } - // } - // }) - todo!() + dbg!("hello {:?}", &block); + + let inner_fn = ItemFn { + attrs: fn_declare.attrs, + vis: fn_declare.vis, + sig: fn_declare.sig, + block: Box::new(block), + }; + + TokenStream::from(quote! { + extern "C" { + fn #extern_fn_name(buffer: u64) -> u64; + } + + #[no_mangle] + #inner_fn + }) } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 7276edc907..cbcfb540a1 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -16,7 +16,8 @@ mod tests { swap: WasiFn<(u32, u32), (u32, u32)>, sort: WasiFn, Vec>, print: WasiFn, - // and_back: WasiFn, + and_back: WasiFn, + imports: WasiFn, } async { @@ -24,6 +25,12 @@ mod tests { .unwrap() .host_function("mystery_number", |input: u32| input + 7) .unwrap() + .host_function("import_noop", |_: ()| ()) + .unwrap() + .host_function("import_identity", |input: u32| input) + .unwrap() + .host_function("import_swap", |(a, b): (u32, u32)| (b, a)) + .unwrap() .init(include_bytes!("../../../plugins/bin/test_plugin.wasm")) .await .unwrap(); @@ -36,7 +43,8 @@ mod tests { swap: runtime.function("swap").unwrap(), sort: runtime.function("sort").unwrap(), print: runtime.function("print").unwrap(), - // and_back: runtime.function("and_back").unwrap(), + and_back: runtime.function("and_back").unwrap(), + imports: runtime.function("imports").unwrap(), }; let unsorted = vec![1, 3, 4, 2, 5]; @@ -49,7 +57,10 @@ mod tests { assert_eq!(runtime.call(&plugin.swap, (1, 2)).await.unwrap(), (2, 1)); assert_eq!(runtime.call(&plugin.sort, unsorted).await.unwrap(), sorted); assert_eq!(runtime.call(&plugin.print, "Hi!".into()).await.unwrap(), ()); - // assert_eq!(runtime.call(&plugin.and_back, 1).await.unwrap(), 8); + assert_eq!(runtime.call(&plugin.and_back, 1).await.unwrap(), 8); + assert_eq!(runtime.call(&plugin.imports, 1).await.unwrap(), 8); + + // dbg!("{}", runtime.call(&plugin.and_back, 1).await.unwrap()); } .block_on() } diff --git a/plugins/test_plugin/src/lib.rs b/plugins/test_plugin/src/lib.rs index fcb0a29e22..f0991d8c59 100644 --- a/plugins/test_plugin/src/lib.rs +++ b/plugins/test_plugin/src/lib.rs @@ -35,10 +35,29 @@ pub fn print(string: String) { eprintln!("to stderr: {}", string); } -// #[import] -// fn mystery_number(input: u32) -> u32; +#[import] +fn mystery_number(input: u32) -> u32; -// #[export] -// pub fn and_back(secret: u32) -> u32 { -// mystery_number(secret) -// } +#[export] +pub fn and_back(secret: u32) -> u32 { + mystery_number(secret) +} + +#[import] +fn import_noop() -> (); + +#[import] +fn import_identity(i: u32) -> u32; + +#[import] +fn import_swap(a: u32, b: u32) -> (u32, u32); + +#[export] +pub fn imports(x: u32) -> u32 { + let a = import_identity(7); + import_noop(); + let (b, c) = import_swap(a, x); + assert_eq!(a, c); + assert_eq!(x, b); + a + b // should be 7 + x +} From 31e3a4d208b340e850b5e2c33b8aa9e1f751e9f5 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 12:52:11 +0200 Subject: [PATCH 34/91] WIP: wrap async closures host-side --- crates/plugin_runtime/src/lib.rs | 8 ++ crates/plugin_runtime/src/wasi.rs | 167 +++++++++++++++++++++++++++++- 2 files changed, 173 insertions(+), 2 deletions(-) diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index cbcfb540a1..afbcfd9278 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -20,6 +20,12 @@ mod tests { imports: WasiFn, } + async fn half(a: u32) -> u32 { + a / 2 + } + + let x = half; + async { let mut runtime = WasiPluginBuilder::new_with_default_ctx() .unwrap() @@ -31,6 +37,8 @@ mod tests { .unwrap() .host_function("import_swap", |(a, b): (u32, u32)| (b, a)) .unwrap() + // .host_function_async("import_half", half) + // .unwrap() .init(include_bytes!("../../../plugins/bin/test_plugin.wasm")) .await .unwrap(); diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 19acf7a6b6..595162c7fd 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -79,11 +79,153 @@ impl WasiPluginBuilder { Self::new(wasi_ctx) } - pub fn host_function( + // pub fn host_function_async( + // mut self, + // name: &str, + // function: impl Fn(A) -> Pin + Send + Sync>> + Sync + Send + 'static, + // ) -> Result + // where + // A: DeserializeOwned + Send, + // R: Serialize + Send, + // { + // self.linker.func_wrap1_async( + // "env", + // &format!("__{}", name), + // move |caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { + // // let function = &function; + // Box::new(async move { + // // grab a handle to the memory + // let mut plugin_memory = match caller.get_export("memory") { + // Some(Extern::Memory(mem)) => mem, + // _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, + // }; + + // let buffer = WasiBuffer::from_u64(packed_buffer); + + // // get the args passed from Guest + // let args = Wasi::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?; + + // // Call the Host-side function + // let result: R = function(args).await; + + // // Serialize the result back to guest + // let result = Wasi::serialize_to_bytes(result).map_err(|_| { + // Trap::new("Could not serialize value returned from function") + // })?; + + // // Ok((buffer, plugin_memory, result)) + // Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; + + // let buffer = Wasi::bytes_to_buffer( + // caller.data().alloc_buffer(), + // &mut plugin_memory, + // &mut caller, + // result, + // ) + // .await?; + + // Ok(buffer.into_u64()) + // }) + // }, + // )?; + // Ok(self) + // } + + // pub fn host_function_async(mut self, name: &str, function: F) -> Result + // where + // F: Fn(u64) -> Pin + Send + Sync + 'static>> + // + Send + // + Sync + // + 'static, + // { + // self.linker.func_wrap1_async( + // "env", + // &format!("__{}", name), + // move |_: Caller<'_, WasiCtxAlloc>, _: u64| { + // // let function = &function; + // Box::new(async { + // let function = function; + // // Call the Host-side function + // let result: u64 = function(7).await; + // Ok(result) + // }) + // }, + // )?; + // Ok(self) + // } + + // pub fn host_function_async(mut self, name: &str, function: F) -> Result + // where + // F: Fn(A) -> Pin + Send + 'static>> + Send + Sync + 'static, + // A: DeserializeOwned + Send, + // R: Serialize + Send + Sync, + // { + // self.linker.func_wrap1_async( + // "env", + // &format!("__{}", name), + // move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { + // let function = |args: Vec| { + // let args = args; + // let args: A = Wasi::deserialize_to_type(&args)?; + // Ok(async { + // let result = function(args); + // Wasi::serialize_to_bytes(result.await).map_err(|_| { + // Trap::new("Could not serialize value returned from function").into() + // }) + // }) + // }; + + // // TODO: use try block once avaliable + // let result: Result<(WasiBuffer, Memory, _), Trap> = (|| { + // // grab a handle to the memory + // let mut plugin_memory = match caller.get_export("memory") { + // Some(Extern::Memory(mem)) => mem, + // _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, + // }; + + // let buffer = WasiBuffer::from_u64(packed_buffer); + + // // get the args passed from Guest + // let args = Wasi::buffer_to_bytes(&mut plugin_memory, &mut caller, &buffer)?; + + // // Call the Host-side function + // let result = function(args); + + // Ok((buffer, plugin_memory, result)) + // })(); + + // Box::new(async move { + // let (buffer, mut plugin_memory, thingo) = result?; + // let thingo: Result<_, Error> = thingo; + // let result: Result, Error> = thingo?.await; + + // // Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; + + // // let buffer = Wasi::bytes_to_buffer( + // // caller.data().alloc_buffer(), + // // &mut plugin_memory, + // // &mut caller, + // // result, + // // ) + // // .await?; + + // // Ok(buffer.into_u64()) + // Ok(27) + // }) + // }, + // )?; + // Ok(self) + // } + + pub fn host_function( mut self, name: &str, function: impl Fn(A) -> R + Send + Sync + 'static, - ) -> Result { + ) -> Result + where + A: DeserializeOwned + Send, + R: Serialize + Send + Sync, + { self.linker.func_wrap1_async( "env", &format!("__{}", name), @@ -335,6 +477,12 @@ impl Wasi { Ok(bytes) } + fn deserialize_to_type(bytes: &[u8]) -> Result { + // serialize the argument using bincode + let bytes = bincode::deserialize(bytes)?; + Ok(bytes) + } + // fn deserialize( // plugin_memory: &mut Memory, // mut store: impl AsContextMut, @@ -382,6 +530,21 @@ impl Wasi { Ok(result) } + /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. + fn buffer_to_bytes<'a>( + plugin_memory: &'a Memory, + store: impl AsContext + 'a, + buffer: &WasiBuffer, + ) -> Result, Error> { + let buffer_start = buffer.ptr as usize; + let buffer_end = buffer_start + buffer.len as usize; + + // read the buffer at this point into a byte array + // deserialize the byte array into the provided serde type + let result = plugin_memory.data(store.as_context())[buffer_start..buffer_end].to_vec(); + Ok(result) + } + async fn buffer_to_free( free_buffer: TypedFunc, mut store: impl AsContextMut, From 018fd46901f1cc9f42737403a6dc955a1b8e5e14 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 12:57:30 +0200 Subject: [PATCH 35/91] Rename WasiPlugin -> Plugin, etc. --- crates/plugin_runtime/src/lib.rs | 16 +++--- .../plugin_runtime/src/{wasi.rs => plugin.rs} | 57 ++++++++----------- 2 files changed, 30 insertions(+), 43 deletions(-) rename crates/plugin_runtime/src/{wasi.rs => plugin.rs} (93%) diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index afbcfd9278..81d7ba7e84 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -1,10 +1,10 @@ -pub mod wasi; -use pollster::FutureExt as _; -pub use wasi::*; +pub mod plugin; +pub use plugin::*; #[cfg(test)] mod tests { use super::*; + use pollster::FutureExt as _; #[test] pub fn test_plugin() { @@ -20,14 +20,12 @@ mod tests { imports: WasiFn, } - async fn half(a: u32) -> u32 { - a / 2 - } - - let x = half; + // async fn half(a: u32) -> u32 { + // a / 2 + // } async { - let mut runtime = WasiPluginBuilder::new_with_default_ctx() + let mut runtime = PluginBuilder::new_with_default_ctx() .unwrap() .host_function("mystery_number", |input: u32| input + 7) .unwrap() diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/plugin.rs similarity index 93% rename from crates/plugin_runtime/src/wasi.rs rename to crates/plugin_runtime/src/plugin.rs index 595162c7fd..98d9c2341b 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -1,19 +1,17 @@ -use std::{ - collections::HashMap, fs::File, future::Future, marker::PhantomData, path::Path, pin::Pin, -}; +use std::{fs::File, marker::PhantomData, path::Path}; use anyhow::{anyhow, Error}; use serde::{de::DeserializeOwned, Serialize}; use wasi_common::{dir, file}; +use wasmtime::Memory; use wasmtime::{ - AsContext, AsContextMut, Caller, Config, Engine, Extern, Instance, Linker, Module, Store, - StoreContext, StoreContextMut, Trap, TypedFunc, WasmParams, + AsContext, AsContextMut, Caller, Config, Engine, Extern, Instance, Linker, Module, Store, Trap, + TypedFunc, }; -use wasmtime::{IntoFunc, Memory}; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; -pub struct WasiResource(u32); +pub struct PluginResource(u32); #[repr(C)] struct WasiBuffer { @@ -50,20 +48,20 @@ impl Clone for WasiFn { } } -pub struct WasiPluginBuilder { +pub struct PluginBuilder { wasi_ctx: WasiCtx, engine: Engine, linker: Linker, } -impl WasiPluginBuilder { +impl PluginBuilder { pub fn new(wasi_ctx: WasiCtx) -> Result { let mut config = Config::default(); config.async_support(true); let engine = Engine::new(&config)?; let linker = Linker::new(&engine); - Ok(WasiPluginBuilder { + Ok(PluginBuilder { // host_functions: HashMap::new(), wasi_ctx, engine, @@ -241,13 +239,13 @@ impl WasiPluginBuilder { let buffer = WasiBuffer::from_u64(packed_buffer); // get the args passed from Guest - let args = Wasi::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?; + let args = Plugin::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?; // Call the Host-side function let result: R = function(args); // Serialize the result back to guest - let result = Wasi::serialize_to_bytes(result).map_err(|_| { + let result = Plugin::serialize_to_bytes(result).map_err(|_| { Trap::new("Could not serialize value returned from function") })?; @@ -257,9 +255,10 @@ impl WasiPluginBuilder { Box::new(async move { let (buffer, mut plugin_memory, result) = result?; - Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; + Plugin::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer) + .await?; - let buffer = Wasi::bytes_to_buffer( + let buffer = Plugin::bytes_to_buffer( caller.data().alloc_buffer(), &mut plugin_memory, &mut caller, @@ -274,21 +273,11 @@ impl WasiPluginBuilder { Ok(self) } - pub async fn init>(self, module: T) -> Result { - Wasi::init(module.as_ref().to_vec(), self).await + pub async fn init>(self, module: T) -> Result { + Plugin::init(module.as_ref().to_vec(), self).await } } -// // TODO: remove -// /// Represents a to-be-initialized plugin. -// /// Please use [`WasiPluginBuilder`], don't use this directly. -// pub struct WasiPlugin { -// pub module: Vec, -// pub wasi_ctx: WasiCtx, -// pub host_functions: -// HashMap) -> Result<(), Error>>>, -// } - #[derive(Copy, Clone)] struct WasiAlloc { alloc_buffer: TypedFunc, @@ -318,14 +307,14 @@ impl WasiCtxAlloc { } } -pub struct Wasi { +pub struct Plugin { engine: Engine, module: Module, store: Store, instance: Instance, } -impl Wasi { +impl Plugin { pub fn dump_memory(data: &[u8]) { for (i, byte) in data.iter().enumerate() { if i % 32 == 0 { @@ -344,8 +333,8 @@ impl Wasi { } } -impl Wasi { - async fn init(module: Vec, plugin: WasiPluginBuilder) -> Result { +impl Plugin { + async fn init(module: Vec, plugin: PluginBuilder) -> Result { // initialize the WebAssembly System Interface context let engine = plugin.engine; let mut linker = plugin.linker; @@ -375,7 +364,7 @@ impl Wasi { free_buffer, }); - Ok(Wasi { + Ok(Plugin { engine, module, store, @@ -385,7 +374,7 @@ impl Wasi { /// 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 { + pub fn attach_path>(&mut self, path: T) -> Result { // grab the WASI context let ctx = self.store.data_mut(); @@ -404,11 +393,11 @@ impl Wasi { // return a handle to the resource ctx.wasi_ctx .insert_dir(fd, dir, caps, file_caps, path.as_ref().to_path_buf()); - Ok(WasiResource(fd)) + Ok(PluginResource(fd)) } /// Returns `true` if the resource existed and was removed. - pub fn remove_resource(&mut self, resource: WasiResource) -> Result<(), Error> { + pub fn remove_resource(&mut self, resource: PluginResource) -> Result<(), Error> { self.store .data_mut() .wasi_ctx From a5a0abb8957f1ab1edc5a39935cced4a7ba1007a Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 12:58:07 +0200 Subject: [PATCH 36/91] Update usage of WasiPlugin -> Plugin --- crates/zed/src/languages/language_plugin.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 60ec2eb68d..f7f7720c3a 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,12 +4,12 @@ use futures::lock::Mutex; use futures::{future::BoxFuture, FutureExt}; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Wasi, WasiFn, WasiPluginBuilder}; +use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; pub async fn new_json(executor: Arc) -> Result { - let plugin = WasiPluginBuilder::new_with_default_ctx()? + let plugin = PluginBuilder::new_with_default_ctx()? .host_function("command", |command: String| { // TODO: actual thing dbg!(&command); @@ -35,11 +35,11 @@ pub struct PluginLspAdapter { label_for_completion: WasiFn>, initialization_options: WasiFn<(), String>, executor: Arc, - runtime: Arc>, + runtime: Arc>, } impl PluginLspAdapter { - pub async fn new(mut plugin: Wasi, executor: Arc) -> Result { + pub async fn new(mut plugin: Plugin, executor: Arc) -> Result { Ok(Self { name: plugin.function("name")?, server_args: plugin.function("server_args")?, From 4565f1a976da9794d781d0139abf2f72429bd58f Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 16:06:39 +0200 Subject: [PATCH 37/91] Add async host functions --- crates/plugin_macros/src/lib.rs | 7 -- crates/plugin_runtime/build.rs | 6 +- crates/plugin_runtime/src/lib.rs | 11 ++- crates/plugin_runtime/src/plugin.rs | 111 ++++++++++++++-------------- plugins/json_language/src/lib.rs | 44 +++++------ plugins/test_plugin/src/lib.rs | 8 ++ 6 files changed, 96 insertions(+), 91 deletions(-) diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 1d86b6ddaf..55f31b8e0c 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -115,11 +115,8 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { }) .unzip(); - dbg!("hello"); - let body = TokenStream::from(quote! { { - // dbg!("executing imported function"); // setup let data: (#( #tys ),*) = (#( #args ),*); let data = ::plugin::bincode::serialize(&data).unwrap(); @@ -137,12 +134,8 @@ pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { } }); - dbg!("hello2"); - let block = parse_macro_input!(body as Block); - dbg!("hello {:?}", &block); - let inner_fn = ItemFn { attrs: fn_declare.attrs, vis: fn_declare.vis, diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 1cc5b859c6..a6f0db38ac 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -10,7 +10,7 @@ fn main() { let _ = std::fs::create_dir_all(base.join("bin")).expect("Could not make plugins bin directory"); - std::process::Command::new("cargo") + let build_successful = std::process::Command::new("cargo") .args([ "build", "--release", @@ -20,7 +20,9 @@ fn main() { base.join("Cargo.toml").to_str().unwrap(), ]) .status() - .expect("Could not build plugins"); + .expect("Could not build plugins") + .success(); + assert!(build_successful); let binaries = std::fs::read_dir(base.join("target/wasm32-wasi/release")) .expect("Could not find compiled plugins in target"); diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 81d7ba7e84..92a75ddb3c 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -18,12 +18,9 @@ mod tests { print: WasiFn, and_back: WasiFn, imports: WasiFn, + half_async: WasiFn, } - // async fn half(a: u32) -> u32 { - // a / 2 - // } - async { let mut runtime = PluginBuilder::new_with_default_ctx() .unwrap() @@ -35,8 +32,8 @@ mod tests { .unwrap() .host_function("import_swap", |(a, b): (u32, u32)| (b, a)) .unwrap() - // .host_function_async("import_half", half) - // .unwrap() + .host_function_async("import_half", |a: u32| async move { a / 2 }) + .unwrap() .init(include_bytes!("../../../plugins/bin/test_plugin.wasm")) .await .unwrap(); @@ -51,6 +48,7 @@ mod tests { print: runtime.function("print").unwrap(), and_back: runtime.function("and_back").unwrap(), imports: runtime.function("imports").unwrap(), + half_async: runtime.function("half_async").unwrap(), }; let unsorted = vec![1, 3, 4, 2, 5]; @@ -65,6 +63,7 @@ mod tests { assert_eq!(runtime.call(&plugin.print, "Hi!".into()).await.unwrap(), ()); assert_eq!(runtime.call(&plugin.and_back, 1).await.unwrap(), 8); assert_eq!(runtime.call(&plugin.imports, 1).await.unwrap(), 8); + assert_eq!(runtime.call(&plugin.half_async, 4).await.unwrap(), 2); // dbg!("{}", runtime.call(&plugin.and_back, 1).await.unwrap()); } diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 98d9c2341b..a37cd9e60c 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -1,3 +1,5 @@ +use std::future::Future; +use std::pin::Pin; use std::{fs::File, marker::PhantomData, path::Path}; use anyhow::{anyhow, Error}; @@ -142,7 +144,7 @@ impl PluginBuilder { // move |_: Caller<'_, WasiCtxAlloc>, _: u64| { // // let function = &function; // Box::new(async { - // let function = function; + // // let function = function; // // Call the Host-side function // let result: u64 = function(7).await; // Ok(result) @@ -152,68 +154,69 @@ impl PluginBuilder { // Ok(self) // } - // pub fn host_function_async(mut self, name: &str, function: F) -> Result - // where - // F: Fn(A) -> Pin + Send + 'static>> + Send + Sync + 'static, - // A: DeserializeOwned + Send, - // R: Serialize + Send + Sync, - // { - // self.linker.func_wrap1_async( - // "env", - // &format!("__{}", name), - // move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { - // let function = |args: Vec| { - // let args = args; - // let args: A = Wasi::deserialize_to_type(&args)?; - // Ok(async { - // let result = function(args); - // Wasi::serialize_to_bytes(result.await).map_err(|_| { - // Trap::new("Could not serialize value returned from function").into() - // }) - // }) - // }; + pub fn host_function_async( + mut self, + name: &str, + function: F, + ) -> Result + where + F: Fn(A) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + A: DeserializeOwned + Send + 'static, + R: Serialize + Send + Sync + 'static, + { + self.linker.func_wrap1_async( + "env", + &format!("__{}", name), + move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { + // TODO: use try block once avaliable + let result: Result<(WasiBuffer, Memory, _), Trap> = (|| { + // grab a handle to the memory + let mut plugin_memory = match caller.get_export("memory") { + Some(Extern::Memory(mem)) => mem, + _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, + }; - // // TODO: use try block once avaliable - // let result: Result<(WasiBuffer, Memory, _), Trap> = (|| { - // // grab a handle to the memory - // let mut plugin_memory = match caller.get_export("memory") { - // Some(Extern::Memory(mem)) => mem, - // _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, - // }; + let buffer = WasiBuffer::from_u64(packed_buffer); - // let buffer = WasiBuffer::from_u64(packed_buffer); + // get the args passed from Guest + let args = Plugin::buffer_to_bytes(&mut plugin_memory, &mut caller, &buffer)?; - // // get the args passed from Guest - // let args = Wasi::buffer_to_bytes(&mut plugin_memory, &mut caller, &buffer)?; + let args: A = Plugin::deserialize_to_type(&args)?; - // // Call the Host-side function - // let result = function(args); + // Call the Host-side function + let result = function(args); - // Ok((buffer, plugin_memory, result)) - // })(); + Ok((buffer, plugin_memory, result)) + })(); - // Box::new(async move { - // let (buffer, mut plugin_memory, thingo) = result?; - // let thingo: Result<_, Error> = thingo; - // let result: Result, Error> = thingo?.await; + Box::new(async move { + let (buffer, mut plugin_memory, future) = result?; - // // Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; + let result: R = future.await; + let result: Result, Error> = Plugin::serialize_to_bytes(result) + .map_err(|_| { + Trap::new("Could not serialize value returned from function").into() + }); + let result = result?; - // // let buffer = Wasi::bytes_to_buffer( - // // caller.data().alloc_buffer(), - // // &mut plugin_memory, - // // &mut caller, - // // result, - // // ) - // // .await?; + Plugin::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer) + .await?; - // // Ok(buffer.into_u64()) - // Ok(27) - // }) - // }, - // )?; - // Ok(self) - // } + let buffer = Plugin::bytes_to_buffer( + caller.data().alloc_buffer(), + &mut plugin_memory, + &mut caller, + result, + ) + .await?; + + Ok(buffer.into_u64()) + }) + }, + )?; + Ok(self) + } pub fn host_function( mut self, diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index f70d620ddb..7299a4748d 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -4,8 +4,8 @@ use serde_json::json; use std::fs; use std::path::PathBuf; -// #[import] -// fn command(string: &str) -> Option; +#[import] +fn command(string: &str) -> Option; // #[no_mangle] // // TODO: switch len from usize to u32? @@ -28,29 +28,29 @@ use std::path::PathBuf; // return new_buffer.leak_to_heap(); // } -extern "C" { - fn __command(buffer: u64) -> u64; -} +// extern "C" { +// fn __command(buffer: u64) -> u64; +// } -#[no_mangle] -fn command(string: &str) -> Option> { - dbg!("executing command: {}", string); - // setup - let data = string; - let data = ::plugin::bincode::serialize(&data).unwrap(); - let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; +// #[no_mangle] +// fn command(string: &str) -> Option> { +// dbg!("executing command: {}", string); +// // setup +// let data = string; +// let data = ::plugin::bincode::serialize(&data).unwrap(); +// let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; - // operation - let new_buffer = unsafe { __command(buffer.into_u64()) }; - let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; - let new_data: Option> = match ::plugin::bincode::deserialize(&new_data) { - Ok(d) => d, - Err(e) => panic!("Data returned from function not deserializable."), - }; +// // operation +// let new_buffer = unsafe { __command(buffer.into_u64()) }; +// let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; +// let new_data: Option> = match ::plugin::bincode::deserialize(&new_data) { +// Ok(d) => d, +// Err(e) => panic!("Data returned from function not deserializable."), +// }; - // teardown - return new_data; -} +// // teardown +// return new_data; +// } // TODO: some sort of macro to generate ABI bindings // extern "C" { diff --git a/plugins/test_plugin/src/lib.rs b/plugins/test_plugin/src/lib.rs index f0991d8c59..34d7d4cb13 100644 --- a/plugins/test_plugin/src/lib.rs +++ b/plugins/test_plugin/src/lib.rs @@ -61,3 +61,11 @@ pub fn imports(x: u32) -> u32 { assert_eq!(x, b); a + b // should be 7 + x } + +#[import] +fn import_half(a: u32) -> u32; + +#[export] +pub fn half_async(a: u32) -> u32 { + import_half(a) +} From f61ef446d3b6f0b6360a94595026f24bb8b8d1c8 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 16:50:09 +0200 Subject: [PATCH 38/91] Documentation pass --- crates/plugin_runtime/src/plugin.rs | 128 +++++++++------------------- plugins/json_language/src/lib.rs | 2 +- 2 files changed, 40 insertions(+), 90 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index a37cd9e60c..27633d9eb9 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -13,6 +13,7 @@ use wasmtime::{ }; use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; +/// Represents a resource currently managed by the plugin, like a file descriptor. pub struct PluginResource(u32); #[repr(C)] @@ -34,6 +35,7 @@ impl WasiBuffer { } } +/// Represents a typed WebAssembly function. pub struct WasiFn { function: TypedFunc, _function_type: PhantomData R>, @@ -50,6 +52,10 @@ impl Clone for WasiFn { } } +/// This struct is used to build a new [`Plugin`], using the builder pattern. +/// Create a new default plugin with `PluginBuilder::new_with_default_ctx`, +/// and add host-side exported functions using `host_function` and `host_function_async`. +/// Finalize the plugin by calling [`init`]. pub struct PluginBuilder { wasi_ctx: WasiCtx, engine: Engine, @@ -57,6 +63,8 @@ pub struct PluginBuilder { } impl PluginBuilder { + /// Create a new [`PluginBuilder`] with the given WASI context. + /// Using the default context is a safe bet, see [`new_with_default_context`]. pub fn new(wasi_ctx: WasiCtx) -> Result { let mut config = Config::default(); config.async_support(true); @@ -71,89 +79,17 @@ impl PluginBuilder { }) } + /// Create a new `PluginBuilder` that inherits the + /// host processes' access to `stdout` and `stderr`. pub fn new_with_default_ctx() -> Result { let wasi_ctx = WasiCtxBuilder::new() - .inherit_stdin() + .inherit_stdout() .inherit_stderr() .build(); Self::new(wasi_ctx) } - // pub fn host_function_async( - // mut self, - // name: &str, - // function: impl Fn(A) -> Pin + Send + Sync>> + Sync + Send + 'static, - // ) -> Result - // where - // A: DeserializeOwned + Send, - // R: Serialize + Send, - // { - // self.linker.func_wrap1_async( - // "env", - // &format!("__{}", name), - // move |caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| { - // // let function = &function; - // Box::new(async move { - // // grab a handle to the memory - // let mut plugin_memory = match caller.get_export("memory") { - // Some(Extern::Memory(mem)) => mem, - // _ => return Err(Trap::new("Could not grab slice of plugin memory"))?, - // }; - - // let buffer = WasiBuffer::from_u64(packed_buffer); - - // // get the args passed from Guest - // let args = Wasi::buffer_to_type(&mut plugin_memory, &mut caller, &buffer)?; - - // // Call the Host-side function - // let result: R = function(args).await; - - // // Serialize the result back to guest - // let result = Wasi::serialize_to_bytes(result).map_err(|_| { - // Trap::new("Could not serialize value returned from function") - // })?; - - // // Ok((buffer, plugin_memory, result)) - // Wasi::buffer_to_free(caller.data().free_buffer(), &mut caller, buffer).await?; - - // let buffer = Wasi::bytes_to_buffer( - // caller.data().alloc_buffer(), - // &mut plugin_memory, - // &mut caller, - // result, - // ) - // .await?; - - // Ok(buffer.into_u64()) - // }) - // }, - // )?; - // Ok(self) - // } - - // pub fn host_function_async(mut self, name: &str, function: F) -> Result - // where - // F: Fn(u64) -> Pin + Send + Sync + 'static>> - // + Send - // + Sync - // + 'static, - // { - // self.linker.func_wrap1_async( - // "env", - // &format!("__{}", name), - // move |_: Caller<'_, WasiCtxAlloc>, _: u64| { - // // let function = &function; - // Box::new(async { - // // let function = function; - // // Call the Host-side function - // let result: u64 = function(7).await; - // Ok(result) - // }) - // }, - // )?; - // Ok(self) - // } - + /// Add an `async` host function. See [`host_function`] for details. pub fn host_function_async( mut self, name: &str, @@ -218,6 +154,22 @@ impl PluginBuilder { Ok(self) } + /// Add a new host function to the given `PluginBuilder`. + /// A host function is a function defined host-side, in Rust, + /// that is accessible guest-side, in WebAssembly. + /// You can specify host-side functions to import using + /// the `#[input]` macro attribute: + /// ```ignore + /// #[input] + /// fn total(counts: Vec) -> f64; + /// ``` + /// When loading a plugin, you need to provide all host functions the plugin imports: + /// ```ignore + /// let plugin = PluginBuilder::new_with_default_context() + /// .host_function("total", |counts| counts.iter().fold(0.0, |tot, n| tot + n)) + /// // and so on... + /// ``` + /// And that's a wrap! pub fn host_function( mut self, name: &str, @@ -276,6 +228,8 @@ impl PluginBuilder { Ok(self) } + /// Initializes a [`Plugin`] from a given compiled Wasm module. + /// Both binary (`.wasm`) and text (`.wat`) module formats are supported. pub async fn init>(self, module: T) -> Result { Plugin::init(module.as_ref().to_vec(), self).await } @@ -310,6 +264,8 @@ impl WasiCtxAlloc { } } +/// Represents a WebAssembly plugin, with access to the WebAssembly System Inferface. +/// Build a new plugin using [`PluginBuilder`]. pub struct Plugin { engine: Engine, module: Module, @@ -318,6 +274,8 @@ pub struct Plugin { } impl Plugin { + /// Dumps the *entirety* of Wasm linear memory to `stdout`. + /// Don't call this unless you're debugging a memory issue! pub fn dump_memory(data: &[u8]) { for (i, byte) in data.iter().enumerate() { if i % 32 == 0 { @@ -400,6 +358,8 @@ impl Plugin { } /// Returns `true` if the resource existed and was removed. + /// Currently the only resource we support is adding scoped paths (e.g. folders and files) + /// to plugins using [`attach_path`]. pub fn remove_resource(&mut self, resource: PluginResource) -> Result<(), Error> { self.store .data_mut() @@ -410,16 +370,6 @@ impl Plugin { 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. // the high level is we take a serde type, serialize it to a byte array, // (we're doing this using bincode for now) @@ -463,12 +413,14 @@ impl Plugin { // This isn't a problem because Wasm stops executing after the function returns, // so the heap is still valid for our inspection when we want to pull things out. + /// Serializes a given type to bytes. fn serialize_to_bytes(item: A) -> Result, Error> { // serialize the argument using bincode let bytes = bincode::serialize(&item)?; Ok(bytes) } + /// Deserializes a given type from bytes. fn deserialize_to_type(bytes: &[u8]) -> Result { // serialize the argument using bincode let bytes = bincode::deserialize(bytes)?; @@ -522,6 +474,7 @@ impl Plugin { Ok(result) } + // TODO: don't allocate a new `Vec`! /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. fn buffer_to_bytes<'a>( plugin_memory: &'a Memory, @@ -570,9 +523,6 @@ impl Plugin { handle: &WasiFn, arg: A, ) -> Result { - // dbg!(&handle.name); - // dbg!(serde_json::to_string(&arg)).unwrap(); - let mut plugin_memory = self .instance .get_memory(&mut self.store, "memory") diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 7299a4748d..5d20bd369a 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -5,7 +5,7 @@ use std::fs; use std::path::PathBuf; #[import] -fn command(string: &str) -> Option; +fn command(string: &str) -> Option>; // #[no_mangle] // // TODO: switch len from usize to u32? From 42fc2789139824edc27cc8ecd1b9c7402d072eb6 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 16:58:56 +0200 Subject: [PATCH 39/91] Comment out label_for_completion for now --- crates/zed/src/languages/language_plugin.rs | 40 ++++++++++----------- plugins/json_language/src/lib.rs | 24 ++++++++++--- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index f7f7720c3a..74e29afdfb 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -32,7 +32,7 @@ pub struct PluginLspAdapter { fetch_latest_server_version: WasiFn<(), Option>, fetch_server_binary: WasiFn<(PathBuf, String), Result>, cached_server_binary: WasiFn>, - label_for_completion: WasiFn>, + // label_for_completion: WasiFn>, initialization_options: WasiFn<(), String>, executor: Arc, runtime: Arc>, @@ -46,7 +46,7 @@ impl PluginLspAdapter { fetch_latest_server_version: plugin.function("fetch_latest_server_version")?, fetch_server_binary: plugin.function("fetch_server_binary")?, cached_server_binary: plugin.function("cached_server_binary")?, - label_for_completion: plugin.function("label_for_completion")?, + // label_for_completion: plugin.function("label_for_completion")?, initialization_options: plugin.function("initialization_options")?, executor, runtime: Arc::new(Mutex::new(plugin)), @@ -132,24 +132,24 @@ impl LspAdapter for PluginLspAdapter { fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - fn label_for_completion( - &self, - item: &lsp::CompletionItem, - language: &language::Language, - ) -> Option { - // TODO: Push more of this method down into the plugin. - use lsp::CompletionItemKind as Kind; - let len = item.label.len(); - let grammar = language.grammar()?; - let kind = format!("{:?}", item.kind?); - let name: String = call_block!(self, &self.label_for_completion, kind).log_err()??; - let highlight_id = grammar.highlight_id_for_name(&name)?; - Some(language::CodeLabel { - text: item.label.clone(), - runs: vec![(0..len, highlight_id)], - filter_range: 0..len, - }) - } + // fn label_for_completion( + // &self, + // item: &lsp::CompletionItem, + // language: &language::Language, + // ) -> Option { + // // TODO: Push more of this method down into the plugin. + // use lsp::CompletionItemKind as Kind; + // let len = item.label.len(); + // let grammar = language.grammar()?; + // let kind = format!("{:?}", item.kind?); + // let name: String = call_block!(self, &self.label_for_completion, kind).log_err()??; + // let highlight_id = grammar.highlight_id_for_name(&name)?; + // Some(language::CodeLabel { + // text: item.label.clone(), + // runs: vec![(0..len, highlight_id)], + // filter_range: 0..len, + // }) + // } fn initialization_options(&self) -> Option { let string: String = call_block!(self, &self.initialization_options, ()).log_err()?; diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 5d20bd369a..11be117129 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -169,10 +169,26 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option { } } -#[export] -pub fn label_for_completion(label: String) -> Option { - None -} +// #[export] +// pub fn label_for_completion( +// item: &lsp::CompletionItem, +// // language: &language::Language, +// ) -> Option { +// // TODO: Push more of this method down into the plugin. +// use lsp::CompletionItemKind as Kind; +// let len = item.label.len(); +// let grammar = language.grammar()?; +// let kind = format!("{:?}", item.kind?); + +// // TODO: implementation + +// let highlight_id = grammar.highlight_id_for_name(&name)?; +// Some(language::CodeLabel { +// text: item.label.clone(), +// runs: vec![(0..len, highlight_id)], +// filter_range: 0..len, +// }) +// } #[export] pub fn initialization_options() -> Option { From e5481e2e6507373464ed49e528b22678ac32f773 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 13 Jun 2022 17:21:55 +0200 Subject: [PATCH 40/91] Switch from std::process::Command to smol::process::Command --- crates/plugin_runtime/src/plugin.rs | 6 ++ crates/zed/src/languages/language_plugin.rs | 10 ++- plugins/json_language/src/lib.rs | 74 --------------------- 3 files changed, 13 insertions(+), 77 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 27633d9eb9..a276213dcc 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -66,6 +66,7 @@ impl PluginBuilder { /// Create a new [`PluginBuilder`] with the given WASI context. /// Using the default context is a safe bet, see [`new_with_default_context`]. pub fn new(wasi_ctx: WasiCtx) -> Result { + dbg!("new plugin"); let mut config = Config::default(); config.async_support(true); let engine = Engine::new(&config)?; @@ -231,6 +232,7 @@ impl PluginBuilder { /// Initializes a [`Plugin`] from a given compiled Wasm module. /// Both binary (`.wasm`) and text (`.wat`) module formats are supported. pub async fn init>(self, module: T) -> Result { + dbg!("initializing plugin"); Plugin::init(module.as_ref().to_vec(), self).await } } @@ -296,6 +298,7 @@ impl Plugin { impl Plugin { async fn init(module: Vec, plugin: PluginBuilder) -> Result { + dbg!("started initialization"); // initialize the WebAssembly System Interface context let engine = plugin.engine; let mut linker = plugin.linker; @@ -311,6 +314,7 @@ impl Plugin { }, ); let module = Module::new(&engine, module)?; + dbg!("created store"); // load the provided module into the asynchronous runtime linker.module_async(&mut store, "", &module).await?; @@ -325,6 +329,8 @@ impl Plugin { free_buffer, }); + dbg!("bound funcitons"); + Ok(Plugin { engine, module, diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 74e29afdfb..9c7796b4b2 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -10,16 +10,20 @@ use util::ResultExt; pub async fn new_json(executor: Arc) -> Result { let plugin = PluginBuilder::new_with_default_ctx()? - .host_function("command", |command: String| { + .host_function_async("command", |command: String| async move { // TODO: actual thing dbg!(&command); let mut args = command.split(' '); let command = args.next().unwrap(); - std::process::Command::new(command) + smol::process::Command::new(command) .args(args) .output() + .await .log_err() - .map(|output| dbg!(output.stdout)) + .map(|output| { + dbg!("done running command"); + output.stdout + }) })? .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) .await?; diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 11be117129..9809085547 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -7,77 +7,11 @@ use std::path::PathBuf; #[import] fn command(string: &str) -> Option>; -// #[no_mangle] -// // TODO: switch len from usize to u32? -// pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer { -// // setup -// let buffer = ::plugin::__Buffer { ptr, len }; -// let data = unsafe { buffer.to_vec() }; - -// // operation -// let data: #ty = match ::plugin::bincode::deserialize(&data) { -// Ok(d) => d, -// Err(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(); - -// // teardown -// let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }; -// return new_buffer.leak_to_heap(); -// } - -// extern "C" { -// fn __command(buffer: u64) -> u64; -// } - -// #[no_mangle] -// fn command(string: &str) -> Option> { -// dbg!("executing command: {}", string); -// // setup -// let data = string; -// let data = ::plugin::bincode::serialize(&data).unwrap(); -// let buffer = unsafe { ::plugin::__Buffer::from_vec(data) }; - -// // operation -// let new_buffer = unsafe { __command(buffer.into_u64()) }; -// let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() }; -// let new_data: Option> = match ::plugin::bincode::deserialize(&new_data) { -// Ok(d) => d, -// Err(e) => panic!("Data returned from function not deserializable."), -// }; - -// // teardown -// return new_data; -// } - -// 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 { - -// } - -// #[no_mangle] -// pub extern "C" fn very_unique_name_of_course() -> impl std::future::Future { -// async move { -// std::fs::read_to_string("heck.txt").unwrap().len() as u32 -// } -// } - const BIN_PATH: &'static str = "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; #[export] pub fn name() -> &'static str { - // let number = unsafe { hello(27) }; - // let number = unsafe { bye(28) }; - // println!("got: {}", number); "vscode-json-languageserver" } @@ -101,7 +35,6 @@ pub fn fetch_latest_server_version() -> Option { // } let output = String::from_utf8(output).unwrap(); - dbg!(&output); let mut info: NpmInfo = serde_json::from_str(&output).ok()?; info.versions.pop() @@ -120,7 +53,6 @@ pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result Result Option { let mut last_version_dir = None; - println!("reading directory"); let mut entries = fs::read_dir(&container_dir).ok()?; while let Some(entry) = entries.next() { - println!("looking at entries"); let entry = entry.ok()?; - println!("some more stuff"); if entry.file_type().ok()?.is_dir() { - println!("this is it!"); - last_version_dir = Some(entry.path()); } } let last_version_dir = last_version_dir?; - println!("here we go"); let bin_path = last_version_dir.join(BIN_PATH); if bin_path.exists() { dbg!(&bin_path); From 92c4552146535877850862de21c6e3e7e6781466 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 1 Jul 2022 11:25:40 +0200 Subject: [PATCH 41/91] Isolate smol::Command hang as a test, does not hang --- crates/plugin_runtime/Cargo.toml | 3 ++- crates/plugin_runtime/src/lib.rs | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index 492b7e14c9..a145b12a19 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -11,4 +11,5 @@ anyhow = { version = "1.0", features = ["std"] } serde = "1.0" serde_json = "1.0" bincode = "1.3" -pollster = "0.2.5" \ No newline at end of file +pollster = "0.2.5" +smol = "1.2.5" diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 92a75ddb3c..12b1c3b4f4 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -19,6 +19,7 @@ mod tests { and_back: WasiFn, imports: WasiFn, half_async: WasiFn, + echo_async: WasiFn, } async { @@ -34,6 +35,22 @@ mod tests { .unwrap() .host_function_async("import_half", |a: u32| async move { a / 2 }) .unwrap() + .host_function_async("command_async", |command: String| async move { + // TODO: actual thing + dbg!(&command); + let mut args = command.split(' '); + let command = args.next().unwrap(); + smol::process::Command::new(command) + .args(args) + .output() + .await + .ok() + .map(|output| { + dbg!("Did run command!"); + output.stdout + }) + }) + .unwrap() .init(include_bytes!("../../../plugins/bin/test_plugin.wasm")) .await .unwrap(); @@ -49,6 +66,7 @@ mod tests { and_back: runtime.function("and_back").unwrap(), imports: runtime.function("imports").unwrap(), half_async: runtime.function("half_async").unwrap(), + echo_async: runtime.function("echo_async").unwrap(), }; let unsorted = vec![1, 3, 4, 2, 5]; @@ -64,6 +82,13 @@ mod tests { assert_eq!(runtime.call(&plugin.and_back, 1).await.unwrap(), 8); assert_eq!(runtime.call(&plugin.imports, 1).await.unwrap(), 8); assert_eq!(runtime.call(&plugin.half_async, 4).await.unwrap(), 2); + assert_eq!( + runtime + .call(&plugin.echo_async, "eko".into()) + .await + .unwrap(), + "eko\n" + ); // dbg!("{}", runtime.call(&plugin.and_back, 1).await.unwrap()); } From 37e04320aa7041f2e7d554724b18def8bc990492 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 1 Jul 2022 19:00:43 +0200 Subject: [PATCH 42/91] Checkpoint --- Cargo.lock | 11 +++++++++ crates/plugin_macros/src/lib.rs | 17 ++++++++++++++ crates/zed/Cargo.toml | 3 +++ crates/zed/src/languages/language_plugin.rs | 26 ++++++++++++++------- plugins/test_plugin/src/lib.rs | 12 ++++++++++ 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68f3132b9a..89534cb02f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1950,6 +1950,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "future-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bab12b2506593396c1339caf22beeb6f5cbe95dac5e376b71a3d17cbe2c4630" +dependencies = [ + "pin-project", +] + [[package]] name = "futures" version = "0.3.21" @@ -3720,6 +3729,7 @@ dependencies = [ "pollster", "serde", "serde_json", + "smol", "wasi-common", "wasmtime", "wasmtime-wasi", @@ -7008,6 +7018,7 @@ dependencies = [ "env_logger", "file_finder", "fsevent", + "future-wrap", "futures", "fuzzy", "go_to_line", diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 55f31b8e0c..8a008dffd0 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -6,6 +6,15 @@ use syn::{ parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatIdent, Type, Visibility, }; +/// Attribute macro to be used guest-side within a plugin. +/// ```ignore +/// #[export] +/// pub fn say_hello() -> String { +/// "Hello from Wasm".into() +/// } +/// ``` +/// This macro makes a function defined guest-side avaliable host-side. +/// Note that all arguments and return types must be `serde`. #[proc_macro_attribute] pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { if !args.is_empty() { @@ -81,6 +90,14 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { }) } +/// Attribute macro to be used guest-side within a plugin. +/// ```ignore +/// #[import] +/// pub fn operating_system_name() -> String; +/// ``` +/// This macro makes a function defined host-side avaliable guest-side. +/// Note that all arguments and return types must be `serde`. +/// All that's provided is a signature, as the function is implemented host-side. #[proc_macro_attribute] pub fn import(args: TokenStream, function: TokenStream) -> TokenStream { if !args.is_empty() { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 433c436003..5bb441831b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -102,6 +102,9 @@ tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", re tree-sitter-typescript = "0.20.1" url = "2.2" +# TODO(isaac): remove this +future-wrap = "0.1.1" + [dev-dependencies] text = { path = "../text", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9c7796b4b2..dbca61d7ec 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use client::http::HttpClient; use futures::lock::Mutex; +use futures::Future; use futures::{future::BoxFuture, FutureExt}; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; @@ -8,6 +9,8 @@ use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; +use future_wrap::*; + pub async fn new_json(executor: Arc) -> Result { let plugin = PluginBuilder::new_with_default_ctx()? .host_function_async("command", |command: String| async move { @@ -15,15 +18,20 @@ pub async fn new_json(executor: Arc) -> Result { dbg!(&command); let mut args = command.split(' '); let command = args.next().unwrap(); - smol::process::Command::new(command) - .args(args) - .output() - .await - .log_err() - .map(|output| { - dbg!("done running command"); - output.stdout - }) + dbg!("Running command"); + let future = smol::process::Command::new(command).args(args).output(); + let future = future.wrap(|fut, cx| { + dbg!("Poll command start"); + let res = fut.poll(cx); + dbg!("Poll command end"); + res + }); + dbg!("blocked on future"); + let future = smol::block_on(future); + future.log_err().map(|output| { + dbg!("done running command"); + output.stdout + }) })? .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) .await?; diff --git a/plugins/test_plugin/src/lib.rs b/plugins/test_plugin/src/lib.rs index 34d7d4cb13..5ea50602f3 100644 --- a/plugins/test_plugin/src/lib.rs +++ b/plugins/test_plugin/src/lib.rs @@ -69,3 +69,15 @@ fn import_half(a: u32) -> u32; pub fn half_async(a: u32) -> u32 { import_half(a) } + +#[import] +fn command_async(command: String) -> Option>; + +#[export] +pub fn echo_async(message: String) -> String { + let command = dbg!(format!("echo {}", message)); + let result = command_async(command); + dbg!(&result); + let result = result.expect("Could not run command"); + String::from_utf8_lossy(&result).to_string() +} From 568017da85beda6fae3e2951124edf9c586a296c Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 1 Jul 2022 22:00:15 +0200 Subject: [PATCH 43/91] Annotate that the bug is due to a deadlock, fixing now --- crates/zed/src/languages/language_plugin.rs | 42 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index dbca61d7ec..762e088333 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -6,6 +6,7 @@ use futures::{future::BoxFuture, FutureExt}; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; +use std::task::Poll; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -20,14 +21,22 @@ pub async fn new_json(executor: Arc) -> Result { let command = args.next().unwrap(); dbg!("Running command"); let future = smol::process::Command::new(command).args(args).output(); + dbg!("Awaiting command"); + + #[no_mangle] + fn heck_point() { + dbg!("command awaited"); + } + let future = future.wrap(|fut, cx| { - dbg!("Poll command start"); + dbg!("Poll command!"); + let res = fut.poll(cx); - dbg!("Poll command end"); res }); + let future = future.await; + heck_point(); dbg!("blocked on future"); - let future = smol::block_on(future); future.log_err().map(|output| { dbg!("done running command"); output.stdout @@ -71,11 +80,32 @@ struct Versions { server_version: String, } +// TODO: is this the root cause? +// sketch: +// - smol command is run, hits await, switches +// - name is run, blocked on +// - smol command is still pending +// - name is stuck for some reason(?) +// - no progress made... +// - deadlock!!!? +// I wish there was high-level instrumentation for this... +// - it's totally a deadlock, the proof is in the pudding + macro_rules! call_block { ($self:ident, $name:expr, $arg:expr) => { - $self - .executor - .block(async { $self.runtime.lock().await.call($name, $arg).await }) + $self.executor.block(async { + dbg!("starting to block on something"); + let locked = $self.runtime.lock(); + dbg!("locked runtime"); + // TODO: No blocking calls! + let mut awaited = locked.await; + dbg!("awaited lock"); + let called = awaited.call($name, $arg); + dbg!("called function"); + let result = called.await; + dbg!("awaited result"); + result + }) }; } From 841a9bd2a7883271344bf5fc0ec262017ae59bdd Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 1 Jul 2022 22:50:31 +0200 Subject: [PATCH 44/91] Make into async trait, will refactor everything Monday --- crates/language/src/language.rs | 46 ++++++++++++++------- crates/zed/src/languages/language_plugin.rs | 45 ++++++++++---------- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 8b8184b174..fef7ace24f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -63,48 +63,62 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); +use async_trait::async_trait; + +#[async_trait] pub trait LspAdapter: 'static + Send + Sync { - fn name(&self) -> LanguageServerName; - fn fetch_latest_server_version( + async fn name(&self) -> LanguageServerName; + async fn fetch_latest_server_version( &self, http: Arc, - ) -> BoxFuture<'static, Result>>; - fn fetch_server_binary( + ) -> Result>; + async fn fetch_server_binary( &self, version: Box, http: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result>; - fn cached_server_binary(&self, container_dir: Arc) - -> BoxFuture<'static, Option>; + container_dir: PathBuf, + ) -> Result; + async fn cached_server_binary( + &self, + container_dir: PathBuf, + ) -> BoxFuture<'static, Option>; - fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option { + async fn label_for_completion( + &self, + _: &lsp::CompletionItem, + _: &Language, + ) -> Option { None } - fn label_for_symbol(&self, _: &str, _: lsp::SymbolKind, _: &Language) -> Option { + async fn label_for_symbol( + &self, + _: &str, + _: lsp::SymbolKind, + _: &Language, + ) -> Option { None } - fn server_args(&self) -> Vec { + async fn server_args(&self) -> Vec { Vec::new() } - fn initialization_options(&self) -> Option { + async fn initialization_options(&self) -> Option { None } - fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] { + async fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] { Default::default() } - fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> { + async fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> { None } - fn id_for_language(&self, _name: &str) -> Option { + async fn id_for_language(&self, _name: &str) -> Option { None } } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 762e088333..cd935fc3f3 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -91,38 +91,41 @@ struct Versions { // I wish there was high-level instrumentation for this... // - it's totally a deadlock, the proof is in the pudding -macro_rules! call_block { - ($self:ident, $name:expr, $arg:expr) => { - $self.executor.block(async { - dbg!("starting to block on something"); - let locked = $self.runtime.lock(); - dbg!("locked runtime"); - // TODO: No blocking calls! - let mut awaited = locked.await; - dbg!("awaited lock"); - let called = awaited.call($name, $arg); - dbg!("called function"); - let result = called.await; - dbg!("awaited result"); - result - }) - }; -} +// macro_rules! call_block { +// ($self:ident, $name:expr, $arg:expr) => { +// $self.executor.block(async { +// dbg!("starting to block on something"); +// let locked = $self.runtime.lock(); +// dbg!("locked runtime"); +// // TODO: No blocking calls! +// let mut awaited = locked.await; +// dbg!("awaited lock"); +// let called = awaited.call($name, $arg); +// dbg!("called function"); +// let result = called.await; +// dbg!("awaited result"); +// result +// }) +// }; +// } +// TODO: convert to async trait + +#[async_trait] impl LspAdapter for PluginLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { let name: String = call_block!(self, &self.name, ()).unwrap(); LanguageServerName(name.into()) } - fn server_args<'a>(&'a self) -> Vec { + async fn server_args<'a>(&'a self) -> Vec { call_block!(self, &self.server_args, ()).unwrap() } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, _: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { // let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); let runtime = self.runtime.clone(); let function = self.fetch_latest_server_version; From 2c637b83bf7c85fb6c10564b0b7b7814ea9aea64 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 4 Jul 2022 11:09:44 +0200 Subject: [PATCH 45/91] Work on updating code to be async --- crates/language/src/language.rs | 92 ++++++++++++++++------------- crates/language/src/proto.rs | 15 +++-- crates/plugin_macros/src/lib.rs | 4 +- crates/plugin_runtime/src/plugin.rs | 2 +- crates/project/src/project.rs | 79 ++++++++++++++----------- 5 files changed, 108 insertions(+), 84 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fef7ace24f..189c455258 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -78,10 +78,7 @@ pub trait LspAdapter: 'static + Send + Sync { http: Arc, container_dir: PathBuf, ) -> Result; - async fn cached_server_binary( - &self, - container_dir: PathBuf, - ) -> BoxFuture<'static, Option>; + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -110,11 +107,11 @@ pub trait LspAdapter: 'static + Send + Sync { None } - async fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] { + async fn disk_based_diagnostic_sources(&self) -> Vec { Default::default() } - async fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> { + async fn disk_based_diagnostics_progress_token(&self) -> Option { None } @@ -179,8 +176,8 @@ pub struct FakeLspAdapter { pub name: &'static str, pub capabilities: lsp::ServerCapabilities, pub initializer: Option>, - pub disk_based_diagnostics_progress_token: Option<&'static str>, - pub disk_based_diagnostics_sources: &'static [&'static str], + pub disk_based_diagnostics_progress_token: Option, + pub disk_based_diagnostics_sources: Vec, } #[derive(Clone, Debug, Deserialize)] @@ -359,7 +356,7 @@ impl LanguageRegistry { let server_binary_path = this .lsp_binary_paths .lock() - .entry(adapter.name()) + .entry(adapter.name().await) .or_insert_with(|| { get_server_binary_path( adapter.clone(), @@ -376,7 +373,7 @@ impl LanguageRegistry { .map_err(|e| anyhow!(e)); let server_binary_path = server_binary_path.await?; - let server_args = adapter.server_args(); + let server_args = adapter.server_args().await; let server = lsp::LanguageServer::new( server_id, &server_binary_path, @@ -402,7 +399,7 @@ async fn get_server_binary_path( download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { - let container_dir: Arc = download_dir.join(adapter.name().0.as_ref()).into(); + let container_dir = download_dir.join(adapter.name().await.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -544,32 +541,42 @@ impl Language { self.config.line_comment.as_deref() } - pub fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] { - self.adapter.as_ref().map_or(&[] as &[_], |adapter| { - adapter.disk_based_diagnostic_sources() - }) - } - - pub fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> { - self.adapter - .as_ref() - .and_then(|adapter| adapter.disk_based_diagnostics_progress_token()) - } - - pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { - if let Some(processor) = self.adapter.as_ref() { - processor.process_diagnostics(diagnostics); + pub async fn disk_based_diagnostic_sources(&self) -> Vec { + match self.adapter.as_ref() { + Some(adapter) => adapter.disk_based_diagnostic_sources().await, + None => Vec::new(), } } - pub fn label_for_completion(&self, completion: &lsp::CompletionItem) -> Option { + pub async fn disk_based_diagnostics_progress_token(&self) -> Option { + if let Some(adapter) = self.adapter.as_ref() { + adapter.disk_based_diagnostics_progress_token().await + } else { + None + } + } + + pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { + if let Some(processor) = self.adapter.as_ref() { + processor.process_diagnostics(diagnostics).await; + } + } + + pub async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + ) -> Option { self.adapter .as_ref()? .label_for_completion(completion, self) + .await } - pub fn label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option { - self.adapter.as_ref()?.label_for_symbol(name, kind, self) + pub async fn label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option { + self.adapter + .as_ref()? + .label_for_symbol(name, kind, self) + .await } pub fn highlight_text<'a>( @@ -678,45 +685,46 @@ impl Default for FakeLspAdapter { capabilities: lsp::LanguageServer::full_capabilities(), initializer: None, disk_based_diagnostics_progress_token: None, - disk_based_diagnostics_sources: &[], + disk_based_diagnostics_sources: Vec::new(), } } } #[cfg(any(test, feature = "test-support"))] +#[async_trait] impl LspAdapter for FakeLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName(self.name.into()) } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, _: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { unreachable!(); } - fn fetch_server_binary( + async fn fetch_server_binary( &self, _: Box, _: Arc, - _: Arc, - ) -> BoxFuture<'static, Result> { + _: PathBuf, + ) -> Result { unreachable!(); } - fn cached_server_binary(&self, _: Arc) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, _: PathBuf) -> Option { unreachable!(); } - fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] { - self.disk_based_diagnostics_sources + async fn disk_based_diagnostic_sources(&self) -> Vec { + self.disk_based_diagnostics_sources.clone() } - fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> { - self.disk_based_diagnostics_progress_token + async fn disk_based_diagnostics_progress_token(&self) -> Option { + self.disk_based_diagnostics_progress_token.clone() } } diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 7c7ec65fd8..f1d7a73872 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -397,7 +397,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completion { } } -pub fn deserialize_completion( +pub async fn deserialize_completion( completion: proto::Completion, language: Option<&Arc>, ) -> Result { @@ -413,12 +413,17 @@ pub fn deserialize_completion( Ok(Completion { old_range: old_start..old_end, new_text: completion.new_text, - label: language - .and_then(|l| l.label_for_completion(&lsp_completion)) - .unwrap_or(CodeLabel::plain( + label: { + let label = match language { + Some(l) => l.label_for_completion(&lsp_completion).await, + None => None, + }; + + label.unwrap_or(CodeLabel::plain( lsp_completion.label.clone(), lsp_completion.filter_text.as_deref(), - )), + )) + }, lsp_completion, }) } diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 8a008dffd0..7993b3df62 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -2,9 +2,7 @@ use core::panic; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{ - parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatIdent, Type, Visibility, -}; +use syn::{parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, Type, Visibility}; /// Attribute macro to be used guest-side within a plugin. /// ```ignore diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index a276213dcc..e4cd05db83 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -1,5 +1,5 @@ use std::future::Future; -use std::pin::Pin; + use std::{fs::File, marker::PhantomData, path::Path}; use anyhow::{anyhow, Error}; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0ac3064e56..ad84ad1e39 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -733,8 +733,9 @@ impl Project { for language in self.languages.to_vec() { if let Some(lsp_adapter) = language.lsp_adapter() { if !settings.enable_language_server(Some(&language.name())) { - let lsp_name = lsp_adapter.name(); - for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { + let lsp_name = lsp_adapter.name().await; + // .await; + for (worktree_id, started_lsp_name) in self.started_language_servers.keys() { if lsp_name == *started_lsp_name { language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); } @@ -1648,11 +1649,10 @@ impl Project { this.create_local_worktree(&abs_path, false, cx) }) .await?; + let name = lsp_adapter.name().await; this.update(&mut cx, |this, cx| { - this.language_server_ids.insert( - (worktree.read(cx).id(), language_server_name), - language_server_id, - ); + this.language_servers + .insert((worktree.read(cx).id(), name), (lsp_adapter, lsp_server)); }); (worktree, PathBuf::new()) }; @@ -1799,10 +1799,10 @@ impl Project { Ok(()) } - fn register_buffer_with_language_server( + async fn register_buffer_with_language_server( &mut self, buffer_handle: &ModelHandle, - cx: &mut ModelContext, + cx: &mut ModelContext<'_, Self>, ) { let buffer = buffer_handle.read(cx); let buffer_id = buffer.remote_id(); @@ -1816,7 +1816,7 @@ impl Project { if let Some(language) = buffer.language() { let worktree_id = file.worktree_id(cx); if let Some(adapter) = language.lsp_adapter() { - language_id = adapter.id_for_language(language.name().as_ref()); + language_id = adapter.id_for_language(language.name().as_ref()).await; language_server = self .language_server_ids .get(&(worktree_id, adapter.name())) @@ -1985,6 +1985,7 @@ impl Project { self.language_server_for_buffer(buffer.read(cx), cx)?; if lsp_adapter .disk_based_diagnostics_progress_token() + .await .is_none() { let server_id = language_server.server_id(); @@ -2074,6 +2075,7 @@ impl Project { self.client.http_client(), cx, ); + self.language_servers.insert( server_id, LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { @@ -2405,7 +2407,6 @@ impl Project { } else { return; }; - let server_name = adapter.name(); let stop = self.stop_language_server(worktree_id, server_name.clone(), cx); cx.spawn_weak(|this, mut cx| async move { @@ -2450,7 +2451,7 @@ impl Project { self.update_diagnostics( server_id, params, - adapter.disk_based_diagnostic_sources(), + adapter.disk_based_diagnostic_sources().await, cx, ) .log_err(); @@ -2460,7 +2461,7 @@ impl Project { &mut self, progress: lsp::ProgressParams, server_id: usize, - disk_based_diagnostics_progress_token: Option<&str>, + disk_based_diagnostics_progress_token: Option, cx: &mut ModelContext, ) { let token = match progress.token { @@ -2486,7 +2487,7 @@ impl Project { match progress { lsp::WorkDoneProgress::Begin(report) => { - if Some(token.as_str()) == disk_based_diagnostics_progress_token { + if Some(token) == disk_based_diagnostics_progress_token { language_server_status.has_pending_diagnostic_updates = true; self.disk_based_diagnostics_started(server_id, cx); self.broadcast_language_server_update( @@ -2517,7 +2518,7 @@ impl Project { } } lsp::WorkDoneProgress::Report(report) => { - if Some(token.as_str()) != disk_based_diagnostics_progress_token { + if Some(token) != disk_based_diagnostics_progress_token { self.on_lsp_work_progress( server_id, token.clone(), @@ -2543,7 +2544,7 @@ impl Project { lsp::WorkDoneProgress::End(_) => { language_server_status.progress_tokens.remove(&token); - if Some(token.as_str()) == disk_based_diagnostics_progress_token { + if Some(token) == disk_based_diagnostics_progress_token { language_server_status.has_pending_diagnostic_updates = false; self.disk_based_diagnostics_finished(server_id, cx); self.broadcast_language_server_update( @@ -2692,7 +2693,7 @@ impl Project { &mut self, language_server_id: usize, params: lsp::PublishDiagnosticsParams, - disk_based_sources: &[&str], + disk_based_sources: Vec, cx: &mut ModelContext, ) -> Result<()> { let abs_path = params @@ -2734,9 +2735,8 @@ impl Project { ); } else { let group_id = post_inc(&mut self.next_diagnostic_group_id); - let is_disk_based = source.map_or(false, |source| { - disk_based_sources.contains(&source.as_str()) - }); + let is_disk_based = + source.map_or(false, |source| disk_based_sources.contains(&source)); sources_by_group_id.insert(group_id, source); primary_diagnostic_group_ids @@ -3307,7 +3307,9 @@ impl Project { .languages .select_language(&path) .and_then(|language| { - language.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) + language + .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) + .await }) .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None)); let signature = this.symbol_signature(worktree_id, &path); @@ -3315,7 +3317,7 @@ impl Project { Some(Symbol { source_worktree_id, worktree_id, - language_server_name: adapter.name(), + language_server_name: adapter.name().await, name: lsp_symbol.name, kind: lsp_symbol.kind, label, @@ -3339,10 +3341,9 @@ impl Project { if let Some(this) = this.upgrade(&cx) { this.read_with(&cx, |this, _| { symbols.extend( - response - .symbols - .into_iter() - .filter_map(|symbol| this.deserialize_symbol(symbol).log_err()), + response.symbols.into_iter().filter_map(|symbol| { + this.deserialize_symbol(symbol).log_err().await + }), ); }) } @@ -3548,7 +3549,7 @@ impl Project { new_text, label: language .as_ref() - .and_then(|l| l.label_for_completion(&lsp_completion)) + .and_then(|l| l.label_for_completion(&lsp_completion).await) .unwrap_or_else(|| { CodeLabel::plain( lsp_completion.label.clone(), @@ -3582,7 +3583,7 @@ impl Project { .completions .into_iter() .map(|completion| { - language::proto::deserialize_completion(completion, language.as_ref()) + language::proto::deserialize_completion(completion, language.as_ref()).await }) .collect() }) @@ -5198,7 +5199,8 @@ impl Project { .completion .ok_or_else(|| anyhow!("invalid completion"))?, language, - )?; + ) + .await?; Ok::<_, anyhow::Error>( this.apply_additional_edits_for_completion(buffer, completion, false, cx), ) @@ -5386,7 +5388,7 @@ impl Project { .symbol .ok_or_else(|| anyhow!("invalid symbol"))?; let symbol = this.read_with(&cx, |this, _| { - let symbol = this.deserialize_symbol(symbol)?; + let symbol = this.deserialize_symbol(symbol).await?; let signature = this.symbol_signature(symbol.worktree_id, &symbol.path); if signature == symbol.signature { Ok(symbol) @@ -5591,7 +5593,7 @@ impl Project { }) } - fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result { + async fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result { let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id); let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id); let start = serialized_symbol @@ -5607,9 +5609,18 @@ impl Project { source_worktree_id, worktree_id, language_server_name: LanguageServerName(serialized_symbol.language_server_name.into()), - label: language - .and_then(|language| language.label_for_symbol(&serialized_symbol.name, kind)) - .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)), + label: { + match language { + Some(language) => { + language + .label_for_symbol(&serialized_symbol.name, kind) + .await + } + None => None, + } + .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)) + }, + name: serialized_symbol.name, path, range: PointUtf16::new(start.row, start.column)..PointUtf16::new(end.row, end.column), @@ -5864,6 +5875,8 @@ impl Project { cx: &AppContext, ) -> Option<(&Arc, &Arc)> { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { + // TODO(isaac): this is not a good idea + let name = cx.background().block(language.lsp_adapter()?.name()); let worktree_id = file.worktree_id(cx); let key = (worktree_id, language.lsp_adapter()?.name()); From 61f5326033812569838633ceadf26eb0e2dce577 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 4 Jul 2022 12:23:36 +0200 Subject: [PATCH 46/91] Add timing instrumentation --- crates/plugin_runtime/README.md | 4 +- crates/plugin_runtime/src/plugin.rs | 18 ++++- crates/zed/src/languages/language_plugin.rs | 81 ++++++++++----------- 3 files changed, 57 insertions(+), 46 deletions(-) diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 4e6693db4c..abd6b1e37c 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -10,7 +10,7 @@ Wasm plugins can be run through `wasmtime`, with supported for sandboxed system ## ABI The interface between the host Rust runtime ('Runtime') and plugins implemented in Wasm ('Plugin') is pretty simple. -`Buffer` is a pair of two 4-byte (`u32`) fields: +`Buffer` is a pair of two 4-byte (`u32`) fields, encoded as a single `u64`. ``` struct Buffer { @@ -21,7 +21,7 @@ struct Buffer { All functions that Plugin exports must have the following properties: -- Have the signature `fn(ptr: u32, len: u32) -> u32`, where the return type is a pointer to a `Buffer`, and the arguments are the component parts of a `Buffer`. +- Have the signature `fn(ptr: u64) -> u64`, where both the argument and return types are a `Buffer`: - The input `Buffer` will contain the input arguments serialized to `bincode`. - The output `Buffer` will contain the output arguments serialized to `bincode`. diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index e4cd05db83..47da59eead 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -69,8 +69,12 @@ impl PluginBuilder { dbg!("new plugin"); let mut config = Config::default(); config.async_support(true); + dbg!("Creating engine"); + let start = std::time::Instant::now(); let engine = Engine::new(&config)?; + dbg!(start.elapsed()); let linker = Linker::new(&engine); + dbg!(start.elapsed()); Ok(PluginBuilder { // host_functions: HashMap::new(), @@ -306,6 +310,8 @@ impl Plugin { // create a store, note that we can't initialize the allocator, // because we can't grab the functions until initialized. + dbg!("Creating store"); + let start = std::time::Instant::now(); let mut store: Store = Store::new( &engine, WasiCtxAlloc { @@ -313,12 +319,20 @@ impl Plugin { alloc: None, }, ); - let module = Module::new(&engine, module)?; - dbg!("created store"); + dbg!(start.elapsed()); + let module = Module::new(&engine, module)?; // load the provided module into the asynchronous runtime linker.module_async(&mut store, "", &module).await?; + dbg!("Instantiating module"); + let start = std::time::Instant::now(); let instance = linker.instantiate_async(&mut store, &module).await?; + let end = dbg!(start.elapsed()); + + dbg!("Instantiating second module"); + let start = std::time::Instant::now(); + let instance2 = linker.instantiate_async(&mut store, &module).await?; + let end = dbg!(start.elapsed()); // now that the module is initialized, // we can initialize the store's allocator diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index cd935fc3f3..42693c2c32 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -15,19 +15,16 @@ use future_wrap::*; pub async fn new_json(executor: Arc) -> Result { let plugin = PluginBuilder::new_with_default_ctx()? .host_function_async("command", |command: String| async move { - // TODO: actual thing dbg!(&command); + + // TODO: actual thing let mut args = command.split(' '); let command = args.next().unwrap(); - dbg!("Running command"); + + dbg!("Running external command"); + + let start = std::time::Instant::now(); let future = smol::process::Command::new(command).args(args).output(); - dbg!("Awaiting command"); - - #[no_mangle] - fn heck_point() { - dbg!("command awaited"); - } - let future = future.wrap(|fut, cx| { dbg!("Poll command!"); @@ -35,12 +32,9 @@ pub async fn new_json(executor: Arc) -> Result { res }); let future = future.await; - heck_point(); - dbg!("blocked on future"); - future.log_err().map(|output| { - dbg!("done running command"); - output.stdout - }) + dbg!(start.elapsed()); + + future.log_err().map(|output| output.stdout) })? .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) .await?; @@ -129,16 +123,17 @@ impl LspAdapter for PluginLspAdapter { // let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); let runtime = self.runtime.clone(); let function = self.fetch_latest_server_version; - async move { - let mut runtime = runtime.lock().await; - let versions: Result> = - runtime.call::<_, Option>(&function, ()).await; - versions - .map_err(|e| anyhow!("{}", e))? - .ok_or_else(|| anyhow!("Could not fetch latest server version")) - .map(|v| Box::new(v) as Box<_>) - } - .boxed() + self.executor + .spawn(async move { + let mut runtime = runtime.lock().await; + let versions: Result> = + runtime.call::<_, Option>(&function, ()).await; + versions + .map_err(|e| anyhow!("{}", e))? + .ok_or_else(|| anyhow!("Could not fetch latest server version")) + .map(|v| Box::new(v) as Box<_>) + }) + .boxed() } fn fetch_server_binary( @@ -150,29 +145,31 @@ impl LspAdapter for PluginLspAdapter { let version = *version.downcast::().unwrap(); let runtime = self.runtime.clone(); let function = self.fetch_server_binary; - async move { - let mut runtime = runtime.lock().await; - let handle = runtime.attach_path(&container_dir)?; - let result: Result = - runtime.call(&function, (container_dir, version)).await?; - runtime.remove_resource(handle)?; - result.map_err(|e| anyhow!("{}", e)) - } - .boxed() + self.executor + .spawn(async move { + let mut runtime = runtime.lock().await; + let handle = runtime.attach_path(&container_dir)?; + let result: Result = + runtime.call(&function, (container_dir, version)).await?; + runtime.remove_resource(handle)?; + result.map_err(|e| anyhow!("{}", e)) + }) + .boxed() } fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { let runtime = self.runtime.clone(); let function = self.cached_server_binary; - async move { - let mut runtime = runtime.lock().await; - let handle = runtime.attach_path(&container_dir).ok()?; - let result: Option = runtime.call(&function, container_dir).await.ok()?; - runtime.remove_resource(handle).ok()?; - result - } - .boxed() + self.executor + .spawn(async move { + let mut runtime = runtime.lock().await; + let handle = runtime.attach_path(&container_dir).ok()?; + let result: Option = runtime.call(&function, container_dir).await.ok()?; + runtime.remove_resource(handle).ok()?; + result + }) + .boxed() } fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} From feb6cf67899101b394b1c2df9ce2e6b5184879bf Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 4 Jul 2022 15:29:15 +0200 Subject: [PATCH 47/91] Allow async to infect some more functions --- crates/plugin_runtime/src/plugin.rs | 17 ++----------- crates/project/src/project.rs | 39 ++++++++++++++++------------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 47da59eead..a1229f8d42 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -302,7 +302,7 @@ impl Plugin { impl Plugin { async fn init(module: Vec, plugin: PluginBuilder) -> Result { - dbg!("started initialization"); + dbg!("Initializing new plugin"); // initialize the WebAssembly System Interface context let engine = plugin.engine; let mut linker = plugin.linker; @@ -310,8 +310,6 @@ impl Plugin { // create a store, note that we can't initialize the allocator, // because we can't grab the functions until initialized. - dbg!("Creating store"); - let start = std::time::Instant::now(); let mut store: Store = Store::new( &engine, WasiCtxAlloc { @@ -319,20 +317,11 @@ impl Plugin { alloc: None, }, ); - dbg!(start.elapsed()); - let module = Module::new(&engine, module)?; + // load the provided module into the asynchronous runtime linker.module_async(&mut store, "", &module).await?; - dbg!("Instantiating module"); - let start = std::time::Instant::now(); let instance = linker.instantiate_async(&mut store, &module).await?; - let end = dbg!(start.elapsed()); - - dbg!("Instantiating second module"); - let start = std::time::Instant::now(); - let instance2 = linker.instantiate_async(&mut store, &module).await?; - let end = dbg!(start.elapsed()); // now that the module is initialized, // we can initialize the store's allocator @@ -343,8 +332,6 @@ impl Plugin { free_buffer, }); - dbg!("bound funcitons"); - Ok(Plugin { engine, module, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ad84ad1e39..eac616a874 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,7 +12,7 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; -use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt}; +use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, SinkExt, StreamExt, TryFutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, @@ -708,7 +708,7 @@ impl Project { }) } - fn on_settings_changed(&mut self, cx: &mut ModelContext) { + async fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { let settings = cx.global::(); let mut language_servers_to_start = Vec::new(); @@ -734,7 +734,6 @@ impl Project { if let Some(lsp_adapter) = language.lsp_adapter() { if !settings.enable_language_server(Some(&language.name())) { let lsp_name = lsp_adapter.name().await; - // .await; for (worktree_id, started_lsp_name) in self.started_language_servers.keys() { if lsp_name == *started_lsp_name { language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); @@ -1893,11 +1892,11 @@ impl Project { }); } - fn on_buffer_event( + async fn on_buffer_event( &mut self, buffer: ModelHandle, event: &BufferEvent, - cx: &mut ModelContext, + cx: &mut ModelContext<'_, Self>, ) -> Option<()> { match event { BufferEvent::Operation(operation) => { @@ -2043,12 +2042,12 @@ impl Project { None } - fn start_language_server( + async fn start_language_server( &mut self, worktree_id: WorktreeId, worktree_path: Arc, language: Arc, - cx: &mut ModelContext, + cx: &mut ModelContext<'_, Self>, ) { if !cx .global::() @@ -2395,12 +2394,12 @@ impl Project { None } - fn restart_language_server( + async fn restart_language_server( &mut self, worktree_id: WorktreeId, fallback_path: Arc, language: Arc, - cx: &mut ModelContext, + cx: &mut ModelContext<'_, Self>, ) { let adapter = if let Some(adapter) = language.lsp_adapter() { adapter @@ -2440,12 +2439,12 @@ impl Project { .detach(); } - fn on_lsp_diagnostics_published( + async fn on_lsp_diagnostics_published( &mut self, server_id: usize, mut params: lsp::PublishDiagnosticsParams, adapter: &Arc, - cx: &mut ModelContext, + cx: &mut ModelContext<'_, Self>, ) { adapter.process_diagnostics(&mut params); self.update_diagnostics( @@ -3339,13 +3338,17 @@ impl Project { let response = request.await?; let mut symbols = Vec::new(); if let Some(this) = this.upgrade(&cx) { - this.read_with(&cx, |this, _| { - symbols.extend( - response.symbols.into_iter().filter_map(|symbol| { - this.deserialize_symbol(symbol).log_err().await - }), - ); - }) + let new_symbols = this.read_with(&cx, |this, _| { + response + .symbols + .into_iter() + .map(|symbol| this.deserialize_symbol(symbol)) + }); + for new_symbol in new_symbols { + if let Some(new_symbol) = new_symbol.await.ok() { + symbols.push(new_symbol); + } + } } Ok(symbols) }) From f4b4212932033871de9ffaf9ca5fa44510ffe692 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 4 Jul 2022 17:40:08 +0200 Subject: [PATCH 48/91] More work on transitioning to async, need to figure out when to stop --- crates/plugin_runtime/src/plugin.rs | 7 +-- crates/project/src/project.rs | 63 +++++++++++++++----------- crates/zed/src/languages/c.rs | 22 ++++----- crates/zed/src/languages/go.rs | 26 +++++------ crates/zed/src/languages/json.rs | 24 +++++----- crates/zed/src/languages/python.rs | 26 +++++------ crates/zed/src/languages/rust.rs | 30 ++++++------ crates/zed/src/languages/typescript.rs | 24 +++++----- 8 files changed, 109 insertions(+), 113 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index a1229f8d42..cbcd31ac3a 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -66,15 +66,11 @@ impl PluginBuilder { /// Create a new [`PluginBuilder`] with the given WASI context. /// Using the default context is a safe bet, see [`new_with_default_context`]. pub fn new(wasi_ctx: WasiCtx) -> Result { - dbg!("new plugin"); let mut config = Config::default(); config.async_support(true); - dbg!("Creating engine"); - let start = std::time::Instant::now(); + config.epoch_interruption(true); let engine = Engine::new(&config)?; - dbg!(start.elapsed()); let linker = Linker::new(&engine); - dbg!(start.elapsed()); Ok(PluginBuilder { // host_functions: HashMap::new(), @@ -317,6 +313,7 @@ impl Plugin { alloc: None, }, ); + // store.epoch_deadline_async_yield_and_update(todo!()); let module = Module::new(&engine, module)?; // load the provided module into the asynchronous runtime diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index eac616a874..47cb2b1e3f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -31,8 +31,8 @@ use language::{ Transaction, }; use lsp::{ - DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString, - MarkedString, + CompletionList, DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, + LanguageString, MarkedString, }; use lsp_command::*; use parking_lot::Mutex; @@ -708,7 +708,7 @@ impl Project { }) } - async fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { + fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { let settings = cx.global::(); let mut language_servers_to_start = Vec::new(); @@ -3302,15 +3302,15 @@ impl Project { path = relativize_path(&worktree_abs_path, &abs_path); } - let label = this - .languages - .select_language(&path) - .and_then(|language| { + let label = match this.languages.select_language(&path) { + Some(language) => { language .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) .await - }) - .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None)); + } + None => None, + } + .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None)); let signature = this.symbol_signature(worktree_id, &path); Some(Symbol { @@ -3550,15 +3550,18 @@ impl Project { Some(Completion { old_range, new_text, - label: language - .as_ref() - .and_then(|l| l.label_for_completion(&lsp_completion).await) + label: { + match language.as_ref() { + Some(l) => l.label_for_completion(&lsp_completion).await, + None => None, + } .unwrap_or_else(|| { CodeLabel::plain( lsp_completion.label.clone(), lsp_completion.filter_text.as_deref(), ) - }), + }) + }, lsp_completion, }) }) @@ -3582,13 +3585,14 @@ impl Project { }) .await; - response - .completions - .into_iter() - .map(|completion| { - language::proto::deserialize_completion(completion, language.as_ref()).await - }) - .collect() + let completions = Vec::new(); + for completion in response.completions.into_iter() { + completions.push( + language::proto::deserialize_completion(completion, language.as_ref()) + .await, + ); + } + completions.into_iter().collect() }) } else { Task::ready(Ok(Default::default())) @@ -5189,7 +5193,7 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result { - let apply_additional_edits = this.update(&mut cx, |this, cx| { + let (buffer, completion) = this.update(&mut cx, |this, cx| { let buffer = this .opened_buffers .get(&envelope.payload.buffer_id) @@ -5202,13 +5206,16 @@ impl Project { .completion .ok_or_else(|| anyhow!("invalid completion"))?, language, - ) - .await?; - Ok::<_, anyhow::Error>( - this.apply_additional_edits_for_completion(buffer, completion, false, cx), - ) + ); + Ok::<_, anyhow::Error>((buffer, completion)) })?; + let completion = completion.await?; + + let apply_additional_edits = this.update(&mut cx, |this, cx| { + this.apply_additional_edits_for_completion(buffer, completion, false, cx) + }); + Ok(proto::ApplyCompletionAdditionalEditsResponse { transaction: apply_additional_edits .await? @@ -5390,8 +5397,10 @@ impl Project { .payload .symbol .ok_or_else(|| anyhow!("invalid symbol"))?; + let symbol = this + .read_with(&cx, |this, _| this.deserialize_symbol(symbol)) + .await?; let symbol = this.read_with(&cx, |this, _| { - let symbol = this.deserialize_symbol(symbol).await?; let signature = this.symbol_signature(symbol.worktree_id, &symbol.path); if signature == symbol.signature { Ok(symbol) diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 5dc36b1971..838db5ef9d 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -13,15 +13,16 @@ use util::{ResultExt, TryFutureExt}; pub struct CLspAdapter; +#[async_trait] impl super::LspAdapter for CLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName("clangd".into()) } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, http: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { async move { let release = latest_github_release("clangd/clangd", http).await?; let asset_name = format!("clangd-mac-{}.zip", release.name); @@ -39,12 +40,12 @@ impl super::LspAdapter for CLspAdapter { .boxed() } - fn fetch_server_binary( + async fn fetch_server_binary( &self, version: Box, http: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result> { + container_dir: PathBuf, + ) -> Result { let version = version.downcast::().unwrap(); async move { let zip_path = container_dir.join(format!("clangd_{}.zip", version.name)); @@ -92,10 +93,7 @@ impl super::LspAdapter for CLspAdapter { .boxed() } - fn cached_server_binary( - &self, - container_dir: Arc, - ) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { async move { let mut last_clangd_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -120,7 +118,7 @@ impl super::LspAdapter for CLspAdapter { .boxed() } - fn label_for_completion( + async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Language, @@ -197,7 +195,7 @@ impl super::LspAdapter for CLspAdapter { Some(CodeLabel::plain(label.to_string(), None)) } - fn label_for_symbol( + async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 978af215c1..eb88d7f887 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -22,19 +22,20 @@ lazy_static! { static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap(); } +#[async_trait] impl super::LspAdapter for GoLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName("gopls".into()) } - fn server_args(&self) -> &[&str] { - &["-mode=stdio"] + async fn server_args(&self) -> Vec { + vec!["-mode=stdio".into()] } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, http: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { async move { let release = latest_github_release("golang/tools", http).await?; let version: Option = release.name.strip_prefix("gopls/v").map(str::to_string); @@ -49,12 +50,12 @@ impl super::LspAdapter for GoLspAdapter { .boxed() } - fn fetch_server_binary( + async fn fetch_server_binary( &self, version: Box, _: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result> { + container_dir: PathBuf, + ) -> Result { let version = version.downcast::>().unwrap(); let this = *self; @@ -115,10 +116,7 @@ impl super::LspAdapter for GoLspAdapter { .boxed() } - fn cached_server_binary( - &self, - container_dir: Arc, - ) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { async move { let mut last_binary_path = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -144,7 +142,7 @@ impl super::LspAdapter for GoLspAdapter { .boxed() } - fn label_for_completion( + async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Language, @@ -244,7 +242,7 @@ impl super::LspAdapter for GoLspAdapter { None } - fn label_for_symbol( + async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 7c500da682..6d05f0a63e 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -19,31 +19,32 @@ impl JsonLspAdapter { "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; } +#[async_trait] impl LspAdapter for JsonLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName("vscode-json-languageserver".into()) } - fn server_args(&self) -> Vec { + async fn server_args(&self) -> Vec { vec!["--stdio".into()] } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, _: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { async move { Ok(Box::new(npm_package_latest_version("vscode-json-languageserver").await?) as Box<_>) } .boxed() } - fn fetch_server_binary( + async fn fetch_server_binary( &self, version: Box, _: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result> { + container_dir: PathBuf, + ) -> Result { let version = version.downcast::().unwrap(); async move { let version_dir = container_dir.join(version.as_str()); @@ -76,10 +77,7 @@ impl LspAdapter for JsonLspAdapter { .boxed() } - fn cached_server_binary( - &self, - container_dir: Arc, - ) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -104,13 +102,13 @@ impl LspAdapter for JsonLspAdapter { .boxed() } - fn initialization_options(&self) -> Option { + async fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true })) } - fn id_for_language(&self, name: &str) -> Option { + async fn id_for_language(&self, name: &str) -> Option { if name == "JSON" { Some("jsonc".into()) } else { diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 3c0acd0945..d5f857ae05 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -17,28 +17,29 @@ impl PythonLspAdapter { const BIN_PATH: &'static str = "node_modules/pyright/langserver.index.js"; } +#[async_trait] impl LspAdapter for PythonLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName("pyright".into()) } - fn server_args(&self) -> &[&str] { - &["--stdio"] + async fn server_args(&self) -> Vec { + vec!["--stdio".into()] } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, _: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { async move { Ok(Box::new(npm_package_latest_version("pyright").await?) as Box<_>) }.boxed() } - fn fetch_server_binary( + async fn fetch_server_binary( &self, version: Box, _: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result> { + container_dir: PathBuf, + ) -> Result { let version = version.downcast::().unwrap(); async move { let version_dir = container_dir.join(version.as_str()); @@ -67,10 +68,7 @@ impl LspAdapter for PythonLspAdapter { .boxed() } - fn cached_server_binary( - &self, - container_dir: Arc, - ) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -95,7 +93,7 @@ impl LspAdapter for PythonLspAdapter { .boxed() } - fn label_for_completion( + async fn label_for_completion( &self, item: &lsp::CompletionItem, language: &language::Language, @@ -116,7 +114,7 @@ impl LspAdapter for PythonLspAdapter { }) } - fn label_for_symbol( + async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 97c1901a85..c82fc896eb 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -20,14 +20,14 @@ use util::{ResultExt, TryFutureExt}; pub struct RustLspAdapter; impl LspAdapter for RustLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName("rust-analyzer".into()) } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, http: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { async move { let release = latest_github_release("rust-analyzer/rust-analyzer", http).await?; let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH); @@ -45,12 +45,12 @@ impl LspAdapter for RustLspAdapter { .boxed() } - fn fetch_server_binary( + async fn fetch_server_binary( &self, version: Box, http: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result> { + container_dir: PathBuf, + ) -> Result { async move { let version = version.downcast::().unwrap(); let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); @@ -86,9 +86,9 @@ impl LspAdapter for RustLspAdapter { .boxed() } - fn cached_server_binary( + async fn cached_server_binary( &self, - container_dir: Arc, + container_dir: PathBuf, ) -> BoxFuture<'static, Option> { async move { let mut last = None; @@ -102,15 +102,15 @@ impl LspAdapter for RustLspAdapter { .boxed() } - fn disk_based_diagnostic_sources(&self) -> &'static [&'static str] { - &["rustc"] + async fn disk_based_diagnostic_sources(&self) -> Vec { + vec!["rustc".into()] } - fn disk_based_diagnostics_progress_token(&self) -> Option<&'static str> { - Some("rustAnalyzer/cargo check") + async fn disk_based_diagnostics_progress_token(&self) -> Option { + Some("rustAnalyzer/cargo check".into()) } - fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { + async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { lazy_static! { static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap(); } @@ -130,7 +130,7 @@ impl LspAdapter for RustLspAdapter { } } - fn label_for_completion( + async fn label_for_completion( &self, completion: &lsp::CompletionItem, language: &Language, @@ -206,7 +206,7 @@ impl LspAdapter for RustLspAdapter { None } - fn label_for_symbol( + async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index cd5309761d..0bd5ad44a9 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -23,22 +23,23 @@ struct Versions { server_version: String, } +#[async_trait] impl LspAdapter for TypeScriptLspAdapter { - fn name(&self) -> LanguageServerName { + async fn name(&self) -> LanguageServerName { LanguageServerName("typescript-language-server".into()) } - fn server_args(&self) -> Vec { + async fn server_args(&self) -> Vec { ["--stdio", "--tsserver-path", "node_modules/typescript/lib"] .into_iter() .map(str::to_string) .collect() } - fn fetch_latest_server_version( + async fn fetch_latest_server_version( &self, _: Arc, - ) -> BoxFuture<'static, Result>> { + ) -> Result> { async move { Ok(Box::new(Versions { typescript_version: npm_package_latest_version("typescript").await?, @@ -48,12 +49,12 @@ impl LspAdapter for TypeScriptLspAdapter { .boxed() } - fn fetch_server_binary( + async fn fetch_server_binary( &self, versions: Box, _: Arc, - container_dir: Arc, - ) -> BoxFuture<'static, Result> { + container_dir: PathBuf, + ) -> Result { let versions = versions.downcast::().unwrap(); async move { let version_dir = container_dir.join(&format!( @@ -95,10 +96,7 @@ impl LspAdapter for TypeScriptLspAdapter { .boxed() } - fn cached_server_binary( - &self, - container_dir: Arc, - ) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -123,7 +121,7 @@ impl LspAdapter for TypeScriptLspAdapter { .boxed() } - fn label_for_completion( + async fn label_for_completion( &self, item: &lsp::CompletionItem, language: &language::Language, @@ -146,7 +144,7 @@ impl LspAdapter for TypeScriptLspAdapter { }) } - fn initialization_options(&self) -> Option { + async fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true })) From 7d128e81aa59cf32b8c342aa2b3a996e0f6bbc17 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 4 Jul 2022 18:25:05 +0200 Subject: [PATCH 49/91] Add lsp_settings_changed: Task to Project, need to resolve cx in Project::on_settings_changed --- crates/language/src/proto.rs | 2 +- crates/project/src/project.rs | 259 +++++++++++++++++++++------------- 2 files changed, 162 insertions(+), 99 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index f1d7a73872..e68639b260 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -399,7 +399,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completion { pub async fn deserialize_completion( completion: proto::Completion, - language: Option<&Arc>, + language: Option>, ) -> Result { let old_start = completion .old_start diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 47cb2b1e3f..015862a05f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -113,6 +113,7 @@ pub struct Project { collaborators: HashMap, client_subscriptions: Vec, _subscriptions: Vec, + lsp_settings_changed: Option>, opened_buffer: (Rc>>, watch::Receiver<()>), shared_buffers: HashMap>, loading_buffers: HashMap< @@ -488,6 +489,7 @@ impl Project { next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), initialized_persistent_state: false, + lsp_settings_changed: None, } }) } @@ -602,6 +604,7 @@ impl Project { buffer_snapshots: Default::default(), nonce: StdRng::from_entropy().gen(), initialized_persistent_state: false, + lsp_settings_changed: None, }; for worktree in worktrees { this.add_worktree(&worktree, cx); @@ -710,51 +713,55 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { let settings = cx.global::(); - - let mut language_servers_to_start = Vec::new(); - for buffer in self.opened_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - let buffer = buffer.read(cx); - if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) - { - if settings.enable_language_server(Some(&language.name())) { - let worktree = file.worktree.read(cx); - language_servers_to_start.push(( - worktree.id(), - worktree.as_local().unwrap().abs_path().clone(), - language.clone(), - )); - } - } - } - } - - let mut language_servers_to_stop = Vec::new(); - for language in self.languages.to_vec() { - if let Some(lsp_adapter) = language.lsp_adapter() { - if !settings.enable_language_server(Some(&language.name())) { - let lsp_name = lsp_adapter.name().await; - for (worktree_id, started_lsp_name) in self.started_language_servers.keys() { - if lsp_name == *started_lsp_name { - language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); + self.lsp_settings_changed = Some(cx.spawn(|_, _| async { + let mut language_servers_to_start = Vec::new(); + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + let buffer = buffer.read(cx); + if let Some((file, language)) = + File::from_dyn(buffer.file()).zip(buffer.language()) + { + if settings.enable_language_server(Some(&language.name())) { + let worktree = file.worktree.read(cx); + language_servers_to_start.push(( + worktree.id(), + worktree.as_local().unwrap().abs_path().clone(), + language.clone(), + )); } } } } - } - // Stop all newly-disabled language servers. - for (worktree_id, adapter_name) in language_servers_to_stop { - self.stop_language_server(worktree_id, adapter_name, cx) - .detach(); - } + let mut language_servers_to_stop = Vec::new(); + for language in self.languages.to_vec() { + if let Some(lsp_adapter) = language.lsp_adapter() { + if !settings.enable_language_server(Some(&language.name())) { + let lsp_name = lsp_adapter.name().await; + for (worktree_id, started_lsp_name) in self.started_language_servers.keys() + { + if lsp_name == *started_lsp_name { + language_servers_to_stop + .push((*worktree_id, started_lsp_name.clone())); + } + } + } + } + } - // Start all the newly-enabled language servers. - for (worktree_id, worktree_path, language) in language_servers_to_start { - self.start_language_server(worktree_id, worktree_path, language, cx); - } + // Stop all newly-disabled language servers. + for (worktree_id, adapter_name) in language_servers_to_stop { + self.stop_language_server(worktree_id, adapter_name, cx) + .detach(); + } - cx.notify(); + // Start all the newly-enabled language servers. + for (worktree_id, worktree_path, language) in language_servers_to_start { + self.start_language_server(worktree_id, worktree_path, language, cx); + } + + cx.notify(); + })) } pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option> { @@ -3482,19 +3489,18 @@ impl Project { let snapshot = this.snapshot(); let clipped_position = this.clip_point_utf16(position, Bias::Left); let mut range_for_token = None; - Ok(completions - .into_iter() - .filter_map(|lsp_completion| { - // For now, we can only handle additional edits if they are returned - // when resolving the completion, not if they are present initially. - if lsp_completion - .additional_text_edits - .as_ref() - .map_or(false, |edits| !edits.is_empty()) - { - return None; - } + let result = Vec::new(); + for lsp_completion in completions.into_iter() { + // For now, we can only handle additional edits if they are returned + // when resolving the completion, not if they are present initially. + if lsp_completion + .additional_text_edits + .as_ref() + .map_or(false, |edits| !edits.is_empty()) + { + continue; + } let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then @@ -3540,9 +3546,17 @@ impl Project { text.clone(), ) } - Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { - log::info!("unsupported insert/replace completion"); - return None; + ( + snapshot.anchor_before(start)..snapshot.anchor_after(end), + edit.new_text.clone(), + ) + } + // If the language server does not provide a range, then infer + // the range based on the syntax tree. + None => { + if position != clipped_position { + log::info!("completion out of expected range"); + continue; } }; @@ -3560,12 +3574,54 @@ impl Project { lsp_completion.label.clone(), lsp_completion.filter_text.as_deref(), ) + let Range { start, end } = range_for_token + .get_or_insert_with(|| { + let offset = position.to_offset(&snapshot); + let (range, kind) = snapshot.surrounding_word(offset); + if kind == Some(CharKind::Word) { + range + } else { + offset..offset + } }) - }, - lsp_completion, - }) - }) - .collect()) + .clone(); + let text = lsp_completion + .insert_text + .as_ref() + .unwrap_or(&lsp_completion.label) + .clone(); + ( + snapshot.anchor_before(start)..snapshot.anchor_after(end), + text.clone(), + ) + } + Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { + log::info!("unsupported insert/replace completion"); + continue; + } + }; + + let completion = Completion { + old_range, + new_text, + label: { + match language.as_ref() { + Some(l) => l.label_for_completion(&lsp_completion).await, + None => None, + } + .unwrap_or_else(|| { + CodeLabel::plain( + lsp_completion.label.clone(), + lsp_completion.filter_text.as_deref(), + ) + }) + }, + lsp_completion, + }; + + result.push(completion); + } + Ok(result) }) }) } else if let Some(project_id) = self.remote_id() { @@ -3587,10 +3643,8 @@ impl Project { let completions = Vec::new(); for completion in response.completions.into_iter() { - completions.push( - language::proto::deserialize_completion(completion, language.as_ref()) - .await, - ); + completions + .push(language::proto::deserialize_completion(completion, language).await); } completions.into_iter().collect() }) @@ -5205,7 +5259,7 @@ impl Project { .payload .completion .ok_or_else(|| anyhow!("invalid completion"))?, - language, + language.cloned(), ); Ok::<_, anyhow::Error>((buffer, completion)) })?; @@ -5605,43 +5659,52 @@ impl Project { }) } - async fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result { - let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id); - let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id); - let start = serialized_symbol - .start - .ok_or_else(|| anyhow!("invalid start"))?; - let end = serialized_symbol - .end - .ok_or_else(|| anyhow!("invalid end"))?; - let kind = unsafe { mem::transmute(serialized_symbol.kind) }; - let path = PathBuf::from(serialized_symbol.path); - let language = self.languages.select_language(&path); - Ok(Symbol { - source_worktree_id, - worktree_id, - language_server_name: LanguageServerName(serialized_symbol.language_server_name.into()), - label: { - match language { - Some(language) => { - language - .label_for_symbol(&serialized_symbol.name, kind) - .await + fn deserialize_symbol( + &self, + serialized_symbol: proto::Symbol, + ) -> impl Future> { + let languages = self.languages.clone(); + async move { + let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id); + let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id); + let start = serialized_symbol + .start + .ok_or_else(|| anyhow!("invalid start"))?; + let end = serialized_symbol + .end + .ok_or_else(|| anyhow!("invalid end"))?; + let kind = unsafe { mem::transmute(serialized_symbol.kind) }; + let path = PathBuf::from(serialized_symbol.path); + let language = languages.select_language(&path); + Ok(Symbol { + source_worktree_id, + worktree_id, + language_server_name: LanguageServerName( + serialized_symbol.language_server_name.into(), + ), + label: { + match language { + Some(language) => { + language + .label_for_symbol(&serialized_symbol.name, kind) + .await + } + None => None, } - None => None, - } - .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)) - }, + .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)) + }, - name: serialized_symbol.name, - path, - range: PointUtf16::new(start.row, start.column)..PointUtf16::new(end.row, end.column), - kind, - signature: serialized_symbol - .signature - .try_into() - .map_err(|_| anyhow!("invalid signature"))?, - }) + name: serialized_symbol.name, + path, + range: PointUtf16::new(start.row, start.column) + ..PointUtf16::new(end.row, end.column), + kind, + signature: serialized_symbol + .signature + .try_into() + .map_err(|_| anyhow!("invalid signature"))?, + }) + } } async fn handle_buffer_saved( From 4dad2eb7d79b50013e5e3417b5755c2d3529fe96 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 09:27:03 +0200 Subject: [PATCH 50/91] Refactor closure to extract async --- crates/project/src/project.rs | 63 +++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 015862a05f..62ce02d71c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3485,11 +3485,18 @@ impl Project { Default::default() }; - source_buffer_handle.read_with(&cx, |this, _| { + struct PartialCompletion>> { + pub old_range: Range, + pub new_text: String, + pub label: Option, + pub lsp_completion: lsp::CompletionItem, + } + + let partial_completions = source_buffer_handle.read_with(&cx, |this, _| { let snapshot = this.snapshot(); let clipped_position = this.clip_point_utf16(position, Bias::Left); let mut range_for_token = None; - let result = Vec::new(); + let mut partial_completions = Vec::new(); for lsp_completion in completions.into_iter() { // For now, we can only handle additional edits if they are returned @@ -3601,28 +3608,48 @@ impl Project { } }; - let completion = Completion { + let label = match language.as_ref() { + Some(l) => Some(l.label_for_completion(&lsp_completion)), + None => None, + }; + + let partial_completion = PartialCompletion { old_range, new_text, - label: { - match language.as_ref() { - Some(l) => l.label_for_completion(&lsp_completion).await, - None => None, - } - .unwrap_or_else(|| { - CodeLabel::plain( - lsp_completion.label.clone(), - lsp_completion.filter_text.as_deref(), - ) - }) - }, + label, lsp_completion, }; - result.push(completion); + partial_completions.push(partial_completion); } - Ok(result) - }) + partial_completions + }); + + let mut result = Vec::new(); + + for pc in partial_completions.into_iter() { + let label = match pc.label { + Some(label) => label.await, + None => None, + } + .unwrap_or_else(|| { + CodeLabel::plain( + pc.lsp_completion.label.clone(), + pc.lsp_completion.filter_text.as_deref(), + ) + }); + + let completion = Completion { + old_range: pc.old_range, + new_text: pc.new_text, + label, + lsp_completion: pc.lsp_completion, + }; + + result.push(completion); + } + + Ok(result) }) } else if let Some(project_id) = self.remote_id() { let rpc = self.client.clone(); From db7b863d8c0e90961a470ca277639cd6c56e0a93 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 09:39:43 +0200 Subject: [PATCH 51/91] Fix on_settings_changed, need to review that impl is correct --- crates/project/src/project.rs | 57 +++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 62ce02d71c..cf4bdc4463 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -713,25 +713,28 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { let settings = cx.global::(); - self.lsp_settings_changed = Some(cx.spawn(|_, _| async { - let mut language_servers_to_start = Vec::new(); - for buffer in self.opened_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - let buffer = buffer.read(cx); - if let Some((file, language)) = - File::from_dyn(buffer.file()).zip(buffer.language()) - { - if settings.enable_language_server(Some(&language.name())) { - let worktree = file.worktree.read(cx); - language_servers_to_start.push(( - worktree.id(), - worktree.as_local().unwrap().abs_path().clone(), - language.clone(), - )); + self.lsp_settings_changed = Some(cx.spawn(|project, cx| async { + let language_servers_to_start = project.update(&mut cx, |project, cx| { + let mut language_servers_to_start = Vec::new(); + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + let buffer = buffer.read(cx); + if let Some((file, language)) = + File::from_dyn(buffer.file()).zip(buffer.language()) + { + if settings.enable_language_server(Some(&language.name())) { + let worktree = file.worktree.read(cx); + language_servers_to_start.push(( + worktree.id(), + worktree.as_local().unwrap().abs_path().clone(), + language.clone(), + )); + } } } } - } + language_servers_to_start + }); let mut language_servers_to_stop = Vec::new(); for language in self.languages.to_vec() { @@ -749,18 +752,20 @@ impl Project { } } - // Stop all newly-disabled language servers. - for (worktree_id, adapter_name) in language_servers_to_stop { - self.stop_language_server(worktree_id, adapter_name, cx) - .detach(); - } + project.update(&mut cx, |project, cx| { + // Stop all newly-disabled language servers. + for (worktree_id, adapter_name) in language_servers_to_stop { + self.stop_language_server(worktree_id, adapter_name, cx) + .detach(); + } - // Start all the newly-enabled language servers. - for (worktree_id, worktree_path, language) in language_servers_to_start { - self.start_language_server(worktree_id, worktree_path, language, cx); - } + // Start all the newly-enabled language servers. + for (worktree_id, worktree_path, language) in language_servers_to_start { + self.start_language_server(worktree_id, worktree_path, language, cx); + } - cx.notify(); + cx.notify(); + }); })) } From 2ff67ef9f6f666e269696bc1a495da0c0ffbcc55 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 12:11:04 +0200 Subject: [PATCH 52/91] Factor out await in doubly-nested for loop --- crates/project/src/project.rs | 79 +++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cf4bdc4463..404ed265b3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3298,11 +3298,31 @@ impl Project { } else { return Ok(Default::default()); }; - this.read_with(&cx, |this, cx| { - let mut symbols = Vec::new(); + + struct PartialSymbol + where + F1: Future, + F2: Future>, + { + source_worktree_id: WorktreeId, + worktree_id: WorktreeId, + language_server_name: F1, + path: PathBuf, + label: Option, + name: String, + kind: lsp::SymbolKind, + range: Range, + signature: [u8; 32], + } + + let partial_symbols = this.read_with(&cx, |this, cx| { + let mut partial_symbols = Vec::new(); for (adapter, source_worktree_id, worktree_abs_path, response) in responses { - symbols.extend(response.into_iter().flatten().filter_map(|lsp_symbol| { - let abs_path = lsp_symbol.location.uri.to_file_path().ok()?; + for lsp_symbol in response.into_iter().flatten() { + let abs_path = match lsp_symbol.location.uri.to_file_path().ok() { + Some(abs_path) => abs_path, + None => continue, + }; let mut worktree_id = source_worktree_id; let path; if let Some((worktree, rel_path)) = @@ -3315,31 +3335,54 @@ impl Project { } let label = match this.languages.select_language(&path) { - Some(language) => { - language - .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) - .await - } + Some(language) => Some( + language.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind), + ), None => None, - } - .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None)); - let signature = this.symbol_signature(worktree_id, &path); + }; - Some(Symbol { + let signature = this.symbol_signature(worktree_id, &path); + let language_server_name = adapter.name(); + + partial_symbols.push(PartialSymbol { source_worktree_id, worktree_id, - language_server_name: adapter.name().await, + language_server_name, name: lsp_symbol.name, kind: lsp_symbol.kind, label, path, range: range_from_lsp(lsp_symbol.location.range), signature, - }) - })); + }); + } } - Ok(symbols) - }) + + partial_symbols + }); + + let mut symbols = Vec::new(); + for ps in partial_symbols.into_iter() { + let label = match ps.label { + Some(label) => label.await, + None => None, + } + .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); + + symbols.push(Symbol { + source_worktree_id: ps.source_worktree_id, + worktree_id: ps.worktree_id, + language_server_name: ps.language_server_name.await, + name: ps.name, + kind: ps.kind, + label, + path: ps.path, + range: ps.range, + signature: ps.signature, + }); + } + + Ok(symbols) }) } else if let Some(project_id) = self.remote_id() { let request = self.client.request(proto::GetProjectSymbols { From ce90dbd06a30170e0beba34491aad8fcbc1e67a1 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 14:39:30 +0200 Subject: [PATCH 53/91] Temporarily comment out closure errors to address other errors in project --- crates/project/src/project.rs | 178 ++++++++++---------- crates/zed/src/languages/c.rs | 103 ++++++----- crates/zed/src/languages/go.rs | 131 +++++++------- crates/zed/src/languages/json.rs | 53 +++--- crates/zed/src/languages/language_plugin.rs | 60 ++++--- crates/zed/src/languages/python.rs | 42 +++-- crates/zed/src/languages/rust.rs | 105 ++++++------ crates/zed/src/languages/typescript.rs | 77 ++++----- 8 files changed, 357 insertions(+), 392 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 404ed265b3..262171e510 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -712,61 +712,64 @@ impl Project { } fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { - let settings = cx.global::(); - self.lsp_settings_changed = Some(cx.spawn(|project, cx| async { - let language_servers_to_start = project.update(&mut cx, |project, cx| { - let mut language_servers_to_start = Vec::new(); - for buffer in self.opened_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - let buffer = buffer.read(cx); - if let Some((file, language)) = - File::from_dyn(buffer.file()).zip(buffer.language()) - { - if settings.enable_language_server(Some(&language.name())) { - let worktree = file.worktree.read(cx); - language_servers_to_start.push(( - worktree.id(), - worktree.as_local().unwrap().abs_path().clone(), - language.clone(), - )); - } - } - } - } - language_servers_to_start - }); + // let settings = cx.global::(); + // self.lsp_settings_changed = Some(cx.spawn(|project, cx| async { + // let language_servers_to_start = project.update(&mut cx, |project, cx| { + // let mut language_servers_to_start = Vec::new(); + // for buffer in self.opened_buffers.values() { + // if let Some(buffer) = buffer.upgrade(cx) { + // let buffer = buffer.read(cx); + // if let Some((file, language)) = + // File::from_dyn(buffer.file()).zip(buffer.language()) + // { + // if settings.enable_language_server(Some(&language.name())) { + // let worktree = file.worktree.read(cx); + // language_servers_to_start.push(( + // worktree.id(), + // worktree.as_local().unwrap().abs_path().clone(), + // language.clone(), + // )); + // } + // } + // } + // } + // language_servers_to_start + // }); - let mut language_servers_to_stop = Vec::new(); - for language in self.languages.to_vec() { - if let Some(lsp_adapter) = language.lsp_adapter() { - if !settings.enable_language_server(Some(&language.name())) { - let lsp_name = lsp_adapter.name().await; - for (worktree_id, started_lsp_name) in self.started_language_servers.keys() - { - if lsp_name == *started_lsp_name { - language_servers_to_stop - .push((*worktree_id, started_lsp_name.clone())); - } - } - } - } - } + // let mut language_servers_to_stop = Vec::new(); + // for language in self.languages.to_vec() { + // if let Some(lsp_adapter) = language.lsp_adapter() { + // if !settings.enable_language_server(Some(&language.name())) { + // let lsp_name = lsp_adapter.name().await; + // for (worktree_id, started_lsp_name) in self.started_language_servers.keys() + // { + // if lsp_name == *started_lsp_name { + // language_servers_to_stop + // .push((*worktree_id, started_lsp_name.clone())); + // } + // } + // } + // } + // } - project.update(&mut cx, |project, cx| { - // Stop all newly-disabled language servers. - for (worktree_id, adapter_name) in language_servers_to_stop { - self.stop_language_server(worktree_id, adapter_name, cx) - .detach(); - } + // project.update(&mut cx, |project, cx| { + // // Stop all newly-disabled language servers. + // for (worktree_id, adapter_name) in language_servers_to_stop { + // self.stop_language_server(worktree_id, adapter_name, cx) + // .detach(); + // } - // Start all the newly-enabled language servers. - for (worktree_id, worktree_path, language) in language_servers_to_start { - self.start_language_server(worktree_id, worktree_path, language, cx); - } + // // Start all the newly-enabled language servers. + // for (worktree_id, worktree_path, language) in language_servers_to_start { + // self.start_language_server(worktree_id, worktree_path, language, cx); + // } - cx.notify(); - }); - })) + // cx.notify(); + // }); + // })) + + // TODO(isaac): uncomment the above + todo!() } pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option> { @@ -2186,6 +2189,7 @@ impl Project { language_server.clone(), cx, ) + } }) .detach(); @@ -2496,9 +2500,12 @@ impl Project { return; } + let same_token = + Some(token.as_ref()) == disk_based_diagnostics_progress_token.as_ref().map(|x| &**x); + match progress { lsp::WorkDoneProgress::Begin(report) => { - if Some(token) == disk_based_diagnostics_progress_token { + if same_token { language_server_status.has_pending_diagnostic_updates = true; self.disk_based_diagnostics_started(server_id, cx); self.broadcast_language_server_update( @@ -2529,7 +2536,7 @@ impl Project { } } lsp::WorkDoneProgress::Report(report) => { - if Some(token) != disk_based_diagnostics_progress_token { + if !same_token { self.on_lsp_work_progress( server_id, token.clone(), @@ -2555,7 +2562,7 @@ impl Project { lsp::WorkDoneProgress::End(_) => { language_server_status.progress_tokens.remove(&token); - if Some(token) == disk_based_diagnostics_progress_token { + if same_token { language_server_status.has_pending_diagnostic_updates = false; self.disk_based_diagnostics_finished(server_id, cx); self.broadcast_language_server_update( @@ -3299,16 +3306,12 @@ impl Project { return Ok(Default::default()); }; - struct PartialSymbol - where - F1: Future, - F2: Future>, - { + struct PartialSymbol { source_worktree_id: WorktreeId, worktree_id: WorktreeId, - language_server_name: F1, + adapter: Arc, path: PathBuf, - label: Option, + language: Option>, name: String, kind: lsp::SymbolKind, range: Range, @@ -3334,23 +3337,17 @@ impl Project { path = relativize_path(&worktree_abs_path, &abs_path); } - let label = match this.languages.select_language(&path) { - Some(language) => Some( - language.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind), - ), - None => None, - }; - + let language = this.languages.select_language(&path).clone(); let signature = this.symbol_signature(worktree_id, &path); - let language_server_name = adapter.name(); partial_symbols.push(PartialSymbol { source_worktree_id, worktree_id, - language_server_name, + // TODO: just pass out single adapter? + adapter: adapter.clone(), name: lsp_symbol.name, kind: lsp_symbol.kind, - label, + language, path, range: range_from_lsp(lsp_symbol.location.range), signature, @@ -3363,16 +3360,18 @@ impl Project { let mut symbols = Vec::new(); for ps in partial_symbols.into_iter() { - let label = match ps.label { - Some(label) => label.await, + let label = match ps.language { + Some(language) => language.label_for_symbol(&ps.name, ps.kind).await, None => None, } .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); + let language_server_name = ps.adapter.name().await; + symbols.push(Symbol { source_worktree_id: ps.source_worktree_id, worktree_id: ps.worktree_id, - language_server_name: ps.language_server_name.await, + language_server_name, name: ps.name, kind: ps.kind, label, @@ -3394,10 +3393,11 @@ impl Project { let mut symbols = Vec::new(); if let Some(this) = this.upgrade(&cx) { let new_symbols = this.read_with(&cx, |this, _| { - response - .symbols - .into_iter() - .map(|symbol| this.deserialize_symbol(symbol)) + let mut new_symbols = Vec::new(); + for symbol in response.symbols.into_iter() { + new_symbols.push(this.deserialize_symbol(symbol)); + } + new_symbols }); for new_symbol in new_symbols { if let Some(new_symbol) = new_symbol.await.ok() { @@ -3533,10 +3533,10 @@ impl Project { Default::default() }; - struct PartialCompletion>> { + struct PartialCompletion { pub old_range: Range, pub new_text: String, - pub label: Option, + pub language: Option>, pub lsp_completion: lsp::CompletionItem, } @@ -3656,15 +3656,10 @@ impl Project { } }; - let label = match language.as_ref() { - Some(l) => Some(l.label_for_completion(&lsp_completion)), - None => None, - }; - let partial_completion = PartialCompletion { old_range, new_text, - label, + language: language.clone(), lsp_completion, }; @@ -3676,8 +3671,8 @@ impl Project { let mut result = Vec::new(); for pc in partial_completions.into_iter() { - let label = match pc.label { - Some(label) => label.await, + let label = match pc.language.as_ref() { + Some(l) => l.label_for_completion(&pc.lsp_completion).await, None => None, } .unwrap_or_else(|| { @@ -3716,10 +3711,11 @@ impl Project { }) .await; - let completions = Vec::new(); + let mut completions = Vec::new(); for completion in response.completions.into_iter() { - completions - .push(language::proto::deserialize_completion(completion, language).await); + completions.push( + language::proto::deserialize_completion(completion, language.clone()).await, + ); } completions.into_iter().collect() }) diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 838db5ef9d..ca72012094 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -1,5 +1,6 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; pub use language::*; @@ -23,21 +24,18 @@ impl super::LspAdapter for CLspAdapter { &self, http: Arc, ) -> Result> { - async move { - let release = latest_github_release("clangd/clangd", http).await?; - let asset_name = format!("clangd-mac-{}.zip", release.name); - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; - let version = GitHubLspBinaryVersion { - name: release.name, - url: asset.browser_download_url.clone(), - }; - Ok(Box::new(version) as Box<_>) - } - .boxed() + let release = latest_github_release("clangd/clangd", http).await?; + let asset_name = format!("clangd-mac-{}.zip", release.name); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + }; + Ok(Box::new(version) as Box<_>) } async fn fetch_server_binary( @@ -47,54 +45,51 @@ impl super::LspAdapter for CLspAdapter { container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); - async move { - let zip_path = container_dir.join(format!("clangd_{}.zip", version.name)); - let version_dir = container_dir.join(format!("clangd_{}", version.name)); - let binary_path = version_dir.join("bin/clangd"); + let zip_path = container_dir.join(format!("clangd_{}.zip", version.name)); + let version_dir = container_dir.join(format!("clangd_{}", version.name)); + let binary_path = version_dir.join("bin/clangd"); - if fs::metadata(&binary_path).await.is_err() { - let mut response = http - .get(&version.url, Default::default(), true) - .await - .context("error downloading release")?; - let mut file = File::create(&zip_path).await?; - if !response.status().is_success() { - Err(anyhow!( - "download failed with status {}", - response.status().to_string() - ))?; - } - futures::io::copy(response.body_mut(), &mut file).await?; + if fs::metadata(&binary_path).await.is_err() { + let mut response = http + .get(&version.url, Default::default(), true) + .await + .context("error downloading release")?; + let mut file = File::create(&zip_path).await?; + if !response.status().is_success() { + Err(anyhow!( + "download failed with status {}", + response.status().to_string() + ))?; + } + futures::io::copy(response.body_mut(), &mut file).await?; - let unzip_status = smol::process::Command::new("unzip") - .current_dir(&container_dir) - .arg(&zip_path) - .output() - .await? - .status; - if !unzip_status.success() { - Err(anyhow!("failed to unzip clangd archive"))?; - } + let unzip_status = smol::process::Command::new("unzip") + .current_dir(&container_dir) + .arg(&zip_path) + .output() + .await? + .status; + if !unzip_status.success() { + Err(anyhow!("failed to unzip clangd archive"))?; + } - 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) } - .boxed() + + Ok(binary_path) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { - async move { + (|| async move { let mut last_clangd_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -113,9 +108,9 @@ impl super::LspAdapter for CLspAdapter { clangd_dir )) } - } + })() + .await .log_err() - .boxed() } async fn label_for_completion( diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index eb88d7f887..6445036a75 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -1,5 +1,6 @@ use super::installation::latest_github_release; use anyhow::{anyhow, Result}; +use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; pub use language::*; @@ -36,18 +37,15 @@ impl super::LspAdapter for GoLspAdapter { &self, http: Arc, ) -> Result> { - async move { - let release = latest_github_release("golang/tools", http).await?; - let version: Option = release.name.strip_prefix("gopls/v").map(str::to_string); - if version.is_none() { - log::warn!( - "couldn't infer gopls version from github release name '{}'", - release.name - ); - } - Ok(Box::new(version) as Box<_>) + let release = latest_github_release("golang/tools", http).await?; + let version: Option = release.name.strip_prefix("gopls/v").map(str::to_string); + if version.is_none() { + log::warn!( + "couldn't infer gopls version from github release name '{}'", + release.name + ); } - .boxed() + Ok(Box::new(version) as Box<_>) } async fn fetch_server_binary( @@ -59,65 +57,62 @@ impl super::LspAdapter for GoLspAdapter { let version = version.downcast::>().unwrap(); let this = *self; - async move { - if let Some(version) = *version { - let binary_path = container_dir.join(&format!("gopls_{version}")); - if let Ok(metadata) = fs::metadata(&binary_path).await { - if metadata.is_file() { - 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() != binary_path - && entry.file_name() != "gobin" - { - fs::remove_file(&entry_path).await.log_err(); - } + if let Some(version) = *version { + let binary_path = container_dir.join(&format!("gopls_{version}")); + if let Ok(metadata) = fs::metadata(&binary_path).await { + if metadata.is_file() { + 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() != binary_path + && entry.file_name() != "gobin" + { + fs::remove_file(&entry_path).await.log_err(); } } } - - return Ok(binary_path.to_path_buf()); } + + return Ok(binary_path.to_path_buf()); } - } else if let Some(path) = this.cached_server_binary(container_dir.clone()).await { - return Ok(path.to_path_buf()); } - - let gobin_dir = container_dir.join("gobin"); - fs::create_dir_all(&gobin_dir).await?; - let install_output = process::Command::new("go") - .env("GO111MODULE", "on") - .env("GOBIN", &gobin_dir) - .args(["install", "golang.org/x/tools/gopls@latest"]) - .output() - .await?; - if !install_output.status.success() { - Err(anyhow!("failed to install gopls. Is go installed?"))?; - } - - let installed_binary_path = gobin_dir.join("gopls"); - let version_output = process::Command::new(&installed_binary_path) - .arg("version") - .output() - .await - .map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?; - let version_stdout = str::from_utf8(&version_output.stdout) - .map_err(|_| anyhow!("gopls version produced invalid utf8"))?; - let version = GOPLS_VERSION_REGEX - .find(version_stdout) - .ok_or_else(|| anyhow!("failed to parse gopls version output"))? - .as_str(); - let binary_path = container_dir.join(&format!("gopls_{version}")); - fs::rename(&installed_binary_path, &binary_path).await?; - - Ok(binary_path.to_path_buf()) + } else if let Some(path) = this.cached_server_binary(container_dir.clone()).await { + return Ok(path.to_path_buf()); } - .boxed() + + let gobin_dir = container_dir.join("gobin"); + fs::create_dir_all(&gobin_dir).await?; + let install_output = process::Command::new("go") + .env("GO111MODULE", "on") + .env("GOBIN", &gobin_dir) + .args(["install", "golang.org/x/tools/gopls@latest"]) + .output() + .await?; + if !install_output.status.success() { + Err(anyhow!("failed to install gopls. Is go installed?"))?; + } + + let installed_binary_path = gobin_dir.join("gopls"); + let version_output = process::Command::new(&installed_binary_path) + .arg("version") + .output() + .await + .map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?; + let version_stdout = str::from_utf8(&version_output.stdout) + .map_err(|_| anyhow!("gopls version produced invalid utf8"))?; + let version = GOPLS_VERSION_REGEX + .find(version_stdout) + .ok_or_else(|| anyhow!("failed to parse gopls version output"))? + .as_str(); + let binary_path = container_dir.join(&format!("gopls_{version}")); + fs::rename(&installed_binary_path, &binary_path).await?; + + Ok(binary_path.to_path_buf()) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { - async move { + (|| async move { let mut last_binary_path = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -137,9 +132,9 @@ impl super::LspAdapter for GoLspAdapter { } else { Err(anyhow!("no cached binary")) } - } + })() + .await .log_err() - .boxed() } async fn label_for_completion( @@ -345,12 +340,12 @@ mod tests { let highlight_field = grammar.highlight_id_for_name("property").unwrap(); assert_eq!( - language.label_for_completion(&lsp::CompletionItem { + smol::block_on(language.label_for_completion(&lsp::CompletionItem { kind: Some(lsp::CompletionItemKind::FUNCTION), label: "Hello".to_string(), detail: Some("func(a B) c.D".to_string()), ..Default::default() - }), + })), Some(CodeLabel { text: "Hello(a B) c.D".to_string(), filter_range: 0..5, @@ -364,12 +359,12 @@ mod tests { // Nested methods assert_eq!( - language.label_for_completion(&lsp::CompletionItem { + smol::block_on(language.label_for_completion(&lsp::CompletionItem { kind: Some(lsp::CompletionItemKind::METHOD), label: "one.two.Three".to_string(), detail: Some("func() [3]interface{}".to_string()), ..Default::default() - }), + })), Some(CodeLabel { text: "one.two.Three() [3]interface{}".to_string(), filter_range: 0..13, @@ -383,12 +378,12 @@ mod tests { // Nested fields assert_eq!( - language.label_for_completion(&lsp::CompletionItem { + smol::block_on(language.label_for_completion(&lsp::CompletionItem { kind: Some(lsp::CompletionItemKind::FIELD), label: "two.Three".to_string(), detail: Some("a.Bcd".to_string()), ..Default::default() - }), + })), Some(CodeLabel { text: "two.Three a.Bcd".to_string(), filter_range: 0..9, diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 6d05f0a63e..04204dc665 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -1,5 +1,6 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; @@ -33,10 +34,7 @@ impl LspAdapter for JsonLspAdapter { &self, _: Arc, ) -> Result> { - async move { - Ok(Box::new(npm_package_latest_version("vscode-json-languageserver").await?) as Box<_>) - } - .boxed() + Ok(Box::new(npm_package_latest_version("vscode-json-languageserver").await?) as Box<_>) } async fn fetch_server_binary( @@ -46,39 +44,36 @@ impl LspAdapter for JsonLspAdapter { container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); - async move { - 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); + 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() { - npm_install_packages( - [("vscode-json-languageserver", version.as_str())], - &version_dir, - ) - .await?; + if fs::metadata(&binary_path).await.is_err() { + npm_install_packages( + [("vscode-json-languageserver", version.as_str())], + &version_dir, + ) + .await?; - 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) } - .boxed() + + Ok(binary_path) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { - async move { + (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -97,9 +92,9 @@ impl LspAdapter for JsonLspAdapter { last_version_dir )) } - } + })() + .await .log_err() - .boxed() } async fn initialization_options(&self) -> Option { diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 42693c2c32..77df85508e 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use async_trait::async_trait; use client::http::HttpClient; use futures::lock::Mutex; use futures::Future; @@ -85,35 +86,26 @@ struct Versions { // I wish there was high-level instrumentation for this... // - it's totally a deadlock, the proof is in the pudding -// macro_rules! call_block { -// ($self:ident, $name:expr, $arg:expr) => { -// $self.executor.block(async { -// dbg!("starting to block on something"); -// let locked = $self.runtime.lock(); -// dbg!("locked runtime"); -// // TODO: No blocking calls! -// let mut awaited = locked.await; -// dbg!("awaited lock"); -// let called = awaited.call($name, $arg); -// dbg!("called function"); -// let result = called.await; -// dbg!("awaited result"); -// result -// }) -// }; -// } - -// TODO: convert to async trait - #[async_trait] impl LspAdapter for PluginLspAdapter { async fn name(&self) -> LanguageServerName { - let name: String = call_block!(self, &self.name, ()).unwrap(); + let name: String = self + .runtime + .lock() + .await + .call(&self.name, ()) + .await + .unwrap(); LanguageServerName(name.into()) } async fn server_args<'a>(&'a self) -> Vec { - call_block!(self, &self.server_args, ()).unwrap() + self.runtime + .lock() + .await + .call(&self.server_args, ()) + .await + .unwrap() } async fn fetch_latest_server_version( @@ -133,15 +125,15 @@ impl LspAdapter for PluginLspAdapter { .ok_or_else(|| anyhow!("Could not fetch latest server version")) .map(|v| Box::new(v) as Box<_>) }) - .boxed() + .await } - fn fetch_server_binary( + async fn fetch_server_binary( &self, version: Box, _: Arc, container_dir: PathBuf, - ) -> BoxFuture<'static, Result> { + ) -> Result { let version = *version.downcast::().unwrap(); let runtime = self.runtime.clone(); let function = self.fetch_server_binary; @@ -154,10 +146,10 @@ impl LspAdapter for PluginLspAdapter { runtime.remove_resource(handle)?; result.map_err(|e| anyhow!("{}", e)) }) - .boxed() + .await } - fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { let runtime = self.runtime.clone(); let function = self.cached_server_binary; @@ -169,10 +161,10 @@ impl LspAdapter for PluginLspAdapter { runtime.remove_resource(handle).ok()?; result }) - .boxed() + .await } - fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + // async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} // fn label_for_completion( // &self, @@ -193,8 +185,14 @@ impl LspAdapter for PluginLspAdapter { // }) // } - fn initialization_options(&self) -> Option { - let string: String = call_block!(self, &self.initialization_options, ()).log_err()?; + async fn initialization_options(&self) -> Option { + let string: String = self + .runtime + .lock() + .await + .call(&self.initialization_options, ()) + .await + .log_err()?; serde_json::from_str(&string).ok() } diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index d5f857ae05..475856b47d 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -1,5 +1,6 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; @@ -31,7 +32,7 @@ impl LspAdapter for PythonLspAdapter { &self, _: Arc, ) -> Result> { - async move { Ok(Box::new(npm_package_latest_version("pyright").await?) as Box<_>) }.boxed() + Ok(Box::new(npm_package_latest_version("pyright").await?) as Box<_>) } async fn fetch_server_binary( @@ -41,35 +42,32 @@ impl LspAdapter for PythonLspAdapter { container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); - async move { - 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); + 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() { - npm_install_packages([("pyright", version.as_str())], &version_dir).await?; + if fs::metadata(&binary_path).await.is_err() { + npm_install_packages([("pyright", version.as_str())], &version_dir).await?; - 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) } - .boxed() + + Ok(binary_path) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { - async move { + (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -88,9 +86,9 @@ impl LspAdapter for PythonLspAdapter { last_version_dir )) } - } + })() + .await .log_err() - .boxed() } async fn label_for_completion( diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index c82fc896eb..bba5b526c9 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -1,6 +1,7 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; +use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; pub use language::*; @@ -19,6 +20,7 @@ use util::{ResultExt, TryFutureExt}; pub struct RustLspAdapter; +#[async_trait] impl LspAdapter for RustLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("rust-analyzer".into()) @@ -28,21 +30,18 @@ impl LspAdapter for RustLspAdapter { &self, http: Arc, ) -> Result> { - async move { - let release = latest_github_release("rust-analyzer/rust-analyzer", http).await?; - let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH); - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; - let version = GitHubLspBinaryVersion { - name: release.name, - url: asset.browser_download_url.clone(), - }; - Ok(Box::new(version) as Box<_>) - } - .boxed() + let release = latest_github_release("rust-analyzer/rust-analyzer", http).await?; + let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + }; + Ok(Box::new(version) as Box<_>) } async fn fetch_server_binary( @@ -51,55 +50,49 @@ impl LspAdapter for RustLspAdapter { http: Arc, container_dir: PathBuf, ) -> Result { - async move { - let version = version.downcast::().unwrap(); - let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); + let version = version.downcast::().unwrap(); + let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); - if fs::metadata(&destination_path).await.is_err() { - let mut response = http - .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 mut file = File::create(&destination_path).await?; - futures::io::copy(decompressed_bytes, &mut file).await?; - fs::set_permissions( - &destination_path, - ::from_mode(0o755), - ) - .await?; + if fs::metadata(&destination_path).await.is_err() { + let mut response = http + .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 mut file = File::create(&destination_path).await?; + futures::io::copy(decompressed_bytes, &mut file).await?; + fs::set_permissions( + &destination_path, + ::from_mode(0o755), + ) + .await?; - 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() != destination_path { - fs::remove_file(&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() != destination_path { + fs::remove_file(&entry_path).await.log_err(); } } } } - - Ok(destination_path) } - .boxed() + + Ok(destination_path) } - async fn cached_server_binary( - &self, - container_dir: PathBuf, - ) -> BoxFuture<'static, Option> { - async move { + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + (|| async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { last = Some(entry?.path()); } last.ok_or_else(|| anyhow!("no cached binary")) - } + })() + .await .log_err() - .boxed() } async fn disk_based_diagnostic_sources(&self) -> Vec { @@ -337,12 +330,12 @@ mod tests { let highlight_field = grammar.highlight_id_for_name("property").unwrap(); assert_eq!( - language.label_for_completion(&lsp::CompletionItem { + smol::block_on(language.label_for_completion(&lsp::CompletionItem { kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), detail: Some("fn(&mut Option) -> Vec".to_string()), ..Default::default() - }), + })), Some(CodeLabel { text: "hello(&mut Option) -> Vec".to_string(), filter_range: 0..5, @@ -358,12 +351,12 @@ mod tests { ); assert_eq!( - language.label_for_completion(&lsp::CompletionItem { + smol::block_on(language.label_for_completion(&lsp::CompletionItem { kind: Some(lsp::CompletionItemKind::FIELD), label: "len".to_string(), detail: Some("usize".to_string()), ..Default::default() - }), + })), Some(CodeLabel { text: "len: usize".to_string(), filter_range: 0..3, @@ -372,12 +365,12 @@ mod tests { ); assert_eq!( - language.label_for_completion(&lsp::CompletionItem { + smol::block_on(language.label_for_completion(&lsp::CompletionItem { kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), detail: Some("fn(&mut Option) -> Vec".to_string()), ..Default::default() - }), + })), Some(CodeLabel { text: "hello(&mut Option) -> Vec".to_string(), filter_range: 0..5, @@ -415,7 +408,7 @@ mod tests { let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap(); assert_eq!( - language.label_for_symbol("hello", lsp::SymbolKind::FUNCTION), + smol::block_on(language.label_for_symbol("hello", lsp::SymbolKind::FUNCTION)), Some(CodeLabel { text: "fn hello".to_string(), filter_range: 3..8, @@ -424,7 +417,7 @@ mod tests { ); assert_eq!( - language.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER), + smol::block_on(language.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER)), Some(CodeLabel { text: "type World".to_string(), filter_range: 5..10, diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 0bd5ad44a9..890317948d 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -1,5 +1,6 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; @@ -40,13 +41,10 @@ impl LspAdapter for TypeScriptLspAdapter { &self, _: Arc, ) -> Result> { - async move { - Ok(Box::new(Versions { - typescript_version: npm_package_latest_version("typescript").await?, - server_version: npm_package_latest_version("typescript-language-server").await?, - }) as Box<_>) - } - .boxed() + Ok(Box::new(Versions { + typescript_version: npm_package_latest_version("typescript").await?, + server_version: npm_package_latest_version("typescript-language-server").await?, + }) as Box<_>) } async fn fetch_server_binary( @@ -56,48 +54,45 @@ impl LspAdapter for TypeScriptLspAdapter { container_dir: PathBuf, ) -> Result { let versions = versions.downcast::().unwrap(); - async move { - let version_dir = container_dir.join(&format!( - "typescript-{}:server-{}", - versions.typescript_version, versions.server_version - )); - fs::create_dir_all(&version_dir) - .await - .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); + let version_dir = container_dir.join(&format!( + "typescript-{}:server-{}", + versions.typescript_version, versions.server_version + )); + 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() { - npm_install_packages( - [ - ("typescript", versions.typescript_version.as_str()), - ( - "typescript-language-server", - &versions.server_version.as_str(), - ), - ], - &version_dir, - ) - .await?; + if fs::metadata(&binary_path).await.is_err() { + npm_install_packages( + [ + ("typescript", versions.typescript_version.as_str()), + ( + "typescript-language-server", + &versions.server_version.as_str(), + ), + ], + &version_dir, + ) + .await?; - 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) } - .boxed() + + Ok(binary_path) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { - async move { + (|| async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -116,9 +111,9 @@ impl LspAdapter for TypeScriptLspAdapter { last_version_dir )) } - } + })() + .await .log_err() - .boxed() } async fn label_for_completion( From 172e27641123dc724278817ca9e7686c91c2c03a Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 15:11:35 +0200 Subject: [PATCH 54/91] Fix warnings and propogate async further --- crates/project/src/project.rs | 20 +++++++++++--------- crates/zed/src/languages.rs | 5 +---- crates/zed/src/languages/c.rs | 10 +++------- crates/zed/src/languages/go.rs | 12 +++--------- crates/zed/src/languages/json.rs | 10 +++------- crates/zed/src/languages/language_plugin.rs | 2 -- crates/zed/src/languages/python.rs | 10 +++------- crates/zed/src/languages/rust.rs | 15 ++++----------- crates/zed/src/languages/typescript.rs | 10 +++------- 9 files changed, 31 insertions(+), 63 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 262171e510..dddaebf551 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -31,8 +31,8 @@ use language::{ Transaction, }; use lsp::{ - CompletionList, DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, - LanguageString, MarkedString, + DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString, + MarkedString, }; use lsp_command::*; use parking_lot::Mutex; @@ -1732,7 +1732,7 @@ impl Project { .await?; this.update(&mut cx, |this, cx| { this.assign_language_to_buffer(&buffer, cx); - this.register_buffer_with_language_server(&buffer, cx); + this.register_buffer_with_language_server(&buffer, cx).await; }); Ok(()) }) @@ -1786,12 +1786,12 @@ impl Project { ))?, } cx.subscribe(buffer, |this, buffer, event, cx| { - this.on_buffer_event(buffer, event, cx); + this.on_buffer_event(buffer, event, cx).await; }) .detach(); self.assign_language_to_buffer(buffer, cx); - self.register_buffer_with_language_server(buffer, cx); + self.register_buffer_with_language_server(buffer, cx).await; cx.observe_release(buffer, |this, buffer, cx| { if let Some(file) = File::from_dyn(buffer.file()) { if file.is_local() { @@ -2052,7 +2052,8 @@ impl Project { let worktree = file.worktree.read(cx).as_local()?; let worktree_id = worktree.id(); let worktree_abs_path = worktree.abs_path().clone(); - self.start_language_server(worktree_id, worktree_abs_path, language, cx); + self.start_language_server(worktree_id, worktree_abs_path, language, cx) + .await; None } @@ -2404,7 +2405,8 @@ impl Project { .collect(); for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info { let language = self.languages.select_language(&full_path)?; - self.restart_language_server(worktree_id, worktree_abs_path, language, cx); + self.restart_language_server(worktree_id, worktree_abs_path, language, cx) + .await; } None @@ -2462,7 +2464,7 @@ impl Project { adapter: &Arc, cx: &mut ModelContext<'_, Self>, ) { - adapter.process_diagnostics(&mut params); + adapter.process_diagnostics(&mut params).await; self.update_diagnostics( server_id, params, @@ -4640,7 +4642,7 @@ impl Project { for (buffer, old_path) in renamed_buffers { self.unregister_buffer_from_language_server(&buffer, old_path, cx); self.assign_language_to_buffer(&buffer, cx); - self.register_buffer_with_language_server(&buffer, cx); + self.register_buffer_with_language_server(&buffer, cx).await; } } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 75bf8212f8..753845ef63 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,7 +1,4 @@ -use gpui::{ - executor::{self, Background}, - Task, -}; +use gpui::executor::Background; pub use language::*; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index ca72012094..54554beaf6 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -2,15 +2,11 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; -use futures::{future::BoxFuture, FutureExt, StreamExt}; +use futures::StreamExt; pub use language::*; use smol::fs::{self, File}; -use std::{ - any::Any, - path::{Path, PathBuf}, - sync::Arc, -}; -use util::{ResultExt, TryFutureExt}; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::ResultExt; pub struct CLspAdapter; diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 6445036a75..314055e5ff 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -2,19 +2,13 @@ use super::installation::latest_github_release; use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::http::HttpClient; -use futures::{future::BoxFuture, FutureExt, StreamExt}; +use futures::StreamExt; pub use language::*; use lazy_static::lazy_static; use regex::Regex; use smol::{fs, process}; -use std::{ - any::Any, - ops::Range, - path::{Path, PathBuf}, - str, - sync::Arc, -}; -use util::{ResultExt, TryFutureExt}; +use std::{any::Any, ops::Range, path::PathBuf, str, sync::Arc}; +use util::ResultExt; #[derive(Copy, Clone)] pub struct GoLspAdapter; diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 04204dc665..29e8e7da02 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -2,16 +2,12 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; -use futures::{future::BoxFuture, FutureExt, StreamExt}; +use futures::StreamExt; use language::{LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; -use std::{ - any::Any, - path::{Path, PathBuf}, - sync::Arc, -}; -use util::{ResultExt, TryFutureExt}; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::ResultExt; pub struct JsonLspAdapter; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 77df85508e..51284afb28 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -3,11 +3,9 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::lock::Mutex; use futures::Future; -use futures::{future::BoxFuture, FutureExt}; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; -use std::task::Poll; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 475856b47d..ca0b24bda7 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -2,15 +2,11 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; -use futures::{future::BoxFuture, FutureExt, StreamExt}; +use futures::StreamExt; use language::{LanguageServerName, LspAdapter}; use smol::fs; -use std::{ - any::Any, - path::{Path, PathBuf}, - sync::Arc, -}; -use util::{ResultExt, TryFutureExt}; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::ResultExt; pub struct PythonLspAdapter; diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index bba5b526c9..7d7c60dea4 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -3,20 +3,13 @@ use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use client::http::HttpClient; -use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; +use futures::{io::BufReader, StreamExt}; pub use language::*; use lazy_static::lazy_static; use regex::Regex; use smol::fs::{self, File}; -use std::{ - any::Any, - borrow::Cow, - env::consts, - path::{Path, PathBuf}, - str, - sync::Arc, -}; -use util::{ResultExt, TryFutureExt}; +use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; +use util::ResultExt; pub struct RustLspAdapter; @@ -290,7 +283,7 @@ mod tests { }, ], }; - RustLspAdapter.process_diagnostics(&mut params); + smol::block_on(RustLspAdapter.process_diagnostics(&mut params)); assert_eq!(params.diagnostics[0].message, "use of moved value `a`"); diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 890317948d..199a7f22ae 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -2,16 +2,12 @@ use super::installation::{npm_install_packages, npm_package_latest_version}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; -use futures::{future::BoxFuture, FutureExt, StreamExt}; +use futures::StreamExt; use language::{LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; -use std::{ - any::Any, - path::{Path, PathBuf}, - sync::Arc, -}; -use util::{ResultExt, TryFutureExt}; +use std::{any::Any, path::PathBuf, sync::Arc}; +use util::ResultExt; pub struct TypeScriptLspAdapter; From 2b0b34141570d8fbb87972c12c7730445b1bd1c7 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 15:24:34 +0200 Subject: [PATCH 55/91] Move await outside of a closure, remove future_wrap dependency --- Cargo.lock | 10 --------- crates/project/src/project.rs | 6 +++-- crates/zed/Cargo.toml | 2 -- crates/zed/src/languages/language_plugin.rs | 25 +++++---------------- 4 files changed, 10 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89534cb02f..3c17a9d8b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1950,15 +1950,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -[[package]] -name = "future-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bab12b2506593396c1339caf22beeb6f5cbe95dac5e376b71a3d17cbe2c4630" -dependencies = [ - "pin-project", -] - [[package]] name = "futures" version = "0.3.21" @@ -7018,7 +7009,6 @@ dependencies = [ "env_logger", "file_finder", "fsevent", - "future-wrap", "futures", "fuzzy", "go_to_line", diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index dddaebf551..ca7052cb8b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1732,8 +1732,9 @@ impl Project { .await?; this.update(&mut cx, |this, cx| { this.assign_language_to_buffer(&buffer, cx); - this.register_buffer_with_language_server(&buffer, cx).await; - }); + this.register_buffer_with_language_server(&buffer, cx) + }) + .await; Ok(()) }) } @@ -1791,6 +1792,7 @@ impl Project { .detach(); self.assign_language_to_buffer(buffer, cx); + // TODO(isaac): should this be done in the background self.register_buffer_with_language_server(buffer, cx).await; cx.observe_release(buffer, |this, buffer, cx| { if let Some(file) = File::from_dyn(buffer.file()) { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 5bb441831b..e85824b692 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -102,8 +102,6 @@ tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", re tree-sitter-typescript = "0.20.1" url = "2.2" -# TODO(isaac): remove this -future-wrap = "0.1.1" [dev-dependencies] text = { path = "../text", features = ["test-support"] } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 51284afb28..9494705c27 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -9,31 +9,18 @@ use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; -use future_wrap::*; - pub async fn new_json(executor: Arc) -> Result { let plugin = PluginBuilder::new_with_default_ctx()? .host_function_async("command", |command: String| async move { dbg!(&command); - - // TODO: actual thing let mut args = command.split(' '); let command = args.next().unwrap(); - - dbg!("Running external command"); - - let start = std::time::Instant::now(); - let future = smol::process::Command::new(command).args(args).output(); - let future = future.wrap(|fut, cx| { - dbg!("Poll command!"); - - let res = fut.poll(cx); - res - }); - let future = future.await; - dbg!(start.elapsed()); - - future.log_err().map(|output| output.stdout) + smol::process::Command::new(command) + .args(args) + .output() + .await + .log_err() + .map(|output| output.stdout) })? .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) .await?; From e4a680f47b6fe3e526868de524a79cfd5d21ac42 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 5 Jul 2022 17:58:22 +0200 Subject: [PATCH 56/91] Uncommented previously commented sections --- crates/project/src/project.rs | 106 +++++++++++++++++----------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ca7052cb8b..c1bfa7f318 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -712,64 +712,64 @@ impl Project { } fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { - // let settings = cx.global::(); - // self.lsp_settings_changed = Some(cx.spawn(|project, cx| async { - // let language_servers_to_start = project.update(&mut cx, |project, cx| { - // let mut language_servers_to_start = Vec::new(); - // for buffer in self.opened_buffers.values() { - // if let Some(buffer) = buffer.upgrade(cx) { - // let buffer = buffer.read(cx); - // if let Some((file, language)) = - // File::from_dyn(buffer.file()).zip(buffer.language()) - // { - // if settings.enable_language_server(Some(&language.name())) { - // let worktree = file.worktree.read(cx); - // language_servers_to_start.push(( - // worktree.id(), - // worktree.as_local().unwrap().abs_path().clone(), - // language.clone(), - // )); - // } - // } - // } - // } - // language_servers_to_start - // }); + let settings = cx.global::(); + self.lsp_settings_changed = Some(cx.spawn(|project, cx| async { + let language_servers_to_start = project.update(&mut cx, |project, cx| { + let mut language_servers_to_start = Vec::new(); + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + let buffer = buffer.read(cx); + if let Some((file, language)) = + File::from_dyn(buffer.file()).zip(buffer.language()) + { + if settings.enable_language_server(Some(&language.name())) { + let worktree = file.worktree.read(cx); + language_servers_to_start.push(( + worktree.id(), + worktree.as_local().unwrap().abs_path().clone(), + language.clone(), + )); + } + } + } + } + language_servers_to_start + }); - // let mut language_servers_to_stop = Vec::new(); - // for language in self.languages.to_vec() { - // if let Some(lsp_adapter) = language.lsp_adapter() { - // if !settings.enable_language_server(Some(&language.name())) { - // let lsp_name = lsp_adapter.name().await; - // for (worktree_id, started_lsp_name) in self.started_language_servers.keys() - // { - // if lsp_name == *started_lsp_name { - // language_servers_to_stop - // .push((*worktree_id, started_lsp_name.clone())); - // } - // } - // } - // } - // } + let mut language_servers_to_stop = Vec::new(); + for language in self.languages.to_vec() { + if let Some(lsp_adapter) = language.lsp_adapter() { + if !settings.enable_language_server(Some(&language.name())) { + let lsp_name = lsp_adapter.name().await; + for (worktree_id, started_lsp_name) in self.started_language_servers.keys() + { + if lsp_name == *started_lsp_name { + language_servers_to_stop + .push((*worktree_id, started_lsp_name.clone())); + } + } + } + } + } - // project.update(&mut cx, |project, cx| { - // // Stop all newly-disabled language servers. - // for (worktree_id, adapter_name) in language_servers_to_stop { - // self.stop_language_server(worktree_id, adapter_name, cx) - // .detach(); - // } + project.update(&mut cx, |project, cx| { + // Stop all newly-disabled language servers. + for (worktree_id, adapter_name) in language_servers_to_stop { + self.stop_language_server(worktree_id, adapter_name, cx) + .detach(); + } - // // Start all the newly-enabled language servers. - // for (worktree_id, worktree_path, language) in language_servers_to_start { - // self.start_language_server(worktree_id, worktree_path, language, cx); - // } + // Start all the newly-enabled language servers. + for (worktree_id, worktree_path, language) in language_servers_to_start { + self.start_language_server(worktree_id, worktree_path, language, cx); + } - // cx.notify(); - // }); - // })) + cx.notify(); + }); + })) - // TODO(isaac): uncomment the above - todo!() + // // TODO(isaac): uncomment the above + // todo!() } pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option> { From 602fe14aa412509d38486e33e3931dca3245978c Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 10:14:49 +0200 Subject: [PATCH 57/91] Going to move LspAdapter from trait to struct --- crates/language/src/language.rs | 17 +++++++++++++++++ crates/project/src/project.rs | 3 +++ 2 files changed, 20 insertions(+) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 189c455258..a3f9475eae 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -65,6 +65,23 @@ pub struct LanguageServerName(pub Arc); use async_trait::async_trait; +// pub struct LspAdapter { +// name: LanguageServerName, +// adapter: Arc, +// } + +// impl LspAdapter { +// async fn new(adapter: Arc) -> Self { +// let name = adapter.name().await; + +// LspAdapter { name, adapter } +// } + +// fn name(&self) -> LanguageServerName { +// self.name +// } +// } + #[async_trait] pub trait LspAdapter: 'static + Send + Sync { async fn name(&self) -> LanguageServerName; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c1bfa7f318..5fdfbcd512 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1787,6 +1787,7 @@ impl Project { ))?, } cx.subscribe(buffer, |this, buffer, event, cx| { + // TODO(isaac): should this be done in the background? this.on_buffer_event(buffer, event, cx).await; }) .detach(); @@ -3310,6 +3311,7 @@ impl Project { return Ok(Default::default()); }; + // TODO(isaac): also use join_all struct PartialSymbol { source_worktree_id: WorktreeId, worktree_id: WorktreeId, @@ -3537,6 +3539,7 @@ impl Project { Default::default() }; + // TODO(isaac): use futures::future::join_all struct PartialCompletion { pub old_range: Range, pub new_text: String, From 0872e9b1a7cab22134163fc7f588dbd1164fb925 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 11:13:28 +0200 Subject: [PATCH 58/91] use join_all to build partial symbols and completions asynchronously --- crates/project/src/project.rs | 77 ++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5fdfbcd512..1f0448f228 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3311,7 +3311,6 @@ impl Project { return Ok(Default::default()); }; - // TODO(isaac): also use join_all struct PartialSymbol { source_worktree_id: WorktreeId, worktree_id: WorktreeId, @@ -3366,28 +3365,30 @@ impl Project { let mut symbols = Vec::new(); for ps in partial_symbols.into_iter() { - let label = match ps.language { - Some(language) => language.label_for_symbol(&ps.name, ps.kind).await, - None => None, - } - .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); + symbols.push(async move { + let label = match ps.language { + Some(language) => language.label_for_symbol(&ps.name, ps.kind).await, + None => None, + } + .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); - let language_server_name = ps.adapter.name().await; + let language_server_name = ps.adapter.name().await; - symbols.push(Symbol { - source_worktree_id: ps.source_worktree_id, - worktree_id: ps.worktree_id, - language_server_name, - name: ps.name, - kind: ps.kind, - label, - path: ps.path, - range: ps.range, - signature: ps.signature, + Symbol { + source_worktree_id: ps.source_worktree_id, + worktree_id: ps.worktree_id, + language_server_name, + name: ps.name, + kind: ps.kind, + label, + path: ps.path, + range: ps.range, + signature: ps.signature, + } }); } - Ok(symbols) + Ok(futures::future::join_all(symbols).await) }) } else if let Some(project_id) = self.remote_id() { let request = self.client.request(proto::GetProjectSymbols { @@ -3678,28 +3679,30 @@ impl Project { let mut result = Vec::new(); for pc in partial_completions.into_iter() { - let label = match pc.language.as_ref() { - Some(l) => l.label_for_completion(&pc.lsp_completion).await, - None => None, - } - .unwrap_or_else(|| { - CodeLabel::plain( - pc.lsp_completion.label.clone(), - pc.lsp_completion.filter_text.as_deref(), - ) + result.push(async move { + let label = match pc.language.as_ref() { + Some(l) => l.label_for_completion(&pc.lsp_completion).await, + None => None, + } + .unwrap_or_else(|| { + CodeLabel::plain( + pc.lsp_completion.label.clone(), + pc.lsp_completion.filter_text.as_deref(), + ) + }); + + let completion = Completion { + old_range: pc.old_range, + new_text: pc.new_text, + label, + lsp_completion: pc.lsp_completion, + }; + + completion }); - - let completion = Completion { - old_range: pc.old_range, - new_text: pc.new_text, - label, - lsp_completion: pc.lsp_completion, - }; - - result.push(completion); } - Ok(result) + Ok(futures::future::join_all(result).await) }) } else if let Some(project_id) = self.remote_id() { let rpc = self.client.clone(); From 4f016d5fc48ab08eab1b471d229a2c33af68363c Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 13:09:14 +0200 Subject: [PATCH 59/91] Switch LspAdapter to struct and revert some async/await --- crates/language/src/language.rs | 138 ++++++++++++++++++++++++-------- crates/project/src/project.rs | 50 +++++------- 2 files changed, 125 insertions(+), 63 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index a3f9475eae..c87cdd30bb 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -7,6 +7,7 @@ pub mod proto; mod tests; use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; use client::http::HttpClient; use collections::HashMap; use futures::{ @@ -43,7 +44,7 @@ pub use outline::{Outline, OutlineItem}; pub use tree_sitter::{Parser, Tree}; thread_local! { - static PARSER: RefCell = RefCell::new(Parser::new()); + static PARSER: RefCell = RefCell::new(Parser::new()); } lazy_static! { @@ -63,38 +64,103 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); -use async_trait::async_trait; +/// Represents a Language Server, with certain cached sync properties. +/// Uses [`LspAdapterTrait`] under the hood, but calls all 'static' methods +/// once at startup, and caches the results. +pub struct LspAdapter { + pub name: LanguageServerName, + pub server_args: Vec, + pub initialization_options: Option, + pub disk_based_diagnostic_sources: Vec, + pub disk_based_diagnostics_progress_token: Option, + pub id_for_language: Option, + pub adapter: Box, +} -// pub struct LspAdapter { -// name: LanguageServerName, -// adapter: Arc, -// } +impl LspAdapter { + pub async fn new(adapter: impl LspAdapterTrait) -> Arc { + let adapter = Box::new(adapter); + let name = adapter.name().await; + let server_args = adapter.server_args().await; + let initialization_options = adapter.initialization_options().await; + let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token().await; + let id_for_language = adapter.id_for_language(name.0.as_ref()).await; -// impl LspAdapter { -// async fn new(adapter: Arc) -> Self { -// let name = adapter.name().await; + Arc::new(LspAdapter { + name, + server_args, + initialization_options, + disk_based_diagnostic_sources, + disk_based_diagnostics_progress_token, + id_for_language, + adapter, + }) + } -// LspAdapter { name, adapter } -// } + pub async fn fetch_latest_server_version( + &self, + http: Arc, + ) -> Result> { + self.adapter.fetch_latest_server_version(http).await + } -// fn name(&self) -> LanguageServerName { -// self.name -// } -// } + pub async fn fetch_server_binary( + &self, + version: Box, + http: Arc, + container_dir: PathBuf, + ) -> Result { + self.adapter + .fetch_server_binary(version, http, container_dir) + .await + } + + pub async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + self.adapter.cached_server_binary(container_dir).await + } + + pub async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { + self.adapter.process_diagnostics(params).await + } + + pub async fn label_for_completion( + &self, + completion_item: &lsp::CompletionItem, + language: &Language, + ) -> Option { + self.adapter + .label_for_completion(completion_item, language) + .await + } + + pub async fn label_for_symbol( + &self, + name: &str, + kind: lsp::SymbolKind, + language: &Language, + ) -> Option { + self.adapter.label_for_symbol(name, kind, language).await + } +} #[async_trait] -pub trait LspAdapter: 'static + Send + Sync { +pub trait LspAdapterTrait: 'static + Send + Sync { async fn name(&self) -> LanguageServerName; + async fn fetch_latest_server_version( &self, http: Arc, ) -> Result>; + async fn fetch_server_binary( &self, version: Box, http: Arc, container_dir: PathBuf, ) -> Result; + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -189,7 +255,9 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D } #[cfg(any(test, feature = "test-support"))] -pub struct FakeLspAdapter { +pub type FakeLspAdapter = Arc; + +pub struct FakeLspAdapterInner { pub name: &'static str, pub capabilities: lsp::ServerCapabilities, pub initializer: Option>, @@ -208,12 +276,12 @@ pub struct BracketPair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) adapter: Option>, + pub(crate) adapter: Option>, #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( mpsc::UnboundedSender, - Arc, + FakeLspAdapter, )>, } @@ -373,7 +441,7 @@ impl LanguageRegistry { let server_binary_path = this .lsp_binary_paths .lock() - .entry(adapter.name().await) + .entry(adapter.name.clone()) .or_insert_with(|| { get_server_binary_path( adapter.clone(), @@ -390,7 +458,7 @@ impl LanguageRegistry { .map_err(|e| anyhow!(e)); let server_binary_path = server_binary_path.await?; - let server_args = adapter.server_args().await; + let server_args = &adapter.server_args; let server = lsp::LanguageServer::new( server_id, &server_binary_path, @@ -410,13 +478,13 @@ impl LanguageRegistry { } async fn get_server_binary_path( - adapter: Arc, + adapter: Arc, language: Arc, http_client: Arc, download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { - let container_dir = download_dir.join(adapter.name().await.0.as_ref()); + let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -452,7 +520,7 @@ async fn get_server_binary_path( } async fn fetch_latest_server_binary_path( - adapter: Arc, + adapter: Arc, language: Arc, http_client: Arc, container_dir: &Path, @@ -501,7 +569,7 @@ impl Language { } } - pub fn lsp_adapter(&self) -> Option> { + pub fn lsp_adapter(&self) -> Option> { self.adapter.clone() } @@ -533,7 +601,7 @@ impl Language { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } - pub fn with_lsp_adapter(mut self, lsp_adapter: Arc) -> Self { + pub fn with_lsp_adapter(mut self, lsp_adapter: Arc) -> Self { self.adapter = Some(lsp_adapter); self } @@ -544,8 +612,8 @@ impl Language { fake_lsp_adapter: FakeLspAdapter, ) -> mpsc::UnboundedReceiver { let (servers_tx, servers_rx) = mpsc::unbounded(); - let adapter = Arc::new(fake_lsp_adapter); - self.fake_adapter = Some((servers_tx, adapter.clone())); + self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); + let adapter = smol::block_on(LspAdapter::new(fake_lsp_adapter)); self.adapter = Some(adapter); servers_rx } @@ -558,16 +626,16 @@ impl Language { self.config.line_comment.as_deref() } - pub async fn disk_based_diagnostic_sources(&self) -> Vec { + pub async fn disk_based_diagnostic_sources(&self) -> &[String] { match self.adapter.as_ref() { - Some(adapter) => adapter.disk_based_diagnostic_sources().await, - None => Vec::new(), + Some(adapter) => &adapter.disk_based_diagnostic_sources, + None => &[], } } - pub async fn disk_based_diagnostics_progress_token(&self) -> Option { + pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> { if let Some(adapter) = self.adapter.as_ref() { - adapter.disk_based_diagnostics_progress_token().await + adapter.disk_based_diagnostics_progress_token.as_deref() } else { None } @@ -695,7 +763,7 @@ impl CodeLabel { } #[cfg(any(test, feature = "test-support"))] -impl Default for FakeLspAdapter { +impl Default for FakeLspAdapterInner { fn default() -> Self { Self { name: "the-fake-language-server", @@ -709,7 +777,7 @@ impl Default for FakeLspAdapter { #[cfg(any(test, feature = "test-support"))] #[async_trait] -impl LspAdapter for FakeLspAdapter { +impl LspAdapterTrait for FakeLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName(self.name.into()) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 1f0448f228..b1d45ac9a2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -740,7 +740,7 @@ impl Project { for language in self.languages.to_vec() { if let Some(lsp_adapter) = language.lsp_adapter() { if !settings.enable_language_server(Some(&language.name())) { - let lsp_name = lsp_adapter.name().await; + let lsp_name = lsp_adapter.name; for (worktree_id, started_lsp_name) in self.started_language_servers.keys() { if lsp_name == *started_lsp_name { @@ -1663,7 +1663,7 @@ impl Project { this.create_local_worktree(&abs_path, false, cx) }) .await?; - let name = lsp_adapter.name().await; + let name = lsp_adapter.name; this.update(&mut cx, |this, cx| { this.language_servers .insert((worktree.read(cx).id(), name), (lsp_adapter, lsp_server)); @@ -1816,7 +1816,7 @@ impl Project { Ok(()) } - async fn register_buffer_with_language_server( + fn register_buffer_with_language_server( &mut self, buffer_handle: &ModelHandle, cx: &mut ModelContext<'_, Self>, @@ -1833,10 +1833,10 @@ impl Project { if let Some(language) = buffer.language() { let worktree_id = file.worktree_id(cx); if let Some(adapter) = language.lsp_adapter() { - language_id = adapter.id_for_language(language.name().as_ref()).await; + language_id = adapter.id_for_language.clone(); language_server = self .language_server_ids - .get(&(worktree_id, adapter.name())) + .get(&(worktree_id, adapter.name.clone())) .and_then(|id| self.language_servers.get(&id)) .and_then(|server_state| { if let LanguageServerState::Running { server, .. } = server_state { @@ -2000,11 +2000,7 @@ impl Project { // that don't support a disk-based progress token. let (lsp_adapter, language_server) = self.language_server_for_buffer(buffer.read(cx), cx)?; - if lsp_adapter - .disk_based_diagnostics_progress_token() - .await - .is_none() - { + if lsp_adapter.disk_based_diagnostics_progress_token.is_none() { let server_id = language_server.server_id(); self.disk_based_diagnostics_finished(server_id, cx); self.broadcast_language_server_update( @@ -2024,10 +2020,9 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, &Arc)> { - self.language_server_ids - .iter() - .filter_map(move |((language_server_worktree_id, _), id)| { + ) -> impl Iterator, Arc)> { + self.language_servers.iter().filter_map( + move |((language_server_worktree_id, _), server)| { if *language_server_worktree_id == worktree_id { if let Some(LanguageServerState::Running { adapter, server }) = self.language_servers.get(&id) @@ -2080,8 +2075,8 @@ impl Project { } else { return; }; - let key = (worktree_id, adapter.name()); + let key = (worktree_id, adapter.name); self.language_server_ids .entry(key.clone()) .or_insert_with(|| { @@ -2115,7 +2110,7 @@ impl Project { this.update(&mut cx, |this, cx| { this.on_lsp_diagnostics_published( server_id, params, &adapter, cx, - ); + ) }); } } @@ -2427,8 +2422,8 @@ impl Project { } else { return; }; - let server_name = adapter.name(); - let stop = self.stop_language_server(worktree_id, server_name.clone(), cx); + + let stop = self.stop_language_server(worktree_id, adapter.name, cx); cx.spawn_weak(|this, mut cx| async move { let (original_root_path, orphaned_worktrees) = stop.await; if let Some(this) = this.upgrade(&cx) { @@ -2464,14 +2459,14 @@ impl Project { &mut self, server_id: usize, mut params: lsp::PublishDiagnosticsParams, - adapter: &Arc, + adapter: &Arc, cx: &mut ModelContext<'_, Self>, ) { adapter.process_diagnostics(&mut params).await; self.update_diagnostics( server_id, params, - adapter.disk_based_diagnostic_sources().await, + &adapter.disk_based_diagnostic_sources, cx, ) .log_err(); @@ -2645,7 +2640,7 @@ impl Project { this: WeakModelHandle, params: lsp::ApplyWorkspaceEditParams, server_id: usize, - adapter: Arc, + adapter: Arc, language_server: Arc, mut cx: AsyncAppContext, ) -> Result { @@ -2716,7 +2711,7 @@ impl Project { &mut self, language_server_id: usize, params: lsp::PublishDiagnosticsParams, - disk_based_sources: Vec, + disk_based_sources: &[String], cx: &mut ModelContext, ) -> Result<()> { let abs_path = params @@ -3314,7 +3309,7 @@ impl Project { struct PartialSymbol { source_worktree_id: WorktreeId, worktree_id: WorktreeId, - adapter: Arc, + adapter: Arc, path: PathBuf, language: Option>, name: String, @@ -3372,7 +3367,7 @@ impl Project { } .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); - let language_server_name = ps.adapter.name().await; + let language_server_name = ps.adapter.name; Symbol { source_worktree_id: ps.source_worktree_id, @@ -4019,7 +4014,7 @@ impl Project { this: ModelHandle, edit: lsp::WorkspaceEdit, push_to_history: bool, - lsp_adapter: Arc, + lsp_adapter: Arc, language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { @@ -6029,10 +6024,9 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<&(Arc, Arc)> { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { - // TODO(isaac): this is not a good idea - let name = cx.background().block(language.lsp_adapter()?.name()); + let name = language.lsp_adapter()?.name; let worktree_id = file.worktree_id(cx); let key = (worktree_id, language.lsp_adapter()?.name()); From 6585daccf93f8844be5e3909eecc8b2bb21b970d Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 14:24:42 +0200 Subject: [PATCH 60/91] Further unpropogate async --- crates/collab/src/integration_tests.rs | 16 +-- crates/editor/src/editor.rs | 14 +-- crates/editor/src/test.rs | 8 +- crates/lsp/src/lsp.rs | 3 + crates/project/src/project.rs | 129 ++++++++------------ crates/zed/src/languages/c.rs | 2 +- crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/json.rs | 4 +- crates/zed/src/languages/language_plugin.rs | 4 +- crates/zed/src/languages/python.rs | 4 +- crates/zed/src/languages/rust.rs | 2 +- crates/zed/src/languages/typescript.rs | 4 +- 12 files changed, 86 insertions(+), 106 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 7767b361c1..0d96dcc8ca 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -1675,7 +1675,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string()]), @@ -1684,7 +1684,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu ..Default::default() }, ..Default::default() - }); + })); client_a.language_registry.add(Arc::new(language)); client_a @@ -2867,7 +2867,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { capabilities: lsp::ServerCapabilities { rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { prepare_provider: Some(true), @@ -2876,7 +2876,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T ..Default::default() }, ..Default::default() - }); + })); client_a.language_registry.add(Arc::new(language)); client_a @@ -3051,10 +3051,10 @@ async fn test_language_server_statuses( }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { name: "the-language-server", ..Default::default() - }); + })); client_a.language_registry.add(Arc::new(language)); client_a @@ -4577,7 +4577,7 @@ async fn test_random_collaboration( }, None, ); - let _fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let _fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { name: "the-fake-language-server", capabilities: lsp::LanguageServer::full_capabilities(), initializer: Some(Box::new({ @@ -4689,7 +4689,7 @@ async fn test_random_collaboration( } })), ..Default::default() - }); + })); host_language_registry.add(Arc::new(language)); let op_start_signal = futures::channel::mpsc::unbounded(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c1e2557555..6d0427ad61 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6233,7 +6233,7 @@ mod tests { platform::{WindowBounds, WindowOptions}, }; use indoc::indoc; - use language::{FakeLspAdapter, LanguageConfig}; + use language::{FakeLspAdapter, LanguageConfig, FakeLspAdapterInner}; use lsp::FakeLanguageServer; use project::FakeFs; use settings::LanguageSettings; @@ -9302,13 +9302,13 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { capabilities: lsp::ServerCapabilities { document_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() }, ..Default::default() - }); + })); let fs = FakeFs::new(cx.background().clone()); fs.insert_file("/file.rs", Default::default()).await; @@ -9414,13 +9414,13 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { capabilities: lsp::ServerCapabilities { document_range_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() }, ..Default::default() - }); + })); let fs = FakeFs::new(cx.background().clone()); fs.insert_file("/file.rs", Default::default()).await; @@ -9526,7 +9526,7 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string(), ":".to_string()]), @@ -9535,7 +9535,7 @@ mod tests { ..Default::default() }, ..Default::default() - }); + })); let text = " one diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index d1316a85a0..a2efc87249 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -9,7 +9,9 @@ use indoc::indoc; use collections::BTreeMap; use gpui::{json, keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle}; -use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection}; +use language::{ + point_to_lsp, FakeLspAdapter, FakeLspAdapterInner, Language, LanguageConfig, Selection, +}; use project::Project; use settings::Settings; use util::{ @@ -457,10 +459,10 @@ impl<'a> EditorLspTestContext<'a> { .unwrap_or(&"txt".to_string()) ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { capabilities, ..Default::default() - }); + })); let project = Project::test(params.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index bb6562629f..8c689808ae 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -258,6 +258,9 @@ impl LanguageServer { } } + /// Initializes a language server. + /// Note that `options` is used directly to construct [`InitializeParams`], + /// which is why it is owned. pub async fn initialize(mut self, options: Option) -> Result> { let root_uri = Url::from_file_path(&self.root_path).unwrap(); #[allow(deprecated)] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b1d45ac9a2..7d75da4443 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -113,7 +113,6 @@ pub struct Project { collaborators: HashMap, client_subscriptions: Vec, _subscriptions: Vec, - lsp_settings_changed: Option>, opened_buffer: (Rc>>, watch::Receiver<()>), shared_buffers: HashMap>, loading_buffers: HashMap< @@ -489,7 +488,6 @@ impl Project { next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), initialized_persistent_state: false, - lsp_settings_changed: None, } }) } @@ -604,7 +602,6 @@ impl Project { buffer_snapshots: Default::default(), nonce: StdRng::from_entropy().gen(), initialized_persistent_state: false, - lsp_settings_changed: None, }; for worktree in worktrees { this.add_worktree(&worktree, cx); @@ -713,63 +710,51 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { let settings = cx.global::(); - self.lsp_settings_changed = Some(cx.spawn(|project, cx| async { - let language_servers_to_start = project.update(&mut cx, |project, cx| { - let mut language_servers_to_start = Vec::new(); - for buffer in self.opened_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - let buffer = buffer.read(cx); - if let Some((file, language)) = - File::from_dyn(buffer.file()).zip(buffer.language()) - { - if settings.enable_language_server(Some(&language.name())) { - let worktree = file.worktree.read(cx); - language_servers_to_start.push(( - worktree.id(), - worktree.as_local().unwrap().abs_path().clone(), - language.clone(), - )); - } - } + + let mut language_servers_to_start = Vec::new(); + for buffer in self.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + let buffer = buffer.read(cx); + if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) + { + if settings.enable_language_server(Some(&language.name())) { + let worktree = file.worktree.read(cx); + language_servers_to_start.push(( + worktree.id(), + worktree.as_local().unwrap().abs_path().clone(), + language.clone(), + )); } } - language_servers_to_start - }); + } + } - let mut language_servers_to_stop = Vec::new(); - for language in self.languages.to_vec() { - if let Some(lsp_adapter) = language.lsp_adapter() { - if !settings.enable_language_server(Some(&language.name())) { - let lsp_name = lsp_adapter.name; - for (worktree_id, started_lsp_name) in self.started_language_servers.keys() - { - if lsp_name == *started_lsp_name { - language_servers_to_stop - .push((*worktree_id, started_lsp_name.clone())); - } + let mut language_servers_to_stop = Vec::new(); + for language in self.languages.to_vec() { + if let Some(lsp_adapter) = language.lsp_adapter() { + if !settings.enable_language_server(Some(&language.name())) { + let lsp_name = &lsp_adapter.name; + for (worktree_id, started_lsp_name) in self.language_servers.keys() { + if lsp_name == started_lsp_name { + language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); } } } } + } - project.update(&mut cx, |project, cx| { - // Stop all newly-disabled language servers. - for (worktree_id, adapter_name) in language_servers_to_stop { - self.stop_language_server(worktree_id, adapter_name, cx) - .detach(); - } + // Stop all newly-disabled language servers. + for (worktree_id, adapter_name) in language_servers_to_stop { + self.stop_language_server(worktree_id, adapter_name, cx) + .detach(); + } - // Start all the newly-enabled language servers. - for (worktree_id, worktree_path, language) in language_servers_to_start { - self.start_language_server(worktree_id, worktree_path, language, cx); - } + // Start all the newly-enabled language servers. + for (worktree_id, worktree_path, language) in language_servers_to_start { + self.start_language_server(worktree_id, worktree_path, language, cx); + } - cx.notify(); - }); - })) - - // // TODO(isaac): uncomment the above - // todo!() + cx.notify(); } pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option> { @@ -1663,7 +1648,7 @@ impl Project { this.create_local_worktree(&abs_path, false, cx) }) .await?; - let name = lsp_adapter.name; + let name = lsp_adapter.name.clone(); this.update(&mut cx, |this, cx| { this.language_servers .insert((worktree.read(cx).id(), name), (lsp_adapter, lsp_server)); @@ -1733,8 +1718,7 @@ impl Project { this.update(&mut cx, |this, cx| { this.assign_language_to_buffer(&buffer, cx); this.register_buffer_with_language_server(&buffer, cx) - }) - .await; + }); Ok(()) }) } @@ -1788,13 +1772,13 @@ impl Project { } cx.subscribe(buffer, |this, buffer, event, cx| { // TODO(isaac): should this be done in the background? - this.on_buffer_event(buffer, event, cx).await; + this.on_buffer_event(buffer, event, cx); }) .detach(); self.assign_language_to_buffer(buffer, cx); // TODO(isaac): should this be done in the background - self.register_buffer_with_language_server(buffer, cx).await; + self.register_buffer_with_language_server(buffer, cx); cx.observe_release(buffer, |this, buffer, cx| { if let Some(file) = File::from_dyn(buffer.file()) { if file.is_local() { @@ -1910,7 +1894,7 @@ impl Project { }); } - async fn on_buffer_event( + fn on_buffer_event( &mut self, buffer: ModelHandle, event: &BufferEvent, @@ -2050,13 +2034,12 @@ impl Project { let worktree = file.worktree.read(cx).as_local()?; let worktree_id = worktree.id(); let worktree_abs_path = worktree.abs_path().clone(); - self.start_language_server(worktree_id, worktree_abs_path, language, cx) - .await; + self.start_language_server(worktree_id, worktree_abs_path, language, cx); None } - async fn start_language_server( + fn start_language_server( &mut self, worktree_id: WorktreeId, worktree_path: Arc, @@ -2324,8 +2307,6 @@ impl Project { }) })), ); - - server_id }); } @@ -2403,14 +2384,13 @@ impl Project { .collect(); for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info { let language = self.languages.select_language(&full_path)?; - self.restart_language_server(worktree_id, worktree_abs_path, language, cx) - .await; + self.restart_language_server(worktree_id, worktree_abs_path, language, cx); } None } - async fn restart_language_server( + fn restart_language_server( &mut self, worktree_id: WorktreeId, fallback_path: Arc, @@ -2423,7 +2403,7 @@ impl Project { return; }; - let stop = self.stop_language_server(worktree_id, adapter.name, cx); + let stop = self.stop_language_server(worktree_id, adapter.name.clone(), cx); cx.spawn_weak(|this, mut cx| async move { let (original_root_path, orphaned_worktrees) = stop.await; if let Some(this) = this.upgrade(&cx) { @@ -2476,7 +2456,7 @@ impl Project { &mut self, progress: lsp::ProgressParams, server_id: usize, - disk_based_diagnostics_progress_token: Option, + disk_based_diagnostics_progress_token: Option<&str>, cx: &mut ModelContext, ) { let token = match progress.token { @@ -2500,8 +2480,7 @@ impl Project { return; } - let same_token = - Some(token.as_ref()) == disk_based_diagnostics_progress_token.as_ref().map(|x| &**x); + let same_token = Some(token.as_ref()) == disk_based_diagnostics_progress_token; match progress { lsp::WorkDoneProgress::Begin(report) => { @@ -3309,7 +3288,7 @@ impl Project { struct PartialSymbol { source_worktree_id: WorktreeId, worktree_id: WorktreeId, - adapter: Arc, + language_server_name: LanguageServerName, path: PathBuf, language: Option>, name: String, @@ -3343,8 +3322,7 @@ impl Project { partial_symbols.push(PartialSymbol { source_worktree_id, worktree_id, - // TODO: just pass out single adapter? - adapter: adapter.clone(), + language_server_name: adapter.name.clone(), name: lsp_symbol.name, kind: lsp_symbol.kind, language, @@ -3367,12 +3345,10 @@ impl Project { } .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); - let language_server_name = ps.adapter.name; - Symbol { source_worktree_id: ps.source_worktree_id, worktree_id: ps.worktree_id, - language_server_name, + language_server_name: ps.language_server_name, name: ps.name, kind: ps.kind, label, @@ -3535,7 +3511,6 @@ impl Project { Default::default() }; - // TODO(isaac): use futures::future::join_all struct PartialCompletion { pub old_range: Range, pub new_text: String, @@ -4645,7 +4620,7 @@ impl Project { for (buffer, old_path) in renamed_buffers { self.unregister_buffer_from_language_server(&buffer, old_path, cx); self.assign_language_to_buffer(&buffer, cx); - self.register_buffer_with_language_server(&buffer, cx).await; + self.register_buffer_with_language_server(&buffer, cx); } } @@ -6026,7 +6001,7 @@ impl Project { cx: &AppContext, ) -> Option<&(Arc, Arc)> { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { - let name = language.lsp_adapter()?.name; + let name = language.lsp_adapter()?.name.clone(); let worktree_id = file.worktree_id(cx); let key = (worktree_id, language.lsp_adapter()?.name()); diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 54554beaf6..9f2d64748b 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -11,7 +11,7 @@ use util::ResultExt; pub struct CLspAdapter; #[async_trait] -impl super::LspAdapter for CLspAdapter { +impl super::LspAdapterTrait for CLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("clangd".into()) } diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 314055e5ff..a8fe908867 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -18,7 +18,7 @@ lazy_static! { } #[async_trait] -impl super::LspAdapter for GoLspAdapter { +impl super::LspAdapterTrait for GoLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("gopls".into()) } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 29e8e7da02..28294d26e1 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; @@ -17,7 +17,7 @@ impl JsonLspAdapter { } #[async_trait] -impl LspAdapter for JsonLspAdapter { +impl LspAdapterTrait for JsonLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("vscode-json-languageserver".into()) } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9494705c27..4dfe7478ab 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,7 +4,7 @@ use client::http::HttpClient; use futures::lock::Mutex; use futures::Future; use gpui::executor::Background; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -72,7 +72,7 @@ struct Versions { // - it's totally a deadlock, the proof is in the pudding #[async_trait] -impl LspAdapter for PluginLspAdapter { +impl LspAdapterTrait for PluginLspAdapter { async fn name(&self) -> LanguageServerName { let name: String = self .runtime diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index ca0b24bda7..c5508ac337 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -15,7 +15,7 @@ impl PythonLspAdapter { } #[async_trait] -impl LspAdapter for PythonLspAdapter { +impl LspAdapterTrait for PythonLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("pyright".into()) } diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 7d7c60dea4..03999b15ff 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -14,7 +14,7 @@ use util::ResultExt; pub struct RustLspAdapter; #[async_trait] -impl LspAdapter for RustLspAdapter { +impl LspAdapterTrait for RustLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("rust-analyzer".into()) } diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 199a7f22ae..8f262d4e70 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; @@ -21,7 +21,7 @@ struct Versions { } #[async_trait] -impl LspAdapter for TypeScriptLspAdapter { +impl LspAdapterTrait for TypeScriptLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("typescript-language-server".into()) } From d009e10a466b9718e2b2403a5a98719d42a439e1 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 15:57:25 +0200 Subject: [PATCH 61/91] Fix all residual errors, need to polish off warnings and TODOS --- crates/editor/src/editor.rs | 2 +- crates/language/src/language.rs | 2 +- crates/zed/src/languages.rs | 28 ++++++++++++++-------------- crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/rust.rs | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6d0427ad61..90f152fc9b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6233,7 +6233,7 @@ mod tests { platform::{WindowBounds, WindowOptions}, }; use indoc::indoc; - use language::{FakeLspAdapter, LanguageConfig, FakeLspAdapterInner}; + use language::{FakeLspAdapter, FakeLspAdapterInner, LanguageConfig}; use lsp::FakeLanguageServer; use project::FakeFs; use settings::LanguageSettings; diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c87cdd30bb..a2da63052f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -78,7 +78,7 @@ pub struct LspAdapter { } impl LspAdapter { - pub async fn new(adapter: impl LspAdapterTrait) -> Arc { + pub async fn new(adapter: T) -> Arc { let adapter = Box::new(adapter); let name = adapter.name().await; let server_args = adapter.server_args().await; diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 753845ef63..f5660667d5 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -22,26 +22,26 @@ pub async fn init(languages: Arc, executor: Arc) { ( "c", tree_sitter_c::language(), - Some(Arc::new(c::CLspAdapter) as Arc), + Some(LspAdapter::new(c::CLspAdapter).await), ), ( "cpp", tree_sitter_cpp::language(), - Some(Arc::new(c::CLspAdapter) as Arc), + Some(LspAdapter::new(c::CLspAdapter).await), ), ( "go", tree_sitter_go::language(), - Some(Arc::new(go::GoLspAdapter) as Arc), + Some(LspAdapter::new(go::GoLspAdapter).await), ), ( "json", tree_sitter_json::language(), - // Some(Arc::new(json::JsonLspAdapter)), - language_plugin::new_json(executor) - .await - .log_err() - .map(|lang| Arc::new(lang) as Arc<_>), + // Some(LspAdapter::new(json::JsonLspAdapter)), + match language_plugin::new_json(executor).await.log_err() { + Some(lang) => Some(LspAdapter::new(lang).await), + None => None, + }, ), ( "markdown", @@ -51,12 +51,12 @@ pub async fn init(languages: Arc, executor: Arc) { ( "python", tree_sitter_python::language(), - Some(Arc::new(python::PythonLspAdapter)), + Some(LspAdapter::new(python::PythonLspAdapter).await), ), ( "rust", tree_sitter_rust::language(), - Some(Arc::new(rust::RustLspAdapter)), + Some(LspAdapter::new(rust::RustLspAdapter).await), ), ( "toml", @@ -66,17 +66,17 @@ pub async fn init(languages: Arc, executor: Arc) { ( "tsx", tree_sitter_typescript::language_tsx(), - Some(Arc::new(typescript::TypeScriptLspAdapter)), + Some(LspAdapter::new(typescript::TypeScriptLspAdapter).await), ), ( "typescript", tree_sitter_typescript::language_typescript(), - Some(Arc::new(typescript::TypeScriptLspAdapter)), + Some(LspAdapter::new(typescript::TypeScriptLspAdapter).await), ), ( "javascript", tree_sitter_typescript::language_tsx(), - Some(Arc::new(typescript::TypeScriptLspAdapter)), + Some(LspAdapter::new(typescript::TypeScriptLspAdapter).await), ), ] { languages.add(Arc::new(language(name, grammar, lsp_adapter))); @@ -86,7 +86,7 @@ pub async fn init(languages: Arc, executor: Arc) { pub(crate) fn language( name: &str, grammar: tree_sitter::Language, - lsp_adapter: Option>, + lsp_adapter: Option>, ) -> Language { let config = toml::from_slice( &LanguageDir::get(&format!("{}/config.toml", name)) diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index a8fe908867..8e8e4b300b 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -314,7 +314,7 @@ mod tests { let language = language( "go", tree_sitter_go::language(), - Some(Arc::new(GoLspAdapter)), + Some(smol::block_on(LspAdapter::new(GoLspAdapter))), ); let theme = SyntaxTheme::new(vec![ diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 03999b15ff..5a38c8d7c5 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -305,7 +305,7 @@ mod tests { let language = language( "rust", tree_sitter_rust::language(), - Some(Arc::new(RustLspAdapter)), + Some(smol::block_on(LspAdapter::new(RustLspAdapter))), ); let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ @@ -384,7 +384,7 @@ mod tests { let language = language( "rust", tree_sitter_rust::language(), - Some(Arc::new(RustLspAdapter)), + Some(smol::block_on(LspAdapter::new(RustLspAdapter))), ); let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ From 39fdbc593b7a86e80aeb18c668d4a1022c72bda5 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 20:08:18 +0200 Subject: [PATCH 62/91] Fix most warnings --- crates/editor/src/editor.rs | 2 +- crates/editor/src/test.rs | 4 +--- crates/project/src/project.rs | 7 +++---- crates/zed/src/languages/json.rs | 2 +- crates/zed/src/languages/language_plugin.rs | 11 +++++------ crates/zed/src/languages/python.rs | 2 +- crates/zed/src/languages/typescript.rs | 2 +- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 90f152fc9b..a0e1ac8c3d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6233,7 +6233,7 @@ mod tests { platform::{WindowBounds, WindowOptions}, }; use indoc::indoc; - use language::{FakeLspAdapter, FakeLspAdapterInner, LanguageConfig}; + use language::{FakeLspAdapterInner, LanguageConfig}; use lsp::FakeLanguageServer; use project::FakeFs; use settings::LanguageSettings; diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index a2efc87249..0b1190e7c8 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -9,9 +9,7 @@ use indoc::indoc; use collections::BTreeMap; use gpui::{json, keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle}; -use language::{ - point_to_lsp, FakeLspAdapter, FakeLspAdapterInner, Language, LanguageConfig, Selection, -}; +use language::{point_to_lsp, FakeLspAdapterInner, Language, LanguageConfig, Selection}; use project::Project; use settings::Settings; use util::{ diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7d75da4443..9f020e240b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1771,13 +1771,11 @@ impl Project { ))?, } cx.subscribe(buffer, |this, buffer, event, cx| { - // TODO(isaac): should this be done in the background? this.on_buffer_event(buffer, event, cx); }) .detach(); self.assign_language_to_buffer(buffer, cx); - // TODO(isaac): should this be done in the background self.register_buffer_with_language_server(buffer, cx); cx.observe_release(buffer, |this, buffer, cx| { if let Some(file) = File::from_dyn(buffer.file()) { @@ -2091,9 +2089,10 @@ impl Project { move |params, mut cx| { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - this.on_lsp_diagnostics_published( + // TODO(isaac): remove block on + smol::block_on(this.on_lsp_diagnostics_published( server_id, params, &adapter, cx, - ) + )) }); } } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 28294d26e1..ab6f03a06f 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapterTrait}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 4dfe7478ab..6c07fc7042 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -2,9 +2,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::lock::Mutex; -use futures::Future; use gpui::executor::Background; -use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapterTrait}; use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -55,10 +54,10 @@ impl PluginLspAdapter { } } -struct Versions { - language_version: String, - server_version: String, -} +// struct Versions { +// language_version: String, +// server_version: String, +// } // TODO: is this the root cause? // sketch: diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index c5508ac337..00b8ec8e08 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapterTrait}; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 8f262d4e70..786e00f248 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapterTrait}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; From 895747476fb893266e4d07910addbdf25b8463bb Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 6 Jul 2022 20:36:38 +0200 Subject: [PATCH 63/91] Done! Finish transition to async, very close to merging --- crates/collab/src/integration_tests.rs | 8 +- crates/editor/src/editor.rs | 8 +- crates/editor/src/test.rs | 4 +- crates/language/src/language.rs | 15 +- crates/plugin_runtime/src/plugin.rs | 2 +- crates/project/src/project.rs | 2928 +++++++++++++++++ crates/project_symbols/src/project_symbols.rs | 2 +- 7 files changed, 2948 insertions(+), 19 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 0d96dcc8ca..67caeb325b 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -1675,7 +1675,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string()]), @@ -2867,7 +2867,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities: lsp::ServerCapabilities { rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { prepare_provider: Some(true), @@ -3051,7 +3051,7 @@ async fn test_language_server_statuses( }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "the-language-server", ..Default::default() })); @@ -4577,7 +4577,7 @@ async fn test_random_collaboration( }, None, ); - let _fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let _fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "the-fake-language-server", capabilities: lsp::LanguageServer::full_capabilities(), initializer: Some(Box::new({ diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a0e1ac8c3d..7129b41266 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6233,7 +6233,7 @@ mod tests { platform::{WindowBounds, WindowOptions}, }; use indoc::indoc; - use language::{FakeLspAdapterInner, LanguageConfig}; + use language::{FakeLspAdapter, LanguageConfig}; use lsp::FakeLanguageServer; use project::FakeFs; use settings::LanguageSettings; @@ -9302,7 +9302,7 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities: lsp::ServerCapabilities { document_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() @@ -9414,7 +9414,7 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities: lsp::ServerCapabilities { document_range_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() @@ -9526,7 +9526,7 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string(), ":".to_string()]), diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 0b1190e7c8..0e5cd0f68d 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -9,7 +9,7 @@ use indoc::indoc; use collections::BTreeMap; use gpui::{json, keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle}; -use language::{point_to_lsp, FakeLspAdapterInner, Language, LanguageConfig, Selection}; +use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection}; use project::Project; use settings::Settings; use util::{ @@ -457,7 +457,7 @@ impl<'a> EditorLspTestContext<'a> { .unwrap_or(&"txt".to_string()) ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapterInner { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities, ..Default::default() })); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index a2da63052f..66b52629db 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -254,10 +254,11 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D } } -#[cfg(any(test, feature = "test-support"))] -pub type FakeLspAdapter = Arc; +// #[cfg(any(test, feature = "test-support"))] +// pub type FakeLspAdapter = Arc; -pub struct FakeLspAdapterInner { +#[cfg(any(test, feature = "test-support"))] +pub struct FakeLspAdapter { pub name: &'static str, pub capabilities: lsp::ServerCapabilities, pub initializer: Option>, @@ -281,7 +282,7 @@ pub struct Language { #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( mpsc::UnboundedSender, - FakeLspAdapter, + Arc, )>, } @@ -609,7 +610,7 @@ impl Language { #[cfg(any(test, feature = "test-support"))] pub fn set_fake_lsp_adapter( &mut self, - fake_lsp_adapter: FakeLspAdapter, + fake_lsp_adapter: Arc, ) -> mpsc::UnboundedReceiver { let (servers_tx, servers_rx) = mpsc::unbounded(); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); @@ -763,7 +764,7 @@ impl CodeLabel { } #[cfg(any(test, feature = "test-support"))] -impl Default for FakeLspAdapterInner { +impl Default for FakeLspAdapter { fn default() -> Self { Self { name: "the-fake-language-server", @@ -777,7 +778,7 @@ impl Default for FakeLspAdapterInner { #[cfg(any(test, feature = "test-support"))] #[async_trait] -impl LspAdapterTrait for FakeLspAdapter { +impl LspAdapterTrait for Arc { async fn name(&self) -> LanguageServerName { LanguageServerName(self.name.into()) } diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index cbcd31ac3a..2d2cab35f1 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -68,7 +68,7 @@ impl PluginBuilder { pub fn new(wasi_ctx: WasiCtx) -> Result { let mut config = Config::default(); config.async_support(true); - config.epoch_interruption(true); + // config.epoch_interruption(true); let engine = Engine::new(&config)?; let linker = Linker::new(&engine); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9f020e240b..160f66eeb5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6302,3 +6302,2931 @@ impl Item for Buffer { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) } } + +#[cfg(test)] +mod tests { + use crate::worktree::WorktreeHandle; + + use super::{Event, *}; + use fs::RealFs; + use futures::{future, StreamExt}; + use gpui::{executor::Deterministic, test::subscribe}; + use language::{ + tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, + OffsetRangeExt, Point, ToPoint, + }; + use lsp::Url; + use serde_json::json; + use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc, task::Poll}; + use unindent::Unindent as _; + use util::{assert_set_eq, test::temp_tree}; + + #[gpui::test] + async fn test_populate_and_search(cx: &mut gpui::TestAppContext) { + let dir = temp_tree(json!({ + "root": { + "apple": "", + "banana": { + "carrot": { + "date": "", + "endive": "", + } + }, + "fennel": { + "grape": "", + } + } + })); + + let root_link_path = dir.path().join("root_link"); + unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); + unix::fs::symlink( + &dir.path().join("root/fennel"), + &dir.path().join("root/finnochio"), + ) + .unwrap(); + + let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; + + project.read_with(cx, |project, cx| { + let tree = project.worktrees(cx).next().unwrap().read(cx); + assert_eq!(tree.file_count(), 5); + assert_eq!( + tree.inode_for_path("fennel/grape"), + tree.inode_for_path("finnochio/grape") + ); + }); + + let cancel_flag = Default::default(); + let results = project + .read_with(cx, |project, cx| { + project.match_paths("bna", false, false, 10, &cancel_flag, cx) + }) + .await; + assert_eq!( + results + .into_iter() + .map(|result| result.path) + .collect::>>(), + vec![ + PathBuf::from("banana/carrot/date").into(), + PathBuf::from("banana/carrot/endive").into(), + ] + ); + } + + #[gpui::test] + async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut rust_language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut json_language = Language::new( + LanguageConfig { + name: "JSON".into(), + path_suffixes: vec!["json".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_rust_servers = rust_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-rust-language-server", + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), "::".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })); + let mut fake_json_servers = json_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-json-language-server", + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/the-root", + json!({ + "test.rs": "const A: i32 = 1;", + "test2.rs": "", + "Cargo.toml": "a = 1", + "package.json": "{\"a\": 1}", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; + project.update(cx, |project, _| { + project.languages.add(Arc::new(rust_language)); + project.languages.add(Arc::new(json_language)); + }); + + // Open a buffer without an associated language server. + let toml_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/Cargo.toml", cx) + }) + .await + .unwrap(); + + // Open a buffer with an associated language server. + let rust_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/test.rs", cx) + }) + .await + .unwrap(); + + // A server is started up, and it is notified about Rust files. + let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), + version: 0, + text: "const A: i32 = 1;".to_string(), + language_id: Default::default() + } + ); + + // The buffer is configured based on the language server's capabilities. + rust_buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer.completion_triggers(), + &[".".to_string(), "::".to_string()] + ); + }); + toml_buffer.read_with(cx, |buffer, _| { + assert!(buffer.completion_triggers().is_empty()); + }); + + // Edit a buffer. The changes are reported to the language server. + rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], cx)); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test.rs").unwrap(), + 1 + ) + ); + + // Open a third buffer with a different associated language server. + let json_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/package.json", cx) + }) + .await + .unwrap(); + + // A json language server is started up and is only notified about the json buffer. + let mut fake_json_server = fake_json_servers.next().await.unwrap(); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), + version: 0, + text: "{\"a\": 1}".to_string(), + language_id: Default::default() + } + ); + + // This buffer is configured based on the second language server's + // capabilities. + json_buffer.read_with(cx, |buffer, _| { + assert_eq!(buffer.completion_triggers(), &[":".to_string()]); + }); + + // When opening another buffer whose language server is already running, + // it is also configured based on the existing language server's capabilities. + let rust_buffer2 = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/test2.rs", cx) + }) + .await + .unwrap(); + rust_buffer2.read_with(cx, |buffer, _| { + assert_eq!( + buffer.completion_triggers(), + &[".".to_string(), "::".to_string()] + ); + }); + + // Changes are reported only to servers matching the buffer's language. + toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], cx)); + rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "let x = 1;")], cx)); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test2.rs").unwrap(), + 1 + ) + ); + + // Save notifications are reported to all servers. + toml_buffer + .update(cx, |buffer, cx| buffer.save(cx)) + .await + .unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap() + ) + ); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap() + ) + ); + + // Renames are reported only to servers matching the buffer's language. + fs.rename( + Path::new("/the-root/test2.rs"), + Path::new("/the-root/test3.rs"), + Default::default(), + ) + .await + .unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test2.rs").unwrap() + ), + ); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(), + version: 0, + text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), + language_id: Default::default() + }, + ); + + rust_buffer2.update(cx, |buffer, cx| { + buffer.update_diagnostics( + DiagnosticSet::from_sorted_entries( + vec![DiagnosticEntry { + diagnostic: Default::default(), + range: Anchor::MIN..Anchor::MAX, + }], + &buffer.snapshot(), + ), + cx, + ); + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..buffer.len(), false) + .count(), + 1 + ); + }); + + // When the rename changes the extension of the file, the buffer gets closed on the old + // language server and gets opened on the new one. + fs.rename( + Path::new("/the-root/test3.rs"), + Path::new("/the-root/test3.json"), + Default::default(), + ) + .await + .unwrap(); + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test3.rs").unwrap(), + ), + ); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(), + version: 0, + text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), + language_id: Default::default() + }, + ); + + // We clear the diagnostics, since the language has changed. + rust_buffer2.read_with(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, usize>(0..buffer.len(), false) + .count(), + 0 + ); + }); + + // The renamed file's version resets after changing language server. + rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], cx)); + assert_eq!( + fake_json_server + .receive_notification::() + .await + .text_document, + lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/test3.json").unwrap(), + 1 + ) + ); + + // Restart language servers + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers( + vec![rust_buffer.clone(), json_buffer.clone()], + cx, + ); + }); + + let mut rust_shutdown_requests = fake_rust_server + .handle_request::(|_, _| future::ready(Ok(()))); + let mut json_shutdown_requests = fake_json_server + .handle_request::(|_, _| future::ready(Ok(()))); + futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next()); + + let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); + let mut fake_json_server = fake_json_servers.next().await.unwrap(); + + // Ensure rust document is reopened in new rust language server + assert_eq!( + fake_rust_server + .receive_notification::() + .await + .text_document, + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), + version: 1, + text: rust_buffer.read_with(cx, |buffer, _| buffer.text()), + language_id: Default::default() + } + ); + + // Ensure json documents are reopened in new json language server + assert_set_eq!( + [ + fake_json_server + .receive_notification::() + .await + .text_document, + fake_json_server + .receive_notification::() + .await + .text_document, + ], + [ + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), + version: 0, + text: json_buffer.read_with(cx, |buffer, _| buffer.text()), + language_id: Default::default() + }, + lsp::TextDocumentItem { + uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(), + version: 1, + text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), + language_id: Default::default() + } + ] + ); + + // Close notifications are reported only to servers matching the buffer's language. + cx.update(|_| drop(json_buffer)); + let close_message = lsp::DidCloseTextDocumentParams { + text_document: lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path("/the-root/package.json").unwrap(), + ), + }; + assert_eq!( + fake_json_server + .receive_notification::() + .await, + close_message, + ); + } + + #[gpui::test] + async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "let a = 1;", + "b.rs": "let b = 2;" + }), + ) + .await; + + let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await; + + let buffer_a = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + let buffer_b = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) + .await + .unwrap(); + + project.update(cx, |project, cx| { + project + .update_diagnostics( + 0, + lsp::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(0, 4), + lsp::Position::new(0, 5), + ), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "error 1".to_string(), + ..Default::default() + }], + }, + &[], + cx, + ) + .unwrap(); + project + .update_diagnostics( + 0, + lsp::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/b.rs").unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(0, 4), + lsp::Position::new(0, 5), + ), + severity: Some(lsp::DiagnosticSeverity::WARNING), + message: "error 2".to_string(), + ..Default::default() + }], + }, + &[], + cx, + ) + .unwrap(); + }); + + buffer_a.read_with(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let ", None), + ("a", Some(DiagnosticSeverity::ERROR)), + (" = 1;", None), + ] + ); + }); + buffer_b.read_with(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let ", None), + ("b", Some(DiagnosticSeverity::WARNING)), + (" = 2;", None), + ] + ); + }); + } + + #[gpui::test] + async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let progress_token = "the-progress-token"; + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_progress_token: Some(progress_token.into()), + disk_based_diagnostics_sources: vec!["disk".into()], + ..Default::default() + })); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "fn a() { A }", + "b.rs": "const y: i32 = 1", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let worktree_id = + project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id()); + + // Cause worktree to start the fake language server + let _buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) + .await + .unwrap(); + + let mut events = subscribe(&project, cx); + + let fake_server = fake_servers.next().await.unwrap(); + fake_server.start_progress(progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsStarted { + language_server_id: 0, + } + ); + + fake_server.notify::( + lsp::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "undefined variable 'A'".to_string(), + ..Default::default() + }], + }, + ); + assert_eq!( + events.next().await.unwrap(), + Event::DiagnosticsUpdated { + language_server_id: 0, + path: (worktree_id, Path::new("a.rs")).into() + } + ); + + fake_server.end_progress(progress_token); + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsFinished { + language_server_id: 0 + } + ); + + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + buffer.read_with(cx, |buffer, _| { + let snapshot = buffer.snapshot(); + let diagnostics = snapshot + .diagnostics_in_range::<_, Point>(0..buffer.len(), false) + .collect::>(); + assert_eq!( + diagnostics, + &[DiagnosticEntry { + range: Point::new(0, 9)..Point::new(0, 10), + diagnostic: Diagnostic { + severity: lsp::DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + }] + ) + }); + + // Ensure publishing empty diagnostics twice only results in one update event. + fake_server.notify::( + lsp::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: Default::default(), + }, + ); + assert_eq!( + events.next().await.unwrap(), + Event::DiagnosticsUpdated { + language_server_id: 0, + path: (worktree_id, Path::new("a.rs")).into() + } + ); + + fake_server.notify::( + lsp::PublishDiagnosticsParams { + uri: Url::from_file_path("/dir/a.rs").unwrap(), + version: None, + diagnostics: Default::default(), + }, + ); + cx.foreground().run_until_parked(); + assert_eq!(futures::poll!(events.next()), Poll::Pending); + } + + #[gpui::test] + async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let progress_token = "the-progress-token"; + let mut language = Language::new( + LanguageConfig { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + disk_based_diagnostics_progress_token: Some(progress_token.into()), + ..Default::default() + })); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": "" })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Simulate diagnostics starting to update. + let fake_server = fake_servers.next().await.unwrap(); + fake_server.start_progress(progress_token).await; + + // Restart the server before the diagnostics finish updating. + project.update(cx, |project, cx| { + project.restart_language_servers_for_buffers([buffer], cx); + }); + let mut events = subscribe(&project, cx); + + // Simulate the newly started server sending more diagnostics. + let fake_server = fake_servers.next().await.unwrap(); + fake_server.start_progress(progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsStarted { + language_server_id: 1 + } + ); + project.read_with(cx, |project, _| { + assert_eq!( + project + .language_servers_running_disk_based_diagnostics() + .collect::>(), + [1] + ); + }); + + // All diagnostics are considered done, despite the old server's diagnostic + // task never completing. + fake_server.end_progress(progress_token); + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsFinished { + language_server_id: 1 + } + ); + project.read_with(cx, |project, _| { + assert_eq!( + project + .language_servers_running_disk_based_diagnostics() + .collect::>(), + [0; 0] + ); + }); + } + + #[gpui::test] + async fn test_toggling_enable_language_server( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + deterministic.forbid_parking(); + + let mut rust = Language::new( + LanguageConfig { + name: Arc::from("Rust"), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_rust_servers = rust.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "rust-lsp", + ..Default::default() + })); + let mut js = Language::new( + LanguageConfig { + name: Arc::from("JavaScript"), + path_suffixes: vec!["js".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_js_servers = js.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "js-lsp", + ..Default::default() + })); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| { + project.languages.add(Arc::new(rust)); + project.languages.add(Arc::new(js)); + }); + + let _rs_buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + let _js_buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx)) + .await + .unwrap(); + + let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server_1 + .receive_notification::() + .await + .text_document + .uri + .as_str(), + "file:///dir/a.rs" + ); + + let mut fake_js_server = fake_js_servers.next().await.unwrap(); + assert_eq!( + fake_js_server + .receive_notification::() + .await + .text_document + .uri + .as_str(), + "file:///dir/b.js" + ); + + // Disable Rust language server, ensuring only that server gets stopped. + cx.update(|cx| { + cx.update_global(|settings: &mut Settings, _| { + settings.language_overrides.insert( + Arc::from("Rust"), + settings::LanguageSettings { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }) + }); + fake_rust_server_1 + .receive_notification::() + .await; + + // Enable Rust and disable JavaScript language servers, ensuring that the + // former gets started again and that the latter stops. + cx.update(|cx| { + cx.update_global(|settings: &mut Settings, _| { + settings.language_overrides.insert( + Arc::from("Rust"), + settings::LanguageSettings { + enable_language_server: Some(true), + ..Default::default() + }, + ); + settings.language_overrides.insert( + Arc::from("JavaScript"), + settings::LanguageSettings { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }) + }); + let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); + assert_eq!( + fake_rust_server_2 + .receive_notification::() + .await + .text_document + .uri + .as_str(), + "file:///dir/a.rs" + ); + fake_js_server + .receive_notification::() + .await; + } + + #[gpui::test] + async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + ..Default::default() + })); + + let text = " + fn a() { A } + fn b() { BB } + fn c() { CCC } + " + .unindent(); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": text })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + let mut fake_server = fake_servers.next().await.unwrap(); + let open_notification = fake_server + .receive_notification::() + .await; + + // Edit the buffer, moving the content down + buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], cx)); + let change_notification_1 = fake_server + .receive_notification::() + .await; + assert!( + change_notification_1.text_document.version > open_notification.text_document.version + ); + + // Report some diagnostics for the initial version of the buffer + fake_server.notify::( + lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), + version: Some(open_notification.text_document.version), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(DiagnosticSeverity::ERROR), + message: "undefined variable 'A'".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)), + severity: Some(DiagnosticSeverity::ERROR), + message: "undefined variable 'BB'".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)), + severity: Some(DiagnosticSeverity::ERROR), + source: Some("disk".to_string()), + message: "undefined variable 'CCC'".to_string(), + ..Default::default() + }, + ], + }, + ); + + // The diagnostics have moved down since they were created. + buffer.next_notification(cx).await; + buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(3, 9)..Point::new(3, 11), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'BB'".to_string(), + is_disk_based: true, + group_id: 1, + is_primary: true, + ..Default::default() + }, + }, + DiagnosticEntry { + range: Point::new(4, 9)..Point::new(4, 12), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'CCC'".to_string(), + is_disk_based: true, + group_id: 2, + is_primary: true, + ..Default::default() + } + } + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, 0..buffer.len()), + [ + ("\n\nfn a() { ".to_string(), None), + ("A".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\nfn b() { ".to_string(), None), + ("BB".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\nfn c() { ".to_string(), None), + ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\n".to_string(), None), + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)), + [ + ("B".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }\nfn c() { ".to_string(), None), + ("CC".to_string(), Some(DiagnosticSeverity::ERROR)), + ] + ); + }); + + // Ensure overlapping diagnostics are highlighted correctly. + fake_server.notify::( + lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), + version: Some(open_notification.text_document.version), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(DiagnosticSeverity::ERROR), + message: "undefined variable 'A'".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)), + severity: Some(DiagnosticSeverity::WARNING), + message: "unreachable statement".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + ], + }, + ); + + buffer.next_notification(cx).await; + buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(2, 9)..Point::new(2, 12), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "unreachable statement".to_string(), + is_disk_based: true, + group_id: 4, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(2, 9)..Point::new(2, 10), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + is_disk_based: true, + group_id: 3, + is_primary: true, + ..Default::default() + }, + } + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)), + [ + ("fn a() { ".to_string(), None), + ("A".to_string(), Some(DiagnosticSeverity::ERROR)), + (" }".to_string(), Some(DiagnosticSeverity::WARNING)), + ("\n".to_string(), None), + ] + ); + assert_eq!( + chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)), + [ + (" }".to_string(), Some(DiagnosticSeverity::WARNING)), + ("\n".to_string(), None), + ] + ); + }); + + // Keep editing the buffer and ensure disk-based diagnostics get translated according to the + // changes since the last save. + buffer.update(cx, |buffer, cx| { + buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], cx); + buffer.edit([(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], cx); + buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], cx); + }); + let change_notification_2 = fake_server + .receive_notification::() + .await; + assert!( + change_notification_2.text_document.version + > change_notification_1.text_document.version + ); + + // Handle out-of-order diagnostics + fake_server.notify::( + lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), + version: Some(change_notification_2.text_document.version), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)), + severity: Some(DiagnosticSeverity::ERROR), + message: "undefined variable 'BB'".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(DiagnosticSeverity::WARNING), + message: "undefined variable 'A'".to_string(), + source: Some("disk".to_string()), + ..Default::default() + }, + ], + }, + ); + + buffer.next_notification(cx).await; + buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer + .snapshot() + .diagnostics_in_range::<_, Point>(0..buffer.len(), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(2, 21)..Point::new(2, 22), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "undefined variable 'A'".to_string(), + is_disk_based: true, + group_id: 6, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(3, 9)..Point::new(3, 14), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'BB'".to_string(), + is_disk_based: true, + group_id: 5, + is_primary: true, + ..Default::default() + }, + } + ] + ); + }); + } + + #[gpui::test] + async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let text = concat!( + "let one = ;\n", // + "let two = \n", + "let three = 3;\n", + ); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({ "a.rs": text })).await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + project.update(cx, |project, cx| { + project + .update_buffer_diagnostics( + &buffer, + vec![ + DiagnosticEntry { + range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "syntax error 1".to_string(), + ..Default::default() + }, + }, + DiagnosticEntry { + range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "syntax error 2".to_string(), + ..Default::default() + }, + }, + ], + None, + cx, + ) + .unwrap(); + }); + + // An empty range is extended forward to include the following character. + // At the end of a line, an empty range is extended backward to include + // the preceding character. + buffer.read_with(cx, |buffer, _| { + let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let one = ", None), + (";", Some(DiagnosticSeverity::ERROR)), + ("\nlet two =", None), + (" ", Some(DiagnosticSeverity::ERROR)), + ("\nlet three = 3;\n", None) + ] + ); + }); + } + + #[gpui::test] + async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Default::default()); + + let text = " + fn a() { + f1(); + } + fn b() { + f2(); + } + fn c() { + f3(); + } + " + .unindent(); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + let mut fake_server = fake_servers.next().await.unwrap(); + let lsp_document_version = fake_server + .receive_notification::() + .await + .text_document + .version; + + // Simulate editing the buffer after the language server computes some edits. + buffer.update(cx, |buffer, cx| { + buffer.edit( + [( + Point::new(0, 0)..Point::new(0, 0), + "// above first function\n", + )], + cx, + ); + buffer.edit( + [( + Point::new(2, 0)..Point::new(2, 0), + " // inside first function\n", + )], + cx, + ); + buffer.edit( + [( + Point::new(6, 4)..Point::new(6, 4), + "// inside second function ", + )], + cx, + ); + + assert_eq!( + buffer.text(), + " + // above first function + fn a() { + // inside first function + f1(); + } + fn b() { + // inside second function f2(); + } + fn c() { + f3(); + } + " + .unindent() + ); + }); + + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &buffer, + vec![ + // replace body of first function + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(3, 0), + ), + new_text: " + fn a() { + f10(); + } + " + .unindent(), + }, + // edit inside second function + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(4, 6), + lsp::Position::new(4, 6), + ), + new_text: "00".into(), + }, + // edit inside third function via two distinct edits + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(7, 5), + lsp::Position::new(7, 5), + ), + new_text: "4000".into(), + }, + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(7, 5), + lsp::Position::new(7, 6), + ), + new_text: "".into(), + }, + ], + Some(lsp_document_version), + cx, + ) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + for (range, new_text) in edits { + buffer.edit([(range, new_text)], cx); + } + assert_eq!( + buffer.text(), + " + // above first function + fn a() { + // inside first function + f10(); + } + fn b() { + // inside second function f200(); + } + fn c() { + f4000(); + } + " + .unindent() + ); + }); + } + + #[gpui::test] + async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let text = " + use a::b; + use a::c; + + fn f() { + b(); + c(); + } + " + .unindent(); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Simulate the language server sending us a small edit in the form of a very large diff. + // Rust-analyzer does this when performing a merge-imports code action. + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &buffer, + [ + // Replace the first use statement without editing the semicolon. + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 4), + lsp::Position::new(0, 8), + ), + new_text: "a::{b, c}".into(), + }, + // Reinsert the remainder of the file between the semicolon and the final + // newline of the file. + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 9), + lsp::Position::new(0, 9), + ), + new_text: "\n\n".into(), + }, + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 9), + lsp::Position::new(0, 9), + ), + new_text: " + fn f() { + b(); + c(); + }" + .unindent(), + }, + // Delete everything after the first newline of the file. + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(1, 0), + lsp::Position::new(7, 0), + ), + new_text: "".into(), + }, + ], + None, + cx, + ) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + let edits = edits + .into_iter() + .map(|(range, text)| { + ( + range.start.to_point(&buffer)..range.end.to_point(&buffer), + text, + ) + }) + .collect::>(); + + assert_eq!( + edits, + [ + (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), + (Point::new(1, 0)..Point::new(2, 0), "".into()) + ] + ); + + for (range, new_text) in edits { + buffer.edit([(range, new_text)], cx); + } + assert_eq!( + buffer.text(), + " + use a::{b, c}; + + fn f() { + b(); + c(); + } + " + .unindent() + ); + }); + } + + #[gpui::test] + async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let text = " + use a::b; + use a::c; + + fn f() { + b(); + c(); + } + " + .unindent(); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": text.clone(), + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + + // Simulate the language server sending us edits in a non-ordered fashion, + // with ranges sometimes being inverted. + let edits = project + .update(cx, |project, cx| { + project.edits_from_lsp( + &buffer, + [ + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 9), + lsp::Position::new(0, 9), + ), + new_text: "\n\n".into(), + }, + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 8), + lsp::Position::new(0, 4), + ), + new_text: "a::{b, c}".into(), + }, + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(1, 0), + lsp::Position::new(7, 0), + ), + new_text: "".into(), + }, + lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 9), + lsp::Position::new(0, 9), + ), + new_text: " + fn f() { + b(); + c(); + }" + .unindent(), + }, + ], + None, + cx, + ) + }) + .await + .unwrap(); + + buffer.update(cx, |buffer, cx| { + let edits = edits + .into_iter() + .map(|(range, text)| { + ( + range.start.to_point(&buffer)..range.end.to_point(&buffer), + text, + ) + }) + .collect::>(); + + assert_eq!( + edits, + [ + (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), + (Point::new(1, 0)..Point::new(2, 0), "".into()) + ] + ); + + for (range, new_text) in edits { + buffer.edit([(range, new_text)], cx); + } + assert_eq!( + buffer.text(), + " + use a::{b, c}; + + fn f() { + b(); + c(); + } + " + .unindent() + ); + }); + } + + fn chunks_with_diagnostics( + buffer: &Buffer, + range: Range, + ) -> Vec<(String, Option)> { + let mut chunks: Vec<(String, Option)> = Vec::new(); + for chunk in buffer.snapshot().chunks(range, true) { + if chunks.last().map_or(false, |prev_chunk| { + prev_chunk.1 == chunk.diagnostic_severity + }) { + chunks.last_mut().unwrap().0.push_str(chunk.text); + } else { + chunks.push((chunk.text.to_string(), chunk.diagnostic_severity)); + } + } + chunks + } + + #[gpui::test] + async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { + let dir = temp_tree(json!({ + "root": { + "dir1": {}, + "dir2": { + "dir3": {} + } + } + })); + + let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; + let cancel_flag = Default::default(); + let results = project + .read_with(cx, |project, cx| { + project.match_paths("dir", false, false, 10, &cancel_flag, cx) + }) + .await; + + assert!(results.is_empty()); + } + + #[gpui::test(iterations = 10)] + async fn test_definition(cx: &mut gpui::TestAppContext) { + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Default::default()); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "const fn a() { A }", + "b.rs": "const y: i32 = crate::a()", + }), + ) + .await; + + let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) + .await + .unwrap(); + + let fake_server = fake_servers.next().await.unwrap(); + fake_server.handle_request::(|params, _| async move { + let params = params.text_document_position_params; + assert_eq!( + params.text_document.uri.to_file_path().unwrap(), + Path::new("/dir/b.rs"), + ); + assert_eq!(params.position, lsp::Position::new(0, 22)); + + Ok(Some(lsp::GotoDefinitionResponse::Scalar( + lsp::Location::new( + lsp::Url::from_file_path("/dir/a.rs").unwrap(), + lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + ), + ))) + }); + + let mut definitions = project + .update(cx, |project, cx| project.definition(&buffer, 22, cx)) + .await + .unwrap(); + + assert_eq!(definitions.len(), 1); + let definition = definitions.pop().unwrap(); + cx.update(|cx| { + let target_buffer = definition.target.buffer.read(cx); + assert_eq!( + target_buffer + .file() + .unwrap() + .as_local() + .unwrap() + .abs_path(cx), + Path::new("/dir/a.rs"), + ); + assert_eq!(definition.target.range.to_offset(target_buffer), 9..10); + assert_eq!( + list_worktrees(&project, cx), + [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)] + ); + + drop(definition); + }); + cx.read(|cx| { + assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]); + }); + + fn list_worktrees<'a>( + project: &'a ModelHandle, + cx: &'a AppContext, + ) -> Vec<(&'a Path, bool)> { + project + .read(cx) + .worktrees(cx) + .map(|worktree| { + let worktree = worktree.read(cx); + ( + worktree.as_local().unwrap().abs_path().as_ref(), + worktree.is_visible(), + ) + }) + .collect::>() + } + } + + #[gpui::test] + async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + Some(tree_sitter_typescript::language_typescript()), + ); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); + + let fake_server = fake_language_servers.next().await.unwrap(); + + let text = "let a = b.fqn"; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len(), cx) + }); + + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { + label: "fullyQualifiedName?".into(), + insert_text: Some("fullyQualifiedName".into()), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].new_text, "fullyQualifiedName"); + assert_eq!( + completions[0].old_range.to_offset(&snapshot), + text.len() - 3..text.len() + ); + + let text = "let a = \"atoms/cmp\""; + buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, text.len() - 1, cx) + }); + + fake_server + .handle_request::(|_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { + label: "component".into(), + ..Default::default() + }, + ]))) + }) + .next() + .await; + let completions = completions.await.unwrap(); + let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].new_text, "component"); + assert_eq!( + completions[0].old_range.to_offset(&snapshot), + text.len() - 4..text.len() - 1 + ); + } + + #[gpui::test(iterations = 10)] + async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { + let mut language = Language::new( + LanguageConfig { + name: "TypeScript".into(), + path_suffixes: vec!["ts".to_string()], + ..Default::default() + }, + None, + ); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "a", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); + + let fake_server = fake_language_servers.next().await.unwrap(); + + // Language server returns code actions that contain commands, and not edits. + let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx)); + fake_server + .handle_request::(|_, _| async move { + Ok(Some(vec![ + lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { + title: "The code action".into(), + command: Some(lsp::Command { + title: "The command".into(), + command: "_the/command".into(), + arguments: Some(vec![json!("the-argument")]), + }), + ..Default::default() + }), + lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { + title: "two".into(), + ..Default::default() + }), + ])) + }) + .next() + .await; + + let action = actions.await.unwrap()[0].clone(); + let apply = project.update(cx, |project, cx| { + project.apply_code_action(buffer.clone(), action, true, cx) + }); + + // Resolving the code action does not populate its edits. In absence of + // edits, we must execute the given command. + fake_server.handle_request::( + |action, _| async move { Ok(action) }, + ); + + // While executing the command, the language server sends the editor + // a `workspaceEdit` request. + fake_server + .handle_request::({ + let fake = fake_server.clone(); + move |params, _| { + assert_eq!(params.command, "_the/command"); + let fake = fake.clone(); + async move { + fake.server + .request::( + lsp::ApplyWorkspaceEditParams { + label: None, + edit: lsp::WorkspaceEdit { + changes: Some( + [( + lsp::Url::from_file_path("/dir/a.ts").unwrap(), + vec![lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 0), + ), + new_text: "X".into(), + }], + )] + .into_iter() + .collect(), + ), + ..Default::default() + }, + }, + ) + .await + .unwrap(); + Ok(Some(json!(null))) + } + } + }) + .next() + .await; + + // Applying the code action returns a project transaction containing the edits + // sent by the language server in its `workspaceEdit` request. + let transaction = apply.await.unwrap(); + assert!(transaction.0.contains_key(&buffer)); + buffer.update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "Xa"); + buffer.undo(cx); + assert_eq!(buffer.text(), "a"); + }); + } + + #[gpui::test] + async fn test_save_file(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "file1": "the old contents", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + buffer + .update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "the old contents"); + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx); + buffer.save(cx) + }) + .await + .unwrap(); + + let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); + assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); + } + + #[gpui::test] + async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "file1": "the old contents", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + buffer + .update(cx, |buffer, cx| { + buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx); + buffer.save(cx) + }) + .await + .unwrap(); + + let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); + assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); + } + + #[gpui::test] + async fn test_save_as(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree("/dir", json!({})).await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer = project.update(cx, |project, cx| { + project.create_buffer("", None, cx).unwrap() + }); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "abc")], cx); + assert!(buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); + project + .update(cx, |project, cx| { + project.save_buffer_as(buffer.clone(), "/dir/file1".into(), cx) + }) + .await + .unwrap(); + assert_eq!(fs.load(Path::new("/dir/file1")).await.unwrap(), "abc"); + buffer.read_with(cx, |buffer, cx| { + assert_eq!(buffer.file().unwrap().full_path(cx), Path::new("dir/file1")); + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); + + let opened_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/file1", cx) + }) + .await + .unwrap(); + assert_eq!(opened_buffer, buffer); + } + + #[gpui::test(retries = 5)] + async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) { + let dir = temp_tree(json!({ + "a": { + "file1": "", + "file2": "", + "file3": "", + }, + "b": { + "c": { + "file4": "", + "file5": "", + } + } + })); + + let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; + let rpc = project.read_with(cx, |p, _| p.client.clone()); + + let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { + let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx)); + async move { buffer.await.unwrap() } + }; + let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| { + project.read_with(cx, |project, cx| { + let tree = project.worktrees(cx).next().unwrap(); + tree.read(cx) + .entry_for_path(path) + .expect(&format!("no entry for path {}", path)) + .id + }) + }; + + let buffer2 = buffer_for_path("a/file2", cx).await; + let buffer3 = buffer_for_path("a/file3", cx).await; + let buffer4 = buffer_for_path("b/c/file4", cx).await; + let buffer5 = buffer_for_path("b/c/file5", cx).await; + + let file2_id = id_for_path("a/file2", &cx); + let file3_id = id_for_path("a/file3", &cx); + let file4_id = id_for_path("b/c/file4", &cx); + + // Create a remote copy of this worktree. + let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()); + let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()); + let (remote, load_task) = cx.update(|cx| { + Worktree::remote( + 1, + 1, + initial_snapshot.to_proto(&Default::default(), true), + rpc.clone(), + cx, + ) + }); + // tree + load_task.await; + + cx.read(|cx| { + assert!(!buffer2.read(cx).is_dirty()); + assert!(!buffer3.read(cx).is_dirty()); + assert!(!buffer4.read(cx).is_dirty()); + assert!(!buffer5.read(cx).is_dirty()); + }); + + // Rename and delete files and directories. + tree.flush_fs_events(&cx).await; + std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); + std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); + std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); + std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); + tree.flush_fs_events(&cx).await; + + let expected_paths = vec![ + "a", + "a/file1", + "a/file2.new", + "b", + "d", + "d/file3", + "d/file4", + ]; + + cx.read(|app| { + assert_eq!( + tree.read(app) + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + expected_paths + ); + + assert_eq!(id_for_path("a/file2.new", &cx), file2_id); + assert_eq!(id_for_path("d/file3", &cx), file3_id); + assert_eq!(id_for_path("d/file4", &cx), file4_id); + + assert_eq!( + buffer2.read(app).file().unwrap().path().as_ref(), + Path::new("a/file2.new") + ); + assert_eq!( + buffer3.read(app).file().unwrap().path().as_ref(), + Path::new("d/file3") + ); + assert_eq!( + buffer4.read(app).file().unwrap().path().as_ref(), + Path::new("d/file4") + ); + assert_eq!( + buffer5.read(app).file().unwrap().path().as_ref(), + Path::new("b/c/file5") + ); + + assert!(!buffer2.read(app).file().unwrap().is_deleted()); + assert!(!buffer3.read(app).file().unwrap().is_deleted()); + assert!(!buffer4.read(app).file().unwrap().is_deleted()); + assert!(buffer5.read(app).file().unwrap().is_deleted()); + }); + + // Update the remote worktree. Check that it becomes consistent with the + // local worktree. + remote.update(cx, |remote, cx| { + let update_message = tree.read(cx).as_local().unwrap().snapshot().build_update( + &initial_snapshot, + 1, + 1, + true, + ); + remote + .as_remote_mut() + .unwrap() + .snapshot + .apply_remote_update(update_message) + .unwrap(); + + assert_eq!( + remote + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + expected_paths + ); + }); + } + + #[gpui::test] + async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "a.txt": "a-contents", + "b.txt": "b-contents", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + // Spawn multiple tasks to open paths, repeating some paths. + let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| { + ( + p.open_local_buffer("/dir/a.txt", cx), + p.open_local_buffer("/dir/b.txt", cx), + p.open_local_buffer("/dir/a.txt", cx), + ) + }); + + let buffer_a_1 = buffer_a_1.await.unwrap(); + let buffer_a_2 = buffer_a_2.await.unwrap(); + let buffer_b = buffer_b.await.unwrap(); + assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents"); + assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents"); + + // There is only one buffer per path. + let buffer_a_id = buffer_a_1.id(); + assert_eq!(buffer_a_2.id(), buffer_a_id); + + // Open the same path again while it is still open. + drop(buffer_a_1); + let buffer_a_3 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx)) + .await + .unwrap(); + + // There's still only one buffer per path. + assert_eq!(buffer_a_3.id(), buffer_a_id); + } + + #[gpui::test] + async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "file1": "abc", + "file2": "def", + "file3": "ghi", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + let buffer1 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + let events = Rc::new(RefCell::new(Vec::new())); + + // initially, the buffer isn't dirty. + buffer1.update(cx, |buffer, cx| { + cx.subscribe(&buffer1, { + let events = events.clone(); + move |_, _, event, _| match event { + BufferEvent::Operation(_) => {} + _ => events.borrow_mut().push(event.clone()), + } + }) + .detach(); + + assert!(!buffer.is_dirty()); + assert!(events.borrow().is_empty()); + + buffer.edit([(1..2, "")], cx); + }); + + // after the first edit, the buffer is dirty, and emits a dirtied event. + buffer1.update(cx, |buffer, cx| { + assert!(buffer.text() == "ac"); + assert!(buffer.is_dirty()); + assert_eq!( + *events.borrow(), + &[language::Event::Edited, language::Event::DirtyChanged] + ); + events.borrow_mut().clear(); + buffer.did_save( + buffer.version(), + buffer.as_rope().fingerprint(), + buffer.file().unwrap().mtime(), + None, + cx, + ); + }); + + // after saving, the buffer is not dirty, and emits a saved event. + buffer1.update(cx, |buffer, cx| { + assert!(!buffer.is_dirty()); + assert_eq!(*events.borrow(), &[language::Event::Saved]); + events.borrow_mut().clear(); + + buffer.edit([(1..1, "B")], cx); + buffer.edit([(2..2, "D")], cx); + }); + + // after editing again, the buffer is dirty, and emits another dirty event. + buffer1.update(cx, |buffer, cx| { + assert!(buffer.text() == "aBDc"); + assert!(buffer.is_dirty()); + assert_eq!( + *events.borrow(), + &[ + language::Event::Edited, + language::Event::DirtyChanged, + language::Event::Edited, + ], + ); + events.borrow_mut().clear(); + + // After restoring the buffer to its previously-saved state, + // the buffer is not considered dirty anymore. + buffer.edit([(1..3, "")], cx); + assert!(buffer.text() == "ac"); + assert!(!buffer.is_dirty()); + }); + + assert_eq!( + *events.borrow(), + &[language::Event::Edited, language::Event::DirtyChanged] + ); + + // When a file is deleted, the buffer is considered dirty. + let events = Rc::new(RefCell::new(Vec::new())); + let buffer2 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) + .await + .unwrap(); + buffer2.update(cx, |_, cx| { + cx.subscribe(&buffer2, { + let events = events.clone(); + move |_, _, event, _| events.borrow_mut().push(event.clone()) + }) + .detach(); + }); + + fs.remove_file("/dir/file2".as_ref(), Default::default()) + .await + .unwrap(); + buffer2.condition(&cx, |b, _| b.is_dirty()).await; + assert_eq!( + *events.borrow(), + &[ + language::Event::DirtyChanged, + language::Event::FileHandleChanged + ] + ); + + // When a file is already dirty when deleted, we don't emit a Dirtied event. + let events = Rc::new(RefCell::new(Vec::new())); + let buffer3 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx)) + .await + .unwrap(); + buffer3.update(cx, |_, cx| { + cx.subscribe(&buffer3, { + let events = events.clone(); + move |_, _, event, _| events.borrow_mut().push(event.clone()) + }) + .detach(); + }); + + buffer3.update(cx, |buffer, cx| { + buffer.edit([(0..0, "x")], cx); + }); + events.borrow_mut().clear(); + fs.remove_file("/dir/file3".as_ref(), Default::default()) + .await + .unwrap(); + buffer3 + .condition(&cx, |_, _| !events.borrow().is_empty()) + .await; + assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]); + cx.read(|cx| assert!(buffer3.read(cx).is_dirty())); + } + + #[gpui::test] + async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { + let initial_contents = "aaa\nbbbbb\nc\n"; + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "the-file": initial_contents, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx)) + .await + .unwrap(); + + let anchors = (0..3) + .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1)))) + .collect::>(); + + // Change the file on disk, adding two new lines of text, and removing + // one line. + buffer.read_with(cx, |buffer, _| { + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); + let new_contents = "AAAA\naaa\nBB\nbbbbb\n"; + fs.save("/dir/the-file".as_ref(), &new_contents.into()) + .await + .unwrap(); + + // Because the buffer was not modified, it is reloaded from disk. Its + // contents are edited according to the diff between the old and new + // file contents. + buffer + .condition(&cx, |buffer, _| buffer.text() == new_contents) + .await; + + buffer.update(cx, |buffer, _| { + assert_eq!(buffer.text(), new_contents); + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + + let anchor_positions = anchors + .iter() + .map(|anchor| anchor.to_point(&*buffer)) + .collect::>(); + assert_eq!( + anchor_positions, + [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)] + ); + }); + + // Modify the buffer + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, " ")], cx); + assert!(buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); + + // Change the file on disk again, adding blank lines to the beginning. + fs.save( + "/dir/the-file".as_ref(), + &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(), + ) + .await + .unwrap(); + + // Because the buffer is modified, it doesn't reload from disk, but is + // marked as having a conflict. + buffer + .condition(&cx, |buffer, _| buffer.has_conflict()) + .await; + } + + #[gpui::test] + async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/the-dir", + json!({ + "a.rs": " + fn foo(mut v: Vec) { + for x in &v { + v.push(1); + } + } + " + .unindent(), + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx)) + .await + .unwrap(); + + let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap(); + let message = lsp::PublishDiagnosticsParams { + uri: buffer_uri.clone(), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), + severity: Some(DiagnosticSeverity::WARNING), + message: "error 1".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: buffer_uri.clone(), + range: lsp::Range::new( + lsp::Position::new(1, 8), + lsp::Position::new(1, 9), + ), + }, + message: "error 1 hint 1".to_string(), + }]), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 1 hint 1".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: buffer_uri.clone(), + range: lsp::Range::new( + lsp::Position::new(1, 8), + lsp::Position::new(1, 9), + ), + }, + message: "original diagnostic".to_string(), + }]), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), + severity: Some(DiagnosticSeverity::ERROR), + message: "error 2".to_string(), + related_information: Some(vec![ + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: buffer_uri.clone(), + range: lsp::Range::new( + lsp::Position::new(1, 13), + lsp::Position::new(1, 15), + ), + }, + message: "error 2 hint 1".to_string(), + }, + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: buffer_uri.clone(), + range: lsp::Range::new( + lsp::Position::new(1, 13), + lsp::Position::new(1, 15), + ), + }, + message: "error 2 hint 2".to_string(), + }, + ]), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 2 hint 1".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: buffer_uri.clone(), + range: lsp::Range::new( + lsp::Position::new(2, 8), + lsp::Position::new(2, 17), + ), + }, + message: "original diagnostic".to_string(), + }]), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 2 hint 2".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: buffer_uri.clone(), + range: lsp::Range::new( + lsp::Position::new(2, 8), + lsp::Position::new(2, 17), + ), + }, + message: "original diagnostic".to_string(), + }]), + ..Default::default() + }, + ], + version: None, + }; + + project + .update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx)) + .unwrap(); + let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot()); + + assert_eq!( + buffer + .diagnostics_in_range::<_, Point>(0..buffer.len(), false) + .collect::>(), + &[ + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(2, 8)..Point::new(2, 17), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 1, + is_primary: true, + ..Default::default() + } + } + ] + ); + + assert_eq!( + buffer.diagnostic_group::(0).collect::>(), + &[ + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() + } + }, + ] + ); + assert_eq!( + buffer.diagnostic_group::(1).collect::>(), + &[ + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() + } + }, + DiagnosticEntry { + range: Point::new(2, 8)..Point::new(2, 17), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 1, + is_primary: true, + ..Default::default() + } + } + ] + ); + } + + #[gpui::test] + async fn test_rename(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: Default::default(), + })), + ..Default::default() + }, + ..Default::default() + })); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;" + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + project.update(cx, |project, _| project.languages.add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/one.rs", cx) + }) + .await + .unwrap(); + + let fake_server = fake_servers.next().await.unwrap(); + + let response = project.update(cx, |project, cx| { + project.prepare_rename(buffer.clone(), 7, cx) + }); + fake_server + .handle_request::(|params, _| async move { + assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); + assert_eq!(params.position, lsp::Position::new(0, 7)); + Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( + lsp::Position::new(0, 6), + lsp::Position::new(0, 9), + )))) + }) + .next() + .await + .unwrap(); + let range = response.await.unwrap().unwrap(); + let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer)); + assert_eq!(range, 6..9); + + let response = project.update(cx, |project, cx| { + project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx) + }); + fake_server + .handle_request::(|params, _| async move { + assert_eq!( + params.text_document_position.text_document.uri.as_str(), + "file:///dir/one.rs" + ); + assert_eq!( + params.text_document_position.position, + lsp::Position::new(0, 7) + ); + assert_eq!(params.new_name, "THREE"); + Ok(Some(lsp::WorkspaceEdit { + changes: Some( + [ + ( + lsp::Url::from_file_path("/dir/one.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 6), + lsp::Position::new(0, 9), + ), + "THREE".to_string(), + )], + ), + ( + lsp::Url::from_file_path("/dir/two.rs").unwrap(), + vec![ + lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 24), + lsp::Position::new(0, 27), + ), + "THREE".to_string(), + ), + lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 35), + lsp::Position::new(0, 38), + ), + "THREE".to_string(), + ), + ], + ), + ] + .into_iter() + .collect(), + ), + ..Default::default() + })) + }) + .next() + .await + .unwrap(); + let mut transaction = response.await.unwrap().0; + assert_eq!(transaction.len(), 2); + assert_eq!( + transaction + .remove_entry(&buffer) + .unwrap() + .0 + .read_with(cx, |buffer, _| buffer.text()), + "const THREE: usize = 1;" + ); + assert_eq!( + transaction + .into_keys() + .next() + .unwrap() + .read_with(cx, |buffer, _| buffer.text()), + "const TWO: usize = one::THREE + one::THREE;" + ); + } + + #[gpui::test] + async fn test_search(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;", + "three.rs": "const THREE: usize = one::ONE + two::TWO;", + "four.rs": "const FOUR: usize = one::ONE + three::THREE;", + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + assert_eq!( + search(&project, SearchQuery::text("TWO", false, true), cx) + .await + .unwrap(), + HashMap::from_iter([ + ("two.rs".to_string(), vec![6..9]), + ("three.rs".to_string(), vec![37..40]) + ]) + ); + + let buffer_4 = project + .update(cx, |project, cx| { + project.open_local_buffer("/dir/four.rs", cx) + }) + .await + .unwrap(); + buffer_4.update(cx, |buffer, cx| { + let text = "two::TWO"; + buffer.edit([(20..28, text), (31..43, text)], cx); + }); + + assert_eq!( + search(&project, SearchQuery::text("TWO", false, true), cx) + .await + .unwrap(), + HashMap::from_iter([ + ("two.rs".to_string(), vec![6..9]), + ("three.rs".to_string(), vec![37..40]), + ("four.rs".to_string(), vec![25..28, 36..39]) + ]) + ); + + async fn search( + project: &ModelHandle, + query: SearchQuery, + cx: &mut gpui::TestAppContext, + ) -> Result>>> { + let results = project + .update(cx, |project, cx| project.search(query, cx)) + .await?; + + Ok(results + .into_iter() + .map(|(buffer, ranges)| { + buffer.read_with(cx, |buffer, _| { + let path = buffer.file().unwrap().path().to_string_lossy().to_string(); + let ranges = ranges + .into_iter() + .map(|range| range.to_offset(buffer)) + .collect::>(); + (path, ranges) + }) + }) + .collect()) + } + } +} diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index ea99767e0a..5e7ce6da0a 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -290,7 +290,7 @@ mod tests { }, None, ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter::default()); + let mut fake_servers = language.set_fake_lsp_adapter(Arc::::default()); let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "test.rs": "" })).await; From a16fc2ba0c55e3af329e591d3bb21cc1d9d9f25a Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 7 Jul 2022 11:34:12 +0200 Subject: [PATCH 64/91] Add basic support for precompiling plugins --- crates/plugin_runtime/Cargo.toml | 3 ++ crates/plugin_runtime/build.rs | 34 +++++++++++++++++---- crates/plugin_runtime/src/lib.rs | 5 ++- crates/plugin_runtime/src/plugin.rs | 16 +++++++--- crates/zed/src/languages/language_plugin.rs | 5 ++- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index a145b12a19..87a93fb1f4 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -13,3 +13,6 @@ serde_json = "1.0" bincode = "1.3" pollster = "0.2.5" smol = "1.2.5" + +[build-dependencies] +wasmtime = "0.38.1" diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index a6f0db38ac..05bb65b2d8 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -1,4 +1,5 @@ -use std::path::Path; +use std::{io::Write, path::Path}; +use wasmtime::{Config, Engine}; fn main() { let base = Path::new("../../plugins"); @@ -28,6 +29,8 @@ fn main() { .expect("Could not find compiled plugins in target"); println!("cargo:warning={:?}", binaries); + let engine = create_engine(); + for file in binaries { let is_wasm = || { let path = file.ok()?.path(); @@ -39,11 +42,30 @@ fn main() { }; if let Some(path) = is_wasm() { - std::fs::copy(&path, base.join("bin").join(path.file_name().unwrap())) - .expect("Could not copy compiled plugin to bin"); + let out_path = base.join("bin").join(path.file_name().unwrap()); + std::fs::copy(&path, &out_path).expect("Could not copy compiled plugin to bin"); + precompile(&out_path, &engine); } } - - // TODO: create .wat versions - // TODO: optimize with wasm-opt +} + +fn create_engine() -> Engine { + let mut config = Config::default(); + config.async_support(true); + // config.epoch_interruption(true); + Engine::new(&config).expect("Could not create engine") +} + +fn precompile(path: &Path, engine: &Engine) { + let bytes = std::fs::read(path).expect("Could not read wasm module"); + let compiled = engine + .precompile_module(&bytes) + .expect("Could not precompile module"); + let out_path = path.parent().unwrap().join(&format!( + "{}.pre", + path.file_name().unwrap().to_string_lossy() + )); + let mut out_file = std::fs::File::create(out_path) + .expect("Could not create output file for precompiled module"); + out_file.write_all(&compiled).unwrap(); } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 12b1c3b4f4..c12b8e525a 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -51,7 +51,10 @@ mod tests { }) }) .unwrap() - .init(include_bytes!("../../../plugins/bin/test_plugin.wasm")) + .init( + false, + include_bytes!("../../../plugins/bin/test_plugin.wasm"), + ) .await .unwrap(); diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 2d2cab35f1..ac97569a50 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -231,9 +231,9 @@ impl PluginBuilder { /// Initializes a [`Plugin`] from a given compiled Wasm module. /// Both binary (`.wasm`) and text (`.wat`) module formats are supported. - pub async fn init>(self, module: T) -> Result { + pub async fn init>(self, precompiled: bool, module: T) -> Result { dbg!("initializing plugin"); - Plugin::init(module.as_ref().to_vec(), self).await + Plugin::init(precompiled, module.as_ref().to_vec(), self).await } } @@ -297,7 +297,11 @@ impl Plugin { } impl Plugin { - async fn init(module: Vec, plugin: PluginBuilder) -> Result { + async fn init( + precompiled: bool, + module: Vec, + plugin: PluginBuilder, + ) -> Result { dbg!("Initializing new plugin"); // initialize the WebAssembly System Interface context let engine = plugin.engine; @@ -314,7 +318,11 @@ impl Plugin { }, ); // store.epoch_deadline_async_yield_and_update(todo!()); - let module = Module::new(&engine, module)?; + let module = if precompiled { + unsafe { Module::deserialize(&engine, module)? } + } else { + Module::new(&engine, module)? + }; // load the provided module into the asynchronous runtime linker.module_async(&mut store, "", &module).await?; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 6c07fc7042..fe74d7d9eb 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -21,7 +21,10 @@ pub async fn new_json(executor: Arc) -> Result { .log_err() .map(|output| output.stdout) })? - .init(include_bytes!("../../../../plugins/bin/json_language.wasm")) + .init( + true, + include_bytes!("../../../../plugins/bin/json_language.wasm.pre"), + ) .await?; PluginLspAdapter::new(plugin, executor).await } From 5cb59dfdab92fdda633846405a755633c960ddce Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 7 Jul 2022 18:14:16 +0200 Subject: [PATCH 65/91] Fix errors resulting from rebase --- Cargo.lock | 109 +- crates/language/src/language.rs | 2 +- crates/plugin_runtime/Cargo.toml | 8 +- crates/project/src/lsp_command.rs | 4 +- crates/project/src/project.rs | 3078 +-------------------------- crates/project/src/project_tests.rs | 42 +- 6 files changed, 140 insertions(+), 3103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c17a9d8b8..f3df25ce58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1183,23 +1183,24 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fa7c3188913c2d11a361e0431e135742372a2709a99b103e79758e11a0a797e" +checksum = "7901fbba05decc537080b07cb3f1cadf53be7b7602ca8255786288a8692ae29a" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29285f70fd396a8f64455a15a6e1d390322e4a5f5186de513141313211b0a23e" +checksum = "37ba1b45d243a4a28e12d26cd5f2507da74e77c45927d40de8b6ffbf088b46b5" dependencies = [ "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", + "cranelift-isle", "gimli", "log", "regalloc2", @@ -1209,33 +1210,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057eac2f202ec95aebfd8d495e88560ac085f6a415b3c6c28529dc5eb116a141" +checksum = "54cc30032171bf230ce22b99c07c3a1de1221cb5375bd6dbe6dbe77d0eed743c" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d93869efd18874a9341cfd8ad66bcb08164e86357a694a0e939d29e87410b9" +checksum = "a23f2672426d2bb4c9c3ef53e023076cfc4d8922f0eeaebaf372c92fae8b5c69" [[package]] name = "cranelift-entity" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e34bd7a1fefa902c90a921b36323f17a398b788fa56a75f07a29d83b6e28808" +checksum = "886c59a5e0de1f06dbb7da80db149c75de10d5e2caca07cdd9fef8a5918a6336" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457018dd2d6ee300953978f63215b5edf3ae42dbdf8c7c038972f10394599f72" +checksum = "ace74eeca11c439a9d4ed1a5cb9df31a54cd0f7fbddf82c8ce4ea8e9ad2a8fe0" dependencies = [ "cranelift-codegen", "log", @@ -1244,10 +1245,16 @@ dependencies = [ ] [[package]] -name = "cranelift-native" -version = "0.84.0" +name = "cranelift-isle" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bba027cc41bf1d0eee2ddf16caba2ee1be682d0214520fff0129d2c6557fda89" +checksum = "db1ae52a5cc2cad0d86fdd3dcb16b7217d2f1e65ab4f5814aa4f014ad335fa43" + +[[package]] +name = "cranelift-native" +version = "0.85.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dadcfb7852900780d37102bce5698bcd401736403f07b52e714ff7a180e0e22f" dependencies = [ "cranelift-codegen", "libc", @@ -1256,9 +1263,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.84.0" +version = "0.85.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b17639ced10b9916c9be120d38c872ea4f9888aa09248568b10056ef0559bfa" +checksum = "c84e3410960389110b88f97776f39f6d2c8becdaa4cd59e390e6b76d9d0e7190" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -4181,9 +4188,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.1.3" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904196c12c9f55d3aea578613219f493ced8e05b3d0c6a42d11cb4142d8b4879" +checksum = "4a8d23b35d7177df3b9d31ed8a9ab4bf625c668be77a319d4f5efd4a5257701c" dependencies = [ "fxhash", "log", @@ -6314,9 +6321,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-cap-std-sync" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1029972e08194fe0ca67a83221945fff9d6d1b0dd8b752c6073b45d0254ac71b" +checksum = "c1c4e73ed64b92ae87b416f4274b3c827180b02b67f835f66a86fc4267b77349" dependencies = [ "anyhow", "async-trait", @@ -6338,9 +6345,9 @@ dependencies = [ [[package]] name = "wasi-common" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396cc8d920f924474f589f6a2202ad9783579ef12f96e6b675d0b2f3917c7126" +checksum = "cc983eb93607a61f64152ec8728bf453f4dfdf22e7ab1784faac3297fe9a035e" dependencies = [ "anyhow", "bitflags", @@ -6431,18 +6438,18 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.84.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77dc97c22bb5ce49a47b745bed8812d30206eff5ef3af31424f2c1820c0974b2" +checksum = "570460c58b21e9150d2df0eaaedbb7816c34bcec009ae0dcc976e40ba81463e7" dependencies = [ "indexmap", ] [[package]] name = "wasmtime" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdd1101bdfa0414a19018ec0a091951a20b695d4d04f858d49f6c4cc53cd8dd" +checksum = "e76e2b2833bb0ece666ccdbed7b71b617d447da11f1bb61f4f2bab2648f745ee" dependencies = [ "anyhow", "async-trait", @@ -6474,9 +6481,9 @@ dependencies = [ [[package]] name = "wasmtime-cache" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79da81ed0724392948ad7a0fb5088ff1bd15fa937356c8c037c6b1c8b5473cde" +checksum = "743a9f142d93318262d7e1fe329394ff2e8f86a1df45ae5e4f0eedba215ca5ce" dependencies = [ "anyhow", "base64 0.13.0", @@ -6494,9 +6501,9 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e78edcfb0daa9a9579ac379d00e2d5a5b2a60c0d653c8c95e8412f2166acb9" +checksum = "5dc0f80afa1ce97083a7168e6b6948d015d6237369e9f4a511d38c9c4ac8fbb9" dependencies = [ "anyhow", "cranelift-codegen", @@ -6516,9 +6523,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4201389132ec467981980549574b33fc70d493b40f2c045c8ce5c7b54fbad97e" +checksum = "0816d9365196f1f447060087e0f87239ccded830bd54970a1168b0c9c8e824c9" dependencies = [ "anyhow", "cranelift-entity", @@ -6536,9 +6543,9 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba6777a84b44f9a384b5c9d511ae3d86534438b7e25d928b8e8e858ecad5df2" +checksum = "715afdb87a3bcf1eae3f098c742d650fb783abdb8a7ca87076ea1cabecabea5d" dependencies = [ "cc", "rustix", @@ -6547,9 +6554,9 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1587ca7752d00862faa540d00fd28e5ccf1ac61ba19756449193f1153cb2b127" +checksum = "5c687f33cfa0f89ec1646929d0ff102087052cf9f0d15533de56526b0da0d1b3" dependencies = [ "addr2line", "anyhow", @@ -6574,9 +6581,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27233ab6c8934b23171c64f215f902ef19d18c1712b46a0674286d1ef28d5dd" +checksum = "b252d1d025f94f3954ba2111f12f3a22826a0764a11c150c2d46623115a69e27" dependencies = [ "lazy_static", "object", @@ -6585,9 +6592,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d3b0b8f13db47db59d616e498fe45295819d04a55f9921af29561827bdb816" +checksum = "ace251693103c9facbbd7df87a29a75e68016e48bc83c09133f2fda6b575e0ab" dependencies = [ "anyhow", "backtrace", @@ -6612,9 +6619,9 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1630d9dca185299bec7f557a7e73b28742fe5590caf19df001422282a0a98ad1" +checksum = "d129b0487a95986692af8708ffde9c50b0568dcefd79200941d475713b4f40bb" dependencies = [ "cranelift-entity", "serde", @@ -6624,9 +6631,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d507b07263a4923440da662c611affc2e671714bb6e5f68f6b068a5736bcd7f" +checksum = "fb49791530b3a3375897a6d5a8bfa9914101ef8a672d01c951e70b46fd953c15" dependencies = [ "anyhow", "wasi-cap-std-sync", @@ -6751,9 +6758,9 @@ dependencies = [ [[package]] name = "wiggle" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e618e90d9f3d6d76943d9f2903c609b62f2ab5d16dedcff4816d38db726dd" +checksum = "91c38020359fabec5e5ce5a3f667af72e9a203bc6fe8caeb8931d3a870754d9d" dependencies = [ "anyhow", "async-trait", @@ -6766,9 +6773,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534d70579cd9aca243e94eeb14a348dbc9ae87e7758ffebfdd4bb4a98d59b9b0" +checksum = "adc4e4420b496b04920ae3e41424029aba95c15a5e2e2b4012d14ec83770a3ef" dependencies = [ "anyhow", "heck 0.4.0", @@ -6781,9 +6788,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "0.37.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87216b1f58eeee44a6385de39e8c03190c209942cfa34344c9ba69426f146d8d" +checksum = "2e541a0be1f2c4d53471d8a9df81c2d8725a3f023d8259f555c65b03d515aaab" dependencies = [ "proc-macro2", "quote", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 66b52629db..572bb8c8ae 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -541,7 +541,7 @@ async fn fetch_latest_server_binary_path( .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; let path = adapter - .fetch_server_binary(version_info, http_client, container_dir.clone()) + .fetch_server_binary(version_info, http_client, container_dir.to_path_buf()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index 87a93fb1f4..21c1ab973d 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -wasmtime = "0.37.0" -wasmtime-wasi = "0.37.0" -wasi-common = "0.37.0" +wasmtime = "0.38" +wasmtime-wasi = "0.38" +wasi-common = "0.38" anyhow = { version = "1.0", features = ["std"] } serde = "1.0" serde_json = "1.0" @@ -15,4 +15,4 @@ pollster = "0.2.5" smol = "1.2.5" [build-dependencies] -wasmtime = "0.38.1" +wasmtime = "0.38" diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 212edb9219..0c30ee2924 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -389,7 +389,7 @@ impl LspCommand for GetDefinition { this.open_local_buffer_via_lsp( target_uri, language_server.server_id(), - lsp_adapter.name(), + lsp_adapter.name.clone(), cx, ) }) @@ -610,7 +610,7 @@ impl LspCommand for GetReferences { this.open_local_buffer_via_lsp( lsp_location.uri, language_server.server_id(), - lsp_adapter.name(), + lsp_adapter.name.clone(), cx, ) }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 160f66eeb5..7adab020b5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -199,7 +199,7 @@ pub enum Event { pub enum LanguageServerState { Starting(Task>>), Running { - adapter: Arc, + adapter: Arc, server: Arc, }, } @@ -708,7 +708,7 @@ impl Project { }) } - fn on_settings_changed(&mut self, cx: &mut ModelContext<'_, Self>) { + fn on_settings_changed(&mut self, cx: &mut ModelContext) { let settings = cx.global::(); let mut language_servers_to_start = Vec::new(); @@ -734,7 +734,7 @@ impl Project { if let Some(lsp_adapter) = language.lsp_adapter() { if !settings.enable_language_server(Some(&language.name())) { let lsp_name = &lsp_adapter.name; - for (worktree_id, started_lsp_name) in self.language_servers.keys() { + for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { if lsp_name == started_lsp_name { language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); } @@ -1627,6 +1627,7 @@ impl Project { }) } + /// LanguageServerName is owned, because it is inserted into a map fn open_local_buffer_via_lsp( &mut self, abs_path: lsp::Url, @@ -1648,10 +1649,11 @@ impl Project { this.create_local_worktree(&abs_path, false, cx) }) .await?; - let name = lsp_adapter.name.clone(); this.update(&mut cx, |this, cx| { - this.language_servers - .insert((worktree.read(cx).id(), name), (lsp_adapter, lsp_server)); + this.language_server_ids.insert( + (worktree.read(cx).id(), language_server_name), + language_server_id, + ); }); (worktree, PathBuf::new()) }; @@ -2002,9 +2004,10 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, Arc)> { - self.language_servers.iter().filter_map( - move |((language_server_worktree_id, _), server)| { + ) -> impl Iterator, &Arc)> { + self.language_server_ids + .iter() + .filter_map(move |((language_server_worktree_id, _), id)| { if *language_server_worktree_id == worktree_id { if let Some(LanguageServerState::Running { adapter, server }) = self.language_servers.get(&id) @@ -2042,7 +2045,7 @@ impl Project { worktree_id: WorktreeId, worktree_path: Arc, language: Arc, - cx: &mut ModelContext<'_, Self>, + cx: &mut ModelContext, ) { if !cx .global::() @@ -2056,8 +2059,8 @@ impl Project { } else { return; }; + let key = (worktree_id, adapter.name.clone()); - let key = (worktree_id, adapter.name); self.language_server_ids .entry(key.clone()) .or_insert_with(|| { @@ -2069,18 +2072,15 @@ impl Project { self.client.http_client(), cx, ); - self.language_servers.insert( server_id, LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { let language_server = language_server?.await.log_err()?; let language_server = language_server - .initialize(adapter.initialization_options()) + .initialize(adapter.initialization_options.clone()) .await .log_err()?; let this = this.upgrade(&cx)?; - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token(); language_server .on_notification::({ @@ -2089,10 +2089,9 @@ impl Project { move |params, mut cx| { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - // TODO(isaac): remove block on - smol::block_on(this.on_lsp_diagnostics_published( + this.on_lsp_diagnostics_published( server_id, params, &adapter, cx, - )) + ); }); } } @@ -2170,11 +2169,13 @@ impl Project { language_server.clone(), cx, ) - } }) .detach(); + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token.clone(); + language_server .on_notification::({ let this = this.downgrade(); @@ -2184,7 +2185,7 @@ impl Project { this.on_lsp_progress( params, server_id, - disk_based_diagnostics_progress_token, + disk_based_diagnostics_progress_token.clone(), cx, ); }); @@ -2258,7 +2259,7 @@ impl Project { continue; }; if file.worktree.read(cx).id() != key.0 - || language.lsp_adapter().map(|a| a.name()) + || language.lsp_adapter().map(|a| a.name.clone()) != Some(key.1.clone()) { continue; @@ -2271,14 +2272,15 @@ impl Project { .or_insert_with(|| vec![(0, buffer.text_snapshot())]); let (version, initial_snapshot) = versions.last().unwrap(); let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - let language_id = - adapter.id_for_language(language.name().as_ref()); language_server .notify::( lsp::DidOpenTextDocumentParams { text_document: lsp::TextDocumentItem::new( uri, - language_id.unwrap_or_default(), + adapter + .id_for_language + .clone() + .unwrap_or_default(), *version, initial_snapshot.text(), ), @@ -2306,6 +2308,8 @@ impl Project { }) })), ); + + server_id }); } @@ -2394,7 +2398,7 @@ impl Project { worktree_id: WorktreeId, fallback_path: Arc, language: Arc, - cx: &mut ModelContext<'_, Self>, + cx: &mut ModelContext, ) { let adapter = if let Some(adapter) = language.lsp_adapter() { adapter @@ -2402,7 +2406,8 @@ impl Project { return; }; - let stop = self.stop_language_server(worktree_id, adapter.name.clone(), cx); + let server_name = adapter.name.clone(); + let stop = self.stop_language_server(worktree_id, server_name.clone(), cx); cx.spawn_weak(|this, mut cx| async move { let (original_root_path, orphaned_worktrees) = stop.await; if let Some(this) = this.upgrade(&cx) { @@ -2455,7 +2460,7 @@ impl Project { &mut self, progress: lsp::ProgressParams, server_id: usize, - disk_based_diagnostics_progress_token: Option<&str>, + disk_based_diagnostics_progress_token: Option, cx: &mut ModelContext, ) { let token = match progress.token { @@ -2479,7 +2484,8 @@ impl Project { return; } - let same_token = Some(token.as_ref()) == disk_based_diagnostics_progress_token; + let same_token = + Some(token.as_ref()) == disk_based_diagnostics_progress_token.as_ref().map(|x| &**x); match progress { lsp::WorkDoneProgress::Begin(report) => { @@ -3533,50 +3539,17 @@ impl Project { { continue; } - let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() - { - // If the language server provides a range to overwrite, then - // check that the range is valid. - Some(lsp::CompletionTextEdit::Edit(edit)) => { - let range = range_from_lsp(edit.range); - let start = snapshot.clip_point_utf16(range.start, Bias::Left); - let end = snapshot.clip_point_utf16(range.end, Bias::Left); - if start != range.start || end != range.end { - log::info!("completion out of expected range"); - return None; - } - ( - snapshot.anchor_before(start)..snapshot.anchor_after(end), - edit.new_text.clone(), - ) - } - // If the language server does not provide a range, then infer - // the range based on the syntax tree. - None => { - if position != clipped_position { - log::info!("completion out of expected range"); - return None; - } - let Range { start, end } = range_for_token - .get_or_insert_with(|| { - let offset = position.to_offset(&snapshot); - let (range, kind) = snapshot.surrounding_word(offset); - if kind == Some(CharKind::Word) { - range - } else { - offset..offset - } - }) - .clone(); - let text = lsp_completion - .insert_text - .as_ref() - .unwrap_or(&lsp_completion.label) - .clone(); - ( - snapshot.anchor_before(start)..snapshot.anchor_after(end), - text.clone(), - ) + + let (old_range, new_text) = match lsp_completion.text_edit.as_ref() { + // If the language server provides a range to overwrite, then + // check that the range is valid. + Some(lsp::CompletionTextEdit::Edit(edit)) => { + let range = range_from_lsp(edit.range); + let start = snapshot.clip_point_utf16(range.start, Bias::Left); + let end = snapshot.clip_point_utf16(range.end, Bias::Left); + if start != range.start || end != range.end { + log::info!("completion out of expected range"); + continue; } ( snapshot.anchor_before(start)..snapshot.anchor_after(end), @@ -3590,22 +3563,6 @@ impl Project { log::info!("completion out of expected range"); continue; } - }; - - LineEnding::normalize(&mut new_text); - Some(Completion { - old_range, - new_text, - label: { - match language.as_ref() { - Some(l) => l.label_for_completion(&lsp_completion).await, - None => None, - } - .unwrap_or_else(|| { - CodeLabel::plain( - lsp_completion.label.clone(), - lsp_completion.filter_text.as_deref(), - ) let Range { start, end } = range_for_token .get_or_insert_with(|| { let offset = position.to_offset(&snapshot); @@ -3690,13 +3647,14 @@ impl Project { }) .await; - let mut completions = Vec::new(); + let mut result = Vec::new(); for completion in response.completions.into_iter() { - completions.push( - language::proto::deserialize_completion(completion, language.clone()).await, - ); + let completion = + language::proto::deserialize_completion(completion, language.clone()) + .await?; + result.push(completion); } - completions.into_iter().collect() + Ok(result) }) } else { Task::ready(Ok(Default::default())) @@ -4066,7 +4024,7 @@ impl Project { this.open_local_buffer_via_lsp( op.text_document.uri, language_server.server_id(), - lsp_adapter.name(), + lsp_adapter.name.clone(), cx, ) }) @@ -5998,11 +5956,11 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<&(Arc, Arc)> { + ) -> Option<(&Arc, &Arc)> { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let name = language.lsp_adapter()?.name.clone(); let worktree_id = file.worktree_id(cx); - let key = (worktree_id, language.lsp_adapter()?.name()); + let key = (worktree_id, name); if let Some(server_id) = self.language_server_ids.get(&key) { if let Some(LanguageServerState::Running { adapter, server }) = @@ -6302,2931 +6260,3 @@ impl Item for Buffer { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) } } - -#[cfg(test)] -mod tests { - use crate::worktree::WorktreeHandle; - - use super::{Event, *}; - use fs::RealFs; - use futures::{future, StreamExt}; - use gpui::{executor::Deterministic, test::subscribe}; - use language::{ - tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, - OffsetRangeExt, Point, ToPoint, - }; - use lsp::Url; - use serde_json::json; - use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc, task::Poll}; - use unindent::Unindent as _; - use util::{assert_set_eq, test::temp_tree}; - - #[gpui::test] - async fn test_populate_and_search(cx: &mut gpui::TestAppContext) { - let dir = temp_tree(json!({ - "root": { - "apple": "", - "banana": { - "carrot": { - "date": "", - "endive": "", - } - }, - "fennel": { - "grape": "", - } - } - })); - - let root_link_path = dir.path().join("root_link"); - unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); - unix::fs::symlink( - &dir.path().join("root/fennel"), - &dir.path().join("root/finnochio"), - ) - .unwrap(); - - let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; - - project.read_with(cx, |project, cx| { - let tree = project.worktrees(cx).next().unwrap().read(cx); - assert_eq!(tree.file_count(), 5); - assert_eq!( - tree.inode_for_path("fennel/grape"), - tree.inode_for_path("finnochio/grape") - ); - }); - - let cancel_flag = Default::default(); - let results = project - .read_with(cx, |project, cx| { - project.match_paths("bna", false, false, 10, &cancel_flag, cx) - }) - .await; - assert_eq!( - results - .into_iter() - .map(|result| result.path) - .collect::>>(), - vec![ - PathBuf::from("banana/carrot/date").into(), - PathBuf::from("banana/carrot/endive").into(), - ] - ); - } - - #[gpui::test] - async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let mut rust_language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut json_language = Language::new( - LanguageConfig { - name: "JSON".into(), - path_suffixes: vec!["json".to_string()], - ..Default::default() - }, - None, - ); - let mut fake_rust_servers = rust_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "the-rust-language-server", - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string(), "::".to_string()]), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - })); - let mut fake_json_servers = json_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "the-json-language-server", - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![":".to_string()]), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - })); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/the-root", - json!({ - "test.rs": "const A: i32 = 1;", - "test2.rs": "", - "Cargo.toml": "a = 1", - "package.json": "{\"a\": 1}", - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; - project.update(cx, |project, _| { - project.languages.add(Arc::new(rust_language)); - project.languages.add(Arc::new(json_language)); - }); - - // Open a buffer without an associated language server. - let toml_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/the-root/Cargo.toml", cx) - }) - .await - .unwrap(); - - // Open a buffer with an associated language server. - let rust_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/the-root/test.rs", cx) - }) - .await - .unwrap(); - - // A server is started up, and it is notified about Rust files. - let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), - version: 0, - text: "const A: i32 = 1;".to_string(), - language_id: Default::default() - } - ); - - // The buffer is configured based on the language server's capabilities. - rust_buffer.read_with(cx, |buffer, _| { - assert_eq!( - buffer.completion_triggers(), - &[".".to_string(), "::".to_string()] - ); - }); - toml_buffer.read_with(cx, |buffer, _| { - assert!(buffer.completion_triggers().is_empty()); - }); - - // Edit a buffer. The changes are reported to the language server. - rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], cx)); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::VersionedTextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/test.rs").unwrap(), - 1 - ) - ); - - // Open a third buffer with a different associated language server. - let json_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/the-root/package.json", cx) - }) - .await - .unwrap(); - - // A json language server is started up and is only notified about the json buffer. - let mut fake_json_server = fake_json_servers.next().await.unwrap(); - assert_eq!( - fake_json_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), - version: 0, - text: "{\"a\": 1}".to_string(), - language_id: Default::default() - } - ); - - // This buffer is configured based on the second language server's - // capabilities. - json_buffer.read_with(cx, |buffer, _| { - assert_eq!(buffer.completion_triggers(), &[":".to_string()]); - }); - - // When opening another buffer whose language server is already running, - // it is also configured based on the existing language server's capabilities. - let rust_buffer2 = project - .update(cx, |project, cx| { - project.open_local_buffer("/the-root/test2.rs", cx) - }) - .await - .unwrap(); - rust_buffer2.read_with(cx, |buffer, _| { - assert_eq!( - buffer.completion_triggers(), - &[".".to_string(), "::".to_string()] - ); - }); - - // Changes are reported only to servers matching the buffer's language. - toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], cx)); - rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "let x = 1;")], cx)); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::VersionedTextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/test2.rs").unwrap(), - 1 - ) - ); - - // Save notifications are reported to all servers. - toml_buffer - .update(cx, |buffer, cx| buffer.save(cx)) - .await - .unwrap(); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap() - ) - ); - assert_eq!( - fake_json_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap() - ) - ); - - // Renames are reported only to servers matching the buffer's language. - fs.rename( - Path::new("/the-root/test2.rs"), - Path::new("/the-root/test3.rs"), - Default::default(), - ) - .await - .unwrap(); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/test2.rs").unwrap() - ), - ); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(), - version: 0, - text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), - language_id: Default::default() - }, - ); - - rust_buffer2.update(cx, |buffer, cx| { - buffer.update_diagnostics( - DiagnosticSet::from_sorted_entries( - vec![DiagnosticEntry { - diagnostic: Default::default(), - range: Anchor::MIN..Anchor::MAX, - }], - &buffer.snapshot(), - ), - cx, - ); - assert_eq!( - buffer - .snapshot() - .diagnostics_in_range::<_, usize>(0..buffer.len(), false) - .count(), - 1 - ); - }); - - // When the rename changes the extension of the file, the buffer gets closed on the old - // language server and gets opened on the new one. - fs.rename( - Path::new("/the-root/test3.rs"), - Path::new("/the-root/test3.json"), - Default::default(), - ) - .await - .unwrap(); - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/test3.rs").unwrap(), - ), - ); - assert_eq!( - fake_json_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(), - version: 0, - text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), - language_id: Default::default() - }, - ); - - // We clear the diagnostics, since the language has changed. - rust_buffer2.read_with(cx, |buffer, _| { - assert_eq!( - buffer - .snapshot() - .diagnostics_in_range::<_, usize>(0..buffer.len(), false) - .count(), - 0 - ); - }); - - // The renamed file's version resets after changing language server. - rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], cx)); - assert_eq!( - fake_json_server - .receive_notification::() - .await - .text_document, - lsp::VersionedTextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/test3.json").unwrap(), - 1 - ) - ); - - // Restart language servers - project.update(cx, |project, cx| { - project.restart_language_servers_for_buffers( - vec![rust_buffer.clone(), json_buffer.clone()], - cx, - ); - }); - - let mut rust_shutdown_requests = fake_rust_server - .handle_request::(|_, _| future::ready(Ok(()))); - let mut json_shutdown_requests = fake_json_server - .handle_request::(|_, _| future::ready(Ok(()))); - futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next()); - - let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); - let mut fake_json_server = fake_json_servers.next().await.unwrap(); - - // Ensure rust document is reopened in new rust language server - assert_eq!( - fake_rust_server - .receive_notification::() - .await - .text_document, - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), - version: 1, - text: rust_buffer.read_with(cx, |buffer, _| buffer.text()), - language_id: Default::default() - } - ); - - // Ensure json documents are reopened in new json language server - assert_set_eq!( - [ - fake_json_server - .receive_notification::() - .await - .text_document, - fake_json_server - .receive_notification::() - .await - .text_document, - ], - [ - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), - version: 0, - text: json_buffer.read_with(cx, |buffer, _| buffer.text()), - language_id: Default::default() - }, - lsp::TextDocumentItem { - uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(), - version: 1, - text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()), - language_id: Default::default() - } - ] - ); - - // Close notifications are reported only to servers matching the buffer's language. - cx.update(|_| drop(json_buffer)); - let close_message = lsp::DidCloseTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path("/the-root/package.json").unwrap(), - ), - }; - assert_eq!( - fake_json_server - .receive_notification::() - .await, - close_message, - ); - } - - #[gpui::test] - async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": "let a = 1;", - "b.rs": "let b = 2;" - }), - ) - .await; - - let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await; - - let buffer_a = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - let buffer_b = project - .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) - .await - .unwrap(); - - project.update(cx, |project, cx| { - project - .update_diagnostics( - 0, - lsp::PublishDiagnosticsParams { - uri: Url::from_file_path("/dir/a.rs").unwrap(), - version: None, - diagnostics: vec![lsp::Diagnostic { - range: lsp::Range::new( - lsp::Position::new(0, 4), - lsp::Position::new(0, 5), - ), - severity: Some(lsp::DiagnosticSeverity::ERROR), - message: "error 1".to_string(), - ..Default::default() - }], - }, - &[], - cx, - ) - .unwrap(); - project - .update_diagnostics( - 0, - lsp::PublishDiagnosticsParams { - uri: Url::from_file_path("/dir/b.rs").unwrap(), - version: None, - diagnostics: vec![lsp::Diagnostic { - range: lsp::Range::new( - lsp::Position::new(0, 4), - lsp::Position::new(0, 5), - ), - severity: Some(lsp::DiagnosticSeverity::WARNING), - message: "error 2".to_string(), - ..Default::default() - }], - }, - &[], - cx, - ) - .unwrap(); - }); - - buffer_a.read_with(cx, |buffer, _| { - let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); - assert_eq!( - chunks - .iter() - .map(|(s, d)| (s.as_str(), *d)) - .collect::>(), - &[ - ("let ", None), - ("a", Some(DiagnosticSeverity::ERROR)), - (" = 1;", None), - ] - ); - }); - buffer_b.read_with(cx, |buffer, _| { - let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); - assert_eq!( - chunks - .iter() - .map(|(s, d)| (s.as_str(), *d)) - .collect::>(), - &[ - ("let ", None), - ("b", Some(DiagnosticSeverity::WARNING)), - (" = 2;", None), - ] - ); - }); - } - - #[gpui::test] - async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let progress_token = "the-progress-token"; - let mut language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - disk_based_diagnostics_progress_token: Some(progress_token.into()), - disk_based_diagnostics_sources: vec!["disk".into()], - ..Default::default() - })); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": "fn a() { A }", - "b.rs": "const y: i32 = 1", - }), - ) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let worktree_id = - project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id()); - - // Cause worktree to start the fake language server - let _buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) - .await - .unwrap(); - - let mut events = subscribe(&project, cx); - - let fake_server = fake_servers.next().await.unwrap(); - fake_server.start_progress(progress_token).await; - assert_eq!( - events.next().await.unwrap(), - Event::DiskBasedDiagnosticsStarted { - language_server_id: 0, - } - ); - - fake_server.notify::( - lsp::PublishDiagnosticsParams { - uri: Url::from_file_path("/dir/a.rs").unwrap(), - version: None, - diagnostics: vec![lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), - severity: Some(lsp::DiagnosticSeverity::ERROR), - message: "undefined variable 'A'".to_string(), - ..Default::default() - }], - }, - ); - assert_eq!( - events.next().await.unwrap(), - Event::DiagnosticsUpdated { - language_server_id: 0, - path: (worktree_id, Path::new("a.rs")).into() - } - ); - - fake_server.end_progress(progress_token); - assert_eq!( - events.next().await.unwrap(), - Event::DiskBasedDiagnosticsFinished { - language_server_id: 0 - } - ); - - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - buffer.read_with(cx, |buffer, _| { - let snapshot = buffer.snapshot(); - let diagnostics = snapshot - .diagnostics_in_range::<_, Point>(0..buffer.len(), false) - .collect::>(); - assert_eq!( - diagnostics, - &[DiagnosticEntry { - range: Point::new(0, 9)..Point::new(0, 10), - diagnostic: Diagnostic { - severity: lsp::DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string(), - group_id: 0, - is_primary: true, - ..Default::default() - } - }] - ) - }); - - // Ensure publishing empty diagnostics twice only results in one update event. - fake_server.notify::( - lsp::PublishDiagnosticsParams { - uri: Url::from_file_path("/dir/a.rs").unwrap(), - version: None, - diagnostics: Default::default(), - }, - ); - assert_eq!( - events.next().await.unwrap(), - Event::DiagnosticsUpdated { - language_server_id: 0, - path: (worktree_id, Path::new("a.rs")).into() - } - ); - - fake_server.notify::( - lsp::PublishDiagnosticsParams { - uri: Url::from_file_path("/dir/a.rs").unwrap(), - version: None, - diagnostics: Default::default(), - }, - ); - cx.foreground().run_until_parked(); - assert_eq!(futures::poll!(events.next()), Poll::Pending); - } - - #[gpui::test] - async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let progress_token = "the-progress-token"; - let mut language = Language::new( - LanguageConfig { - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - None, - ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - disk_based_diagnostics_sources: vec!["disk".into()], - disk_based_diagnostics_progress_token: Some(progress_token.into()), - ..Default::default() - })); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree("/dir", json!({ "a.rs": "" })).await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - // Simulate diagnostics starting to update. - let fake_server = fake_servers.next().await.unwrap(); - fake_server.start_progress(progress_token).await; - - // Restart the server before the diagnostics finish updating. - project.update(cx, |project, cx| { - project.restart_language_servers_for_buffers([buffer], cx); - }); - let mut events = subscribe(&project, cx); - - // Simulate the newly started server sending more diagnostics. - let fake_server = fake_servers.next().await.unwrap(); - fake_server.start_progress(progress_token).await; - assert_eq!( - events.next().await.unwrap(), - Event::DiskBasedDiagnosticsStarted { - language_server_id: 1 - } - ); - project.read_with(cx, |project, _| { - assert_eq!( - project - .language_servers_running_disk_based_diagnostics() - .collect::>(), - [1] - ); - }); - - // All diagnostics are considered done, despite the old server's diagnostic - // task never completing. - fake_server.end_progress(progress_token); - assert_eq!( - events.next().await.unwrap(), - Event::DiskBasedDiagnosticsFinished { - language_server_id: 1 - } - ); - project.read_with(cx, |project, _| { - assert_eq!( - project - .language_servers_running_disk_based_diagnostics() - .collect::>(), - [0; 0] - ); - }); - } - - #[gpui::test] - async fn test_toggling_enable_language_server( - deterministic: Arc, - cx: &mut gpui::TestAppContext, - ) { - deterministic.forbid_parking(); - - let mut rust = Language::new( - LanguageConfig { - name: Arc::from("Rust"), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - None, - ); - let mut fake_rust_servers = rust.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "rust-lsp", - ..Default::default() - })); - let mut js = Language::new( - LanguageConfig { - name: Arc::from("JavaScript"), - path_suffixes: vec!["js".to_string()], - ..Default::default() - }, - None, - ); - let mut fake_js_servers = js.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "js-lsp", - ..Default::default() - })); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| { - project.languages.add(Arc::new(rust)); - project.languages.add(Arc::new(js)); - }); - - let _rs_buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - let _js_buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx)) - .await - .unwrap(); - - let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap(); - assert_eq!( - fake_rust_server_1 - .receive_notification::() - .await - .text_document - .uri - .as_str(), - "file:///dir/a.rs" - ); - - let mut fake_js_server = fake_js_servers.next().await.unwrap(); - assert_eq!( - fake_js_server - .receive_notification::() - .await - .text_document - .uri - .as_str(), - "file:///dir/b.js" - ); - - // Disable Rust language server, ensuring only that server gets stopped. - cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.language_overrides.insert( - Arc::from("Rust"), - settings::LanguageSettings { - enable_language_server: Some(false), - ..Default::default() - }, - ); - }) - }); - fake_rust_server_1 - .receive_notification::() - .await; - - // Enable Rust and disable JavaScript language servers, ensuring that the - // former gets started again and that the latter stops. - cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.language_overrides.insert( - Arc::from("Rust"), - settings::LanguageSettings { - enable_language_server: Some(true), - ..Default::default() - }, - ); - settings.language_overrides.insert( - Arc::from("JavaScript"), - settings::LanguageSettings { - enable_language_server: Some(false), - ..Default::default() - }, - ); - }) - }); - let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); - assert_eq!( - fake_rust_server_2 - .receive_notification::() - .await - .text_document - .uri - .as_str(), - "file:///dir/a.rs" - ); - fake_js_server - .receive_notification::() - .await; - } - - #[gpui::test] - async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let mut language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - disk_based_diagnostics_sources: vec!["disk".into()], - ..Default::default() - })); - - let text = " - fn a() { A } - fn b() { BB } - fn c() { CCC } - " - .unindent(); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree("/dir", json!({ "a.rs": text })).await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - let mut fake_server = fake_servers.next().await.unwrap(); - let open_notification = fake_server - .receive_notification::() - .await; - - // Edit the buffer, moving the content down - buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], cx)); - let change_notification_1 = fake_server - .receive_notification::() - .await; - assert!( - change_notification_1.text_document.version > open_notification.text_document.version - ); - - // Report some diagnostics for the initial version of the buffer - fake_server.notify::( - lsp::PublishDiagnosticsParams { - uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), - version: Some(open_notification.text_document.version), - diagnostics: vec![ - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), - severity: Some(DiagnosticSeverity::ERROR), - message: "undefined variable 'A'".to_string(), - source: Some("disk".to_string()), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)), - severity: Some(DiagnosticSeverity::ERROR), - message: "undefined variable 'BB'".to_string(), - source: Some("disk".to_string()), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)), - severity: Some(DiagnosticSeverity::ERROR), - source: Some("disk".to_string()), - message: "undefined variable 'CCC'".to_string(), - ..Default::default() - }, - ], - }, - ); - - // The diagnostics have moved down since they were created. - buffer.next_notification(cx).await; - buffer.read_with(cx, |buffer, _| { - assert_eq!( - buffer - .snapshot() - .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false) - .collect::>(), - &[ - DiagnosticEntry { - range: Point::new(3, 9)..Point::new(3, 11), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'BB'".to_string(), - is_disk_based: true, - group_id: 1, - is_primary: true, - ..Default::default() - }, - }, - DiagnosticEntry { - range: Point::new(4, 9)..Point::new(4, 12), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'CCC'".to_string(), - is_disk_based: true, - group_id: 2, - is_primary: true, - ..Default::default() - } - } - ] - ); - assert_eq!( - chunks_with_diagnostics(buffer, 0..buffer.len()), - [ - ("\n\nfn a() { ".to_string(), None), - ("A".to_string(), Some(DiagnosticSeverity::ERROR)), - (" }\nfn b() { ".to_string(), None), - ("BB".to_string(), Some(DiagnosticSeverity::ERROR)), - (" }\nfn c() { ".to_string(), None), - ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)), - (" }\n".to_string(), None), - ] - ); - assert_eq!( - chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)), - [ - ("B".to_string(), Some(DiagnosticSeverity::ERROR)), - (" }\nfn c() { ".to_string(), None), - ("CC".to_string(), Some(DiagnosticSeverity::ERROR)), - ] - ); - }); - - // Ensure overlapping diagnostics are highlighted correctly. - fake_server.notify::( - lsp::PublishDiagnosticsParams { - uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), - version: Some(open_notification.text_document.version), - diagnostics: vec![ - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), - severity: Some(DiagnosticSeverity::ERROR), - message: "undefined variable 'A'".to_string(), - source: Some("disk".to_string()), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)), - severity: Some(DiagnosticSeverity::WARNING), - message: "unreachable statement".to_string(), - source: Some("disk".to_string()), - ..Default::default() - }, - ], - }, - ); - - buffer.next_notification(cx).await; - buffer.read_with(cx, |buffer, _| { - assert_eq!( - buffer - .snapshot() - .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false) - .collect::>(), - &[ - DiagnosticEntry { - range: Point::new(2, 9)..Point::new(2, 12), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::WARNING, - message: "unreachable statement".to_string(), - is_disk_based: true, - group_id: 4, - is_primary: true, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(2, 9)..Point::new(2, 10), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string(), - is_disk_based: true, - group_id: 3, - is_primary: true, - ..Default::default() - }, - } - ] - ); - assert_eq!( - chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)), - [ - ("fn a() { ".to_string(), None), - ("A".to_string(), Some(DiagnosticSeverity::ERROR)), - (" }".to_string(), Some(DiagnosticSeverity::WARNING)), - ("\n".to_string(), None), - ] - ); - assert_eq!( - chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)), - [ - (" }".to_string(), Some(DiagnosticSeverity::WARNING)), - ("\n".to_string(), None), - ] - ); - }); - - // Keep editing the buffer and ensure disk-based diagnostics get translated according to the - // changes since the last save. - buffer.update(cx, |buffer, cx| { - buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], cx); - buffer.edit([(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")], cx); - buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], cx); - }); - let change_notification_2 = fake_server - .receive_notification::() - .await; - assert!( - change_notification_2.text_document.version - > change_notification_1.text_document.version - ); - - // Handle out-of-order diagnostics - fake_server.notify::( - lsp::PublishDiagnosticsParams { - uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(), - version: Some(change_notification_2.text_document.version), - diagnostics: vec![ - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)), - severity: Some(DiagnosticSeverity::ERROR), - message: "undefined variable 'BB'".to_string(), - source: Some("disk".to_string()), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), - severity: Some(DiagnosticSeverity::WARNING), - message: "undefined variable 'A'".to_string(), - source: Some("disk".to_string()), - ..Default::default() - }, - ], - }, - ); - - buffer.next_notification(cx).await; - buffer.read_with(cx, |buffer, _| { - assert_eq!( - buffer - .snapshot() - .diagnostics_in_range::<_, Point>(0..buffer.len(), false) - .collect::>(), - &[ - DiagnosticEntry { - range: Point::new(2, 21)..Point::new(2, 22), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::WARNING, - message: "undefined variable 'A'".to_string(), - is_disk_based: true, - group_id: 6, - is_primary: true, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(3, 9)..Point::new(3, 14), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'BB'".to_string(), - is_disk_based: true, - group_id: 5, - is_primary: true, - ..Default::default() - }, - } - ] - ); - }); - } - - #[gpui::test] - async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let text = concat!( - "let one = ;\n", // - "let two = \n", - "let three = 3;\n", - ); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree("/dir", json!({ "a.rs": text })).await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - project.update(cx, |project, cx| { - project - .update_buffer_diagnostics( - &buffer, - vec![ - DiagnosticEntry { - range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "syntax error 1".to_string(), - ..Default::default() - }, - }, - DiagnosticEntry { - range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "syntax error 2".to_string(), - ..Default::default() - }, - }, - ], - None, - cx, - ) - .unwrap(); - }); - - // An empty range is extended forward to include the following character. - // At the end of a line, an empty range is extended backward to include - // the preceding character. - buffer.read_with(cx, |buffer, _| { - let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); - assert_eq!( - chunks - .iter() - .map(|(s, d)| (s.as_str(), *d)) - .collect::>(), - &[ - ("let one = ", None), - (";", Some(DiagnosticSeverity::ERROR)), - ("\nlet two =", None), - (" ", Some(DiagnosticSeverity::ERROR)), - ("\nlet three = 3;\n", None) - ] - ); - }); - } - - #[gpui::test] - async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let mut language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut fake_servers = language.set_fake_lsp_adapter(Default::default()); - - let text = " - fn a() { - f1(); - } - fn b() { - f2(); - } - fn c() { - f3(); - } - " - .unindent(); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": text.clone(), - }), - ) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - let mut fake_server = fake_servers.next().await.unwrap(); - let lsp_document_version = fake_server - .receive_notification::() - .await - .text_document - .version; - - // Simulate editing the buffer after the language server computes some edits. - buffer.update(cx, |buffer, cx| { - buffer.edit( - [( - Point::new(0, 0)..Point::new(0, 0), - "// above first function\n", - )], - cx, - ); - buffer.edit( - [( - Point::new(2, 0)..Point::new(2, 0), - " // inside first function\n", - )], - cx, - ); - buffer.edit( - [( - Point::new(6, 4)..Point::new(6, 4), - "// inside second function ", - )], - cx, - ); - - assert_eq!( - buffer.text(), - " - // above first function - fn a() { - // inside first function - f1(); - } - fn b() { - // inside second function f2(); - } - fn c() { - f3(); - } - " - .unindent() - ); - }); - - let edits = project - .update(cx, |project, cx| { - project.edits_from_lsp( - &buffer, - vec![ - // replace body of first function - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 0), - lsp::Position::new(3, 0), - ), - new_text: " - fn a() { - f10(); - } - " - .unindent(), - }, - // edit inside second function - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(4, 6), - lsp::Position::new(4, 6), - ), - new_text: "00".into(), - }, - // edit inside third function via two distinct edits - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(7, 5), - lsp::Position::new(7, 5), - ), - new_text: "4000".into(), - }, - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(7, 5), - lsp::Position::new(7, 6), - ), - new_text: "".into(), - }, - ], - Some(lsp_document_version), - cx, - ) - }) - .await - .unwrap(); - - buffer.update(cx, |buffer, cx| { - for (range, new_text) in edits { - buffer.edit([(range, new_text)], cx); - } - assert_eq!( - buffer.text(), - " - // above first function - fn a() { - // inside first function - f10(); - } - fn b() { - // inside second function f200(); - } - fn c() { - f4000(); - } - " - .unindent() - ); - }); - } - - #[gpui::test] - async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let text = " - use a::b; - use a::c; - - fn f() { - b(); - c(); - } - " - .unindent(); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": text.clone(), - }), - ) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - // Simulate the language server sending us a small edit in the form of a very large diff. - // Rust-analyzer does this when performing a merge-imports code action. - let edits = project - .update(cx, |project, cx| { - project.edits_from_lsp( - &buffer, - [ - // Replace the first use statement without editing the semicolon. - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 4), - lsp::Position::new(0, 8), - ), - new_text: "a::{b, c}".into(), - }, - // Reinsert the remainder of the file between the semicolon and the final - // newline of the file. - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 9), - lsp::Position::new(0, 9), - ), - new_text: "\n\n".into(), - }, - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 9), - lsp::Position::new(0, 9), - ), - new_text: " - fn f() { - b(); - c(); - }" - .unindent(), - }, - // Delete everything after the first newline of the file. - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(1, 0), - lsp::Position::new(7, 0), - ), - new_text: "".into(), - }, - ], - None, - cx, - ) - }) - .await - .unwrap(); - - buffer.update(cx, |buffer, cx| { - let edits = edits - .into_iter() - .map(|(range, text)| { - ( - range.start.to_point(&buffer)..range.end.to_point(&buffer), - text, - ) - }) - .collect::>(); - - assert_eq!( - edits, - [ - (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), - (Point::new(1, 0)..Point::new(2, 0), "".into()) - ] - ); - - for (range, new_text) in edits { - buffer.edit([(range, new_text)], cx); - } - assert_eq!( - buffer.text(), - " - use a::{b, c}; - - fn f() { - b(); - c(); - } - " - .unindent() - ); - }); - } - - #[gpui::test] - async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let text = " - use a::b; - use a::c; - - fn f() { - b(); - c(); - } - " - .unindent(); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": text.clone(), - }), - ) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) - .await - .unwrap(); - - // Simulate the language server sending us edits in a non-ordered fashion, - // with ranges sometimes being inverted. - let edits = project - .update(cx, |project, cx| { - project.edits_from_lsp( - &buffer, - [ - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 9), - lsp::Position::new(0, 9), - ), - new_text: "\n\n".into(), - }, - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 8), - lsp::Position::new(0, 4), - ), - new_text: "a::{b, c}".into(), - }, - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(1, 0), - lsp::Position::new(7, 0), - ), - new_text: "".into(), - }, - lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 9), - lsp::Position::new(0, 9), - ), - new_text: " - fn f() { - b(); - c(); - }" - .unindent(), - }, - ], - None, - cx, - ) - }) - .await - .unwrap(); - - buffer.update(cx, |buffer, cx| { - let edits = edits - .into_iter() - .map(|(range, text)| { - ( - range.start.to_point(&buffer)..range.end.to_point(&buffer), - text, - ) - }) - .collect::>(); - - assert_eq!( - edits, - [ - (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()), - (Point::new(1, 0)..Point::new(2, 0), "".into()) - ] - ); - - for (range, new_text) in edits { - buffer.edit([(range, new_text)], cx); - } - assert_eq!( - buffer.text(), - " - use a::{b, c}; - - fn f() { - b(); - c(); - } - " - .unindent() - ); - }); - } - - fn chunks_with_diagnostics( - buffer: &Buffer, - range: Range, - ) -> Vec<(String, Option)> { - let mut chunks: Vec<(String, Option)> = Vec::new(); - for chunk in buffer.snapshot().chunks(range, true) { - if chunks.last().map_or(false, |prev_chunk| { - prev_chunk.1 == chunk.diagnostic_severity - }) { - chunks.last_mut().unwrap().0.push_str(chunk.text); - } else { - chunks.push((chunk.text.to_string(), chunk.diagnostic_severity)); - } - } - chunks - } - - #[gpui::test] - async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { - let dir = temp_tree(json!({ - "root": { - "dir1": {}, - "dir2": { - "dir3": {} - } - } - })); - - let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; - let cancel_flag = Default::default(); - let results = project - .read_with(cx, |project, cx| { - project.match_paths("dir", false, false, 10, &cancel_flag, cx) - }) - .await; - - assert!(results.is_empty()); - } - - #[gpui::test(iterations = 10)] - async fn test_definition(cx: &mut gpui::TestAppContext) { - let mut language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut fake_servers = language.set_fake_lsp_adapter(Default::default()); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.rs": "const fn a() { A }", - "b.rs": "const y: i32 = crate::a()", - }), - ) - .await; - - let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - - let buffer = project - .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx)) - .await - .unwrap(); - - let fake_server = fake_servers.next().await.unwrap(); - fake_server.handle_request::(|params, _| async move { - let params = params.text_document_position_params; - assert_eq!( - params.text_document.uri.to_file_path().unwrap(), - Path::new("/dir/b.rs"), - ); - assert_eq!(params.position, lsp::Position::new(0, 22)); - - Ok(Some(lsp::GotoDefinitionResponse::Scalar( - lsp::Location::new( - lsp::Url::from_file_path("/dir/a.rs").unwrap(), - lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), - ), - ))) - }); - - let mut definitions = project - .update(cx, |project, cx| project.definition(&buffer, 22, cx)) - .await - .unwrap(); - - assert_eq!(definitions.len(), 1); - let definition = definitions.pop().unwrap(); - cx.update(|cx| { - let target_buffer = definition.target.buffer.read(cx); - assert_eq!( - target_buffer - .file() - .unwrap() - .as_local() - .unwrap() - .abs_path(cx), - Path::new("/dir/a.rs"), - ); - assert_eq!(definition.target.range.to_offset(target_buffer), 9..10); - assert_eq!( - list_worktrees(&project, cx), - [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)] - ); - - drop(definition); - }); - cx.read(|cx| { - assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]); - }); - - fn list_worktrees<'a>( - project: &'a ModelHandle, - cx: &'a AppContext, - ) -> Vec<(&'a Path, bool)> { - project - .read(cx) - .worktrees(cx) - .map(|worktree| { - let worktree = worktree.read(cx); - ( - worktree.as_local().unwrap().abs_path().as_ref(), - worktree.is_visible(), - ) - }) - .collect::>() - } - } - - #[gpui::test] - async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { - let mut language = Language::new( - LanguageConfig { - name: "TypeScript".into(), - path_suffixes: vec!["ts".to_string()], - ..Default::default() - }, - Some(tree_sitter_typescript::language_typescript()), - ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.ts": "", - }), - ) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) - .await - .unwrap(); - - let fake_server = fake_language_servers.next().await.unwrap(); - - let text = "let a = b.fqn"; - buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); - let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len(), cx) - }); - - fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp::CompletionResponse::Array(vec![ - lsp::CompletionItem { - label: "fullyQualifiedName?".into(), - insert_text: Some("fullyQualifiedName".into()), - ..Default::default() - }, - ]))) - }) - .next() - .await; - let completions = completions.await.unwrap(); - let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].new_text, "fullyQualifiedName"); - assert_eq!( - completions[0].old_range.to_offset(&snapshot), - text.len() - 3..text.len() - ); - - let text = "let a = \"atoms/cmp\""; - buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); - let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len() - 1, cx) - }); - - fake_server - .handle_request::(|_, _| async move { - Ok(Some(lsp::CompletionResponse::Array(vec![ - lsp::CompletionItem { - label: "component".into(), - ..Default::default() - }, - ]))) - }) - .next() - .await; - let completions = completions.await.unwrap(); - let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].new_text, "component"); - assert_eq!( - completions[0].old_range.to_offset(&snapshot), - text.len() - 4..text.len() - 1 - ); - } - - #[gpui::test(iterations = 10)] - async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { - let mut language = Language::new( - LanguageConfig { - name: "TypeScript".into(), - path_suffixes: vec!["ts".to_string()], - ..Default::default() - }, - None, - ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.ts": "a", - }), - ) - .await; - - let project = Project::test(fs, ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) - .await - .unwrap(); - - let fake_server = fake_language_servers.next().await.unwrap(); - - // Language server returns code actions that contain commands, and not edits. - let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx)); - fake_server - .handle_request::(|_, _| async move { - Ok(Some(vec![ - lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { - title: "The code action".into(), - command: Some(lsp::Command { - title: "The command".into(), - command: "_the/command".into(), - arguments: Some(vec![json!("the-argument")]), - }), - ..Default::default() - }), - lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { - title: "two".into(), - ..Default::default() - }), - ])) - }) - .next() - .await; - - let action = actions.await.unwrap()[0].clone(); - let apply = project.update(cx, |project, cx| { - project.apply_code_action(buffer.clone(), action, true, cx) - }); - - // Resolving the code action does not populate its edits. In absence of - // edits, we must execute the given command. - fake_server.handle_request::( - |action, _| async move { Ok(action) }, - ); - - // While executing the command, the language server sends the editor - // a `workspaceEdit` request. - fake_server - .handle_request::({ - let fake = fake_server.clone(); - move |params, _| { - assert_eq!(params.command, "_the/command"); - let fake = fake.clone(); - async move { - fake.server - .request::( - lsp::ApplyWorkspaceEditParams { - label: None, - edit: lsp::WorkspaceEdit { - changes: Some( - [( - lsp::Url::from_file_path("/dir/a.ts").unwrap(), - vec![lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 0), - lsp::Position::new(0, 0), - ), - new_text: "X".into(), - }], - )] - .into_iter() - .collect(), - ), - ..Default::default() - }, - }, - ) - .await - .unwrap(); - Ok(Some(json!(null))) - } - } - }) - .next() - .await; - - // Applying the code action returns a project transaction containing the edits - // sent by the language server in its `workspaceEdit` request. - let transaction = apply.await.unwrap(); - assert!(transaction.0.contains_key(&buffer)); - buffer.update(cx, |buffer, cx| { - assert_eq!(buffer.text(), "Xa"); - buffer.undo(cx); - assert_eq!(buffer.text(), "a"); - }); - } - - #[gpui::test] - async fn test_save_file(cx: &mut gpui::TestAppContext) { - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "file1": "the old contents", - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) - .await - .unwrap(); - buffer - .update(cx, |buffer, cx| { - assert_eq!(buffer.text(), "the old contents"); - buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx); - buffer.save(cx) - }) - .await - .unwrap(); - - let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); - assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); - } - - #[gpui::test] - async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "file1": "the old contents", - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await; - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) - .await - .unwrap(); - buffer - .update(cx, |buffer, cx| { - buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], cx); - buffer.save(cx) - }) - .await - .unwrap(); - - let new_text = fs.load(Path::new("/dir/file1")).await.unwrap(); - assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text())); - } - - #[gpui::test] - async fn test_save_as(cx: &mut gpui::TestAppContext) { - let fs = FakeFs::new(cx.background()); - fs.insert_tree("/dir", json!({})).await; - - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let buffer = project.update(cx, |project, cx| { - project.create_buffer("", None, cx).unwrap() - }); - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, "abc")], cx); - assert!(buffer.is_dirty()); - assert!(!buffer.has_conflict()); - }); - project - .update(cx, |project, cx| { - project.save_buffer_as(buffer.clone(), "/dir/file1".into(), cx) - }) - .await - .unwrap(); - assert_eq!(fs.load(Path::new("/dir/file1")).await.unwrap(), "abc"); - buffer.read_with(cx, |buffer, cx| { - assert_eq!(buffer.file().unwrap().full_path(cx), Path::new("dir/file1")); - assert!(!buffer.is_dirty()); - assert!(!buffer.has_conflict()); - }); - - let opened_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/dir/file1", cx) - }) - .await - .unwrap(); - assert_eq!(opened_buffer, buffer); - } - - #[gpui::test(retries = 5)] - async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) { - let dir = temp_tree(json!({ - "a": { - "file1": "", - "file2": "", - "file3": "", - }, - "b": { - "c": { - "file4": "", - "file5": "", - } - } - })); - - let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; - let rpc = project.read_with(cx, |p, _| p.client.clone()); - - let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { - let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx)); - async move { buffer.await.unwrap() } - }; - let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| { - project.read_with(cx, |project, cx| { - let tree = project.worktrees(cx).next().unwrap(); - tree.read(cx) - .entry_for_path(path) - .expect(&format!("no entry for path {}", path)) - .id - }) - }; - - let buffer2 = buffer_for_path("a/file2", cx).await; - let buffer3 = buffer_for_path("a/file3", cx).await; - let buffer4 = buffer_for_path("b/c/file4", cx).await; - let buffer5 = buffer_for_path("b/c/file5", cx).await; - - let file2_id = id_for_path("a/file2", &cx); - let file3_id = id_for_path("a/file3", &cx); - let file4_id = id_for_path("b/c/file4", &cx); - - // Create a remote copy of this worktree. - let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()); - let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()); - let (remote, load_task) = cx.update(|cx| { - Worktree::remote( - 1, - 1, - initial_snapshot.to_proto(&Default::default(), true), - rpc.clone(), - cx, - ) - }); - // tree - load_task.await; - - cx.read(|cx| { - assert!(!buffer2.read(cx).is_dirty()); - assert!(!buffer3.read(cx).is_dirty()); - assert!(!buffer4.read(cx).is_dirty()); - assert!(!buffer5.read(cx).is_dirty()); - }); - - // Rename and delete files and directories. - tree.flush_fs_events(&cx).await; - std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); - std::fs::remove_file(dir.path().join("b/c/file5")).unwrap(); - std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); - std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap(); - tree.flush_fs_events(&cx).await; - - let expected_paths = vec![ - "a", - "a/file1", - "a/file2.new", - "b", - "d", - "d/file3", - "d/file4", - ]; - - cx.read(|app| { - assert_eq!( - tree.read(app) - .paths() - .map(|p| p.to_str().unwrap()) - .collect::>(), - expected_paths - ); - - assert_eq!(id_for_path("a/file2.new", &cx), file2_id); - assert_eq!(id_for_path("d/file3", &cx), file3_id); - assert_eq!(id_for_path("d/file4", &cx), file4_id); - - assert_eq!( - buffer2.read(app).file().unwrap().path().as_ref(), - Path::new("a/file2.new") - ); - assert_eq!( - buffer3.read(app).file().unwrap().path().as_ref(), - Path::new("d/file3") - ); - assert_eq!( - buffer4.read(app).file().unwrap().path().as_ref(), - Path::new("d/file4") - ); - assert_eq!( - buffer5.read(app).file().unwrap().path().as_ref(), - Path::new("b/c/file5") - ); - - assert!(!buffer2.read(app).file().unwrap().is_deleted()); - assert!(!buffer3.read(app).file().unwrap().is_deleted()); - assert!(!buffer4.read(app).file().unwrap().is_deleted()); - assert!(buffer5.read(app).file().unwrap().is_deleted()); - }); - - // Update the remote worktree. Check that it becomes consistent with the - // local worktree. - remote.update(cx, |remote, cx| { - let update_message = tree.read(cx).as_local().unwrap().snapshot().build_update( - &initial_snapshot, - 1, - 1, - true, - ); - remote - .as_remote_mut() - .unwrap() - .snapshot - .apply_remote_update(update_message) - .unwrap(); - - assert_eq!( - remote - .paths() - .map(|p| p.to_str().unwrap()) - .collect::>(), - expected_paths - ); - }); - } - - #[gpui::test] - async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "a.txt": "a-contents", - "b.txt": "b-contents", - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - - // Spawn multiple tasks to open paths, repeating some paths. - let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| { - ( - p.open_local_buffer("/dir/a.txt", cx), - p.open_local_buffer("/dir/b.txt", cx), - p.open_local_buffer("/dir/a.txt", cx), - ) - }); - - let buffer_a_1 = buffer_a_1.await.unwrap(); - let buffer_a_2 = buffer_a_2.await.unwrap(); - let buffer_b = buffer_b.await.unwrap(); - assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents"); - assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents"); - - // There is only one buffer per path. - let buffer_a_id = buffer_a_1.id(); - assert_eq!(buffer_a_2.id(), buffer_a_id); - - // Open the same path again while it is still open. - drop(buffer_a_1); - let buffer_a_3 = project - .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx)) - .await - .unwrap(); - - // There's still only one buffer per path. - assert_eq!(buffer_a_3.id(), buffer_a_id); - } - - #[gpui::test] - async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "file1": "abc", - "file2": "def", - "file3": "ghi", - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - - let buffer1 = project - .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) - .await - .unwrap(); - let events = Rc::new(RefCell::new(Vec::new())); - - // initially, the buffer isn't dirty. - buffer1.update(cx, |buffer, cx| { - cx.subscribe(&buffer1, { - let events = events.clone(); - move |_, _, event, _| match event { - BufferEvent::Operation(_) => {} - _ => events.borrow_mut().push(event.clone()), - } - }) - .detach(); - - assert!(!buffer.is_dirty()); - assert!(events.borrow().is_empty()); - - buffer.edit([(1..2, "")], cx); - }); - - // after the first edit, the buffer is dirty, and emits a dirtied event. - buffer1.update(cx, |buffer, cx| { - assert!(buffer.text() == "ac"); - assert!(buffer.is_dirty()); - assert_eq!( - *events.borrow(), - &[language::Event::Edited, language::Event::DirtyChanged] - ); - events.borrow_mut().clear(); - buffer.did_save( - buffer.version(), - buffer.as_rope().fingerprint(), - buffer.file().unwrap().mtime(), - None, - cx, - ); - }); - - // after saving, the buffer is not dirty, and emits a saved event. - buffer1.update(cx, |buffer, cx| { - assert!(!buffer.is_dirty()); - assert_eq!(*events.borrow(), &[language::Event::Saved]); - events.borrow_mut().clear(); - - buffer.edit([(1..1, "B")], cx); - buffer.edit([(2..2, "D")], cx); - }); - - // after editing again, the buffer is dirty, and emits another dirty event. - buffer1.update(cx, |buffer, cx| { - assert!(buffer.text() == "aBDc"); - assert!(buffer.is_dirty()); - assert_eq!( - *events.borrow(), - &[ - language::Event::Edited, - language::Event::DirtyChanged, - language::Event::Edited, - ], - ); - events.borrow_mut().clear(); - - // After restoring the buffer to its previously-saved state, - // the buffer is not considered dirty anymore. - buffer.edit([(1..3, "")], cx); - assert!(buffer.text() == "ac"); - assert!(!buffer.is_dirty()); - }); - - assert_eq!( - *events.borrow(), - &[language::Event::Edited, language::Event::DirtyChanged] - ); - - // When a file is deleted, the buffer is considered dirty. - let events = Rc::new(RefCell::new(Vec::new())); - let buffer2 = project - .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) - .await - .unwrap(); - buffer2.update(cx, |_, cx| { - cx.subscribe(&buffer2, { - let events = events.clone(); - move |_, _, event, _| events.borrow_mut().push(event.clone()) - }) - .detach(); - }); - - fs.remove_file("/dir/file2".as_ref(), Default::default()) - .await - .unwrap(); - buffer2.condition(&cx, |b, _| b.is_dirty()).await; - assert_eq!( - *events.borrow(), - &[ - language::Event::DirtyChanged, - language::Event::FileHandleChanged - ] - ); - - // When a file is already dirty when deleted, we don't emit a Dirtied event. - let events = Rc::new(RefCell::new(Vec::new())); - let buffer3 = project - .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx)) - .await - .unwrap(); - buffer3.update(cx, |_, cx| { - cx.subscribe(&buffer3, { - let events = events.clone(); - move |_, _, event, _| events.borrow_mut().push(event.clone()) - }) - .detach(); - }); - - buffer3.update(cx, |buffer, cx| { - buffer.edit([(0..0, "x")], cx); - }); - events.borrow_mut().clear(); - fs.remove_file("/dir/file3".as_ref(), Default::default()) - .await - .unwrap(); - buffer3 - .condition(&cx, |_, _| !events.borrow().is_empty()) - .await; - assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]); - cx.read(|cx| assert!(buffer3.read(cx).is_dirty())); - } - - #[gpui::test] - async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { - let initial_contents = "aaa\nbbbbb\nc\n"; - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "the-file": initial_contents, - }), - ) - .await; - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx)) - .await - .unwrap(); - - let anchors = (0..3) - .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1)))) - .collect::>(); - - // Change the file on disk, adding two new lines of text, and removing - // one line. - buffer.read_with(cx, |buffer, _| { - assert!(!buffer.is_dirty()); - assert!(!buffer.has_conflict()); - }); - let new_contents = "AAAA\naaa\nBB\nbbbbb\n"; - fs.save("/dir/the-file".as_ref(), &new_contents.into()) - .await - .unwrap(); - - // Because the buffer was not modified, it is reloaded from disk. Its - // contents are edited according to the diff between the old and new - // file contents. - buffer - .condition(&cx, |buffer, _| buffer.text() == new_contents) - .await; - - buffer.update(cx, |buffer, _| { - assert_eq!(buffer.text(), new_contents); - assert!(!buffer.is_dirty()); - assert!(!buffer.has_conflict()); - - let anchor_positions = anchors - .iter() - .map(|anchor| anchor.to_point(&*buffer)) - .collect::>(); - assert_eq!( - anchor_positions, - [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)] - ); - }); - - // Modify the buffer - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, " ")], cx); - assert!(buffer.is_dirty()); - assert!(!buffer.has_conflict()); - }); - - // Change the file on disk again, adding blank lines to the beginning. - fs.save( - "/dir/the-file".as_ref(), - &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(), - ) - .await - .unwrap(); - - // Because the buffer is modified, it doesn't reload from disk, but is - // marked as having a conflict. - buffer - .condition(&cx, |buffer, _| buffer.has_conflict()) - .await; - } - - #[gpui::test] - async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/the-dir", - json!({ - "a.rs": " - fn foo(mut v: Vec) { - for x in &v { - v.push(1); - } - } - " - .unindent(), - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await; - let buffer = project - .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx)) - .await - .unwrap(); - - let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap(); - let message = lsp::PublishDiagnosticsParams { - uri: buffer_uri.clone(), - diagnostics: vec![ - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), - severity: Some(DiagnosticSeverity::WARNING), - message: "error 1".to_string(), - related_information: Some(vec![lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: buffer_uri.clone(), - range: lsp::Range::new( - lsp::Position::new(1, 8), - lsp::Position::new(1, 9), - ), - }, - message: "error 1 hint 1".to_string(), - }]), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), - severity: Some(DiagnosticSeverity::HINT), - message: "error 1 hint 1".to_string(), - related_information: Some(vec![lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: buffer_uri.clone(), - range: lsp::Range::new( - lsp::Position::new(1, 8), - lsp::Position::new(1, 9), - ), - }, - message: "original diagnostic".to_string(), - }]), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), - severity: Some(DiagnosticSeverity::ERROR), - message: "error 2".to_string(), - related_information: Some(vec![ - lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: buffer_uri.clone(), - range: lsp::Range::new( - lsp::Position::new(1, 13), - lsp::Position::new(1, 15), - ), - }, - message: "error 2 hint 1".to_string(), - }, - lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: buffer_uri.clone(), - range: lsp::Range::new( - lsp::Position::new(1, 13), - lsp::Position::new(1, 15), - ), - }, - message: "error 2 hint 2".to_string(), - }, - ]), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), - severity: Some(DiagnosticSeverity::HINT), - message: "error 2 hint 1".to_string(), - related_information: Some(vec![lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: buffer_uri.clone(), - range: lsp::Range::new( - lsp::Position::new(2, 8), - lsp::Position::new(2, 17), - ), - }, - message: "original diagnostic".to_string(), - }]), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), - severity: Some(DiagnosticSeverity::HINT), - message: "error 2 hint 2".to_string(), - related_information: Some(vec![lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: buffer_uri.clone(), - range: lsp::Range::new( - lsp::Position::new(2, 8), - lsp::Position::new(2, 17), - ), - }, - message: "original diagnostic".to_string(), - }]), - ..Default::default() - }, - ], - version: None, - }; - - project - .update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx)) - .unwrap(); - let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - - assert_eq!( - buffer - .diagnostics_in_range::<_, Point>(0..buffer.len(), false) - .collect::>(), - &[ - DiagnosticEntry { - range: Point::new(1, 8)..Point::new(1, 9), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::WARNING, - message: "error 1".to_string(), - group_id: 0, - is_primary: true, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(1, 8)..Point::new(1, 9), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 1 hint 1".to_string(), - group_id: 0, - is_primary: false, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(1, 13)..Point::new(1, 15), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 2 hint 1".to_string(), - group_id: 1, - is_primary: false, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(1, 13)..Point::new(1, 15), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 2 hint 2".to_string(), - group_id: 1, - is_primary: false, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(2, 8)..Point::new(2, 17), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "error 2".to_string(), - group_id: 1, - is_primary: true, - ..Default::default() - } - } - ] - ); - - assert_eq!( - buffer.diagnostic_group::(0).collect::>(), - &[ - DiagnosticEntry { - range: Point::new(1, 8)..Point::new(1, 9), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::WARNING, - message: "error 1".to_string(), - group_id: 0, - is_primary: true, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(1, 8)..Point::new(1, 9), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 1 hint 1".to_string(), - group_id: 0, - is_primary: false, - ..Default::default() - } - }, - ] - ); - assert_eq!( - buffer.diagnostic_group::(1).collect::>(), - &[ - DiagnosticEntry { - range: Point::new(1, 13)..Point::new(1, 15), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 2 hint 1".to_string(), - group_id: 1, - is_primary: false, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(1, 13)..Point::new(1, 15), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 2 hint 2".to_string(), - group_id: 1, - is_primary: false, - ..Default::default() - } - }, - DiagnosticEntry { - range: Point::new(2, 8)..Point::new(2, 17), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "error 2".to_string(), - group_id: 1, - is_primary: true, - ..Default::default() - } - } - ] - ); - } - - #[gpui::test] - async fn test_rename(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let mut language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { - prepare_provider: Some(true), - work_done_progress_options: Default::default(), - })), - ..Default::default() - }, - ..Default::default() - })); - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "one.rs": "const ONE: usize = 1;", - "two.rs": "const TWO: usize = one::ONE + one::ONE;" - }), - ) - .await; - - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - project.update(cx, |project, _| project.languages.add(Arc::new(language))); - let buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/dir/one.rs", cx) - }) - .await - .unwrap(); - - let fake_server = fake_servers.next().await.unwrap(); - - let response = project.update(cx, |project, cx| { - project.prepare_rename(buffer.clone(), 7, cx) - }); - fake_server - .handle_request::(|params, _| async move { - assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); - assert_eq!(params.position, lsp::Position::new(0, 7)); - Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( - lsp::Position::new(0, 6), - lsp::Position::new(0, 9), - )))) - }) - .next() - .await - .unwrap(); - let range = response.await.unwrap().unwrap(); - let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer)); - assert_eq!(range, 6..9); - - let response = project.update(cx, |project, cx| { - project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx) - }); - fake_server - .handle_request::(|params, _| async move { - assert_eq!( - params.text_document_position.text_document.uri.as_str(), - "file:///dir/one.rs" - ); - assert_eq!( - params.text_document_position.position, - lsp::Position::new(0, 7) - ); - assert_eq!(params.new_name, "THREE"); - Ok(Some(lsp::WorkspaceEdit { - changes: Some( - [ - ( - lsp::Url::from_file_path("/dir/one.rs").unwrap(), - vec![lsp::TextEdit::new( - lsp::Range::new( - lsp::Position::new(0, 6), - lsp::Position::new(0, 9), - ), - "THREE".to_string(), - )], - ), - ( - lsp::Url::from_file_path("/dir/two.rs").unwrap(), - vec![ - lsp::TextEdit::new( - lsp::Range::new( - lsp::Position::new(0, 24), - lsp::Position::new(0, 27), - ), - "THREE".to_string(), - ), - lsp::TextEdit::new( - lsp::Range::new( - lsp::Position::new(0, 35), - lsp::Position::new(0, 38), - ), - "THREE".to_string(), - ), - ], - ), - ] - .into_iter() - .collect(), - ), - ..Default::default() - })) - }) - .next() - .await - .unwrap(); - let mut transaction = response.await.unwrap().0; - assert_eq!(transaction.len(), 2); - assert_eq!( - transaction - .remove_entry(&buffer) - .unwrap() - .0 - .read_with(cx, |buffer, _| buffer.text()), - "const THREE: usize = 1;" - ); - assert_eq!( - transaction - .into_keys() - .next() - .unwrap() - .read_with(cx, |buffer, _| buffer.text()), - "const TWO: usize = one::THREE + one::THREE;" - ); - } - - #[gpui::test] - async fn test_search(cx: &mut gpui::TestAppContext) { - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/dir", - json!({ - "one.rs": "const ONE: usize = 1;", - "two.rs": "const TWO: usize = one::ONE + one::ONE;", - "three.rs": "const THREE: usize = one::ONE + two::TWO;", - "four.rs": "const FOUR: usize = one::ONE + three::THREE;", - }), - ) - .await; - let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; - assert_eq!( - search(&project, SearchQuery::text("TWO", false, true), cx) - .await - .unwrap(), - HashMap::from_iter([ - ("two.rs".to_string(), vec![6..9]), - ("three.rs".to_string(), vec![37..40]) - ]) - ); - - let buffer_4 = project - .update(cx, |project, cx| { - project.open_local_buffer("/dir/four.rs", cx) - }) - .await - .unwrap(); - buffer_4.update(cx, |buffer, cx| { - let text = "two::TWO"; - buffer.edit([(20..28, text), (31..43, text)], cx); - }); - - assert_eq!( - search(&project, SearchQuery::text("TWO", false, true), cx) - .await - .unwrap(), - HashMap::from_iter([ - ("two.rs".to_string(), vec![6..9]), - ("three.rs".to_string(), vec![37..40]), - ("four.rs".to_string(), vec![25..28, 36..39]) - ]) - ); - - async fn search( - project: &ModelHandle, - query: SearchQuery, - cx: &mut gpui::TestAppContext, - ) -> Result>>> { - let results = project - .update(cx, |project, cx| project.search(query, cx)) - .await?; - - Ok(results - .into_iter() - .map(|(buffer, ranges)| { - buffer.read_with(cx, |buffer, _| { - let path = buffer.file().unwrap().path().to_string_lossy().to_string(); - let ranges = ranges - .into_iter() - .map(|range| range.to_offset(buffer)) - .collect::>(); - (path, ranges) - }) - }) - .collect()) - } - } -} diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 00f7bb8c94..e16edf27d2 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -86,7 +86,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { }, None, ); - let mut fake_rust_servers = rust_language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_rust_servers = rust_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "the-rust-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { @@ -96,8 +96,8 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { ..Default::default() }, ..Default::default() - }); - let mut fake_json_servers = json_language.set_fake_lsp_adapter(FakeLspAdapter { + })); + let mut fake_json_servers = json_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "the-json-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { @@ -107,7 +107,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { ..Default::default() }, ..Default::default() - }); + })); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -611,11 +611,11 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { - disk_based_diagnostics_progress_token: Some(progress_token), - disk_based_diagnostics_sources: &["disk"], + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_progress_token: Some(progress_token.into()), + disk_based_diagnostics_sources: vec!["disk".into()], ..Default::default() - }); + })); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -734,11 +734,11 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }, None, ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { - disk_based_diagnostics_sources: &["disk"], - disk_based_diagnostics_progress_token: Some(progress_token), + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + disk_based_diagnostics_progress_token: Some(progress_token.into()), ..Default::default() - }); + })); let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "a.rs": "" })).await; @@ -813,10 +813,10 @@ async fn test_toggling_enable_language_server( }, None, ); - let mut fake_rust_servers = rust.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_rust_servers = rust.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "rust-lsp", ..Default::default() - }); + })); let mut js = Language::new( LanguageConfig { name: Arc::from("JavaScript"), @@ -825,10 +825,10 @@ async fn test_toggling_enable_language_server( }, None, ); - let mut fake_js_servers = js.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_js_servers = js.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { name: "js-lsp", ..Default::default() - }); + })); let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) @@ -934,10 +934,10 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { - disk_based_diagnostics_sources: &["disk"], + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], ..Default::default() - }); + })); let text = " fn a() { A } @@ -2841,7 +2841,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter { + let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { capabilities: lsp::ServerCapabilities { rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { prepare_provider: Some(true), @@ -2850,7 +2850,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { ..Default::default() }, ..Default::default() - }); + })); let fs = FakeFs::new(cx.background()); fs.insert_tree( From 73620dad061055ff4483b1b0e15c5da5946d0b7e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 8 Jul 2022 14:37:27 +0200 Subject: [PATCH 66/91] Add channel to notify project when languages are added --- crates/language/src/language.rs | 8 +++++ crates/project/src/project.rs | 36 +++++++++++++++++-- crates/project/src/project_tests.rs | 39 +++++++++++++++++++-- crates/zed/src/languages/language_plugin.rs | 1 + 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 572bb8c8ae..bab8d90f7c 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -18,6 +18,7 @@ use gpui::{MutableAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; +use postage::watch; use regex::Regex; use serde::{de, Deserialize, Deserializer}; use serde_json::Value; @@ -316,6 +317,7 @@ pub struct LanguageRegistry { Shared>>>, >, >, + subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>, } impl LanguageRegistry { @@ -328,6 +330,7 @@ impl LanguageRegistry { lsp_binary_statuses_rx, login_shell_env_loaded: login_shell_env_loaded.shared(), lsp_binary_paths: Default::default(), + subscription: RwLock::new(watch::channel()), } } @@ -338,6 +341,11 @@ impl LanguageRegistry { pub fn add(&self, language: Arc) { self.languages.write().push(language.clone()); + *self.subscription.write().0.borrow_mut() = (); + } + + pub fn subscribe(&self) -> watch::Receiver<()> { + self.subscription.read().1.clone() } pub fn set_theme(&self, theme: &SyntaxTheme) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7adab020b5..96e33de17e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -125,6 +125,7 @@ pub struct Project { buffer_snapshots: HashMap>, nonce: u128, initialized_persistent_state: bool, + _maintain_buffer_languages: Task<()>, } #[derive(Error, Debug)] @@ -472,6 +473,7 @@ impl Project { opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx), client_subscriptions: Vec::new(), _subscriptions: vec![cx.observe_global::(Self::on_settings_changed)], + _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), active_entry: None, languages, client, @@ -549,6 +551,7 @@ impl Project { loading_local_worktrees: Default::default(), active_entry: None, collaborators: Default::default(), + _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), languages, user_store: user_store.clone(), project_store, @@ -2019,6 +2022,34 @@ impl Project { }) } + fn maintain_buffer_languages( + languages: &LanguageRegistry, + cx: &mut ModelContext, + ) -> Task<()> { + let mut subscription = languages.subscribe(); + cx.spawn_weak(|project, mut cx| async move { + while let Some(_) = subscription.next().await { + if let Some(project) = project.upgrade(&cx) { + project.update(&mut cx, |project, cx| { + let mut buffers_without_language = Vec::new(); + for buffer in project.opened_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + if buffer.read(cx).language().is_none() { + buffers_without_language.push(buffer); + } + } + } + + for buffer in buffers_without_language { + project.assign_language_to_buffer(&buffer, cx); + project.register_buffer_with_language_server(&buffer, cx); + } + }); + } + } + }) + } + fn assign_language_to_buffer( &mut self, buffer: &ModelHandle, @@ -2089,9 +2120,10 @@ impl Project { move |params, mut cx| { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - this.on_lsp_diagnostics_published( + // TODO(isaac): remove block on + smol::block_on(this.on_lsp_diagnostics_published( server_id, params, &adapter, cx, - ); + )); }); } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index e16edf27d2..cce598c685 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -122,11 +122,46 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { .await; let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; - project.update(cx, |project, _| { - project.languages.add(Arc::new(rust_language)); + + // Open a buffer before languages have been added + let json_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/the-root/package.json", cx) + }) + .await + .unwrap(); + + // Assert that this buffer does not have a language + assert!(json_buffer.read_with(cx, |buffer, _| { buffer.language().is_none() })); + + // Now we add the languages to the project, and subscribe to the watcher + project.update(cx, |project, cx| { + // Get a handle to the channel and clear out default item + let mut recv = project.languages.subscribe(); + recv.blocking_recv(); + + // Add, then wait to be notified that JSON has been added project.languages.add(Arc::new(json_language)); + recv.blocking_recv(); + + // Add, then wait to be notified that Rust has been added + project.languages.add(Arc::new(rust_language)); + recv.blocking_recv(); + // Uncommenting this would cause the thread to block indefinitely: + // recv.blocking_recv(); + + // Force the assignment, we know the watcher has been notified + // but have no way to wait for the watcher to assign to the project + project.assign_language_to_buffer(&json_buffer, cx); }); + // Assert that the opened buffer does have a language, and that it is JSON + let name = json_buffer.read_with(cx, |buffer, _| buffer.language().map(|l| l.name())); + assert_eq!(name, Some("JSON".into())); + + // Close the JSON buffer we opened + cx.update(|_| drop(json_buffer)); + // Open a buffer without an associated language server. let toml_buffer = project .update(cx, |project, cx| { diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index fe74d7d9eb..664a3e2c0f 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -26,6 +26,7 @@ pub async fn new_json(executor: Arc) -> Result { include_bytes!("../../../../plugins/bin/json_language.wasm.pre"), ) .await?; + // smol::Timer::after(std::time::Duration::from_secs(5)).await; PluginLspAdapter::new(plugin, executor).await } From 6f99d59d38ea9a5f72292f371fb0080ec93bee7e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 8 Jul 2022 16:08:40 +0200 Subject: [PATCH 67/91] Require theme directly when creating language --- crates/language/src/language.rs | 19 ++++++++++++------- crates/project/src/project.rs | 10 ++++++++-- crates/zed/src/main.rs | 1 + plugins/json_language/src/lib.rs | 5 ----- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bab8d90f7c..c5d546298f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -14,7 +14,7 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt, }; -use gpui::{MutableAppContext, Task}; +use gpui::{AsyncAppContext, MutableAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; @@ -339,8 +339,9 @@ impl LanguageRegistry { Self::new(Task::ready(())) } - pub fn add(&self, language: Arc) { + pub fn add(&self, language: Arc, theme: &SyntaxTheme) { self.languages.write().push(language.clone()); + language.set_theme(theme); *self.subscription.write().0.borrow_mut() = (); } @@ -387,11 +388,14 @@ impl LanguageRegistry { .read() .iter() .find(|language| { - language - .config - .path_suffixes - .iter() - .any(|suffix| dbg!(path_suffixes.contains(&Some(dbg!(suffix.as_str()))))) + language.config.path_suffixes.iter().any(|suffix| { + if path_suffixes.contains(&Some(suffix.as_str())) { + dbg!(format!("found {}", suffix)); + true + } else { + false + } + }) }) .cloned() } @@ -713,6 +717,7 @@ impl Language { if let Some(highlights_query) = &grammar.highlights_query { *grammar.highlight_map.lock() = HighlightMap::new(highlights_query.capture_names(), theme); + dbg!("highlighting"); } } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 96e33de17e..511a5bbf6d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2028,7 +2028,7 @@ impl Project { ) -> Task<()> { let mut subscription = languages.subscribe(); cx.spawn_weak(|project, mut cx| async move { - while let Some(_) = subscription.next().await { + while let Some(()) = subscription.next().await { if let Some(project) = project.upgrade(&cx) { project.update(&mut cx, |project, cx| { let mut buffers_without_language = Vec::new(); @@ -2041,8 +2041,10 @@ impl Project { } for buffer in buffers_without_language { + dbg!("notified that new language was added"); project.assign_language_to_buffer(&buffer, cx); project.register_buffer_with_language_server(&buffer, cx); + dbg!(buffer.read(cx).language().map(|x| x.name())); } }); } @@ -2055,6 +2057,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option<()> { + dbg!("assigning language to buffer"); // If the buffer has a language, set it and start the language server if we haven't already. let full_path = buffer.read(cx).file()?.full_path(cx); let language = self.languages.select_language(&full_path)?; @@ -2078,6 +2081,7 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { + dbg!(format!("starting lsp for {:?}", language.name())); if !cx .global::() .enable_language_server(Some(&language.name())) @@ -2343,6 +2347,8 @@ impl Project { server_id }); + + dbg!("Done starting lsp"); } // Returns a list of all of the worktrees which no longer have a language server and the root path @@ -3273,8 +3279,8 @@ impl Project { position: T, cx: &mut ModelContext, ) -> Task>> { + // dbg!("getting highlights"); let position = position.to_point_utf16(buffer.read(cx)); - self.request_lsp(buffer.clone(), GetDocumentHighlights { position }, cx) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 4fceaed2d8..71fc253453 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -226,6 +226,7 @@ fn main() { let languages = languages.clone(); |cx| async move { init_languages.await; + dbg!("all languages initialized, starting setting highlighting"); cx.read(|cx| languages.set_theme(&cx.global::().theme.editor.syntax)); } }) diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 9809085547..fe6d268ac9 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -27,13 +27,8 @@ pub fn fetch_latest_server_version() -> Option { versions: Vec, } - // TODO: command returns error code let output = command("npm info vscode-json-languageserver --json").expect("could not run command"); - // if !output.is_ok() { - // return None; - // } - let output = String::from_utf8(output).unwrap(); let mut info: NpmInfo = serde_json::from_str(&output).ok()?; From 988f388165452f9647c0f5a82ffc119bc7aeaef5 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 8 Jul 2022 18:11:28 +0200 Subject: [PATCH 68/91] Added theme to language --- crates/editor/src/display_map.rs | 18 +++++++++--------- crates/language/src/language.rs | 26 ++++++++++++++++++-------- crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/rust.rs | 4 ++-- crates/zed/src/main.rs | 6 ++++-- test.rs | 1 + 6 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 test.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 917553c791..a305bf88f5 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -959,10 +959,10 @@ pub mod tests { }"# .unindent(); - let theme = SyntaxTheme::new(vec![ + let theme = Arc::new(SyntaxTheme::new(vec![ ("mod.body".to_string(), Color::red().into()), ("fn.name".to_string(), Color::blue().into()), - ]); + ])); let language = Arc::new( Language::new( LanguageConfig { @@ -980,7 +980,7 @@ pub mod tests { ) .unwrap(), ); - language.set_theme(&theme); + language.set_theme(theme.clone()); cx.update(|cx| { let mut settings = Settings::test(cx); settings.language_settings.tab_size = Some(2.try_into().unwrap()); @@ -1049,10 +1049,10 @@ pub mod tests { }"# .unindent(); - let theme = SyntaxTheme::new(vec![ + let theme = Arc::new(SyntaxTheme::new(vec![ ("mod.body".to_string(), Color::red().into()), ("fn.name".to_string(), Color::blue().into()), - ]); + ])); let language = Arc::new( Language::new( LanguageConfig { @@ -1070,7 +1070,7 @@ pub mod tests { ) .unwrap(), ); - language.set_theme(&theme); + language.set_theme(theme.clone()); cx.update(|cx| cx.set_global(Settings::test(cx))); @@ -1120,10 +1120,10 @@ pub mod tests { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| cx.set_global(Settings::test(cx))); - let theme = SyntaxTheme::new(vec![ + let theme = Arc::new(SyntaxTheme::new(vec![ ("operator".to_string(), Color::red().into()), ("string".to_string(), Color::green().into()), - ]); + ])); let language = Arc::new( Language::new( LanguageConfig { @@ -1141,7 +1141,7 @@ pub mod tests { ) .unwrap(), ); - language.set_theme(&theme); + language.set_theme(theme.clone()); let (text, highlighted_ranges) = marked_text_ranges(r#"const[] [a]: B = "c [d]""#); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c5d546298f..c6a850feb8 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -279,6 +279,7 @@ pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, pub(crate) adapter: Option>, + pub(crate) theme: RwLock>>, #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( @@ -339,9 +340,8 @@ impl LanguageRegistry { Self::new(Task::ready(())) } - pub fn add(&self, language: Arc, theme: &SyntaxTheme) { + pub fn add(&self, language: Arc) { self.languages.write().push(language.clone()); - language.set_theme(theme); *self.subscription.write().0.borrow_mut() = (); } @@ -349,9 +349,9 @@ impl LanguageRegistry { self.subscription.read().1.clone() } - pub fn set_theme(&self, theme: &SyntaxTheme) { + pub fn set_theme(&self, theme: Arc) { for language in self.languages.read().iter() { - language.set_theme(theme); + language.set_theme(theme.clone()); } } @@ -576,6 +576,7 @@ impl Language { }) }), adapter: None, + theme: Default::default(), #[cfg(any(test, feature = "test-support"))] fake_adapter: None, @@ -712,12 +713,21 @@ impl Language { c.is_whitespace() || self.config.autoclose_before.contains(c) } - pub fn set_theme(&self, theme: &SyntaxTheme) { + /// Sets the theme to the given theme, and then calls [`highlight`]. + pub fn set_theme(&self, theme: Arc) { + *self.theme.write() = Some(theme.clone()); + self.highlight() + } + + /// Highlights the grammar according to the current theme, + /// if one has been set using [`set_theme`]. + pub fn highlight(&self) { if let Some(grammar) = self.grammar.as_ref() { if let Some(highlights_query) = &grammar.highlights_query { - *grammar.highlight_map.lock() = - HighlightMap::new(highlights_query.capture_names(), theme); - dbg!("highlighting"); + if let Some(theme) = self.theme.read().as_ref() { + *grammar.highlight_map.lock() = + HighlightMap::new(highlights_query.capture_names(), &theme); + } } } } diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 8e8e4b300b..22857427b2 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -324,7 +324,7 @@ mod tests { ("number".into(), Color::yellow().into()), ("property".into(), Color::white().into()), ]); - language.set_theme(&theme); + language.set_theme(theme.into()); let grammar = language.grammar().unwrap(); let highlight_function = grammar.highlight_id_for_name("function").unwrap(); diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 5a38c8d7c5..796a28538f 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -315,7 +315,7 @@ mod tests { ("property".into(), Color::white().into()), ]); - language.set_theme(&theme); + language.set_theme(theme.into()); let highlight_function = grammar.highlight_id_for_name("function").unwrap(); let highlight_type = grammar.highlight_id_for_name("type").unwrap(); @@ -394,7 +394,7 @@ mod tests { ("property".into(), Color::white().into()), ]); - language.set_theme(&theme); + language.set_theme(theme.into()); let highlight_function = grammar.highlight_id_for_name("function").unwrap(); let highlight_type = grammar.highlight_id_for_name("type").unwrap(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 71fc253453..4961504448 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -217,7 +217,7 @@ fn main() { cx.observe_global::({ let languages = languages.clone(); move |cx| { - languages.set_theme(&cx.global::().theme.editor.syntax); + languages.set_theme(cx.global::().theme.editor.syntax.clone()); } }) .detach(); @@ -227,7 +227,9 @@ fn main() { |cx| async move { init_languages.await; dbg!("all languages initialized, starting setting highlighting"); - cx.read(|cx| languages.set_theme(&cx.global::().theme.editor.syntax)); + cx.read(|cx| { + languages.set_theme(cx.global::().theme.editor.syntax.clone()) + }); } }) .detach(); diff --git a/test.rs b/test.rs new file mode 100644 index 0000000000..0df70d1f2d --- /dev/null +++ b/test.rs @@ -0,0 +1 @@ +const A: usize = 32; From 3e8b2305672857f172248a689b271634e13612a2 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 8 Jul 2022 21:19:07 +0200 Subject: [PATCH 69/91] Highlight languages as languages load --- crates/language/src/language.rs | 6 ++++++ crates/zed/src/main.rs | 4 ++-- test.rs | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 test.rs diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c6a850feb8..a3aa65e53f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -319,6 +319,7 @@ pub struct LanguageRegistry { >, >, subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>, + theme: RwLock>>, } impl LanguageRegistry { @@ -332,6 +333,7 @@ impl LanguageRegistry { login_shell_env_loaded: login_shell_env_loaded.shared(), lsp_binary_paths: Default::default(), subscription: RwLock::new(watch::channel()), + theme: Default::default(), } } @@ -341,6 +343,9 @@ impl LanguageRegistry { } pub fn add(&self, language: Arc) { + if let Some(theme) = self.theme.read().clone() { + language.set_theme(theme); + } self.languages.write().push(language.clone()); *self.subscription.write().0.borrow_mut() = (); } @@ -350,6 +355,7 @@ impl LanguageRegistry { } pub fn set_theme(&self, theme: Arc) { + *self.theme.write() = Some(theme.clone()); for language in self.languages.read().iter() { language.set_theme(theme.clone()); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 4961504448..58d16e215b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -225,8 +225,8 @@ fn main() { cx.spawn({ let languages = languages.clone(); |cx| async move { - init_languages.await; - dbg!("all languages initialized, starting setting highlighting"); + // init_languages.await; + dbg!("not all languages initialized, but starting setting highlighting"); cx.read(|cx| { languages.set_theme(cx.global::().theme.editor.syntax.clone()) }); diff --git a/test.rs b/test.rs deleted file mode 100644 index 0df70d1f2d..0000000000 --- a/test.rs +++ /dev/null @@ -1 +0,0 @@ -const A: usize = 32; From 8931218dc6ef6d1476c4537f3ff6b17dc74f0370 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 8 Jul 2022 21:28:35 +0200 Subject: [PATCH 70/91] Remove debug statements --- crates/language/src/language.rs | 13 +++++-------- crates/plugin_runtime/src/lib.rs | 2 -- crates/plugin_runtime/src/plugin.rs | 2 -- crates/project/src/project.rs | 6 ------ crates/zed/src/main.rs | 1 - plugins/json_language/src/lib.rs | 1 - plugins/test_plugin/src/lib.rs | 3 +-- 7 files changed, 6 insertions(+), 22 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index a3aa65e53f..38f2306eeb 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -394,14 +394,11 @@ impl LanguageRegistry { .read() .iter() .find(|language| { - language.config.path_suffixes.iter().any(|suffix| { - if path_suffixes.contains(&Some(suffix.as_str())) { - dbg!(format!("found {}", suffix)); - true - } else { - false - } - }) + language + .config + .path_suffixes + .iter() + .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) }) .cloned() } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index c12b8e525a..cf460f73cb 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -92,8 +92,6 @@ mod tests { .unwrap(), "eko\n" ); - - // dbg!("{}", runtime.call(&plugin.and_back, 1).await.unwrap()); } .block_on() } diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index ac97569a50..d0fff54f87 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -232,7 +232,6 @@ impl PluginBuilder { /// Initializes a [`Plugin`] from a given compiled Wasm module. /// Both binary (`.wasm`) and text (`.wat`) module formats are supported. pub async fn init>(self, precompiled: bool, module: T) -> Result { - dbg!("initializing plugin"); Plugin::init(precompiled, module.as_ref().to_vec(), self).await } } @@ -302,7 +301,6 @@ impl Plugin { module: Vec, plugin: PluginBuilder, ) -> Result { - dbg!("Initializing new plugin"); // initialize the WebAssembly System Interface context let engine = plugin.engine; let mut linker = plugin.linker; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 511a5bbf6d..3defd81573 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2041,7 +2041,6 @@ impl Project { } for buffer in buffers_without_language { - dbg!("notified that new language was added"); project.assign_language_to_buffer(&buffer, cx); project.register_buffer_with_language_server(&buffer, cx); dbg!(buffer.read(cx).language().map(|x| x.name())); @@ -2057,7 +2056,6 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option<()> { - dbg!("assigning language to buffer"); // If the buffer has a language, set it and start the language server if we haven't already. let full_path = buffer.read(cx).file()?.full_path(cx); let language = self.languages.select_language(&full_path)?; @@ -2081,7 +2079,6 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - dbg!(format!("starting lsp for {:?}", language.name())); if !cx .global::() .enable_language_server(Some(&language.name())) @@ -2347,8 +2344,6 @@ impl Project { server_id }); - - dbg!("Done starting lsp"); } // Returns a list of all of the worktrees which no longer have a language server and the root path @@ -3279,7 +3274,6 @@ impl Project { position: T, cx: &mut ModelContext, ) -> Task>> { - // dbg!("getting highlights"); let position = position.to_point_utf16(buffer.read(cx)); self.request_lsp(buffer.clone(), GetDocumentHighlights { position }, cx) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 58d16e215b..be81944e08 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -226,7 +226,6 @@ fn main() { let languages = languages.clone(); |cx| async move { // init_languages.await; - dbg!("not all languages initialized, but starting setting highlighting"); cx.read(|cx| { languages.set_theme(cx.global::().theme.editor.syntax.clone()) }); diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index fe6d268ac9..6a9e128698 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -82,7 +82,6 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option { let last_version_dir = last_version_dir?; let bin_path = last_version_dir.join(BIN_PATH); if bin_path.exists() { - dbg!(&bin_path); Some(bin_path) } else { println!("no binary found"); diff --git a/plugins/test_plugin/src/lib.rs b/plugins/test_plugin/src/lib.rs index 5ea50602f3..769232a26a 100644 --- a/plugins/test_plugin/src/lib.rs +++ b/plugins/test_plugin/src/lib.rs @@ -75,9 +75,8 @@ fn command_async(command: String) -> Option>; #[export] pub fn echo_async(message: String) -> String { - let command = dbg!(format!("echo {}", message)); + let command = format!("echo {}", message); let result = command_async(command); - dbg!(&result); let result = result.expect("Could not run command"); String::from_utf8_lossy(&result).to_string() } From 8c91c5c575908a044360d2fefbeb38f473c093ec Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 10:37:51 +0200 Subject: [PATCH 71/91] Minor fixes found during review --- crates/plugin_macros/Cargo.toml | 1 - crates/project/src/project.rs | 20 ++++++++++++++------ crates/zed/src/main.rs | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/plugin_macros/Cargo.toml b/crates/plugin_macros/Cargo.toml index 1ba3c64831..1fa21a8fff 100644 --- a/crates/plugin_macros/Cargo.toml +++ b/crates/plugin_macros/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" proc-macro = true [dependencies] -# TODO: remove "extra-traits" syn = { version = "1.0", features = ["full", "extra-traits"] } quote = "1.0" proc-macro2 = "1.0" diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3defd81573..8095ceeab7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1722,7 +1722,7 @@ impl Project { .await?; this.update(&mut cx, |this, cx| { this.assign_language_to_buffer(&buffer, cx); - this.register_buffer_with_language_server(&buffer, cx) + this.register_buffer_with_language_server(&buffer, cx); }); Ok(()) }) @@ -2120,6 +2120,14 @@ impl Project { let adapter = adapter.clone(); move |params, mut cx| { if let Some(this) = this.upgrade(&cx) { + // cx.spawn(|cx| { + // this.update(&mut cx, |this, cx| { + // this.on_lsp_diagnostics_published( + // server_id, params, adapter, cx, + // ) + // }) + // }) + // .detach(); this.update(&mut cx, |this, cx| { // TODO(isaac): remove block on smol::block_on(this.on_lsp_diagnostics_published( @@ -3414,11 +3422,11 @@ impl Project { } new_symbols }); - for new_symbol in new_symbols { - if let Some(new_symbol) = new_symbol.await.ok() { - symbols.push(new_symbol); - } - } + symbols = futures::future::join_all(new_symbols.into_iter()) + .await + .into_iter() + .filter_map(|symbol| symbol.ok()) + .collect::>(); } Ok(symbols) }) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index be81944e08..36dac1aa40 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -225,10 +225,10 @@ fn main() { cx.spawn({ let languages = languages.clone(); |cx| async move { - // init_languages.await; cx.read(|cx| { languages.set_theme(cx.global::().theme.editor.syntax.clone()) }); + init_languages.await; } }) .detach(); From 5ec828a3e202a9dad292d2972f0ce615a1efc873 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 10:39:14 +0200 Subject: [PATCH 72/91] Remove unused struct fields --- crates/plugin_runtime/src/plugin.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index d0fff54f87..56fb738e0a 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -268,8 +268,6 @@ impl WasiCtxAlloc { /// Represents a WebAssembly plugin, with access to the WebAssembly System Inferface. /// Build a new plugin using [`PluginBuilder`]. pub struct Plugin { - engine: Engine, - module: Module, store: Store, instance: Instance, } @@ -293,9 +291,7 @@ impl Plugin { } println!(); } -} -impl Plugin { async fn init( precompiled: bool, module: Vec, @@ -335,12 +331,7 @@ impl Plugin { free_buffer, }); - Ok(Plugin { - engine, - module, - store, - instance, - }) + Ok(Plugin { store, instance }) } /// Attaches a file or directory the the given system path to the runtime. From 14bccb4a90f0855b1b52d0af74d403482319c848 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 10:56:21 +0200 Subject: [PATCH 73/91] More cleanup during review --- Cargo.lock | 1 - crates/plugin_macros/Cargo.toml | 2 +- crates/plugin_runtime/README.md | 13 ++- crates/plugin_runtime/build.rs | 3 +- crates/plugin_runtime/src/lib.rs | 7 +- crates/zed/Cargo.toml | 2 - crates/zed/src/languages.rs | 3 +- crates/zed/src/languages/language_plugin.rs | 19 ----- plugins/json_language/src/sketch.rs | 88 --------------------- 9 files changed, 16 insertions(+), 122 deletions(-) delete mode 100644 plugins/json_language/src/sketch.rs diff --git a/Cargo.lock b/Cargo.lock index f3df25ce58..f690e0d07a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7034,7 +7034,6 @@ dependencies = [ "num_cpus", "outline", "parking_lot 0.11.2", - "plugin", "plugin_runtime", "postage", "project", diff --git a/crates/plugin_macros/Cargo.toml b/crates/plugin_macros/Cargo.toml index 1fa21a8fff..b0a2c4e09b 100644 --- a/crates/plugin_macros/Cargo.toml +++ b/crates/plugin_macros/Cargo.toml @@ -11,4 +11,4 @@ syn = { version = "1.0", features = ["full", "extra-traits"] } quote = "1.0" proc-macro2 = "1.0" serde = "1.0" -bincode = "1.3" \ No newline at end of file +bincode = "1.3" diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index abd6b1e37c..1bebeccaf2 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -39,10 +39,19 @@ Here's an example Rust Wasm plugin that doubles the value of every float in a `V ```rust use plugin::prelude::*; -#[bind] +#[export] pub fn double(mut x: Vec) -> Vec { x.into_iter().map(|x| x * 2.0).collect() } ``` -All the serialization code is automatically generated by `#[bind]`. \ No newline at end of file +All the serialization code is automatically generated by `#[export]`. + +You can specify functions that must be defined host-side by using the `#[import]` attribute. This attribute must be attached to a function signature: + +```rust +#[import] +fn run(command: String) -> Vec; +``` + +The `#[import]` macro will generate a function body that performs the proper serialization/deserialization needed to call out to the host rust runtime. Note that the same ABI is used for both `#[import]` and `#[export]`. \ No newline at end of file diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 05bb65b2d8..e8120c9654 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -5,7 +5,7 @@ fn main() { let base = Path::new("../../plugins"); // println!("cargo:rerun-if-changed=../../plugins/*"); - println!("cargo:warning=Rebuilding plugins..."); + println!("cargo:warning=Precompiling plugins..."); let _ = std::fs::remove_dir_all(base.join("bin")); let _ = @@ -27,7 +27,6 @@ fn main() { let binaries = std::fs::read_dir(base.join("target/wasm32-wasi/release")) .expect("Could not find compiled plugins in target"); - println!("cargo:warning={:?}", binaries); let engine = create_engine(); diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index cf460f73cb..555d925874 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -36,8 +36,6 @@ mod tests { .host_function_async("import_half", |a: u32| async move { a / 2 }) .unwrap() .host_function_async("command_async", |command: String| async move { - // TODO: actual thing - dbg!(&command); let mut args = command.split(' '); let command = args.next().unwrap(); smol::process::Command::new(command) @@ -45,10 +43,7 @@ mod tests { .output() .await .ok() - .map(|output| { - dbg!("Did run command!"); - output.stdout - }) + .map(|output| output.stdout) }) .unwrap() .init( diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e85824b692..68fa67d3b3 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -39,7 +39,6 @@ journal = { path = "../journal" } language = { path = "../language" } lsp = { path = "../lsp" } outline = { path = "../outline" } -plugin = { path = "../plugin" } plugin_runtime = { path = "../plugin_runtime" } project = { path = "../project" } project_panel = { path = "../project_panel" } @@ -102,7 +101,6 @@ tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", re tree-sitter-typescript = "0.20.1" url = "2.2" - [dev-dependencies] text = { path = "../text", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index f5660667d5..a45c4122e8 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -7,6 +7,7 @@ use util::ResultExt; mod c; mod go; mod installation; +mod json; mod language_plugin; mod python; mod rust; @@ -37,7 +38,7 @@ pub async fn init(languages: Arc, executor: Arc) { ( "json", tree_sitter_json::language(), - // Some(LspAdapter::new(json::JsonLspAdapter)), + // Some(LspAdapter::new(json::JsonLspAdapter).await), match language_plugin::new_json(executor).await.log_err() { Some(lang) => Some(LspAdapter::new(lang).await), None => None, diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 664a3e2c0f..d205c262e2 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -11,7 +11,6 @@ use util::ResultExt; pub async fn new_json(executor: Arc) -> Result { let plugin = PluginBuilder::new_with_default_ctx()? .host_function_async("command", |command: String| async move { - dbg!(&command); let mut args = command.split(' '); let command = args.next().unwrap(); smol::process::Command::new(command) @@ -26,7 +25,6 @@ pub async fn new_json(executor: Arc) -> Result { include_bytes!("../../../../plugins/bin/json_language.wasm.pre"), ) .await?; - // smol::Timer::after(std::time::Duration::from_secs(5)).await; PluginLspAdapter::new(plugin, executor).await } @@ -58,22 +56,6 @@ impl PluginLspAdapter { } } -// struct Versions { -// language_version: String, -// server_version: String, -// } - -// TODO: is this the root cause? -// sketch: -// - smol command is run, hits await, switches -// - name is run, blocked on -// - smol command is still pending -// - name is stuck for some reason(?) -// - no progress made... -// - deadlock!!!? -// I wish there was high-level instrumentation for this... -// - it's totally a deadlock, the proof is in the pudding - #[async_trait] impl LspAdapterTrait for PluginLspAdapter { async fn name(&self) -> LanguageServerName { @@ -100,7 +82,6 @@ impl LspAdapterTrait for PluginLspAdapter { &self, _: Arc, ) -> Result> { - // let versions: Result> = call_block!(self, "fetch_latest_server_version", ()); let runtime = self.runtime.clone(); let function = self.fetch_latest_server_version; self.executor diff --git a/plugins/json_language/src/sketch.rs b/plugins/json_language/src/sketch.rs deleted file mode 100644 index def823fcd4..0000000000 --- a/plugins/json_language/src/sketch.rs +++ /dev/null @@ -1,88 +0,0 @@ - -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 From 3ad8d5363cc81ed39ec4e0458f2e460996887fc0 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 12:11:00 +0200 Subject: [PATCH 74/91] Remove the blocking call and inline on_lsp_diagnostics_published --- crates/language/src/language.rs | 2 +- crates/project/src/project.rs | 57 ++++++++++++--------------------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 38f2306eeb..230bb6c7d3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -14,7 +14,7 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt, }; -use gpui::{AsyncAppContext, MutableAppContext, Task}; +use gpui::{MutableAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8095ceeab7..371e2be9cf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,7 +12,7 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; -use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, SinkExt, StreamExt, TryFutureExt}; +use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, @@ -2118,23 +2118,24 @@ impl Project { .on_notification::({ let this = this.downgrade(); let adapter = adapter.clone(); - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - // cx.spawn(|cx| { - // this.update(&mut cx, |this, cx| { - // this.on_lsp_diagnostics_published( - // server_id, params, adapter, cx, - // ) - // }) - // }) - // .detach(); - this.update(&mut cx, |this, cx| { - // TODO(isaac): remove block on - smol::block_on(this.on_lsp_diagnostics_published( - server_id, params, &adapter, cx, - )); - }); - } + move |mut params, cx| { + let this = this.clone(); + let adapter = adapter.clone(); + cx.spawn(|mut cx| async move { + adapter.process_diagnostics(&mut params).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.update_diagnostics( + server_id, + params, + &adapter.disk_based_diagnostic_sources, + cx, + ) + .log_err(); + }); + } + }) + .detach(); } }) .detach(); @@ -2480,23 +2481,6 @@ impl Project { .detach(); } - async fn on_lsp_diagnostics_published( - &mut self, - server_id: usize, - mut params: lsp::PublishDiagnosticsParams, - adapter: &Arc, - cx: &mut ModelContext<'_, Self>, - ) { - adapter.process_diagnostics(&mut params).await; - self.update_diagnostics( - server_id, - params, - &adapter.disk_based_diagnostic_sources, - cx, - ) - .log_err(); - } - fn on_lsp_progress( &mut self, progress: lsp::ProgressParams, @@ -3580,7 +3564,7 @@ impl Project { continue; } - let (old_range, new_text) = match lsp_completion.text_edit.as_ref() { + let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then // check that the range is valid. Some(lsp::CompletionTextEdit::Edit(edit)) => { @@ -3630,6 +3614,7 @@ impl Project { } }; + LineEnding::normalize(&mut new_text); let partial_completion = PartialCompletion { old_range, new_text, From ec327a30c3d6f611f21492d1621125f1407d34c8 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 15:54:03 +0200 Subject: [PATCH 75/91] Fix minor issues pointed out in the review --- crates/collab/src/integration_tests.rs | 277 +++++++++--------- crates/editor/src/display_map.rs | 18 +- crates/editor/src/editor.rs | 46 +-- crates/editor/src/test.rs | 10 +- crates/language/src/language.rs | 35 +-- crates/language/src/proto.rs | 20 +- crates/plugin/Cargo.toml | 2 +- crates/plugin/src/lib.rs | 5 +- crates/plugin_macros/src/lib.rs | 1 - crates/plugin_runtime/README.md | 1 + crates/plugin_runtime/build.rs | 4 +- crates/plugin_runtime/src/plugin.rs | 4 +- crates/project/src/project_tests.rs | 124 ++++---- crates/project_symbols/src/project_symbols.rs | 4 +- crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/rust.rs | 4 +- crates/zed/src/main.rs | 6 +- 17 files changed, 293 insertions(+), 270 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 67caeb325b..62cfea9e35 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -1440,7 +1440,7 @@ async fn test_collaborating_with_diagnostics( }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); // Share a project as client A @@ -1675,16 +1675,18 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string()]), + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + ..Default::default() + }), ..Default::default() - }), + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; client_a.language_registry.add(Arc::new(language)); client_a @@ -1928,7 +1930,7 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); // Here we insert a fake tree with a directory that exists on disk. This is needed @@ -2014,7 +2016,7 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); client_a @@ -2123,7 +2125,7 @@ async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); client_a @@ -2303,7 +2305,7 @@ async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppC }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await; @@ -2400,7 +2402,7 @@ async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await; @@ -2488,7 +2490,7 @@ async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); client_a @@ -2591,7 +2593,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); client_a @@ -2662,7 +2664,7 @@ async fn test_collaborating_with_code_actions( }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; client_a.language_registry.add(Arc::new(language)); client_a @@ -2867,16 +2869,18 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { - prepare_provider: Some(true), - work_done_progress_options: Default::default(), - })), + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: Default::default(), + })), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; client_a.language_registry.add(Arc::new(language)); client_a @@ -3051,10 +3055,12 @@ async fn test_language_server_statuses( }, Some(tree_sitter_rust::language()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "the-language-server", - ..Default::default() - })); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-language-server", + ..Default::default() + })) + .await; client_a.language_registry.add(Arc::new(language)); client_a @@ -4577,119 +4583,124 @@ async fn test_random_collaboration( }, None, ); - let _fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "the-fake-language-server", - capabilities: lsp::LanguageServer::full_capabilities(), - initializer: Some(Box::new({ - let rng = rng.clone(); - let fs = fs.clone(); - let project = host_project.downgrade(); - move |fake_server: &mut FakeLanguageServer| { - fake_server.handle_request::(|_, _| async move { - Ok(Some(lsp::CompletionResponse::Array(vec![ - lsp::CompletionItem { - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(0, 0), - lsp::Position::new(0, 0), - ), - new_text: "the-new-text".to_string(), - })), - ..Default::default() + let _fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-fake-language-server", + capabilities: lsp::LanguageServer::full_capabilities(), + initializer: Some(Box::new({ + let rng = rng.clone(); + let fs = fs.clone(); + let project = host_project.downgrade(); + move |fake_server: &mut FakeLanguageServer| { + fake_server.handle_request::( + |_, _| async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 0), + ), + new_text: "the-new-text".to_string(), + })), + ..Default::default() + }, + ]))) }, - ]))) - }); + ); - fake_server.handle_request::( - |_, _| async move { - Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction( - lsp::CodeAction { - title: "the-code-action".to_string(), - ..Default::default() - }, - )])) - }, - ); + fake_server.handle_request::( + |_, _| async move { + Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction( + lsp::CodeAction { + title: "the-code-action".to_string(), + ..Default::default() + }, + )])) + }, + ); - fake_server.handle_request::( - |params, _| async move { - Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( - params.position, - params.position, - )))) - }, - ); + fake_server.handle_request::( + |params, _| async move { + Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( + params.position, + params.position, + )))) + }, + ); - fake_server.handle_request::({ - let fs = fs.clone(); - let rng = rng.clone(); - move |_, _| { + fake_server.handle_request::({ let fs = fs.clone(); let rng = rng.clone(); - async move { - let files = fs.files().await; - let mut rng = rng.lock(); - let count = rng.gen_range::(1..3); - let files = (0..count) - .map(|_| files.choose(&mut *rng).unwrap()) - .collect::>(); - log::info!("LSP: Returning definitions in files {:?}", &files); - Ok(Some(lsp::GotoDefinitionResponse::Array( - files - .into_iter() - .map(|file| lsp::Location { - uri: lsp::Url::from_file_path(file).unwrap(), - range: Default::default(), - }) - .collect(), - ))) + move |_, _| { + let fs = fs.clone(); + let rng = rng.clone(); + async move { + let files = fs.files().await; + let mut rng = rng.lock(); + let count = rng.gen_range::(1..3); + let files = (0..count) + .map(|_| files.choose(&mut *rng).unwrap()) + .collect::>(); + log::info!("LSP: Returning definitions in files {:?}", &files); + Ok(Some(lsp::GotoDefinitionResponse::Array( + files + .into_iter() + .map(|file| lsp::Location { + uri: lsp::Url::from_file_path(file).unwrap(), + range: Default::default(), + }) + .collect(), + ))) + } } - } - }); + }); - fake_server.handle_request::({ - let rng = rng.clone(); - let project = project.clone(); - move |params, mut cx| { - let highlights = if let Some(project) = project.upgrade(&cx) { - project.update(&mut cx, |project, cx| { - let path = params - .text_document_position_params - .text_document - .uri - .to_file_path() - .unwrap(); - let (worktree, relative_path) = - project.find_local_worktree(&path, cx)?; - let project_path = - ProjectPath::from((worktree.read(cx).id(), relative_path)); - let buffer = project.get_open_buffer(&project_path, cx)?.read(cx); + fake_server.handle_request::({ + let rng = rng.clone(); + let project = project.clone(); + move |params, mut cx| { + let highlights = if let Some(project) = project.upgrade(&cx) { + project.update(&mut cx, |project, cx| { + let path = params + .text_document_position_params + .text_document + .uri + .to_file_path() + .unwrap(); + let (worktree, relative_path) = + project.find_local_worktree(&path, cx)?; + let project_path = + ProjectPath::from((worktree.read(cx).id(), relative_path)); + let buffer = + project.get_open_buffer(&project_path, cx)?.read(cx); - let mut highlights = Vec::new(); - let highlight_count = rng.lock().gen_range(1..=5); - let mut prev_end = 0; - for _ in 0..highlight_count { - let range = - buffer.random_byte_range(prev_end, &mut *rng.lock()); + let mut highlights = Vec::new(); + let highlight_count = rng.lock().gen_range(1..=5); + let mut prev_end = 0; + for _ in 0..highlight_count { + let range = + buffer.random_byte_range(prev_end, &mut *rng.lock()); - highlights.push(lsp::DocumentHighlight { - range: range_to_lsp(range.to_point_utf16(buffer)), - kind: Some(lsp::DocumentHighlightKind::READ), - }); - prev_end = range.end; - } - Some(highlights) - }) - } else { - None - }; - async move { Ok(highlights) } - } - }); - } - })), - ..Default::default() - })); + highlights.push(lsp::DocumentHighlight { + range: range_to_lsp(range.to_point_utf16(buffer)), + kind: Some(lsp::DocumentHighlightKind::READ), + }); + prev_end = range.end; + } + Some(highlights) + }) + } else { + None + }; + async move { Ok(highlights) } + } + }); + } + })), + ..Default::default() + })) + .await; host_language_registry.add(Arc::new(language)); let op_start_signal = futures::channel::mpsc::unbounded(); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a305bf88f5..917553c791 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -959,10 +959,10 @@ pub mod tests { }"# .unindent(); - let theme = Arc::new(SyntaxTheme::new(vec![ + let theme = SyntaxTheme::new(vec![ ("mod.body".to_string(), Color::red().into()), ("fn.name".to_string(), Color::blue().into()), - ])); + ]); let language = Arc::new( Language::new( LanguageConfig { @@ -980,7 +980,7 @@ pub mod tests { ) .unwrap(), ); - language.set_theme(theme.clone()); + language.set_theme(&theme); cx.update(|cx| { let mut settings = Settings::test(cx); settings.language_settings.tab_size = Some(2.try_into().unwrap()); @@ -1049,10 +1049,10 @@ pub mod tests { }"# .unindent(); - let theme = Arc::new(SyntaxTheme::new(vec![ + let theme = SyntaxTheme::new(vec![ ("mod.body".to_string(), Color::red().into()), ("fn.name".to_string(), Color::blue().into()), - ])); + ]); let language = Arc::new( Language::new( LanguageConfig { @@ -1070,7 +1070,7 @@ pub mod tests { ) .unwrap(), ); - language.set_theme(theme.clone()); + language.set_theme(&theme); cx.update(|cx| cx.set_global(Settings::test(cx))); @@ -1120,10 +1120,10 @@ pub mod tests { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| cx.set_global(Settings::test(cx))); - let theme = Arc::new(SyntaxTheme::new(vec![ + let theme = SyntaxTheme::new(vec![ ("operator".to_string(), Color::red().into()), ("string".to_string(), Color::green().into()), - ])); + ]); let language = Arc::new( Language::new( LanguageConfig { @@ -1141,7 +1141,7 @@ pub mod tests { ) .unwrap(), ); - language.set_theme(theme.clone()); + language.set_theme(&theme); let (text, highlighted_ranges) = marked_text_ranges(r#"const[] [a]: B = "c [d]""#); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7129b41266..93e027a7ec 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9302,13 +9302,15 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - document_formatting_provider: Some(lsp::OneOf::Left(true)), + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + document_formatting_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; let fs = FakeFs::new(cx.background().clone()); fs.insert_file("/file.rs", Default::default()).await; @@ -9414,13 +9416,15 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - document_range_formatting_provider: Some(lsp::OneOf::Left(true)), + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + document_range_formatting_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; let fs = FakeFs::new(cx.background().clone()); fs.insert_file("/file.rs", Default::default()).await; @@ -9526,16 +9530,18 @@ mod tests { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + ..Default::default() + }), ..Default::default() - }), + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; let text = " one diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 0e5cd0f68d..768459f837 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -457,10 +457,12 @@ impl<'a> EditorLspTestContext<'a> { .unwrap_or(&"txt".to_string()) ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities, - ..Default::default() - })); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities, + ..Default::default() + })) + .await; let project = Project::test(params.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 230bb6c7d3..76d6b2e6af 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -31,7 +31,7 @@ use std::{ str, sync::Arc, }; -use theme::SyntaxTheme; +use theme::{SyntaxTheme, Theme}; use tree_sitter::{self, Query}; use util::ResultExt; @@ -255,9 +255,6 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D } } -// #[cfg(any(test, feature = "test-support"))] -// pub type FakeLspAdapter = Arc; - #[cfg(any(test, feature = "test-support"))] pub struct FakeLspAdapter { pub name: &'static str, @@ -279,7 +276,6 @@ pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, pub(crate) adapter: Option>, - pub(crate) theme: RwLock>>, #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( @@ -319,7 +315,7 @@ pub struct LanguageRegistry { >, >, subscription: RwLock<(watch::Sender<()>, watch::Receiver<()>)>, - theme: RwLock>>, + theme: RwLock>>, } impl LanguageRegistry { @@ -344,7 +340,7 @@ impl LanguageRegistry { pub fn add(&self, language: Arc) { if let Some(theme) = self.theme.read().clone() { - language.set_theme(theme); + language.set_theme(&theme.editor.syntax); } self.languages.write().push(language.clone()); *self.subscription.write().0.borrow_mut() = (); @@ -354,10 +350,10 @@ impl LanguageRegistry { self.subscription.read().1.clone() } - pub fn set_theme(&self, theme: Arc) { + pub fn set_theme(&self, theme: Arc) { *self.theme.write() = Some(theme.clone()); for language in self.languages.read().iter() { - language.set_theme(theme.clone()); + language.set_theme(&theme.editor.syntax); } } @@ -579,7 +575,6 @@ impl Language { }) }), adapter: None, - theme: Default::default(), #[cfg(any(test, feature = "test-support"))] fake_adapter: None, @@ -624,13 +619,13 @@ impl Language { } #[cfg(any(test, feature = "test-support"))] - pub fn set_fake_lsp_adapter( + pub async fn set_fake_lsp_adapter( &mut self, fake_lsp_adapter: Arc, ) -> mpsc::UnboundedReceiver { let (servers_tx, servers_rx) = mpsc::unbounded(); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); - let adapter = smol::block_on(LspAdapter::new(fake_lsp_adapter)); + let adapter = LspAdapter::new(fake_lsp_adapter).await; self.adapter = Some(adapter); servers_rx } @@ -716,21 +711,11 @@ impl Language { c.is_whitespace() || self.config.autoclose_before.contains(c) } - /// Sets the theme to the given theme, and then calls [`highlight`]. - pub fn set_theme(&self, theme: Arc) { - *self.theme.write() = Some(theme.clone()); - self.highlight() - } - - /// Highlights the grammar according to the current theme, - /// if one has been set using [`set_theme`]. - pub fn highlight(&self) { + pub fn set_theme(&self, theme: &SyntaxTheme) { if let Some(grammar) = self.grammar.as_ref() { if let Some(highlights_query) = &grammar.highlights_query { - if let Some(theme) = self.theme.read().as_ref() { - *grammar.highlight_map.lock() = - HighlightMap::new(highlights_query.capture_names(), &theme); - } + *grammar.highlight_map.lock() = + HighlightMap::new(highlights_query.capture_names(), theme); } } } diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index e68639b260..5cb3feb214 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -410,20 +410,18 @@ pub async fn deserialize_completion( .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid old end"))?; let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?; + let label = match language { + Some(l) => l.label_for_completion(&lsp_completion).await, + None => None, + }; + Ok(Completion { old_range: old_start..old_end, new_text: completion.new_text, - label: { - let label = match language { - Some(l) => l.label_for_completion(&lsp_completion).await, - None => None, - }; - - label.unwrap_or(CodeLabel::plain( - lsp_completion.label.clone(), - lsp_completion.filter_text.as_deref(), - )) - }, + label: label.unwrap_or(CodeLabel::plain( + lsp_completion.label.clone(), + lsp_completion.filter_text.as_deref(), + )), lsp_completion, }) } diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml index c4f22c94d3..6f37c458d9 100644 --- a/crates/plugin/Cargo.toml +++ b/crates/plugin/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" [dependencies] serde = "1.0" bincode = "1.3" -plugin_macros = { path = "../plugin_macros" } \ No newline at end of file +plugin_macros = { path = "../plugin_macros" } diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs index 06ece26cdb..bcb5b770dc 100644 --- a/crates/plugin/src/lib.rs +++ b/crates/plugin/src/lib.rs @@ -1,7 +1,10 @@ pub use bincode; pub use serde; -// TODO: move the implementation to one place? +/// This is the buffer that is used Wasm side. +/// Note that it mirrors the functionality of +/// the `WasiBuffer` found in `plugin_runtime/src/plugin.rs`, +/// But has a few different methods. pub struct __Buffer { pub ptr: u32, // *const u8, pub len: u32, // usize, diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 7993b3df62..3f708658fd 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -67,7 +67,6 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream { #inner_fn #[no_mangle] - // TODO: switch len from usize to u32? pub extern "C" fn #outer_fn_name(packed_buffer: u64) -> u64 { // setup let data = unsafe { ::plugin::__Buffer::from_u64(packed_buffer).to_vec() }; diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 1bebeccaf2..91ada4b13c 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -31,6 +31,7 @@ All functions that Plugin exports must have the following properties: Additionally, Plugin must export an: - `__alloc_buffer` function that, given a `u32` length, returns a `u32` pointer to a buffer of that length. +- `__free_buffer` function that, given a buffer encoded as a `u64`, frees the buffer at the given location, and does not return anything. Note that all of these requirements are automatically fullfilled for any Rust Wasm plugin that uses the `plugin` crate, and imports the `prelude`. diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index e8120c9654..5bdc842ea1 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -4,8 +4,8 @@ use wasmtime::{Config, Engine}; fn main() { let base = Path::new("../../plugins"); - // println!("cargo:rerun-if-changed=../../plugins/*"); - println!("cargo:warning=Precompiling plugins..."); + println!("cargo:rerun-if-changed={}", base.display()); + println!("cargo:warning=Rebuilding precompiled plugins..."); let _ = std::fs::remove_dir_all(base.join("bin")); let _ = diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 56fb738e0a..4c8773cf9c 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -16,7 +16,9 @@ use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder}; /// Represents a resource currently managed by the plugin, like a file descriptor. pub struct PluginResource(u32); -#[repr(C)] +/// This is the buffer that is used Host side. +/// Note that it mirrors the functionality of +/// the `__Buffer` found in the `plugin/src/lib.rs` prelude. struct WasiBuffer { ptr: u32, len: u32, diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index cce598c685..04c6893d09 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -86,28 +86,32 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { }, None, ); - let mut fake_rust_servers = rust_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "the-rust-language-server", - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string(), "::".to_string()]), + let mut fake_rust_servers = rust_language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-rust-language-server", + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), "::".to_string()]), + ..Default::default() + }), ..Default::default() - }), + }, ..Default::default() - }, - ..Default::default() - })); - let mut fake_json_servers = json_language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "the-json-language-server", - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![":".to_string()]), + })) + .await; + let mut fake_json_servers = json_language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-json-language-server", + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![":".to_string()]), + ..Default::default() + }), ..Default::default() - }), + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -646,11 +650,13 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - disk_based_diagnostics_progress_token: Some(progress_token.into()), - disk_based_diagnostics_sources: vec!["disk".into()], - ..Default::default() - })); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_progress_token: Some(progress_token.into()), + disk_based_diagnostics_sources: vec!["disk".into()], + ..Default::default() + })) + .await; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -769,11 +775,13 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC }, None, ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - disk_based_diagnostics_sources: vec!["disk".into()], - disk_based_diagnostics_progress_token: Some(progress_token.into()), - ..Default::default() - })); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + disk_based_diagnostics_progress_token: Some(progress_token.into()), + ..Default::default() + })) + .await; let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "a.rs": "" })).await; @@ -848,10 +856,12 @@ async fn test_toggling_enable_language_server( }, None, ); - let mut fake_rust_servers = rust.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "rust-lsp", - ..Default::default() - })); + let mut fake_rust_servers = rust + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "rust-lsp", + ..Default::default() + })) + .await; let mut js = Language::new( LanguageConfig { name: Arc::from("JavaScript"), @@ -860,10 +870,12 @@ async fn test_toggling_enable_language_server( }, None, ); - let mut fake_js_servers = js.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - name: "js-lsp", - ..Default::default() - })); + let mut fake_js_servers = js + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "js-lsp", + ..Default::default() + })) + .await; let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) @@ -969,10 +981,12 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - disk_based_diagnostics_sources: vec!["disk".into()], - ..Default::default() - })); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_sources: vec!["disk".into()], + ..Default::default() + })) + .await; let text = " fn a() { A } @@ -1311,7 +1325,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await; let text = " fn a() { @@ -1712,7 +1726,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1811,7 +1825,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_typescript::language_typescript()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1895,7 +1909,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_typescript::language_typescript()), ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1948,7 +1962,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { }, None, ); - let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -2876,16 +2890,18 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { }, Some(tree_sitter_rust::language()), ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { - prepare_provider: Some(true), - work_done_progress_options: Default::default(), - })), + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: Default::default(), + })), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - })); + })) + .await; let fs = FakeFs::new(cx.background()); fs.insert_tree( diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 5e7ce6da0a..10425f63a8 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -290,7 +290,9 @@ mod tests { }, None, ); - let mut fake_servers = language.set_fake_lsp_adapter(Arc::::default()); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::::default()) + .await; let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "test.rs": "" })).await; diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 22857427b2..8e8e4b300b 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -324,7 +324,7 @@ mod tests { ("number".into(), Color::yellow().into()), ("property".into(), Color::white().into()), ]); - language.set_theme(theme.into()); + language.set_theme(&theme); let grammar = language.grammar().unwrap(); let highlight_function = grammar.highlight_id_for_name("function").unwrap(); diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 796a28538f..5a38c8d7c5 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -315,7 +315,7 @@ mod tests { ("property".into(), Color::white().into()), ]); - language.set_theme(theme.into()); + language.set_theme(&theme); let highlight_function = grammar.highlight_id_for_name("function").unwrap(); let highlight_type = grammar.highlight_id_for_name("type").unwrap(); @@ -394,7 +394,7 @@ mod tests { ("property".into(), Color::white().into()), ]); - language.set_theme(theme.into()); + language.set_theme(&theme); let highlight_function = grammar.highlight_id_for_name("function").unwrap(); let highlight_type = grammar.highlight_id_for_name("type").unwrap(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 36dac1aa40..723738cf28 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -217,7 +217,7 @@ fn main() { cx.observe_global::({ let languages = languages.clone(); move |cx| { - languages.set_theme(cx.global::().theme.editor.syntax.clone()); + languages.set_theme(cx.global::().theme.clone()); } }) .detach(); @@ -225,9 +225,7 @@ fn main() { cx.spawn({ let languages = languages.clone(); |cx| async move { - cx.read(|cx| { - languages.set_theme(cx.global::().theme.editor.syntax.clone()) - }); + cx.read(|cx| languages.set_theme(cx.global::().theme.clone())); init_languages.await; } }) From 0600157c38b6f9bab2ba81d409de2cffce07d47a Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 15:55:07 +0200 Subject: [PATCH 76/91] Restore main version of Project::completions and convert to async --- crates/project/src/project.rs | 92 ++++++++++++----------------------- 1 file changed, 31 insertions(+), 61 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 371e2be9cf..6ec816a290 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2043,7 +2043,6 @@ impl Project { for buffer in buffers_without_language { project.assign_language_to_buffer(&buffer, cx); project.register_buffer_with_language_server(&buffer, cx); - dbg!(buffer.read(cx).language().map(|x| x.name())); } }); } @@ -3540,20 +3539,11 @@ impl Project { Default::default() }; - struct PartialCompletion { - pub old_range: Range, - pub new_text: String, - pub language: Option>, - pub lsp_completion: lsp::CompletionItem, - } - - let partial_completions = source_buffer_handle.read_with(&cx, |this, _| { + let completions = source_buffer_handle.read_with(&cx, |this, _| { let snapshot = this.snapshot(); let clipped_position = this.clip_point_utf16(position, Bias::Left); let mut range_for_token = None; - let mut partial_completions = Vec::new(); - - for lsp_completion in completions.into_iter() { + completions.into_iter().filter_map(move |lsp_completion| { // For now, we can only handle additional edits if they are returned // when resolving the completion, not if they are present initially. if lsp_completion @@ -3561,7 +3551,7 @@ impl Project { .as_ref() .map_or(false, |edits| !edits.is_empty()) { - continue; + return None; } let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { @@ -3573,7 +3563,7 @@ impl Project { let end = snapshot.clip_point_utf16(range.end, Bias::Left); if start != range.start || end != range.end { log::info!("completion out of expected range"); - continue; + return None; } ( snapshot.anchor_before(start)..snapshot.anchor_after(end), @@ -3585,7 +3575,7 @@ impl Project { None => { if position != clipped_position { log::info!("completion out of expected range"); - continue; + return None; } let Range { start, end } = range_for_token .get_or_insert_with(|| { @@ -3610,50 +3600,34 @@ impl Project { } Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { log::info!("unsupported insert/replace completion"); - continue; + return None; } }; LineEnding::normalize(&mut new_text); - let partial_completion = PartialCompletion { - old_range, - new_text, - language: language.clone(), - lsp_completion, - }; - - partial_completions.push(partial_completion); - } - partial_completions + let language = language.clone(); + Some(async move { + let label = if let Some(language) = language { + language.label_for_completion(&lsp_completion).await + } else { + None + }; + Completion { + old_range, + new_text, + label: label.unwrap_or_else(|| { + CodeLabel::plain( + lsp_completion.label.clone(), + lsp_completion.filter_text.as_deref(), + ) + }), + lsp_completion, + } + }) + }) }); - let mut result = Vec::new(); - - for pc in partial_completions.into_iter() { - result.push(async move { - let label = match pc.language.as_ref() { - Some(l) => l.label_for_completion(&pc.lsp_completion).await, - None => None, - } - .unwrap_or_else(|| { - CodeLabel::plain( - pc.lsp_completion.label.clone(), - pc.lsp_completion.filter_text.as_deref(), - ) - }); - - let completion = Completion { - old_range: pc.old_range, - new_text: pc.new_text, - label, - lsp_completion: pc.lsp_completion, - }; - - completion - }); - } - - Ok(futures::future::join_all(result).await) + Ok(futures::future::join_all(completions).await) }) } else if let Some(project_id) = self.remote_id() { let rpc = self.client.clone(); @@ -3672,14 +3646,10 @@ impl Project { }) .await; - let mut result = Vec::new(); - for completion in response.completions.into_iter() { - let completion = - language::proto::deserialize_completion(completion, language.clone()) - .await?; - result.push(completion); - } - Ok(result) + let completions = response.completions.into_iter().map(|completion| { + language::proto::deserialize_completion(completion, language.clone()) + }); + futures::future::try_join_all(completions).await }) } else { Task::ready(Ok(Default::default())) From bc94d0d1a976bfc117c9d936d671f0fdfe0d638e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:22:58 +0200 Subject: [PATCH 77/91] Restore main version of Project::symbols and convert to async --- crates/project/src/project.rs | 104 +++++++++++++--------------------- 1 file changed, 38 insertions(+), 66 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 6ec816a290..a479d26076 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3312,27 +3312,11 @@ impl Project { } else { return Ok(Default::default()); }; - - struct PartialSymbol { - source_worktree_id: WorktreeId, - worktree_id: WorktreeId, - language_server_name: LanguageServerName, - path: PathBuf, - language: Option>, - name: String, - kind: lsp::SymbolKind, - range: Range, - signature: [u8; 32], - } - - let partial_symbols = this.read_with(&cx, |this, cx| { - let mut partial_symbols = Vec::new(); + let symbols = this.read_with(&cx, |this, cx| { + let mut symbols = Vec::new(); for (adapter, source_worktree_id, worktree_abs_path, response) in responses { - for lsp_symbol in response.into_iter().flatten() { - let abs_path = match lsp_symbol.location.uri.to_file_path().ok() { - Some(abs_path) => abs_path, - None => continue, - }; + symbols.extend(response.into_iter().flatten().filter_map(|lsp_symbol| { + let abs_path = lsp_symbol.location.uri.to_file_path().ok()?; let mut worktree_id = source_worktree_id; let path; if let Some((worktree, rel_path)) = @@ -3344,49 +3328,37 @@ impl Project { path = relativize_path(&worktree_abs_path, &abs_path); } - let language = this.languages.select_language(&path).clone(); let signature = this.symbol_signature(worktree_id, &path); + let language = this.languages.select_language(&path); + let language_server_name = adapter.name.clone(); - partial_symbols.push(PartialSymbol { - source_worktree_id, - worktree_id, - language_server_name: adapter.name.clone(), - name: lsp_symbol.name, - kind: lsp_symbol.kind, - language, - path, - range: range_from_lsp(lsp_symbol.location.range), - signature, - }); - } + Some(async move { + let label = if let Some(language) = language { + language + .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) + .await + } else { + None + }; + + Symbol { + source_worktree_id, + worktree_id, + language_server_name, + label: label.unwrap_or_else(|| { + CodeLabel::plain(lsp_symbol.name.clone(), None) + }), + kind: lsp_symbol.kind, + name: lsp_symbol.name, + path, + range: range_from_lsp(lsp_symbol.location.range), + signature, + } + }) + })); } - - partial_symbols + symbols }); - - let mut symbols = Vec::new(); - for ps in partial_symbols.into_iter() { - symbols.push(async move { - let label = match ps.language { - Some(language) => language.label_for_symbol(&ps.name, ps.kind).await, - None => None, - } - .unwrap_or_else(|| CodeLabel::plain(ps.name.clone(), None)); - - Symbol { - source_worktree_id: ps.source_worktree_id, - worktree_id: ps.worktree_id, - language_server_name: ps.language_server_name, - name: ps.name, - kind: ps.kind, - label, - path: ps.path, - range: ps.range, - signature: ps.signature, - } - }); - } - Ok(futures::future::join_all(symbols).await) }) } else if let Some(project_id) = self.remote_id() { @@ -3399,16 +3371,16 @@ impl Project { let mut symbols = Vec::new(); if let Some(this) = this.upgrade(&cx) { let new_symbols = this.read_with(&cx, |this, _| { - let mut new_symbols = Vec::new(); - for symbol in response.symbols.into_iter() { - new_symbols.push(this.deserialize_symbol(symbol)); - } - new_symbols + response + .symbols + .into_iter() + .map(|symbol| this.deserialize_symbol(symbol)) + .collect::>() }); - symbols = futures::future::join_all(new_symbols.into_iter()) + symbols = futures::future::join_all(new_symbols) .await .into_iter() - .filter_map(|symbol| symbol.ok()) + .filter_map(|symbol| symbol.log_err()) .collect::>(); } Ok(symbols) From be41ad44a7263c9ed28f970f502cbcc175135df9 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:40:12 +0200 Subject: [PATCH 78/91] Fix minor issues in plugin and project raised during review --- crates/plugin_runtime/src/plugin.rs | 13 ++++++------- crates/project/src/project.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 4c8773cf9c..7473577d84 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -119,7 +119,8 @@ impl PluginBuilder { let buffer = WasiBuffer::from_u64(packed_buffer); // get the args passed from Guest - let args = Plugin::buffer_to_bytes(&mut plugin_memory, &mut caller, &buffer)?; + let args = + Plugin::buffer_to_bytes(&mut plugin_memory, caller.as_context(), &buffer)?; let args: A = Plugin::deserialize_to_type(&args)?; @@ -477,19 +478,18 @@ impl Plugin { Ok(result) } - // TODO: don't allocate a new `Vec`! /// Takes a `(ptr, len)` pair and returns the corresponding deserialized buffer. fn buffer_to_bytes<'a>( plugin_memory: &'a Memory, - store: impl AsContext + 'a, - buffer: &WasiBuffer, - ) -> Result, Error> { + store: wasmtime::StoreContext<'a, WasiCtxAlloc>, + buffer: &'a WasiBuffer, + ) -> Result<&'a [u8], Error> { let buffer_start = buffer.ptr as usize; let buffer_end = buffer_start + buffer.len as usize; // read the buffer at this point into a byte array // deserialize the byte array into the provided serde type - let result = plugin_memory.data(store.as_context())[buffer_start..buffer_end].to_vec(); + let result = &plugin_memory.data(store)[buffer_start..buffer_end]; Ok(result) } @@ -519,7 +519,6 @@ impl Plugin { }) } - // TODO: dont' use as for conversions /// Asynchronously calls a function defined Guest-side. pub async fn call( &mut self, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a479d26076..e09ded315f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1806,7 +1806,7 @@ impl Project { fn register_buffer_with_language_server( &mut self, buffer_handle: &ModelHandle, - cx: &mut ModelContext<'_, Self>, + cx: &mut ModelContext, ) { let buffer = buffer_handle.read(cx); let buffer_id = buffer.remote_id(); @@ -1901,7 +1901,7 @@ impl Project { &mut self, buffer: ModelHandle, event: &BufferEvent, - cx: &mut ModelContext<'_, Self>, + cx: &mut ModelContext, ) -> Option<()> { match event { BufferEvent::Operation(operation) => { @@ -2508,12 +2508,12 @@ impl Project { return; } - let same_token = + let is_disk_based_diagnostics_progress = Some(token.as_ref()) == disk_based_diagnostics_progress_token.as_ref().map(|x| &**x); match progress { lsp::WorkDoneProgress::Begin(report) => { - if same_token { + if is_disk_based_diagnostics_progress { language_server_status.has_pending_diagnostic_updates = true; self.disk_based_diagnostics_started(server_id, cx); self.broadcast_language_server_update( @@ -2544,7 +2544,7 @@ impl Project { } } lsp::WorkDoneProgress::Report(report) => { - if !same_token { + if !is_disk_based_diagnostics_progress { self.on_lsp_work_progress( server_id, token.clone(), @@ -2570,7 +2570,7 @@ impl Project { lsp::WorkDoneProgress::End(_) => { language_server_status.progress_tokens.remove(&token); - if same_token { + if is_disk_based_diagnostics_progress { language_server_status.has_pending_diagnostic_updates = false; self.disk_based_diagnostics_finished(server_id, cx); self.broadcast_language_server_update( From 638f881fe461d219fa24b32fe971f25d88e16745 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:41:30 +0200 Subject: [PATCH 79/91] Remove json host-side implementation, rely on plugin implementation --- crates/zed/src/languages.rs | 2 - crates/zed/src/languages/json.rs | 109 ------------------------------- 2 files changed, 111 deletions(-) delete mode 100644 crates/zed/src/languages/json.rs diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index a45c4122e8..772664d0ac 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -7,7 +7,6 @@ use util::ResultExt; mod c; mod go; mod installation; -mod json; mod language_plugin; mod python; mod rust; @@ -38,7 +37,6 @@ pub async fn init(languages: Arc, executor: Arc) { ( "json", tree_sitter_json::language(), - // Some(LspAdapter::new(json::JsonLspAdapter).await), match language_plugin::new_json(executor).await.log_err() { Some(lang) => Some(LspAdapter::new(lang).await), None => None, diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs deleted file mode 100644 index ab6f03a06f..0000000000 --- a/crates/zed/src/languages/json.rs +++ /dev/null @@ -1,109 +0,0 @@ -use super::installation::{npm_install_packages, npm_package_latest_version}; -use anyhow::{anyhow, Context, Result}; -use async_trait::async_trait; -use client::http::HttpClient; -use futures::StreamExt; -use language::{LanguageServerName, LspAdapterTrait}; -use serde_json::json; -use smol::fs; -use std::{any::Any, path::PathBuf, sync::Arc}; -use util::ResultExt; - -pub struct JsonLspAdapter; - -impl JsonLspAdapter { - const BIN_PATH: &'static str = - "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; -} - -#[async_trait] -impl LspAdapterTrait for JsonLspAdapter { - async fn name(&self) -> LanguageServerName { - LanguageServerName("vscode-json-languageserver".into()) - } - - async fn server_args(&self) -> Vec { - vec!["--stdio".into()] - } - - async fn fetch_latest_server_version( - &self, - _: Arc, - ) -> Result> { - Ok(Box::new(npm_package_latest_version("vscode-json-languageserver").await?) as Box<_>) - } - - async fn fetch_server_binary( - &self, - version: Box, - _: Arc, - container_dir: PathBuf, - ) -> Result { - let version = version.downcast::().unwrap(); - 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() { - npm_install_packages( - [("vscode-json-languageserver", version.as_str())], - &version_dir, - ) - .await?; - - 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) - } - - async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { - (|| async move { - 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 bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { - Ok(bin_path) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() - } - - async fn initialization_options(&self) -> Option { - Some(json!({ - "provideFormatter": true - })) - } - - async fn id_for_language(&self, name: &str) -> Option { - if name == "JSON" { - Some("jsonc".into()) - } else { - None - } - } -} From c4bf71d22275b4614521f40577686bf4e05675fe Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:47:21 +0200 Subject: [PATCH 80/91] Convert go lsp tests from sync #[test] to async #[gpui::test] --- crates/zed/src/languages/go.rs | 48 +++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 8e8e4b300b..47d6e4447f 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -309,12 +309,12 @@ mod tests { use gpui::color::Color; use theme::SyntaxTheme; - #[test] - fn test_go_label_for_completion() { + #[gpui::test] + async fn test_go_label_for_completion() { let language = language( "go", tree_sitter_go::language(), - Some(smol::block_on(LspAdapter::new(GoLspAdapter))), + Some(LspAdapter::new(GoLspAdapter).await), ); let theme = SyntaxTheme::new(vec![ @@ -334,12 +334,14 @@ mod tests { let highlight_field = grammar.highlight_id_for_name("property").unwrap(); assert_eq!( - smol::block_on(language.label_for_completion(&lsp::CompletionItem { - kind: Some(lsp::CompletionItemKind::FUNCTION), - label: "Hello".to_string(), - detail: Some("func(a B) c.D".to_string()), - ..Default::default() - })), + language + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), + label: "Hello".to_string(), + detail: Some("func(a B) c.D".to_string()), + ..Default::default() + }) + .await, Some(CodeLabel { text: "Hello(a B) c.D".to_string(), filter_range: 0..5, @@ -353,12 +355,14 @@ mod tests { // Nested methods assert_eq!( - smol::block_on(language.label_for_completion(&lsp::CompletionItem { - kind: Some(lsp::CompletionItemKind::METHOD), - label: "one.two.Three".to_string(), - detail: Some("func() [3]interface{}".to_string()), - ..Default::default() - })), + language + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::METHOD), + label: "one.two.Three".to_string(), + detail: Some("func() [3]interface{}".to_string()), + ..Default::default() + }) + .await, Some(CodeLabel { text: "one.two.Three() [3]interface{}".to_string(), filter_range: 0..13, @@ -372,12 +376,14 @@ mod tests { // Nested fields assert_eq!( - smol::block_on(language.label_for_completion(&lsp::CompletionItem { - kind: Some(lsp::CompletionItemKind::FIELD), - label: "two.Three".to_string(), - detail: Some("a.Bcd".to_string()), - ..Default::default() - })), + language + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FIELD), + label: "two.Three".to_string(), + detail: Some("a.Bcd".to_string()), + ..Default::default() + }) + .await, Some(CodeLabel { text: "two.Three a.Bcd".to_string(), filter_range: 0..9, From 836719526c14d2e0b6f0c76e48dff837c528af86 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:50:49 +0200 Subject: [PATCH 81/91] Remove stale commented code --- crates/zed/src/languages/language_plugin.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index d205c262e2..10ef71eb28 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -133,8 +133,6 @@ impl LspAdapterTrait for PluginLspAdapter { .await } - // async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - // fn label_for_completion( // &self, // item: &lsp::CompletionItem, From 0bdbbdd9b6e2ab61edf8b52398286b299fcf8238 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:55:08 +0200 Subject: [PATCH 82/91] Convert rust lsp tests from sync #[test] to async #[gpui::test] --- crates/zed/src/languages/rust.rs | 68 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 5a38c8d7c5..71f633bca4 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -259,8 +259,8 @@ mod tests { use gpui::{color::Color, MutableAppContext}; use theme::SyntaxTheme; - #[test] - fn test_process_rust_diagnostics() { + #[gpui::test] + async fn test_process_rust_diagnostics() { let mut params = lsp::PublishDiagnosticsParams { uri: lsp::Url::from_file_path("/a").unwrap(), version: None, @@ -283,7 +283,7 @@ mod tests { }, ], }; - smol::block_on(RustLspAdapter.process_diagnostics(&mut params)); + RustLspAdapter.process_diagnostics(&mut params).await; assert_eq!(params.diagnostics[0].message, "use of moved value `a`"); @@ -300,12 +300,12 @@ mod tests { ); } - #[test] - fn test_rust_label_for_completion() { + #[gpui::test] + async fn test_rust_label_for_completion() { let language = language( "rust", tree_sitter_rust::language(), - Some(smol::block_on(LspAdapter::new(RustLspAdapter))), + Some(LspAdapter::new(RustLspAdapter).await), ); let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ @@ -323,12 +323,14 @@ mod tests { let highlight_field = grammar.highlight_id_for_name("property").unwrap(); assert_eq!( - smol::block_on(language.label_for_completion(&lsp::CompletionItem { - kind: Some(lsp::CompletionItemKind::FUNCTION), - label: "hello(…)".to_string(), - detail: Some("fn(&mut Option) -> Vec".to_string()), - ..Default::default() - })), + language + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), + label: "hello(…)".to_string(), + detail: Some("fn(&mut Option) -> Vec".to_string()), + ..Default::default() + }) + .await, Some(CodeLabel { text: "hello(&mut Option) -> Vec".to_string(), filter_range: 0..5, @@ -344,12 +346,14 @@ mod tests { ); assert_eq!( - smol::block_on(language.label_for_completion(&lsp::CompletionItem { - kind: Some(lsp::CompletionItemKind::FIELD), - label: "len".to_string(), - detail: Some("usize".to_string()), - ..Default::default() - })), + language + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FIELD), + label: "len".to_string(), + detail: Some("usize".to_string()), + ..Default::default() + }) + .await, Some(CodeLabel { text: "len: usize".to_string(), filter_range: 0..3, @@ -358,12 +362,14 @@ mod tests { ); assert_eq!( - smol::block_on(language.label_for_completion(&lsp::CompletionItem { - kind: Some(lsp::CompletionItemKind::FUNCTION), - label: "hello(…)".to_string(), - detail: Some("fn(&mut Option) -> Vec".to_string()), - ..Default::default() - })), + language + .label_for_completion(&lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::FUNCTION), + label: "hello(…)".to_string(), + detail: Some("fn(&mut Option) -> Vec".to_string()), + ..Default::default() + }) + .await, Some(CodeLabel { text: "hello(&mut Option) -> Vec".to_string(), filter_range: 0..5, @@ -379,12 +385,12 @@ mod tests { ); } - #[test] - fn test_rust_label_for_symbol() { + #[gpui::test] + async fn test_rust_label_for_symbol() { let language = language( "rust", tree_sitter_rust::language(), - Some(smol::block_on(LspAdapter::new(RustLspAdapter))), + Some(LspAdapter::new(RustLspAdapter).await), ); let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ @@ -401,7 +407,9 @@ mod tests { let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap(); assert_eq!( - smol::block_on(language.label_for_symbol("hello", lsp::SymbolKind::FUNCTION)), + language + .label_for_symbol("hello", lsp::SymbolKind::FUNCTION) + .await, Some(CodeLabel { text: "fn hello".to_string(), filter_range: 3..8, @@ -410,7 +418,9 @@ mod tests { ); assert_eq!( - smol::block_on(language.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER)), + language + .label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER) + .await, Some(CodeLabel { text: "type World".to_string(), filter_range: 5..10, From 1dd92c3c282a46c457db93d28f7421b0ceb4cafa Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 16:59:59 +0200 Subject: [PATCH 83/91] Remove plugin build script in favor of build.rs plugin builder --- script/build-plugins | 46 -------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100755 script/build-plugins diff --git a/script/build-plugins b/script/build-plugins deleted file mode 100755 index 32f9cedc14..0000000000 --- a/script/build-plugins +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -set -e - -# echo "Clearing cached plugins..." -# cargo clean --manifest-path plugins/Cargo.toml - -echo "Building Wasm plugins..." -# cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml -cargo build --release --target wasm32-wasi --manifest-path plugins/Cargo.toml - -echo -echo "Extracting binaries..." -rm -rf plugins/bin -mkdir plugins/bin - -for f in plugins/target/wasm32-wasi/release/*.wasm -do - name=$(basename $f) - cp $f plugins/bin/$name - echo "- Extracted plugin $name" -done - -echo -echo "Creating .wat versions (for human inspection)..." - -for f in plugins/bin/*.wasm -do - name=$(basename $f) - base=$(echo $name | sed "s/\..*//") - wasm2wat $f --output plugins/bin/$base.wat - echo "- Converted $base.wasm -> $base.wat" -done - -echo -echo "Optimizing plugins using wasm-opt..." - -for f in plugins/bin/*.wasm -do - name=$(basename $f) - wasm-opt -Oz $f --output $f - echo "- Optimized $name" -done - -echo -echo "Done!" \ No newline at end of file From 19d19271f62bccac6a0707632678ef0777ede9a2 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 18:01:12 +0200 Subject: [PATCH 84/91] Remove stale label_for_completion impl from JSON plugin --- crates/zed/src/languages/language_plugin.rs | 21 --------------------- plugins/json_language/src/lib.rs | 21 --------------------- 2 files changed, 42 deletions(-) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 10ef71eb28..f31f606f02 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -34,7 +34,6 @@ pub struct PluginLspAdapter { fetch_latest_server_version: WasiFn<(), Option>, fetch_server_binary: WasiFn<(PathBuf, String), Result>, cached_server_binary: WasiFn>, - // label_for_completion: WasiFn>, initialization_options: WasiFn<(), String>, executor: Arc, runtime: Arc>, @@ -48,7 +47,6 @@ impl PluginLspAdapter { fetch_latest_server_version: plugin.function("fetch_latest_server_version")?, fetch_server_binary: plugin.function("fetch_server_binary")?, cached_server_binary: plugin.function("cached_server_binary")?, - // label_for_completion: plugin.function("label_for_completion")?, initialization_options: plugin.function("initialization_options")?, executor, runtime: Arc::new(Mutex::new(plugin)), @@ -133,25 +131,6 @@ impl LspAdapterTrait for PluginLspAdapter { .await } - // fn label_for_completion( - // &self, - // item: &lsp::CompletionItem, - // language: &language::Language, - // ) -> Option { - // // TODO: Push more of this method down into the plugin. - // use lsp::CompletionItemKind as Kind; - // let len = item.label.len(); - // let grammar = language.grammar()?; - // let kind = format!("{:?}", item.kind?); - // let name: String = call_block!(self, &self.label_for_completion, kind).log_err()??; - // let highlight_id = grammar.highlight_id_for_name(&name)?; - // Some(language::CodeLabel { - // text: item.label.clone(), - // runs: vec![(0..len, highlight_id)], - // filter_range: 0..len, - // }) - // } - async fn initialization_options(&self) -> Option { let string: String = self .runtime diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 6a9e128698..5bb25d8077 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -89,27 +89,6 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option { } } -// #[export] -// pub fn label_for_completion( -// item: &lsp::CompletionItem, -// // language: &language::Language, -// ) -> Option { -// // TODO: Push more of this method down into the plugin. -// use lsp::CompletionItemKind as Kind; -// let len = item.label.len(); -// let grammar = language.grammar()?; -// let kind = format!("{:?}", item.kind?); - -// // TODO: implementation - -// let highlight_id = grammar.highlight_id_for_name(&name)?; -// Some(language::CodeLabel { -// text: item.label.clone(), -// runs: vec![(0..len, highlight_id)], -// filter_range: 0..len, -// }) -// } - #[export] pub fn initialization_options() -> Option { Some("{ \"provideFormatter\": true }".to_string()) From 38f8191ce813e5c6aab83cbf63faf2e9086856c4 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 18:20:50 +0200 Subject: [PATCH 85/91] Add comment linking engine creating code together --- crates/plugin_runtime/build.rs | 6 +++++- crates/plugin_runtime/src/plugin.rs | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 5bdc842ea1..3eac41e0e7 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -48,7 +48,11 @@ fn main() { } } -fn create_engine() -> Engine { +/// Creates a default engine for compiling Wasm. +/// N.B.: this must create the same `Engine` as +/// the `create_default_engine` function +/// in `plugin_runtime/src/plugin.rs`. +fn create_default_engine() -> Engine { let mut config = Config::default(); config.async_support(true); // config.epoch_interruption(true); diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 7473577d84..d2570410f9 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -64,14 +64,22 @@ pub struct PluginBuilder { linker: Linker, } +/// Creates a default engine for compiling Wasm. +/// N.B.: this must create the same `Engine` as +/// the `create_default_engine` function +/// in `plugin_runtime/build.rs`. +pub fn create_default_engine() -> Result { + let mut config = Config::default(); + config.async_support(true); + // config.epoch_interruption(true); + Engine::new(&config) +} + impl PluginBuilder { /// Create a new [`PluginBuilder`] with the given WASI context. /// Using the default context is a safe bet, see [`new_with_default_context`]. pub fn new(wasi_ctx: WasiCtx) -> Result { - let mut config = Config::default(); - config.async_support(true); - // config.epoch_interruption(true); - let engine = Engine::new(&config)?; + let engine = create_default_engine()?; let linker = Linker::new(&engine); Ok(PluginBuilder { From 41918101ed695a2f690d2d20ab4cb199d699ef7b Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 18:29:27 +0200 Subject: [PATCH 86/91] Add wasm32-wasi to CI workflow --- .github/workflows/ci.yml | 14 ++++++++++++++ crates/plugin_runtime/build.rs | 2 +- script/bundle | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc0b687170..000b9034ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,13 @@ jobs: target: x86_64-apple-darwin profile: minimal + - name: Install Rust wasm32-wasi target + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-wasi + profile: minimal + - name: Install Node uses: actions/setup-node@v2 with: @@ -71,6 +78,13 @@ jobs: toolchain: stable target: aarch64-apple-darwin profile: minimal + + - name: Install Rust wasm32-wasi target + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-wasi + profile: minimal - name: Install Node uses: actions/setup-node@v2 diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 3eac41e0e7..f164f317ca 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -28,7 +28,7 @@ fn main() { let binaries = std::fs::read_dir(base.join("target/wasm32-wasi/release")) .expect("Could not find compiled plugins in target"); - let engine = create_engine(); + let engine = create_default_engine(); for file in binaries { let is_wasm = || { diff --git a/script/bundle b/script/bundle index fc6bab355f..42ed7d9244 100755 --- a/script/bundle +++ b/script/bundle @@ -6,6 +6,7 @@ export ZED_BUNDLE=true echo "Installing cargo bundle" cargo install cargo-bundle --version 0.5.0 +rustup target add wasm32-wasi # Deal with versions of macOS that don't include libstdc++ headers export CXXFLAGS="-stdlib=libc++" From 031162b473eb57752a4341bdf5c775d4dd7584ba Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 18:36:33 +0200 Subject: [PATCH 87/91] Remove spurious warnings --- crates/plugin_runtime/build.rs | 1 - plugins/Cargo.lock | 4 ++-- plugins/json_language/src/lib.rs | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index f164f317ca..f99812d162 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -5,7 +5,6 @@ fn main() { let base = Path::new("../../plugins"); println!("cargo:rerun-if-changed={}", base.display()); - println!("cargo:warning=Rebuilding precompiled plugins..."); let _ = std::fs::remove_dir_all(base.join("bin")); let _ = diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index 8b6fccd276..3a302d301b 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa", "ryu", diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 5bb25d8077..b3ee55b261 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,6 +1,5 @@ use plugin::prelude::*; use serde::Deserialize; -use serde_json::json; use std::fs; use std::path::PathBuf; From aeb1b89c25ce41b2d894f38c8fb84b4f8d23c17e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 11 Jul 2022 21:13:52 +0200 Subject: [PATCH 88/91] Make plugin build profile contingent on host build profile --- crates/plugin_runtime/build.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index f99812d162..542be4fb5f 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -10,21 +10,27 @@ fn main() { let _ = std::fs::create_dir_all(base.join("bin")).expect("Could not make plugins bin directory"); + let (profile_flags, profile_target) = match std::env::var("PROFILE").unwrap().as_str() { + "debug" => (&[][..], "debug"), + "release" => (&["--release"][..], "release"), + unknown => panic!("unknown profile `{}`", unknown), + }; + let build_successful = std::process::Command::new("cargo") .args([ "build", - "--release", "--target", "wasm32-wasi", "--manifest-path", base.join("Cargo.toml").to_str().unwrap(), ]) + .args(profile_flags) .status() .expect("Could not build plugins") .success(); assert!(build_successful); - let binaries = std::fs::read_dir(base.join("target/wasm32-wasi/release")) + let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target)) .expect("Could not find compiled plugins in target"); let engine = create_default_engine(); From d8b22a200e828673eecb685075db1e23e52838a2 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 12 Jul 2022 09:29:38 +0200 Subject: [PATCH 89/91] Rename LspAdapterTrait to LspAdapter and LspAdapter to CachedLspAdapter --- crates/language/src/language.rs | 28 ++++++++++----------- crates/project/src/project.rs | 16 ++++++------ crates/zed/src/languages.rs | 20 +++++++-------- crates/zed/src/languages/c.rs | 2 +- crates/zed/src/languages/go.rs | 4 +-- crates/zed/src/languages/language_plugin.rs | 4 +-- crates/zed/src/languages/python.rs | 4 +-- crates/zed/src/languages/rust.rs | 8 +++--- crates/zed/src/languages/typescript.rs | 4 +-- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 76d6b2e6af..c423d966c0 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -66,20 +66,20 @@ pub trait ToLspPosition { pub struct LanguageServerName(pub Arc); /// Represents a Language Server, with certain cached sync properties. -/// Uses [`LspAdapterTrait`] under the hood, but calls all 'static' methods +/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. -pub struct LspAdapter { +pub struct CachedLspAdapter { pub name: LanguageServerName, pub server_args: Vec, pub initialization_options: Option, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, pub id_for_language: Option, - pub adapter: Box, + pub adapter: Box, } -impl LspAdapter { - pub async fn new(adapter: T) -> Arc { +impl CachedLspAdapter { + pub async fn new(adapter: T) -> Arc { let adapter = Box::new(adapter); let name = adapter.name().await; let server_args = adapter.server_args().await; @@ -89,7 +89,7 @@ impl LspAdapter { adapter.disk_based_diagnostics_progress_token().await; let id_for_language = adapter.id_for_language(name.0.as_ref()).await; - Arc::new(LspAdapter { + Arc::new(CachedLspAdapter { name, server_args, initialization_options, @@ -147,7 +147,7 @@ impl LspAdapter { } #[async_trait] -pub trait LspAdapterTrait: 'static + Send + Sync { +pub trait LspAdapter: 'static + Send + Sync { async fn name(&self) -> LanguageServerName; async fn fetch_latest_server_version( @@ -275,7 +275,7 @@ pub struct BracketPair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) adapter: Option>, + pub(crate) adapter: Option>, #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( @@ -490,7 +490,7 @@ impl LanguageRegistry { } async fn get_server_binary_path( - adapter: Arc, + adapter: Arc, language: Arc, http_client: Arc, download_dir: Arc, @@ -532,7 +532,7 @@ async fn get_server_binary_path( } async fn fetch_latest_server_binary_path( - adapter: Arc, + adapter: Arc, language: Arc, http_client: Arc, container_dir: &Path, @@ -581,7 +581,7 @@ impl Language { } } - pub fn lsp_adapter(&self) -> Option> { + pub fn lsp_adapter(&self) -> Option> { self.adapter.clone() } @@ -613,7 +613,7 @@ impl Language { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } - pub fn with_lsp_adapter(mut self, lsp_adapter: Arc) -> Self { + pub fn with_lsp_adapter(mut self, lsp_adapter: Arc) -> Self { self.adapter = Some(lsp_adapter); self } @@ -625,7 +625,7 @@ impl Language { ) -> mpsc::UnboundedReceiver { let (servers_tx, servers_rx) = mpsc::unbounded(); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); - let adapter = LspAdapter::new(fake_lsp_adapter).await; + let adapter = CachedLspAdapter::new(fake_lsp_adapter).await; self.adapter = Some(adapter); servers_rx } @@ -789,7 +789,7 @@ impl Default for FakeLspAdapter { #[cfg(any(test, feature = "test-support"))] #[async_trait] -impl LspAdapterTrait for Arc { +impl LspAdapter for Arc { async fn name(&self) -> LanguageServerName { LanguageServerName(self.name.into()) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e09ded315f..aa93077569 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -24,9 +24,9 @@ use language::{ deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, }, - range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CharKind, CodeAction, CodeLabel, - Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, - Language, LanguageRegistry, LanguageServerName, LineEnding, LocalFile, LspAdapter, + range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, + CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, + File as _, Language, LanguageRegistry, LanguageServerName, LineEnding, LocalFile, OffsetRangeExt, Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, }; @@ -200,7 +200,7 @@ pub enum Event { pub enum LanguageServerState { Starting(Task>>), Running { - adapter: Arc, + adapter: Arc, server: Arc, }, } @@ -2007,7 +2007,7 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, &Arc)> { + ) -> impl Iterator, &Arc)> { self.language_server_ids .iter() .filter_map(move |((language_server_worktree_id, _), id)| { @@ -2648,7 +2648,7 @@ impl Project { this: WeakModelHandle, params: lsp::ApplyWorkspaceEditParams, server_id: usize, - adapter: Arc, + adapter: Arc, language_server: Arc, mut cx: AsyncAppContext, ) -> Result { @@ -3913,7 +3913,7 @@ impl Project { this: ModelHandle, edit: lsp::WorkspaceEdit, push_to_history: bool, - lsp_adapter: Arc, + lsp_adapter: Arc, language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { @@ -5923,7 +5923,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let name = language.lsp_adapter()?.name.clone(); let worktree_id = file.worktree_id(cx); diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 772664d0ac..e1cc1d61c2 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -22,23 +22,23 @@ pub async fn init(languages: Arc, executor: Arc) { ( "c", tree_sitter_c::language(), - Some(LspAdapter::new(c::CLspAdapter).await), + Some(CachedLspAdapter::new(c::CLspAdapter).await), ), ( "cpp", tree_sitter_cpp::language(), - Some(LspAdapter::new(c::CLspAdapter).await), + Some(CachedLspAdapter::new(c::CLspAdapter).await), ), ( "go", tree_sitter_go::language(), - Some(LspAdapter::new(go::GoLspAdapter).await), + Some(CachedLspAdapter::new(go::GoLspAdapter).await), ), ( "json", tree_sitter_json::language(), match language_plugin::new_json(executor).await.log_err() { - Some(lang) => Some(LspAdapter::new(lang).await), + Some(lang) => Some(CachedLspAdapter::new(lang).await), None => None, }, ), @@ -50,12 +50,12 @@ pub async fn init(languages: Arc, executor: Arc) { ( "python", tree_sitter_python::language(), - Some(LspAdapter::new(python::PythonLspAdapter).await), + Some(CachedLspAdapter::new(python::PythonLspAdapter).await), ), ( "rust", tree_sitter_rust::language(), - Some(LspAdapter::new(rust::RustLspAdapter).await), + Some(CachedLspAdapter::new(rust::RustLspAdapter).await), ), ( "toml", @@ -65,17 +65,17 @@ pub async fn init(languages: Arc, executor: Arc) { ( "tsx", tree_sitter_typescript::language_tsx(), - Some(LspAdapter::new(typescript::TypeScriptLspAdapter).await), + Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), ), ( "typescript", tree_sitter_typescript::language_typescript(), - Some(LspAdapter::new(typescript::TypeScriptLspAdapter).await), + Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), ), ( "javascript", tree_sitter_typescript::language_tsx(), - Some(LspAdapter::new(typescript::TypeScriptLspAdapter).await), + Some(CachedLspAdapter::new(typescript::TypeScriptLspAdapter).await), ), ] { languages.add(Arc::new(language(name, grammar, lsp_adapter))); @@ -85,7 +85,7 @@ pub async fn init(languages: Arc, executor: Arc) { pub(crate) fn language( name: &str, grammar: tree_sitter::Language, - lsp_adapter: Option>, + lsp_adapter: Option>, ) -> Language { let config = toml::from_slice( &LanguageDir::get(&format!("{}/config.toml", name)) diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 9f2d64748b..54554beaf6 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -11,7 +11,7 @@ use util::ResultExt; pub struct CLspAdapter; #[async_trait] -impl super::LspAdapterTrait for CLspAdapter { +impl super::LspAdapter for CLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("clangd".into()) } diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 47d6e4447f..3fa47428c9 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -18,7 +18,7 @@ lazy_static! { } #[async_trait] -impl super::LspAdapterTrait for GoLspAdapter { +impl super::LspAdapter for GoLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("gopls".into()) } @@ -314,7 +314,7 @@ mod tests { let language = language( "go", tree_sitter_go::language(), - Some(LspAdapter::new(GoLspAdapter).await), + Some(CachedLspAdapter::new(GoLspAdapter).await), ); let theme = SyntaxTheme::new(vec![ diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index f31f606f02..ac649c425d 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerName, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapter}; use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -55,7 +55,7 @@ impl PluginLspAdapter { } #[async_trait] -impl LspAdapterTrait for PluginLspAdapter { +impl LspAdapter for PluginLspAdapter { async fn name(&self) -> LanguageServerName { let name: String = self .runtime diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 00b8ec8e08..ca0b24bda7 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapter}; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -15,7 +15,7 @@ impl PythonLspAdapter { } #[async_trait] -impl LspAdapterTrait for PythonLspAdapter { +impl LspAdapter for PythonLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("pyright".into()) } diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 71f633bca4..18d49f78d4 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -14,7 +14,7 @@ use util::ResultExt; pub struct RustLspAdapter; #[async_trait] -impl LspAdapterTrait for RustLspAdapter { +impl LspAdapter for RustLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("rust-analyzer".into()) } @@ -255,7 +255,7 @@ impl LspAdapterTrait for RustLspAdapter { #[cfg(test)] mod tests { use super::*; - use crate::languages::{language, LspAdapter}; + use crate::languages::{language, CachedLspAdapter}; use gpui::{color::Color, MutableAppContext}; use theme::SyntaxTheme; @@ -305,7 +305,7 @@ mod tests { let language = language( "rust", tree_sitter_rust::language(), - Some(LspAdapter::new(RustLspAdapter).await), + Some(CachedLspAdapter::new(RustLspAdapter).await), ); let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ @@ -390,7 +390,7 @@ mod tests { let language = language( "rust", tree_sitter_rust::language(), - Some(LspAdapter::new(RustLspAdapter).await), + Some(CachedLspAdapter::new(RustLspAdapter).await), ); let grammar = language.grammar().unwrap(); let theme = SyntaxTheme::new(vec![ diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 786e00f248..199a7f22ae 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; -use language::{LanguageServerName, LspAdapterTrait}; +use language::{LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; @@ -21,7 +21,7 @@ struct Versions { } #[async_trait] -impl LspAdapterTrait for TypeScriptLspAdapter { +impl LspAdapter for TypeScriptLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("typescript-language-server".into()) } From c4f10befe83c81d1b7dd0c282d5f7e275d784ddf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Jul 2022 09:53:58 +0200 Subject: [PATCH 90/91] Use latest Rust version on CI Co-Authored-By: Isaac Clayton --- .github/workflows/ci.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 000b9034ac..1557c5a7a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,9 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: stable - target: x86_64-apple-darwin + target: aarch64-apple-darwin profile: minimal + default: true - name: Install Rust wasm32-wasi target uses: actions-rs/toolchain@v1 @@ -65,19 +66,20 @@ jobs: APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} steps: - - name: Install Rust x86_64-apple-darwin target - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-apple-darwin - profile: minimal - - name: Install Rust aarch64-apple-darwin target uses: actions-rs/toolchain@v1 with: toolchain: stable target: aarch64-apple-darwin profile: minimal + default: true + + - name: Install Rust x86_64-apple-darwin target + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-apple-darwin + profile: minimal - name: Install Rust wasm32-wasi target uses: actions-rs/toolchain@v1 From 815de6da616e952af2faeba7bdaf2b5714e7f169 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Tue, 12 Jul 2022 10:24:43 +0200 Subject: [PATCH 91/91] Rewrite test_managing_language_servers to add languages after buffers are open Co-Authored-By: Antonio Scandurra --- crates/project/src/project_tests.rs | 60 +++++++++-------------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 04c6893d09..817afe058c 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -67,7 +67,10 @@ async fn test_populate_and_search(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { +async fn test_managing_language_servers( + deterministic: Arc, + cx: &mut gpui::TestAppContext, +) { cx.foreground().forbid_parking(); let mut rust_language = Language::new( @@ -127,45 +130,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; - // Open a buffer before languages have been added - let json_buffer = project - .update(cx, |project, cx| { - project.open_local_buffer("/the-root/package.json", cx) - }) - .await - .unwrap(); - - // Assert that this buffer does not have a language - assert!(json_buffer.read_with(cx, |buffer, _| { buffer.language().is_none() })); - - // Now we add the languages to the project, and subscribe to the watcher - project.update(cx, |project, cx| { - // Get a handle to the channel and clear out default item - let mut recv = project.languages.subscribe(); - recv.blocking_recv(); - - // Add, then wait to be notified that JSON has been added - project.languages.add(Arc::new(json_language)); - recv.blocking_recv(); - - // Add, then wait to be notified that Rust has been added - project.languages.add(Arc::new(rust_language)); - recv.blocking_recv(); - // Uncommenting this would cause the thread to block indefinitely: - // recv.blocking_recv(); - - // Force the assignment, we know the watcher has been notified - // but have no way to wait for the watcher to assign to the project - project.assign_language_to_buffer(&json_buffer, cx); - }); - - // Assert that the opened buffer does have a language, and that it is JSON - let name = json_buffer.read_with(cx, |buffer, _| buffer.language().map(|l| l.name())); - assert_eq!(name, Some("JSON".into())); - - // Close the JSON buffer we opened - cx.update(|_| drop(json_buffer)); - // Open a buffer without an associated language server. let toml_buffer = project .update(cx, |project, cx| { @@ -174,13 +138,27 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { .await .unwrap(); - // Open a buffer with an associated language server. + // Open a buffer with an associated language server before the language for it has been loaded. let rust_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/test.rs", cx) }) .await .unwrap(); + rust_buffer.read_with(cx, |buffer, _| { + assert_eq!(buffer.language().map(|l| l.name()), None); + }); + + // Now we add the languages to the project, and ensure they get assigned to all + // the relevant open buffers. + project.update(cx, |project, _| { + project.languages.add(Arc::new(json_language)); + project.languages.add(Arc::new(rust_language)); + }); + deterministic.run_until_parked(); + rust_buffer.read_with(cx, |buffer, _| { + assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into())); + }); // A server is started up, and it is notified about Rust files. let mut fake_rust_server = fake_rust_servers.next().await.unwrap();