From 3fb1cd07260afa2ebcf0aadd1dd96a87b51adb5d Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 13 Jul 2022 16:31:47 +0200 Subject: [PATCH 01/31] Fix issue where precompiled plugins were compiled with the wrong settings --- crates/plugin_runtime/build.rs | 24 +++++++++++++++------ crates/zed/src/languages/language_plugin.rs | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index d1b1b5841137f33ab0f250fb498f299edd574629..b323bdc34e9d22855c233fbf2bf6ac6dc2633e38 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -43,7 +43,8 @@ fn main() { assert!(build_successful); // Find all compiled binaries - let engine = create_default_engine(); + let epoch_engine = create_epoch_engine(); + let fuel_engine = create_fuel_engine(); let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target)) .expect("Could not find compiled plugins in target"); @@ -61,26 +62,35 @@ fn main() { if let Some(path) = is_wasm() { 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); + precompile(&out_path, &epoch_engine, "epoch"); + precompile(&out_path, &fuel_engine, "fuel"); } } } -/// Creates a default engine for compiling Wasm. -fn create_default_engine() -> Engine { +fn create_epoch_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) { +fn create_fuel_engine() -> Engine { + let mut config = Config::default(); + config.async_support(true); + config.consume_fuel(true); + Engine::new(&config).expect("Could not create engine") +} + +fn precompile(path: &Path, engine: &Engine, engine_name: &str) { 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() + "{}.{}.pre", + path.file_name().unwrap().to_string_lossy(), + engine_name, )); let mut out_file = std::fs::File::create(out_path) .expect("Could not create output file for precompiled module"); diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 6f4bdab78ef38559e8cd06576a6f39fa608d73f1..690e6d385c81bb9d9afc3c15fb85ae32cbd9eead 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -26,7 +26,7 @@ pub async fn new_json(executor: Arc) -> Result { .map(|output| output.stdout) })? .init(PluginBinary::Precompiled(include_bytes!( - "../../../../plugins/bin/json_language.wasm.pre" + "../../../../plugins/bin/json_language.wasm.epoch.pre" ))) .await?; From 562e22814f0920ee0e2928b3d317abeafe98103d Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 13 Jul 2022 17:08:43 +0200 Subject: [PATCH 02/31] Remove .pre suffix use .epoch and .fuel instead --- crates/plugin_runtime/README.md | 2 ++ crates/plugin_runtime/build.rs | 2 +- crates/zed/src/languages/language_plugin.rs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 8ac843cb02f0491dbdc09250e51734440abe561a..4088041fe67b5f865bb6eec2ed71ea1fe090d141 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -154,6 +154,8 @@ Plugins in the `plugins` directory are automatically recompiled and serialized t - `plugin.wasm.pre` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-agnostic cranelift-specific IR. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin. +> TODO: Rework precompiled plugins. + For all intents and purposes, it is *highly recommended* that you use precompiled plugins where possible, as they are much more lightweight and take much less time to instantiate. ### Instantiating a plugin diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index b323bdc34e9d22855c233fbf2bf6ac6dc2633e38..8803cb2fb74c0d12595c0db35d69092a305eb99b 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -88,7 +88,7 @@ fn precompile(path: &Path, engine: &Engine, engine_name: &str) { .precompile_module(&bytes) .expect("Could not precompile module"); let out_path = path.parent().unwrap().join(&format!( - "{}.{}.pre", + "{}.{}", path.file_name().unwrap().to_string_lossy(), engine_name, )); diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 690e6d385c81bb9d9afc3c15fb85ae32cbd9eead..090dc6b31358f8d868ee3421e77318592a84f878 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -26,7 +26,7 @@ pub async fn new_json(executor: Arc) -> Result { .map(|output| output.stdout) })? .init(PluginBinary::Precompiled(include_bytes!( - "../../../../plugins/bin/json_language.wasm.epoch.pre" + "../../../../plugins/bin/json_language.wasm.epoch" ))) .await?; From 8bb8e851df5ca7dc13f38372cfbf367056bf5f0a Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 13 Jul 2022 18:03:14 +0200 Subject: [PATCH 03/31] Remove epoch-based metering --- crates/plugin_runtime/README.md | 2 - crates/plugin_runtime/build.rs | 25 ++-- crates/plugin_runtime/src/lib.rs | 2 +- crates/plugin_runtime/src/plugin.rs | 154 ++++---------------- crates/zed/src/languages/language_plugin.rs | 10 +- 5 files changed, 41 insertions(+), 152 deletions(-) diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 4088041fe67b5f865bb6eec2ed71ea1fe090d141..8ac843cb02f0491dbdc09250e51734440abe561a 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -154,8 +154,6 @@ Plugins in the `plugins` directory are automatically recompiled and serialized t - `plugin.wasm.pre` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-agnostic cranelift-specific IR. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin. -> TODO: Rework precompiled plugins. - For all intents and purposes, it is *highly recommended* that you use precompiled plugins where possible, as they are much more lightweight and take much less time to instantiate. ### Instantiating a plugin diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 8803cb2fb74c0d12595c0db35d69092a305eb99b..93ba175c18357735725f0bf1c93db8f775dc8b5a 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -43,8 +43,7 @@ fn main() { assert!(build_successful); // Find all compiled binaries - let epoch_engine = create_epoch_engine(); - let fuel_engine = create_fuel_engine(); + let engine = create_default_engine(); let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target)) .expect("Could not find compiled plugins in target"); @@ -62,35 +61,29 @@ fn main() { if let Some(path) = is_wasm() { 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, &epoch_engine, "epoch"); - precompile(&out_path, &fuel_engine, "fuel"); + precompile(&out_path, &engine); } } } -fn create_epoch_engine() -> Engine { - let mut config = Config::default(); - config.async_support(true); - config.epoch_interruption(true); - Engine::new(&config).expect("Could not create engine") -} - -fn create_fuel_engine() -> Engine { +/// Creates an engine with the default configuration. +/// N.B. This must create an engine with the same config as the one +/// in `plugin_runtime/build.rs`. +fn create_default_engine() -> Engine { let mut config = Config::default(); config.async_support(true); config.consume_fuel(true); - Engine::new(&config).expect("Could not create engine") + Engine::new(&config).expect("Could not create precompilation engine") } -fn precompile(path: &Path, engine: &Engine, engine_name: &str) { +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(), - engine_name, )); let mut out_file = std::fs::File::create(out_path) .expect("Could not create output file for precompiled module"); diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index b008a98c287541c2cd73067fee6a59edd4d79fda..8665a108c927898be821f030a16cbe7649cc2400 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -23,7 +23,7 @@ mod tests { } async { - let mut runtime = PluginBuilder::new_fuel_with_default_ctx(PluginYield::default_fuel()) + let mut runtime = PluginBuilder::new_default() .unwrap() .host_function("mystery_number", |input: u32| input + 7) .unwrap() diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 2767d9c4b876e0697f817774a155e7aab9a22561..2748a3f3f7f19426496faa67d5480e0723f726c9 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -1,6 +1,5 @@ use std::future::Future; -use std::time::Duration; use std::{fs::File, marker::PhantomData, path::Path}; use anyhow::{anyhow, Error}; @@ -55,34 +54,14 @@ impl Clone for WasiFn { } } -pub struct PluginYieldEpoch { - delta: u64, - epoch: std::time::Duration, -} - -pub struct PluginYieldFuel { +pub struct Metering { initial: u64, refill: u64, } -pub enum PluginYield { - Epoch { - yield_epoch: PluginYieldEpoch, - initialize_incrementer: Box () + Send>, - }, - Fuel(PluginYieldFuel), -} - -impl PluginYield { - pub fn default_epoch() -> PluginYieldEpoch { - PluginYieldEpoch { - delta: 1, - epoch: Duration::from_millis(1), - } - } - - pub fn default_fuel() -> PluginYieldFuel { - PluginYieldFuel { +impl Default for Metering { + fn default() -> Self { + Metering { initial: 1000, refill: 1000, } @@ -97,110 +76,44 @@ pub struct PluginBuilder { wasi_ctx: WasiCtx, engine: Engine, linker: Linker, - yield_when: PluginYield, + metering: Metering, } -impl PluginBuilder { - /// Creates an engine with the proper configuration given the yield mechanism in use - fn create_engine(yield_when: &PluginYield) -> Result<(Engine, Linker), Error> { - let mut config = Config::default(); - config.async_support(true); - - match yield_when { - PluginYield::Epoch { .. } => { - config.epoch_interruption(true); - } - PluginYield::Fuel(_) => { - config.consume_fuel(true); - } - } - - let engine = Engine::new(&config)?; - let linker = Linker::new(&engine); - Ok((engine, linker)) - } - - /// Create a new [`PluginBuilder`] with the given WASI context. - /// Using the default context is a safe bet, see [`new_with_default_context`]. - /// This plugin will yield after each fixed configurable epoch. - pub fn new_epoch( - wasi_ctx: WasiCtx, - yield_epoch: PluginYieldEpoch, - spawn_detached_future: C, - ) -> Result - where - C: FnOnce(std::pin::Pin + Send + 'static>>) -> () - + Send - + 'static, - { - // we can't create the future until after initializing - // because we need the engine to load the plugin - let epoch = yield_epoch.epoch; - let initialize_incrementer = Box::new(move |engine: Engine| { - spawn_detached_future(Box::pin(async move { - loop { - smol::Timer::after(epoch).await; - engine.increment_epoch(); - } - })) - }); - - let yield_when = PluginYield::Epoch { - yield_epoch, - initialize_incrementer, - }; - let (engine, linker) = Self::create_engine(&yield_when)?; - - Ok(PluginBuilder { - wasi_ctx, - engine, - linker, - yield_when, - }) - } +/// Creates an engine with the default configuration. +/// N.B. This must create an engine with the same config as the one +/// in `plugin_runtime/build.rs`. +fn create_default_engine() -> Result { + let mut config = Config::default(); + config.async_support(true); + config.consume_fuel(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`]. /// This plugin will yield after a configurable amount of fuel is consumed. - pub fn new_fuel(wasi_ctx: WasiCtx, yield_fuel: PluginYieldFuel) -> Result { - let yield_when = PluginYield::Fuel(yield_fuel); - let (engine, linker) = Self::create_engine(&yield_when)?; + pub fn new(wasi_ctx: WasiCtx, metering: Metering) -> Result { + let engine = create_default_engine()?; + let linker = Linker::new(&engine); Ok(PluginBuilder { wasi_ctx, engine, linker, - yield_when, + metering, }) } - /// Create a new `WasiCtx` that inherits the - /// host processes' access to `stdout` and `stderr`. - fn default_ctx() -> WasiCtx { - WasiCtxBuilder::new() - .inherit_stdout() - .inherit_stderr() - .build() - } - - /// Create a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]). - /// This plugin will yield after each fixed configurable epoch. - pub fn new_epoch_with_default_ctx( - yield_epoch: PluginYieldEpoch, - spawn_detached_future: C, - ) -> Result - where - C: FnOnce(std::pin::Pin + Send + 'static>>) -> () - + Send - + 'static, - { - Self::new_epoch(Self::default_ctx(), yield_epoch, spawn_detached_future) - } - /// Create a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]). /// This plugin will yield after a configurable amount of fuel is consumed. - pub fn new_fuel_with_default_ctx(yield_fuel: PluginYieldFuel) -> Result { - Self::new_fuel(Self::default_ctx(), yield_fuel) + pub fn new_default() -> Result { + let default_ctx = WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build(); + let metering = Metering::default(); + Self::new(default_ctx, metering) } /// Add an `async` host function. See [`host_function`] for details. @@ -433,19 +346,8 @@ impl Plugin { }; // set up automatic yielding based on configuration - match plugin.yield_when { - PluginYield::Epoch { - yield_epoch: PluginYieldEpoch { delta, .. }, - initialize_incrementer, - } => { - store.epoch_deadline_async_yield_and_update(delta); - initialize_incrementer(engine); - } - PluginYield::Fuel(PluginYieldFuel { initial, refill }) => { - store.add_fuel(initial).unwrap(); - store.out_of_fuel_async_yield(u64::MAX, refill); - } - } + store.add_fuel(plugin.metering.initial).unwrap(); + store.out_of_fuel_async_yield(u64::MAX, plugin.metering.refill); // 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 090dc6b31358f8d868ee3421e77318592a84f878..e5513e97bf14e4d87960a5b46bd2572ff415e970 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -5,16 +5,12 @@ use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, PluginYield, WasiFn}; +use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; pub async fn new_json(executor: Arc) -> Result { - let executor_ref = executor.clone(); - let plugin = - PluginBuilder::new_epoch_with_default_ctx(PluginYield::default_epoch(), move |future| { - executor_ref.spawn(future).detach() - })? + let plugin = PluginBuilder::new_default()? .host_function_async("command", |command: String| async move { let mut args = command.split(' '); let command = args.next().unwrap(); @@ -26,7 +22,7 @@ pub async fn new_json(executor: Arc) -> Result { .map(|output| output.stdout) })? .init(PluginBinary::Precompiled(include_bytes!( - "../../../../plugins/bin/json_language.wasm.epoch" + "../../../../plugins/bin/json_language.wasm.pre" ))) .await?; From 5e7456df4ea49f45cae4620ebda4bc0390e62938 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 13 Jul 2022 20:19:56 +0200 Subject: [PATCH 04/31] Fix docs --- crates/plugin_runtime/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 93ba175c18357735725f0bf1c93db8f775dc8b5a..8a0c5c57de75a425d3b9faf80714a630da27f138 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -68,7 +68,7 @@ fn main() { /// Creates an engine with the default configuration. /// N.B. This must create an engine with the same config as the one -/// in `plugin_runtime/build.rs`. +/// in `plugin_runtime/src/plugin.rs`. fn create_default_engine() -> Engine { let mut config = Config::default(); config.async_support(true); From dddeb66e2a4a086466367a5ebd5019cc704652fc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 13 Jul 2022 13:56:39 -0700 Subject: [PATCH 05/31] Temporarily remove JSON plugin + restore native JSON LspAdapter --- crates/zed/src/languages.rs | 9 +- crates/zed/src/languages/json.rs | 106 ++++++++++++++++++++ crates/zed/src/languages/language_plugin.rs | 27 +---- 3 files changed, 111 insertions(+), 31 deletions(-) create mode 100644 crates/zed/src/languages/json.rs diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index e1cc1d61c2a7b597798616bf3641ae9df304a972..82f5701f7f9ef4fbac61d8693bc9388137c39958 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -2,11 +2,11 @@ use gpui::executor::Background; pub use language::*; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; -use util::ResultExt; mod c; mod go; mod installation; +mod json; mod language_plugin; mod python; mod rust; @@ -17,7 +17,7 @@ mod typescript; #[exclude = "*.rs"] struct LanguageDir; -pub async fn init(languages: Arc, executor: Arc) { +pub async fn init(languages: Arc, _executor: Arc) { for (name, grammar, lsp_adapter) in [ ( "c", @@ -37,10 +37,7 @@ pub async fn init(languages: Arc, executor: Arc) { ( "json", tree_sitter_json::language(), - match language_plugin::new_json(executor).await.log_err() { - Some(lang) => Some(CachedLspAdapter::new(lang).await), - None => None, - }, + Some(CachedLspAdapter::new(json::JsonLspAdapter).await), ), ( "markdown", diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b6569d3362fdb1e551f194f45a1201ac8f4e7fe --- /dev/null +++ b/crates/zed/src/languages/json.rs @@ -0,0 +1,106 @@ +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 collections::HashMap; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter}; +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 LspAdapter 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 language_ids(&self) -> HashMap { + [("JSON".into(), "jsonc".into())].into_iter().collect() + } +} diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 6f4bdab78ef38559e8cd06576a6f39fa608d73f1..0f8d503adce5a7974a5719502f9d9094e54076e1 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -5,34 +5,10 @@ use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, PluginYield, WasiFn}; +use plugin_runtime::{Plugin, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; -pub async fn new_json(executor: Arc) -> Result { - let executor_ref = executor.clone(); - let plugin = - PluginBuilder::new_epoch_with_default_ctx(PluginYield::default_epoch(), move |future| { - executor_ref.spawn(future).detach() - })? - .host_function_async("command", |command: String| async move { - let mut args = command.split(' '); - let command = args.next().unwrap(); - smol::process::Command::new(command) - .args(args) - .output() - .await - .log_err() - .map(|output| output.stdout) - })? - .init(PluginBinary::Precompiled(include_bytes!( - "../../../../plugins/bin/json_language.wasm.pre" - ))) - .await?; - - PluginLspAdapter::new(plugin, executor).await -} - pub struct PluginLspAdapter { name: WasiFn<(), String>, server_args: WasiFn<(), Vec>, @@ -46,6 +22,7 @@ pub struct PluginLspAdapter { } impl PluginLspAdapter { + #[allow(unused)] pub async fn new(mut plugin: Plugin, executor: Arc) -> Result { Ok(Self { name: plugin.function("name")?, From d796b543e0f2a8255e46b7862396f790f51317a2 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Mon, 11 Jul 2022 14:14:33 -0700 Subject: [PATCH 06/31] WIP add basic context menu and make progress toward adding quick actions to it --- Cargo.lock | 1 + crates/editor/Cargo.toml | 1 + crates/editor/src/editor.rs | 14 +++-- crates/editor/src/element.rs | 24 +++++++++ crates/editor/src/mouse_context_menu.rs | 61 ++++++++++++++++++++++ crates/editor/src/selections_collection.rs | 38 +++++++++++++- 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 crates/editor/src/mouse_context_menu.rs diff --git a/Cargo.lock b/Cargo.lock index 6c87bb055f7fcbbbffaa3cbd0f5daa818d32f038..14016ae243dfb89addad7b9709fc6eac844c9d9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,6 +1611,7 @@ dependencies = [ "anyhow", "clock", "collections", + "context_menu", "ctor", "env_logger", "futures", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 6e1e3b09bd9b56f228b8a9c8524de5fc9d7e6fe3..dfd4938742d3a365d477c01c8d54e2ce9bdb2e9b 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -23,6 +23,7 @@ test-support = [ text = { path = "../text" } clock = { path = "../clock" } collections = { path = "../collections" } +context_menu = { path = "../context_menu" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } language = { path = "../language" } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e58f1fc341c8404e33d0dab583de934a0bf205e5..baa346f7f0d9da88ab69efc0459c08a78f9434d4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4,6 +4,7 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; +mod mouse_context_menu; pub mod movement; mod multi_buffer; pub mod selections_collection; @@ -319,6 +320,7 @@ pub fn init(cx: &mut MutableAppContext) { hover_popover::init(cx); link_go_to_definition::init(cx); + mouse_context_menu::init(cx); workspace::register_project_item::(cx); workspace::register_followable_item::(cx); @@ -425,6 +427,7 @@ pub struct Editor { background_highlights: BTreeMap Color, Vec>)>, nav_history: Option, context_menu: Option, + mouse_context_menu: ViewHandle, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, available_code_actions: Option<(ModelHandle, Arc<[CodeAction]>)>, @@ -1010,11 +1013,11 @@ impl Editor { background_highlights: Default::default(), nav_history: None, context_menu: None, + mouse_context_menu: cx.add_view(|cx| context_menu::ContextMenu::new(cx)), completion_tasks: Default::default(), next_completion_id: 0, available_code_actions: Default::default(), code_actions_task: Default::default(), - document_highlights_task: Default::default(), pending_rename: Default::default(), searchable: true, @@ -1596,7 +1599,7 @@ impl Editor { s.delete(newest_selection.id) } - s.set_pending_range(start..end, mode); + s.set_pending_anchor_range(start..end, mode); }); } @@ -5780,7 +5783,12 @@ impl View for Editor { }); } - EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed() + Stack::new() + .with_child( + EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed(), + ) + .with_child(ChildView::new(&self.mouse_context_menu).boxed()) + .boxed() } fn ui_name() -> &'static str { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 56f664566ecd03bba53cbd7960e3ac5be8d33287..99d60ed9a279a581e9973a1c888486992a46af21 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7,6 +7,7 @@ use crate::{ display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, hover_popover::HoverAt, link_go_to_definition::{CmdChanged, GoToFetchedDefinition, UpdateGoToDefinitionLink}, + mouse_context_menu::DeployMouseContextMenu, EditorStyle, }; use clock::ReplicaId; @@ -152,6 +153,24 @@ impl EditorElement { true } + fn mouse_right_down( + &self, + position: Vector2F, + layout: &mut LayoutState, + paint: &mut PaintState, + cx: &mut EventContext, + ) -> bool { + if !paint.text_bounds.contains_point(position) { + return false; + } + + let snapshot = self.snapshot(cx.app); + let (point, _) = paint.point_for_position(&snapshot, layout, position); + + cx.dispatch_action(DeployMouseContextMenu { position, point }); + true + } + fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool { if self.view(cx.app.as_ref()).is_selecting() { cx.dispatch_action(Select(SelectPhase::End)); @@ -1482,6 +1501,11 @@ impl Element for EditorElement { paint, cx, ), + Event::MouseDown(MouseEvent { + button: MouseButton::Right, + position, + .. + }) => self.mouse_right_down(*position, layout, paint, cx), Event::MouseUp(MouseEvent { button: MouseButton::Left, position, diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs new file mode 100644 index 0000000000000000000000000000000000000000..1313ad346fd751cf8d80d11add4e5cbc89d5de0f --- /dev/null +++ b/crates/editor/src/mouse_context_menu.rs @@ -0,0 +1,61 @@ +use context_menu::{ContextMenu, ContextMenuItem}; +use gpui::{ + geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, Task, ViewContext, + ViewHandle, +}; + +use crate::{ + DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, Rename, SelectMode, +}; + +#[derive(Clone, PartialEq)] +pub struct DeployMouseContextMenu { + pub position: Vector2F, + pub point: DisplayPoint, +} + +impl_internal_actions!(editor, [DeployMouseContextMenu]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(deploy_context_menu); +} + +pub struct MouseContextMenuState { + pub context_menu: ViewHandle, + pub task: Option>, +} + +pub fn deploy_context_menu( + editor: &mut Editor, + &DeployMouseContextMenu { position, point }: &DeployMouseContextMenu, + cx: &mut ViewContext, +) { + // Don't show context menu for inline editors + if editor.mode() != EditorMode::Full { + return; + } + + // Don't show the context menu if there isn't a project associated with this editor + if editor.project.is_none() { + return; + } + + // Move the cursor to the clicked location so that dispatched actions make sense + editor.change_selections(None, cx, |s| { + s.clear_disjoint(); + s.set_pending_display_range(point..point, SelectMode::Character); + }); + + editor.mouse_context_menu.update(cx, |menu, cx| { + menu.show( + position, + vec![ + ContextMenuItem::item("Rename Symbol", Rename), + ContextMenuItem::item("Go To Definition", GoToDefinition), + ContextMenuItem::item("Find All References", FindAllReferences), + ], + cx, + ); + }); + cx.notify(); +} diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 026144db647fbc6a0692660eb4e481b8da4538c1..a05a46fc6d363ec3b92f51120cf283e9788ddbcf 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -384,7 +384,7 @@ impl<'a> MutableSelectionsCollection<'a> { } } - pub fn set_pending_range(&mut self, range: Range, mode: SelectMode) { + pub fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection: Selection { id: post_inc(&mut self.collection.next_selection_id), @@ -398,6 +398,42 @@ impl<'a> MutableSelectionsCollection<'a> { self.selections_changed = true; } + pub fn set_pending_display_range(&mut self, range: Range, mode: SelectMode) { + let (start, end, reversed) = { + let display_map = self.display_map(); + let buffer = self.buffer(); + let mut start = range.start; + let mut end = range.end; + let reversed = if start > end { + mem::swap(&mut start, &mut end); + true + } else { + false + }; + + let end_bias = if end > start { Bias::Left } else { Bias::Right }; + ( + buffer.anchor_before(start.to_point(&display_map)), + buffer.anchor_at(end.to_point(&display_map), end_bias), + reversed, + ) + }; + + let new_pending = PendingSelection { + selection: Selection { + id: post_inc(&mut self.collection.next_selection_id), + start, + end, + reversed, + goal: SelectionGoal::None, + }, + mode, + }; + + self.collection.pending = Some(new_pending); + self.selections_changed = true; + } + pub fn set_pending(&mut self, selection: Selection, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection, mode }); self.selections_changed = true; From b850e41d6f0515b9f7bc12ed6d6a30222dde1b08 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 12 Jul 2022 13:34:23 -0700 Subject: [PATCH 07/31] Add editor mouse context menu with some basic refactorings and an entry to pop the code actions --- crates/editor/src/mouse_context_menu.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 1313ad346fd751cf8d80d11add4e5cbc89d5de0f..be083b2bce2e463685b12bf9c0cd55e5d43ab4ba 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,11 +1,9 @@ -use context_menu::{ContextMenu, ContextMenuItem}; -use gpui::{ - geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, Task, ViewContext, - ViewHandle, -}; +use context_menu::ContextMenuItem; +use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext}; use crate::{ DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, Rename, SelectMode, + ToggleCodeActions, }; #[derive(Clone, PartialEq)] @@ -20,11 +18,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(deploy_context_menu); } -pub struct MouseContextMenuState { - pub context_menu: ViewHandle, - pub task: Option>, -} - pub fn deploy_context_menu( editor: &mut Editor, &DeployMouseContextMenu { position, point }: &DeployMouseContextMenu, @@ -53,6 +46,12 @@ pub fn deploy_context_menu( ContextMenuItem::item("Rename Symbol", Rename), ContextMenuItem::item("Go To Definition", GoToDefinition), ContextMenuItem::item("Find All References", FindAllReferences), + ContextMenuItem::item( + "Code Actions", + ToggleCodeActions { + deployed_from_indicator: false, + }, + ), ], cx, ); From 5366ed4404364d1612782acd52ae12b881ee9abe Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 12 Jul 2022 15:35:01 -0700 Subject: [PATCH 08/31] Add basic test for editor context menu --- crates/context_menu/src/context_menu.rs | 4 +++ crates/editor/src/mouse_context_menu.rs | 43 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 85a6cd1e19cbc851c1e4ad71d1216460c5b334e9..39477bc927b742d90de3c0e23b53c177d36faf25 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -124,6 +124,10 @@ impl ContextMenu { } } + pub fn visible(&self) -> bool { + self.visible + } + fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext) { if let Some(ix) = self .items diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index be083b2bce2e463685b12bf9c0cd55e5d43ab4ba..f30bc0a6788ca72fbab2f138bf7665344b333b9b 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -58,3 +58,46 @@ pub fn deploy_context_menu( }); cx.notify(); } + +#[cfg(test)] +mod tests { + use indoc::indoc; + + use crate::test::EditorLspTestContext; + + use super::*; + + #[gpui::test] + async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) { + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + fn te|st() + do_work();"}); + let point = cx.display_point(indoc! {" + fn test() + do_w|ork();"}); + cx.update_editor(|editor, cx| { + deploy_context_menu( + editor, + &DeployMouseContextMenu { + position: Default::default(), + point, + }, + cx, + ) + }); + + cx.assert_editor_state(indoc! {" + fn test() + do_w|ork();"}); + cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible())); + } +} From 7f3018c3f63cfee9036dba8b017d598f075469a7 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 12 Jul 2022 13:09:01 -0700 Subject: [PATCH 09/31] add show_completions_on_input setting to disable popping the completions menu automatically --- crates/editor/src/editor.rs | 324 ++++++++++----------- crates/editor/src/link_go_to_definition.rs | 86 +++--- crates/editor/src/test.rs | 56 +++- crates/settings/src/settings.rs | 5 + crates/util/src/test/marked_text.rs | 2 +- styles/package-lock.json | 1 + 6 files changed, 258 insertions(+), 216 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e58f1fc341c8404e33d0dab583de934a0bf205e5..b6d59ab3f85e5d9037e12474b6b4a383f2d420cf 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1937,6 +1937,10 @@ impl Editor { } fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { + if !cx.global::().show_completions_on_input { + return; + } + let selection = self.selections.newest_anchor(); if self .buffer @@ -6225,7 +6229,8 @@ pub fn styled_runs_for_code_label<'a>( #[cfg(test)] mod tests { use crate::test::{ - assert_text_with_selections, build_editor, select_ranges, EditorTestContext, + assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext, + EditorTestContext, }; use super::*; @@ -6236,7 +6241,6 @@ mod tests { }; use indoc::indoc; use language::{FakeLspAdapter, LanguageConfig}; - use lsp::FakeLanguageServer; use project::FakeFs; use settings::EditorSettings; use std::{cell::RefCell, rc::Rc, time::Instant}; @@ -6244,7 +6248,9 @@ mod tests { use unindent::Unindent; use util::{ assert_set_eq, - test::{marked_text_by, marked_text_ranges, marked_text_ranges_by, sample_text}, + test::{ + marked_text_by, marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker, + }, }; use workspace::{FollowableItem, ItemHandle, NavigationEntry, Pane}; @@ -9524,199 +9530,182 @@ mod tests { #[gpui::test] async fn test_completion(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(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string(), ":".to_string()]), - ..Default::default() - }), + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), ..Default::default() - }, + }), ..Default::default() - })) - .await; + }, + cx, + ) + .await; - let text = " - one + cx.set_state(indoc! {" + one| two - three - " - .unindent(); - - let fs = FakeFs::new(cx.background().clone()); - fs.insert_file("/file.rs", text).await; - - let project = Project::test(fs, ["/file.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("/file.rs", cx)) - .await - .unwrap(); - let mut fake_server = fake_servers.next().await.unwrap(); - - let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx)); - - editor.update(cx, |editor, cx| { - editor.project = Some(project); - editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(0, 3)..Point::new(0, 3)]) - }); - editor.handle_input(&Input(".".to_string()), cx); - }); - + three"}); + cx.simulate_keystroke("."); handle_completion_request( - &mut fake_server, - "/file.rs", - Point::new(0, 4), - vec![ - (Point::new(0, 4)..Point::new(0, 4), "first_completion"), - (Point::new(0, 4)..Point::new(0, 4), "second_completion"), - ], + &mut cx, + indoc! {" + one.|<> + two + three"}, + vec!["first_completion", "second_completion"], ) .await; - editor - .condition(&cx, |editor, _| editor.context_menu_visible()) + cx.condition(|editor, _| editor.context_menu_visible()) .await; - - let apply_additional_edits = editor.update(cx, |editor, cx| { + let apply_additional_edits = cx.update_editor(|editor, cx| { editor.move_down(&MoveDown, cx); - let apply_additional_edits = editor + editor .confirm_completion(&ConfirmCompletion::default(), cx) - .unwrap(); - assert_eq!( - editor.text(cx), - " - one.second_completion - two - three - " - .unindent() - ); - apply_additional_edits + .unwrap() }); + cx.assert_editor_state(indoc! {" + one.second_completion| + two + three"}); handle_resolve_completion_request( - &mut fake_server, - Some((Point::new(2, 5)..Point::new(2, 5), "\nadditional edit")), + &mut cx, + Some(( + indoc! {" + one.second_completion + two + three<>"}, + "\nadditional edit", + )), ) .await; apply_additional_edits.await.unwrap(); - assert_eq!( - editor.read_with(cx, |editor, cx| editor.text(cx)), - " - one.second_completion - two - three - additional edit - " - .unindent() - ); - - editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| { - s.select_ranges([ - Point::new(1, 3)..Point::new(1, 3), - Point::new(2, 5)..Point::new(2, 5), - ]) - }); + cx.assert_editor_state(indoc! {" + one.second_completion| + two + three + additional edit"}); - editor.handle_input(&Input(" ".to_string()), cx); - assert!(editor.context_menu.is_none()); - editor.handle_input(&Input("s".to_string()), cx); - assert!(editor.context_menu.is_none()); - }); + cx.set_state(indoc! {" + one.second_completion + two| + three| + additional edit"}); + cx.simulate_keystroke(" "); + assert!(cx.editor(|e, _| e.context_menu.is_none())); + cx.simulate_keystroke("s"); + assert!(cx.editor(|e, _| e.context_menu.is_none())); + cx.assert_editor_state(indoc! {" + one.second_completion + two s| + three s| + additional edit"}); handle_completion_request( - &mut fake_server, - "/file.rs", - Point::new(2, 7), - vec![ - (Point::new(2, 6)..Point::new(2, 7), "fourth_completion"), - (Point::new(2, 6)..Point::new(2, 7), "fifth_completion"), - (Point::new(2, 6)..Point::new(2, 7), "sixth_completion"), - ], + &mut cx, + indoc! {" + one.second_completion + two s + three + additional edit"}, + vec!["fourth_completion", "fifth_completion", "sixth_completion"], ) .await; - editor - .condition(&cx, |editor, _| editor.context_menu_visible()) + cx.condition(|editor, _| editor.context_menu_visible()) .await; - editor.update(cx, |editor, cx| { - editor.handle_input(&Input("i".to_string()), cx); - }); + cx.simulate_keystroke("i"); handle_completion_request( - &mut fake_server, - "/file.rs", - Point::new(2, 8), - vec![ - (Point::new(2, 6)..Point::new(2, 8), "fourth_completion"), - (Point::new(2, 6)..Point::new(2, 8), "fifth_completion"), - (Point::new(2, 6)..Point::new(2, 8), "sixth_completion"), - ], + &mut cx, + indoc! {" + one.second_completion + two si + three + additional edit"}, + vec!["fourth_completion", "fifth_completion", "sixth_completion"], ) .await; - editor - .condition(&cx, |editor, _| editor.context_menu_visible()) + cx.condition(|editor, _| editor.context_menu_visible()) .await; - let apply_additional_edits = editor.update(cx, |editor, cx| { - let apply_additional_edits = editor + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor .confirm_completion(&ConfirmCompletion::default(), cx) - .unwrap(); - assert_eq!( - editor.text(cx), - " - one.second_completion - two sixth_completion - three sixth_completion - additional edit - " - .unindent() - ); - apply_additional_edits + .unwrap() + }); + cx.assert_editor_state(indoc! {" + one.second_completion + two sixth_completion| + three sixth_completion| + additional edit"}); + + handle_resolve_completion_request(&mut cx, None).await; + apply_additional_edits.await.unwrap(); + + cx.update(|cx| { + cx.update_global::(|settings, _| { + settings.show_completions_on_input = false; + }) + }); + cx.set_state("editor|"); + cx.simulate_keystroke("."); + assert!(cx.editor(|e, _| e.context_menu.is_none())); + cx.simulate_keystrokes(["c", "l", "o"]); + cx.assert_editor_state("editor.clo|"); + assert!(cx.editor(|e, _| e.context_menu.is_none())); + cx.update_editor(|editor, cx| { + editor.show_completions(&ShowCompletions, cx); + }); + handle_completion_request(&mut cx, "editor.", vec!["close", "clobber"]).await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() }); - handle_resolve_completion_request(&mut fake_server, None).await; + cx.assert_editor_state("editor.close|"); + handle_resolve_completion_request(&mut cx, None).await; apply_additional_edits.await.unwrap(); - async fn handle_completion_request( - fake: &mut FakeLanguageServer, - path: &'static str, - position: Point, - completions: Vec<(Range, &'static str)>, + // Handle completion request passing a marked string specifying where the completion + // should be triggered from using '|' character, what range should be replaced, and what completions + // should be returned using '<' and '>' to delimit the range + async fn handle_completion_request<'a>( + cx: &mut EditorLspTestContext<'a>, + marked_string: &str, + completions: Vec<&'static str>, ) { - fake.handle_request::(move |params, _| { + let complete_from_marker: TextRangeMarker = '|'.into(); + let replace_range_marker: TextRangeMarker = ('<', '>').into(); + let (_, mut marked_ranges) = marked_text_ranges_by( + marked_string, + vec![complete_from_marker.clone(), replace_range_marker.clone()], + ); + + let complete_from_position = + cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start); + let replace_range = + cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone()); + + cx.handle_request::(move |url, params, _| { let completions = completions.clone(); async move { - assert_eq!( - params.text_document_position.text_document.uri, - lsp::Url::from_file_path(path).unwrap() - ); + assert_eq!(params.text_document_position.text_document.uri, url.clone()); assert_eq!( params.text_document_position.position, - lsp::Position::new(position.row, position.column) + complete_from_position ); Ok(Some(lsp::CompletionResponse::Array( completions .iter() - .map(|(range, new_text)| lsp::CompletionItem { - label: new_text.to_string(), + .map(|completion_text| lsp::CompletionItem { + label: completion_text.to_string(), text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new( - lsp::Position::new(range.start.row, range.start.column), - lsp::Position::new(range.start.row, range.start.column), - ), - new_text: new_text.to_string(), + range: replace_range.clone(), + new_text: completion_text.to_string(), })), ..Default::default() }) @@ -9728,23 +9717,26 @@ mod tests { .await; } - async fn handle_resolve_completion_request( - fake: &mut FakeLanguageServer, - edit: Option<(Range, &'static str)>, + async fn handle_resolve_completion_request<'a>( + cx: &mut EditorLspTestContext<'a>, + edit: Option<(&'static str, &'static str)>, ) { - fake.handle_request::(move |_, _| { + let edit = edit.map(|(marked_string, new_text)| { + let replace_range_marker: TextRangeMarker = ('<', '>').into(); + let (_, mut marked_ranges) = + marked_text_ranges_by(marked_string, vec![replace_range_marker.clone()]); + + let replace_range = cx + .to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone()); + + vec![lsp::TextEdit::new(replace_range, new_text.to_string())] + }); + + cx.handle_request::(move |_, _, _| { let edit = edit.clone(); async move { Ok(lsp::CompletionItem { - additional_text_edits: edit.map(|(range, new_text)| { - vec![lsp::TextEdit::new( - lsp::Range::new( - lsp::Position::new(range.start.row, range.start.column), - lsp::Position::new(range.end.row, range.end.column), - ), - new_text.to_string(), - )] - }), + additional_text_edits: edit, ..Default::default() }) } diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 2e59a7240258bebf2fe948ee524d0f3a8506f2da..f034df64b6eea4528048bb365d3f74fee14cc463 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -342,17 +342,16 @@ mod tests { test();"}); let mut requests = - cx.lsp - .handle_request::(move |_, _| async move { - Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ - lsp::LocationLink { - origin_selection_range: Some(symbol_range), - target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), - target_range, - target_selection_range: target_range, - }, - ]))) - }); + cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: Some(symbol_range), + target_uri: url.clone(), + target_range, + target_selection_range: target_range, + }, + ]))) + }); cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, @@ -387,18 +386,17 @@ mod tests { // Response without source range still highlights word cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None); let mut requests = - cx.lsp - .handle_request::(move |_, _| async move { - Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ - lsp::LocationLink { - // No origin range - origin_selection_range: None, - target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), - target_range, - target_selection_range: target_range, - }, - ]))) - }); + cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + // No origin range + origin_selection_range: None, + target_uri: url.clone(), + target_range, + target_selection_range: target_range, + }, + ]))) + }); cx.update_editor(|editor, cx| { update_go_to_definition_link( editor, @@ -495,17 +493,16 @@ mod tests { test();"}); let mut requests = - cx.lsp - .handle_request::(move |_, _| async move { - Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ - lsp::LocationLink { - origin_selection_range: Some(symbol_range), - target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), - target_range, - target_selection_range: target_range, - }, - ]))) - }); + cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: Some(symbol_range), + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); cx.update_editor(|editor, cx| { cmd_changed(editor, &CmdChanged { cmd_down: true }, cx); }); @@ -584,17 +581,16 @@ mod tests { test();"}); let mut requests = - cx.lsp - .handle_request::(move |_, _| async move { - Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ - lsp::LocationLink { - origin_selection_range: None, - target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), - target_range, - target_selection_range: target_range, - }, - ]))) - }); + cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: None, + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); cx.update_workspace(|workspace, cx| { go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx); }); diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 4e10f516c1b2d83a343b94066bec336faa730abf..dd05a14bd67bbb4e82d1f33a9d1b95be663cc44b 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -4,12 +4,14 @@ use std::{ sync::Arc, }; -use futures::StreamExt; +use anyhow::Result; +use futures::{Future, StreamExt}; 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 lsp::request; use project::Project; use settings::Settings; use util::{ @@ -110,6 +112,13 @@ impl<'a> EditorTestContext<'a> { } } + pub fn condition( + &self, + predicate: impl FnMut(&Editor, &AppContext) -> bool, + ) -> impl Future { + self.editor.condition(self.cx, predicate) + } + pub fn editor(&mut self, read: F) -> T where F: FnOnce(&Editor, &AppContext) -> T, @@ -424,6 +433,7 @@ pub struct EditorLspTestContext<'a> { pub cx: EditorTestContext<'a>, pub lsp: lsp::FakeLanguageServer, pub workspace: ViewHandle, + pub editor_lsp_url: lsp::Url, } impl<'a> EditorLspTestContext<'a> { @@ -497,6 +507,7 @@ impl<'a> EditorLspTestContext<'a> { }, lsp, workspace, + editor_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), } } @@ -520,11 +531,15 @@ impl<'a> EditorLspTestContext<'a> { pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range { let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]); assert_eq!(unmarked, self.cx.buffer_text()); + let offset_range = ranges.remove(&('[', ']').into()).unwrap()[0].clone(); + self.to_lsp_range(offset_range) + } + + pub fn to_lsp_range(&mut self, range: Range) -> lsp::Range { let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); + let start_point = range.start.to_point(&snapshot.buffer_snapshot); + let end_point = range.end.to_point(&snapshot.buffer_snapshot); - let offset_range = ranges.remove(&('[', ']').into()).unwrap()[0].clone(); - let start_point = offset_range.start.to_point(&snapshot.buffer_snapshot); - let end_point = offset_range.end.to_point(&snapshot.buffer_snapshot); self.editor(|editor, cx| { let buffer = editor.buffer().read(cx); let start = point_to_lsp( @@ -546,12 +561,45 @@ impl<'a> EditorLspTestContext<'a> { }) } + pub fn to_lsp(&mut self, offset: usize) -> lsp::Position { + let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); + let point = offset.to_point(&snapshot.buffer_snapshot); + + self.editor(|editor, cx| { + let buffer = editor.buffer().read(cx); + point_to_lsp( + buffer + .point_to_buffer_offset(point, cx) + .unwrap() + .1 + .to_point_utf16(&buffer.read(cx)), + ) + }) + } + pub fn update_workspace(&mut self, update: F) -> T where F: FnOnce(&mut Workspace, &mut ViewContext) -> T, { self.workspace.update(self.cx.cx, update) } + + pub fn handle_request( + &self, + mut handler: F, + ) -> futures::channel::mpsc::UnboundedReceiver<()> + where + T: 'static + request::Request, + T::Params: 'static + Send, + F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut, + Fut: 'static + Send + Future>, + { + let url = self.editor_lsp_url.clone(); + self.lsp.handle_request::(move |params, cx| { + let url = url.clone(); + handler(url, params, cx) + }) + } } impl<'a> Deref for EditorLspTestContext<'a> { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 6073b1ef15cd46a0a5c4c3fe5a4b4e8a9238420f..807587ac86ec9e9cf552b0dc99ffea41e4d6b068 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -25,6 +25,7 @@ pub struct Settings { pub buffer_font_size: f32, pub default_buffer_font_size: f32, pub hover_popover_enabled: bool, + pub show_completions_on_input: bool, pub vim_mode: bool, pub autosave: Autosave, pub editor_defaults: EditorSettings, @@ -83,6 +84,8 @@ pub struct SettingsFileContent { #[serde(default)] pub hover_popover_enabled: Option, #[serde(default)] + pub show_completions_on_input: Option, + #[serde(default)] pub vim_mode: Option, #[serde(default)] pub autosave: Option, @@ -118,6 +121,7 @@ impl Settings { buffer_font_size: defaults.buffer_font_size.unwrap(), default_buffer_font_size: defaults.buffer_font_size.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), + show_completions_on_input: defaults.show_completions_on_input.unwrap(), projects_online_by_default: defaults.projects_online_by_default.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), @@ -219,6 +223,7 @@ impl Settings { buffer_font_size: 14., default_buffer_font_size: 14., hover_popover_enabled: true, + show_completions_on_input: true, vim_mode: false, autosave: Autosave::Off, editor_defaults: EditorSettings { diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index 4529c8c803d35017e969220a3bccc43e8894affc..2a5969c26564e3603182e1b743b71494e31604f5 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -24,7 +24,7 @@ pub fn marked_text(marked_text: &str) -> (String, Vec) { (unmarked_text, markers.remove(&'|').unwrap_or_default()) } -#[derive(Eq, PartialEq, Hash)] +#[derive(Clone, Eq, PartialEq, Hash)] pub enum TextRangeMarker { Empty(char), Range(char, char), diff --git a/styles/package-lock.json b/styles/package-lock.json index 2eb6d3a1bfaeaa206f0cc8a0a03efa0387d144c2..49304dc2fa98dfec942bf6687be56cefc13cdf80 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From e7b1060bca7c7478f27061ee0a65e16cfb923bca Mon Sep 17 00:00:00 2001 From: K Simmons Date: Wed, 13 Jul 2022 14:29:47 -0700 Subject: [PATCH 10/31] fix merge error to use new default settings flow --- assets/settings/default.json | 16 +++------------- crates/settings/src/settings.rs | 4 ++++ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 2417d0f5d437f44957aad24128521c4899a5a678..414f83982ecdae4f2805234df0f5a7d4e917387b 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1,29 +1,25 @@ { // The name of the Zed theme to use for the UI "theme": "cave-dark", - // The name of a font to use for rendering text in the editor "buffer_font_family": "Zed Mono", - // The default font size for text in the editor "buffer_font_size": 15, - // Whether to enable vim modes and key bindings "vim_mode": false, - // Whether to show the informational hover box when moving the mouse // over symbols in the editor. "hover_popover_enabled": true, - + // Whether to pop the completions menu while typing in an editor without + // explicitly requesting it. + "show_completions_on_input": true, // Whether new projects should start out 'online'. Online projects // appear in the contacts panel under your name, so that your contacts // can see which projects you are working on. Regardless of this // setting, projects keep their last online status when you reopen them. "projects_online_by_default": true, - // Whether to use language servers to provide code intelligence. "enable_language_server": true, - // When to automatically save edited buffers. This setting can // take four values. // @@ -36,7 +32,6 @@ // 4. Save when idle for a certain amount of time: // "autosave": { "after_delay": {"milliseconds": 500} }, "autosave": "off", - // How to auto-format modified buffers when saving them. This // setting can take three values: // @@ -52,7 +47,6 @@ // } // }, "format_on_save": "language_server", - // How to soft-wrap long lines of text. This setting can take // three values: // @@ -63,18 +57,14 @@ // 2. Soft wrap lines at the preferred line length // "soft_wrap": "preferred_line_length", "soft_wrap": "none", - // The column at which to soft-wrap lines, for buffers where soft-wrap // is enabled. "preferred_line_length": 80, - // Whether to indent lines using tab characters, as opposed to multiple // spaces. "hard_tabs": false, - // How many columns a tab should occupy. "tab_size": 4, - // Different settings for specific languages. "languages": { "Plain Text": { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 807587ac86ec9e9cf552b0dc99ffea41e4d6b068..63e75364e65484e3fa4aaaaa2ca2b2790299309c 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -164,6 +164,10 @@ impl Settings { merge(&mut self.buffer_font_size, data.buffer_font_size); merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); + merge( + &mut self.show_completions_on_input, + data.show_completions_on_input, + ); merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); From 4775d839d753350733a3f723b112b40091aad514 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 13 Jul 2022 15:04:03 -0700 Subject: [PATCH 11/31] Fix incomplete language names list being used for JSON schema For now, since initializing the languages themselves is still async, create a parallel duplicated code path that is synchronous, and just provided the language names. --- crates/settings/src/settings.rs | 11 ++++++++--- crates/zed/src/languages.rs | 16 ++++++++++++++++ crates/zed/src/zed.rs | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 6073b1ef15cd46a0a5c4c3fe5a4b4e8a9238420f..2897b0ab839ce2f60ded50a9697802fa79a1942e 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -248,7 +248,7 @@ impl Settings { pub fn settings_file_json_schema( theme_names: Vec, - language_names: Vec, + language_names: &[String], ) -> serde_json::Value { let settings = SchemaSettings::draft07().with(|settings| { settings.option_add_null_type = false; @@ -275,8 +275,13 @@ pub fn settings_file_json_schema( instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), object: Some(Box::new(ObjectValidation { properties: language_names - .into_iter() - .map(|name| (name, Schema::new_ref("#/definitions/EditorSettings".into()))) + .iter() + .map(|name| { + ( + name.clone(), + Schema::new_ref("#/definitions/EditorSettings".into()), + ) + }) .collect(), ..Default::default() })), diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 82f5701f7f9ef4fbac61d8693bc9388137c39958..8dc20bdbd16dda3709d3a199c43494bb9b6ef892 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,5 +1,6 @@ use gpui::executor::Background; pub use language::*; +use lazy_static::lazy_static; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; @@ -17,6 +18,21 @@ mod typescript; #[exclude = "*.rs"] struct LanguageDir; +// TODO - Remove this once the `init` function is synchronous again. +lazy_static! { + pub static ref LANGUAGE_NAMES: Vec = LanguageDir::iter() + .filter_map(|path| { + if path.ends_with("config.toml") { + let config = LanguageDir::get(&path)?; + let config = toml::from_slice::(&config.data).ok()?; + Some(config.name.to_string()) + } else { + None + } + }) + .collect(); +} + pub async fn init(languages: Arc, _executor: Arc) { for (name, grammar, lsp_adapter) in [ ( diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d27a5b7c5be0cfd6d4d4e673be95f378d7b54a4e..a033be4dee7a0b6a9b48e0c66cfe31dc14009810 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -209,7 +209,7 @@ pub fn initialize_workspace( cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); let theme_names = app_state.themes.list().collect(); - let language_names = app_state.languages.language_names(); + let language_names = &languages::LANGUAGE_NAMES; workspace.project().update(cx, |project, cx| { let action_names = cx.all_action_names().collect::>(); From 48624b796efe13d24a731ccdf9452ee0b3f03ce2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 13 Jul 2022 15:59:11 -0700 Subject: [PATCH 12/31] 0.47.0 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14016ae243dfb89addad7b9709fc6eac844c9d9a..e666405a110ab805494b9cd79f63ce48ee2b7601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6991,7 +6991,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.46.0" +version = "0.47.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 0c5f3ddfdd8e8860510cffd870d6f3ed49270226..5535711266ba2d7adfad5216367086c2649b9458 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.46.0" +version = "0.47.0" [lib] name = "zed" From f55b24ddeef03991f6e0b396a16f674d21d4d3f5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 13 Jul 2022 16:39:22 -0700 Subject: [PATCH 13/31] Fixed modal terminal bindings to not show in command palette --- assets/keymaps/default.json | 7 ++++++- crates/terminal/src/modal.rs | 5 ++++- styles/package-lock.json | 1 - 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 0e13bae79487d7d972de09fe007567c33b6a358c..833fefd0f2cf24b5546c97661f0128bd7b08a02d 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -409,7 +409,6 @@ "bindings": { "ctrl-c": "terminal::Sigint", "escape": "terminal::Escape", - "shift-escape": "terminal::DeployModal", "ctrl-d": "terminal::Quit", "backspace": "terminal::Del", "enter": "terminal::Return", @@ -422,5 +421,11 @@ "cmd-c": "terminal::Copy", "ctrl-l": "terminal::Clear" } + }, + { + "context": "ModalTerminal", + "bindings": { + "shift-escape": "terminal::DeployModal" + } } ] \ No newline at end of file diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index 1130050690061e22703fd8594d4bf799630f76b7..708f96856b03522d5dabab61cae178a340c9db27 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -16,8 +16,11 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon if let Some(StoredConnection(stored_connection)) = possible_connection { // Create a view from the stored connection workspace.toggle_modal(cx, |_, cx| { - cx.add_view(|cx| Terminal::from_connection(stored_connection, true, cx)) + cx.add_view(|cx| Terminal::from_connection(stored_connection.clone(), true, cx)) }); + cx.set_global::>(Some(StoredConnection( + stored_connection.clone(), + ))); } else { // No connection was stored, create a new terminal if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { diff --git a/styles/package-lock.json b/styles/package-lock.json index 49304dc2fa98dfec942bf6687be56cefc13cdf80..2eb6d3a1bfaeaa206f0cc8a0a03efa0387d144c2 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From 8292ba15ed934e8d975e7b59e3dfa278baace275 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 13 Jul 2022 17:05:36 -0700 Subject: [PATCH 14/31] Avoid copying all default settings to initial user settings This would cause top-level default editor settings to override language-specific default settings. Co-authored-by: Nathan Sobo --- ...-comments.json => initial_user_settings.json} | 3 +++ crates/zed/src/zed.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) rename assets/settings/{header-comments.json => initial_user_settings.json} (91%) diff --git a/assets/settings/header-comments.json b/assets/settings/initial_user_settings.json similarity index 91% rename from assets/settings/header-comments.json rename to assets/settings/initial_user_settings.json index 6180d310df2959f89e0d4847778e67436b3557de..4c90b8a002d5f8e9ef025df578bfcb5adfc26476 100644 --- a/assets/settings/header-comments.json +++ b/assets/settings/initial_user_settings.json @@ -6,3 +6,6 @@ // To see all of Zed's default settings without changing your // custom settings, run the `open default settings` command // from the command palette or from `Zed` application menu. +{ + "buffer_font_size": 15 +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a033be4dee7a0b6a9b48e0c66cfe31dc14009810..3a2bbb9be8dc4f1ae99a0b4f5e55067296558033 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -102,14 +102,14 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { let app_state = app_state.clone(); move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { open_config_file(&SETTINGS_PATH, app_state.clone(), cx, || { - let header = Assets.load("settings/header-comments.json").unwrap(); - let json = Assets.load("settings/default.json").unwrap(); - let header = str::from_utf8(header.as_ref()).unwrap(); - let json = str::from_utf8(json.as_ref()).unwrap(); - let mut content = Rope::new(); - content.push(header); - content.push(json); - content + str::from_utf8( + Assets + .load("settings/initial_user_settings.json") + .unwrap() + .as_ref(), + ) + .unwrap() + .into() }); } }); From df838c74ca80630b3c731637771afa4ae3a52986 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 13 Jul 2022 17:31:59 -0700 Subject: [PATCH 15/31] 0.47.1 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e666405a110ab805494b9cd79f63ce48ee2b7601..72cb4d292d1c7f1326bd661dee9b7fea76fec93f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6991,7 +6991,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.47.0" +version = "0.47.1" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 5535711266ba2d7adfad5216367086c2649b9458..37bfbdee8a312aa501923a813e3251dc2b71c65e 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.47.0" +version = "0.47.1" [lib] name = "zed" From 52b29c092882aa473d74f039ecc32486dc25797a Mon Sep 17 00:00:00 2001 From: K Simmons Date: Wed, 13 Jul 2022 20:14:44 -0700 Subject: [PATCH 16/31] When tab closed, move to previous tab, and when opening an existing tab, reorder it to be after the current active tab --- crates/workspace/src/pane.rs | 51 ++++++++++++++++++++++--------- crates/workspace/src/workspace.rs | 6 ++-- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 87b6ea7547d3223dd2669d17d298eb765235a7c1..f5309ebb58e1474ec4d5e4eb55b6a807b0654291 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -71,10 +71,10 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { - pane.activate_item(action.0, true, true, cx); + pane.activate_item(action.0, true, true, false, cx); }); cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { - pane.activate_item(pane.items.len() - 1, true, true, cx); + pane.activate_item(pane.items.len() - 1, true, true, false, cx); }); cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { pane.activate_prev_item(cx); @@ -288,7 +288,7 @@ impl Pane { { let prev_active_item_index = pane.active_item_index; pane.nav_history.borrow_mut().set_mode(mode); - pane.activate_item(index, true, true, cx); + pane.activate_item(index, true, true, false, cx); pane.nav_history .borrow_mut() .set_mode(NavigationMode::Normal); @@ -380,7 +380,7 @@ impl Pane { && item.project_entry_ids(cx).as_slice() == &[project_entry_id] { let item = item.boxed_clone(); - pane.activate_item(ix, true, focus_item, cx); + pane.activate_item(ix, true, focus_item, true, cx); return Some(item); } } @@ -404,9 +404,11 @@ impl Pane { cx: &mut ViewContext, ) { // Prevent adding the same item to the pane more than once. + // If there is already an active item, reorder the desired item to be after it + // and activate it. if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) { pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, activate_pane, focus_item, cx) + pane.activate_item(item_ix, activate_pane, focus_item, true, cx) }); return; } @@ -426,7 +428,7 @@ impl Pane { }; pane.items.insert(item_ix, item); - pane.activate_item(item_ix, activate_pane, focus_item, cx); + pane.activate_item(item_ix, activate_pane, focus_item, false, cx); cx.notify(); }); } @@ -465,13 +467,31 @@ impl Pane { pub fn activate_item( &mut self, - index: usize, + mut index: usize, activate_pane: bool, focus_item: bool, + move_after_current_active: bool, cx: &mut ViewContext, ) { use NavigationMode::{GoingBack, GoingForward}; if index < self.items.len() { + if move_after_current_active { + // If there is already an active item, reorder the desired item to be after it + // and activate it. + if self.active_item_index != index && self.active_item_index < self.items.len() { + let pane_to_activate = self.items.remove(index); + if self.active_item_index < index { + index = self.active_item_index + 1; + } else if self.active_item_index < self.items.len() + 1 { + index = self.active_item_index; + // Index is less than active_item_index. Reordering will decrement the + // active_item_index, so adjust it accordingly + self.active_item_index = index - 1; + } + self.items.insert(index, pane_to_activate); + } + } + let prev_active_item_ix = mem::replace(&mut self.active_item_index, index); if prev_active_item_ix != self.active_item_index || matches!(self.nav_history.borrow().mode, GoingBack | GoingForward) @@ -502,7 +522,7 @@ impl Pane { } else if self.items.len() > 0 { index = self.items.len() - 1; } - self.activate_item(index, true, true, cx); + self.activate_item(index, true, true, false, cx); } pub fn activate_next_item(&mut self, cx: &mut ViewContext) { @@ -512,7 +532,7 @@ impl Pane { } else { index = 0; } - self.activate_item(index, true, true, cx); + self.activate_item(index, true, true, false, cx); } pub fn close_active_item( @@ -641,10 +661,13 @@ impl Pane { pane.update(&mut cx, |pane, cx| { if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { if item_ix == pane.active_item_index { - if item_ix + 1 < pane.items.len() { - pane.activate_next_item(cx); - } else if item_ix > 0 { + // Activate the previous item if possible. + // This returns the user to the previously opened tab if they closed + // a ne item they just navigated to. + if item_ix > 0 { pane.activate_prev_item(cx); + } else if item_ix + 1 < pane.items.len() { + pane.activate_next_item(cx); } } @@ -712,7 +735,7 @@ impl Pane { if has_conflict && can_save { let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); + pane.activate_item(item_ix, true, true, false, cx); cx.prompt( PromptLevel::Warning, CONFLICT_MESSAGE, @@ -733,7 +756,7 @@ impl Pane { }); let should_save = if should_prompt_for_save && !will_autosave { let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); + pane.activate_item(item_ix, true, true, false, cx); cx.prompt( PromptLevel::Warning, DIRTY_MESSAGE, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index da62fe7e54c9db8de0ff545dd01d50e3b1b3154c..5ff2243bef3d08d1e7c64a729d22c49cc4f6eb8d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -562,7 +562,7 @@ impl ItemHandle for ViewHandle { if T::should_activate_item_on_event(event) { pane.update(cx, |pane, cx| { if let Some(ix) = pane.index_for_item(&item) { - pane.activate_item(ix, true, true, cx); + pane.activate_item(ix, true, true, false, cx); pane.activate(cx); } }); @@ -1507,7 +1507,7 @@ impl Workspace { }); if let Some((pane, ix)) = result { self.activate_pane(pane.clone(), cx); - pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); + pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, false, cx)); true } else { false @@ -2880,7 +2880,7 @@ mod tests { let close_items = workspace.update(cx, |workspace, cx| { pane.update(cx, |pane, cx| { - pane.activate_item(1, true, true, cx); + pane.activate_item(1, true, true, false, cx); assert_eq!(pane.active_item().unwrap().id(), item2.id()); }); From 07d269234f4872df5ef02b711396358df56fc63a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 Jul 2022 11:49:10 +0200 Subject: [PATCH 17/31] Differentiate among tabs with the same name This commit introduces a new, optional `Item::tab_description` method that lets implementers define a description for the tab with a certain `detail`. When two or more tabs match the same description, we will increase the `detail` until tabs don't match anymore or increasing the `detail` doesn't disambiguate tabs any further. As soon as we find a valid `detail` that disambiguates tabs enough, we will pass it to `Item::tab_content`. In `Editor`, this is implemented by showing more and more of the path's suffix as `detail` is increased. --- crates/diagnostics/src/diagnostics.rs | 7 ++- crates/editor/src/editor.rs | 2 +- crates/editor/src/items.rs | 82 +++++++++++++++++++++++++-- crates/editor/src/multi_buffer.rs | 9 +-- crates/language/src/buffer.rs | 4 +- crates/project/src/worktree.rs | 5 +- crates/search/src/project_search.rs | 7 ++- crates/terminal/src/terminal.rs | 7 ++- crates/theme/src/theme.rs | 1 + crates/workspace/src/pane.rs | 43 +++++++++++++- crates/workspace/src/workspace.rs | 25 ++++++-- styles/src/styleTree/workspace.ts | 4 ++ 12 files changed, 171 insertions(+), 25 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ecc1b2df681414d322d0420ea2e67632e6b5cebc..1e89ba08535946ad0f02f0412787aaedd30790bc 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -501,7 +501,12 @@ impl ProjectDiagnosticsEditor { } impl workspace::Item for ProjectDiagnosticsEditor { - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { + fn tab_content( + &self, + _detail: Option, + style: &theme::Tab, + cx: &AppContext, + ) -> ElementBox { render_summary( &self.summary, &style.label.text, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6adefcf62adeab6c2ce52079ac18eaa1a77c1fd0..b0373c0fcb4d40b4c3e71b34675ea3c6e5fe68a1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1073,7 +1073,7 @@ impl Editor { &self.buffer } - pub fn title(&self, cx: &AppContext) -> String { + pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> { self.buffer().read(cx).title(cx) } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 0e3aca1447043aaba3354fb925babcbaa324b35f..f703acdfdbba709812baa3a22eca661af021f4ab 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,4 +1,6 @@ -use crate::{Anchor, Autoscroll, Editor, Event, ExcerptId, NavigationData, ToPoint as _}; +use crate::{ + Anchor, Autoscroll, Editor, Event, ExcerptId, MultiBuffer, NavigationData, ToPoint as _, +}; use anyhow::{anyhow, Result}; use futures::FutureExt; use gpui::{ @@ -10,7 +12,12 @@ use project::{File, Project, ProjectEntryId, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; use smallvec::SmallVec; -use std::{fmt::Write, path::PathBuf, time::Duration}; +use std::{ + borrow::Cow, + fmt::Write, + path::{Path, PathBuf}, + time::Duration, +}; use text::{Point, Selection}; use util::TryFutureExt; use workspace::{FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView}; @@ -292,9 +299,39 @@ impl Item for Editor { } } - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { - let title = self.title(cx); - Label::new(title, style.label.clone()).boxed() + fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option> { + match path_for_buffer(&self.buffer, detail, true, cx)? { + Cow::Borrowed(path) => Some(path.to_string_lossy()), + Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()), + } + } + + fn tab_content( + &self, + detail: Option, + style: &theme::Tab, + cx: &AppContext, + ) -> ElementBox { + Flex::row() + .with_child( + Label::new(self.title(cx).into(), style.label.clone()) + .aligned() + .boxed(), + ) + .with_children(detail.and_then(|detail| { + let path = path_for_buffer(&self.buffer, detail, false, cx)?; + Some( + Label::new( + path.to_string_lossy().into(), + style.description.text.clone(), + ) + .contained() + .with_style(style.description.container) + .aligned() + .boxed(), + ) + })) + .boxed() } fn project_path(&self, cx: &AppContext) -> Option { @@ -534,3 +571,38 @@ impl StatusItemView for CursorPosition { cx.notify(); } } + +fn path_for_buffer<'a>( + buffer: &ModelHandle, + mut depth: usize, + include_filename: bool, + cx: &'a AppContext, +) -> Option> { + let file = buffer.read(cx).as_singleton()?.read(cx).file()?; + + let mut path = file.path().as_ref(); + depth += 1; + while depth > 0 { + if let Some(parent) = path.parent() { + path = parent; + depth -= 1; + } else { + break; + } + } + + if depth > 0 { + let full_path = file.full_path(cx); + if include_filename { + Some(full_path.into()) + } else { + Some(full_path.parent().unwrap().to_path_buf().into()) + } + } else { + let mut path = file.path().strip_prefix(path).unwrap(); + if !include_filename { + path = path.parent().unwrap(); + } + Some(path.into()) + } +} diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 5f069d223f8eb643a97789d802830a73ce05b70e..d5b85b0aee86dca3f995e047ca998a865cfeac5e 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -14,6 +14,7 @@ use language::{ use settings::Settings; use smallvec::SmallVec; use std::{ + borrow::Cow, cell::{Ref, RefCell}, cmp, fmt, io, iter::{self, FromIterator}, @@ -1194,14 +1195,14 @@ impl MultiBuffer { .collect() } - pub fn title(&self, cx: &AppContext) -> String { - if let Some(title) = self.title.clone() { - return title; + pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { + if let Some(title) = self.title.as_ref() { + return title.into(); } if let Some(buffer) = self.as_singleton() { if let Some(file) = buffer.read(cx).file() { - return file.file_name(cx).to_string_lossy().into(); + return file.file_name(cx).to_string_lossy(); } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 0d56ac19798baad3fb3bf22b53ab9f599d3b3166..ee24539287aa97169a1b7ab49df4080c6240a40f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -20,7 +20,7 @@ use std::{ any::Any, cmp::{self, Ordering}, collections::{BTreeMap, HashMap}, - ffi::OsString, + ffi::OsStr, future::Future, iter::{self, Iterator, Peekable}, mem, @@ -185,7 +185,7 @@ pub trait File: Send + Sync { /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. - fn file_name(&self, cx: &AppContext) -> OsString; + fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr; fn is_deleted(&self) -> bool; diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index dbd4b443a1b0b4bac0a66409f6fc203760cd940b..cc972b9bcdefc86ad2e675637f014096b83ed034 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1646,11 +1646,10 @@ impl language::File for File { /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. - fn file_name(&self, cx: &AppContext) -> OsString { + fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr { self.path .file_name() - .map(|name| name.into()) - .unwrap_or_else(|| OsString::from(&self.worktree.read(cx).root_name)) + .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name)) } fn is_deleted(&self) -> bool { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index e1acc6a77127240fe60340640b9d5154c3f9509d..5098222ae0f9a0aed5eb1052c276ce48975ffa85 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -220,7 +220,12 @@ impl Item for ProjectSearchView { .update(cx, |editor, cx| editor.deactivated(cx)); } - fn tab_content(&self, tab_theme: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { + fn tab_content( + &self, + _detail: Option, + tab_theme: &theme::Tab, + cx: &gpui::AppContext, + ) -> ElementBox { let settings = cx.global::(); let search_theme = &settings.theme.search; Flex::row() diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 12c092d6e6ffd78fb326aa1eb5e79f534fb41f38..71587ef13529d04275cbda1c77faaf635d23dd55 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -324,7 +324,12 @@ impl View for Terminal { } impl Item for Terminal { - fn tab_content(&self, tab_theme: &theme::Tab, cx: &gpui::AppContext) -> ElementBox { + fn tab_content( + &self, + _detail: Option, + tab_theme: &theme::Tab, + cx: &gpui::AppContext, + ) -> ElementBox { let settings = cx.global::(); let search_theme = &settings.theme.search; //TODO properly integrate themes diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 7936a9b6bb864f2f6cdc949fe2672af2069bde50..2299bc3477fa55fef6ab1d956c3646de408a16c8 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -93,6 +93,7 @@ pub struct Tab { pub container: ContainerStyle, #[serde(flatten)] pub label: LabelStyle, + pub description: ContainedText, pub spacing: f32, pub icon_width: f32, pub icon_close: Color, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 87b6ea7547d3223dd2669d17d298eb765235a7c1..6862cc0028eb4f648b53d1a2713fd593aff3f516 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -840,8 +840,10 @@ impl Pane { } else { None }; + let mut row = Flex::row().scrollable::(1, autoscroll, cx); - for (ix, item) in self.items.iter().enumerate() { + for (ix, (item, detail)) in self.items.iter().zip(self.tab_details(cx)).enumerate() { + let detail = if detail == 0 { None } else { Some(detail) }; let is_active = ix == self.active_item_index; row.add_child({ @@ -850,7 +852,7 @@ impl Pane { } else { theme.workspace.tab.clone() }; - let title = item.tab_content(&tab_style, cx); + let title = item.tab_content(detail, &tab_style, cx); let mut style = if is_active { theme.workspace.active_tab.clone() @@ -971,6 +973,43 @@ impl Pane { row.boxed() }) } + + fn tab_details(&self, cx: &AppContext) -> Vec { + let mut tab_details = (0..self.items.len()).map(|_| 0).collect::>(); + + let mut tab_descriptions = HashMap::default(); + let mut done = false; + while !done { + done = true; + + // Store item indices by their tab description. + for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() { + if let Some(description) = item.tab_description(*detail, cx) { + if *detail == 0 + || Some(&description) != item.tab_description(detail - 1, cx).as_ref() + { + tab_descriptions + .entry(description) + .or_insert(Vec::new()) + .push(ix); + } + } + } + + // If two or more items have the same tab description, increase their level + // of detail and try again. + for (_, item_ixs) in tab_descriptions.drain() { + if item_ixs.len() > 1 { + done = false; + for ix in item_ixs { + tab_details[ix] += 1; + } + } + } + } + + tab_details + } } impl Entity for Pane { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index da62fe7e54c9db8de0ff545dd01d50e3b1b3154c..d72704da0120f979081a72457bbd2690794b9177 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -256,7 +256,11 @@ pub trait Item: View { fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { false } - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; + fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option> { + None + } + fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) + -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn is_singleton(&self, cx: &AppContext) -> bool; @@ -409,7 +413,9 @@ impl FollowableItemHandle for ViewHandle { } pub trait ItemHandle: 'static + fmt::Debug { - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; + fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option>; + fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) + -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn is_singleton(&self, cx: &AppContext) -> bool; @@ -463,8 +469,17 @@ impl dyn ItemHandle { } impl ItemHandle for ViewHandle { - fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { - self.read(cx).tab_content(style, cx) + fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option> { + self.read(cx).tab_description(detail, cx) + } + + fn tab_content( + &self, + detail: Option, + style: &theme::Tab, + cx: &AppContext, + ) -> ElementBox { + self.read(cx).tab_content(detail, style, cx) } fn project_path(&self, cx: &AppContext) -> Option { @@ -3277,7 +3292,7 @@ mod tests { } impl Item for TestItem { - fn tab_content(&self, _: &theme::Tab, _: &AppContext) -> ElementBox { + fn tab_content(&self, _: Option, _: &theme::Tab, _: &AppContext) -> ElementBox { Empty::new().boxed() } diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 36d47bed9215680b2fd63367dd7127d28a9d9310..ef5b1fe69ce8c4feaf7b4fdcdc0f2622a19680f2 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -27,6 +27,10 @@ export default function workspace(theme: Theme) { left: 8, right: 8, }, + description: { + margin: { left: 6, top: 1 }, + ...text(theme, "sans", "muted", { size: "2xs" }) + } }; const activeTab = { From 80b45ef93bbafdd483a5704ab47cac88a3197ac0 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 14 Jul 2022 13:23:04 +0200 Subject: [PATCH 18/31] Precompile plugins depending on target triple --- crates/plugin_runtime/Cargo.toml | 2 +- crates/plugin_runtime/build.rs | 19 ++++++--- crates/zed/build.rs | 4 ++ crates/zed/src/languages.rs | 6 +++ crates/zed/src/languages/language_plugin.rs | 45 +++++++++++---------- styles/package-lock.json | 1 + 6 files changed, 50 insertions(+), 27 deletions(-) diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index 21c1ab973d44132ab59415a50034be13b661dee1..a8c0a063a87c2a2972933409f5e965249f42d8f1 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -15,4 +15,4 @@ pollster = "0.2.5" smol = "1.2.5" [build-dependencies] -wasmtime = "0.38" +wasmtime = { version = "0.38", features = ["all-arch"] } diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 8a0c5c57de75a425d3b9faf80714a630da27f138..49c3f89a808b0db003f8f0522e2e4b1b2985d6de 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -27,6 +27,11 @@ fn main() { unknown => panic!("unknown profile `{}`", unknown), }; + // Get the target architecture for pre-cross-compilation of plugins + // and write it to disk to be used when embedding plugins + let target_triple = std::env::var("TARGET").unwrap().to_string(); + println!("cargo:rerun-if-env-changed=TARGET"); + // Invoke cargo to build the plugins let build_successful = std::process::Command::new("cargo") .args([ @@ -43,7 +48,7 @@ fn main() { assert!(build_successful); // Find all compiled binaries - let engine = create_default_engine(); + let engine = create_default_engine(&target_triple); let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target)) .expect("Could not find compiled plugins in target"); @@ -61,7 +66,7 @@ fn main() { if let Some(path) = is_wasm() { 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); + precompile(&out_path, &engine, &target_triple); } } } @@ -69,21 +74,25 @@ fn main() { /// Creates an engine with the default configuration. /// N.B. This must create an engine with the same config as the one /// in `plugin_runtime/src/plugin.rs`. -fn create_default_engine() -> Engine { +fn create_default_engine(target_triple: &str) -> Engine { let mut config = Config::default(); + config + .target(target_triple) + .expect(&format!("Could not set target to `{}`", target_triple)); config.async_support(true); config.consume_fuel(true); Engine::new(&config).expect("Could not create precompilation engine") } -fn precompile(path: &Path, engine: &Engine) { +fn precompile(path: &Path, engine: &Engine, target_triple: &str) { 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(), + target_triple, )); let mut out_file = std::fs::File::create(out_path) .expect("Could not create output file for precompiled module"); diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 7a0646dda6d7e25f7ef2d258a3bf74941c420d3e..3d1cb3d170c547346d8bd8b659c698fab774cdf5 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -2,6 +2,10 @@ use std::process::Command; fn main() { println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.14"); + println!( + "cargo:rustc-env=TARGET_TRIPLE={}", + std::env::var("TARGET").unwrap() + ); let output = Command::new("npm") .current_dir("../../styles") diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 8dc20bdbd16dda3709d3a199c43494bb9b6ef892..8dc37a3afe3f5c47a46086305e960fb545bd6847 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -3,6 +3,7 @@ pub use language::*; use lazy_static::lazy_static; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; +// use util::ResultExt; mod c; mod go; @@ -54,6 +55,11 @@ pub async fn init(languages: Arc, _executor: Arc) "json", tree_sitter_json::language(), Some(CachedLspAdapter::new(json::JsonLspAdapter).await), + // TODO: switch back to plugin + // match language_plugin::new_json(executor).await.log_err() { + // Some(lang) => Some(CachedLspAdapter::new(lang).await), + // None => None, + // }, ), ( "markdown", diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 4fbc63a667f507c087d77da3ddc4d1e6c063b914..c37d61f801c2cf073f2af3defd702ab3a527bc09 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -5,30 +5,33 @@ use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; -// use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; -use plugin_runtime::{Plugin, WasiFn}; +use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; -// pub async fn new_json(executor: Arc) -> Result { -// let plugin = PluginBuilder::new_default()? -// .host_function_async("command", |command: String| async move { -// let mut args = command.split(' '); -// let command = args.next().unwrap(); -// smol::process::Command::new(command) -// .args(args) -// .output() -// .await -// .log_err() -// .map(|output| output.stdout) -// })? -// .init(PluginBinary::Precompiled(include_bytes!( -// "../../../../plugins/bin/json_language.wasm.pre" -// ))) -// .await?; -// -// PluginLspAdapter::new(plugin, executor).await -// } +#[allow(dead_code)] +pub async fn new_json(executor: Arc) -> Result { + let bytes = include_bytes!(concat!( + "../../../../plugins/bin/json_language.wasm.", + env!("TARGET_TRIPLE"), + )); + + let plugin = PluginBuilder::new_default()? + .host_function_async("command", |command: String| async move { + let mut args = command.split(' '); + let command = args.next().unwrap(); + smol::process::Command::new(command) + .args(args) + .output() + .await + .log_err() + .map(|output| output.stdout) + })? + .init(PluginBinary::Precompiled(bytes)) + .await?; + + PluginLspAdapter::new(plugin, executor).await +} pub struct PluginLspAdapter { name: WasiFn<(), String>, diff --git a/styles/package-lock.json b/styles/package-lock.json index 2eb6d3a1bfaeaa206f0cc8a0a03efa0387d144c2..49304dc2fa98dfec942bf6687be56cefc13cdf80 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From af1ad474e3a9cb9e9e6feddb446e19b556f732f7 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 14 Jul 2022 13:46:41 +0200 Subject: [PATCH 19/31] Update docs --- crates/plugin_runtime/README.md | 34 +++++++++++++++++++++++++-------- crates/plugin_runtime/build.rs | 2 -- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 8ac843cb02f0491dbdc09250e51734440abe561a..012f1563625135f5ca088f4e0272b0cb6820f65d 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -152,7 +152,7 @@ Plugins in the `plugins` directory are automatically recompiled and serialized t - `plugin.wasm` is the plugin compiled to Wasm. As a baseline, this should be about 4MB for debug builds and 2MB for release builds, but it depends on the specific plugin being built. -- `plugin.wasm.pre` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-agnostic cranelift-specific IR. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin. +- `plugin.wasm.the-target-triple` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-specific native code, determined by the `TARGET` cargo exposes at compile-time. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin. For all intents and purposes, it is *highly recommended* that you use precompiled plugins where possible, as they are much more lightweight and take much less time to instantiate. @@ -246,18 +246,36 @@ Once all imports are marked, we can instantiate the plugin. To instantiate the p ```rust let plugin = builder .init( - true, - include_bytes!("../../../plugins/bin/cool_plugin.wasm.pre"), + PluginBinary::Precompiled(bytes), ) .await .unwrap(); ``` -The `.init` method currently takes two arguments: - -1. First, the 'precompiled' flag, indicating whether the plugin is *normal* (`.wasm`) or precompiled (`.wasm.pre`). When using a precompiled plugin, set this flag to `true`. - -2. Second, the raw plugin Wasm itself, as an array of bytes. When not precompiled, this can be either the Wasm binary format (`.wasm`) or the Wasm textual format (`.wat`). When precompiled, this must be the precompiled plugin (`.wasm.pre`). +The `.init` method takes a single argument containing the plugin binary. + +1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). + +2. If precompiled, use `PluginBinary::Precompiled(bytes)`. This supports precompiled plugins ending in `.wasm.the-target-triple`. You need to be extra-careful when using precompiled plugins to ensure that the plugin target matches the target of the binary you are compiling: + + a. To tell the current crate what the current `target` triple is, add the following line to the crate's `build.rs`: + + ```rust + println!( + "cargo:rustc-env=TARGET_TRIPLE={}", + std::env::var("TARGET").unwrap() + ); + ``` + + b. To get the correct precompiled binary in the crate itself, read this exposed environment variable and include the plugin: + + ```rust + let bytes = include_bytes!(concat!( + "../../../plugins/bin/plugin.wasm.", + env!("TARGET_TRIPLE"), + )); + let precompiled = PluginBinary::Precompiled(bytes); + ``` The `.init` method is asynchronous, and must be `.await`ed upon. If the plugin is malformed or doesn't import the right functions, an error will be raised. diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 49c3f89a808b0db003f8f0522e2e4b1b2985d6de..dbfdebf9a88bcced99daa1d3373b872e2c6623ad 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -28,9 +28,7 @@ fn main() { }; // Get the target architecture for pre-cross-compilation of plugins - // and write it to disk to be used when embedding plugins let target_triple = std::env::var("TARGET").unwrap().to_string(); - println!("cargo:rerun-if-env-changed=TARGET"); // Invoke cargo to build the plugins let build_successful = std::process::Command::new("cargo") From b3ac63b7b50f42711e6950e3f4509f326ff77dbe Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 14 Jul 2022 15:05:54 +0200 Subject: [PATCH 20/31] Remove triple-based suffix --- crates/plugin_runtime/README.md | 23 ++------------------- crates/plugin_runtime/build.rs | 18 ++++++++-------- crates/zed/build.rs | 4 ---- crates/zed/src/languages/language_plugin.rs | 9 +++----- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 012f1563625135f5ca088f4e0272b0cb6820f65d..38d1c0bb5d1d2bc44f0c150c8667774a998befaf 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -152,7 +152,7 @@ Plugins in the `plugins` directory are automatically recompiled and serialized t - `plugin.wasm` is the plugin compiled to Wasm. As a baseline, this should be about 4MB for debug builds and 2MB for release builds, but it depends on the specific plugin being built. -- `plugin.wasm.the-target-triple` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-specific native code, determined by the `TARGET` cargo exposes at compile-time. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin. +- `plugin.wasm.pre` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-specific native code, determined by the `TARGET` cargo exposes at compile-time. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin. For all intents and purposes, it is *highly recommended* that you use precompiled plugins where possible, as they are much more lightweight and take much less time to instantiate. @@ -256,26 +256,7 @@ The `.init` method takes a single argument containing the plugin binary. 1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). -2. If precompiled, use `PluginBinary::Precompiled(bytes)`. This supports precompiled plugins ending in `.wasm.the-target-triple`. You need to be extra-careful when using precompiled plugins to ensure that the plugin target matches the target of the binary you are compiling: - - a. To tell the current crate what the current `target` triple is, add the following line to the crate's `build.rs`: - - ```rust - println!( - "cargo:rustc-env=TARGET_TRIPLE={}", - std::env::var("TARGET").unwrap() - ); - ``` - - b. To get the correct precompiled binary in the crate itself, read this exposed environment variable and include the plugin: - - ```rust - let bytes = include_bytes!(concat!( - "../../../plugins/bin/plugin.wasm.", - env!("TARGET_TRIPLE"), - )); - let precompiled = PluginBinary::Precompiled(bytes); - ``` +2. If precompiled, use `PluginBinary::Precompiled(bytes)`. This supports precompiled plugins ending in `.wasm.pre`. You need to be extra-careful when using precompiled plugins to ensure that the plugin target matches the target of the binary you are compiling. The `.init` method is asynchronous, and must be `.await`ed upon. If the plugin is malformed or doesn't import the right functions, an error will be raised. diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index dbfdebf9a88bcced99daa1d3373b872e2c6623ad..0614659b5ed19b629df947ff10c92494ab65fdaa 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -26,10 +26,6 @@ fn main() { "release" => (&["--release"][..], "release"), unknown => panic!("unknown profile `{}`", unknown), }; - - // Get the target architecture for pre-cross-compilation of plugins - let target_triple = std::env::var("TARGET").unwrap().to_string(); - // Invoke cargo to build the plugins let build_successful = std::process::Command::new("cargo") .args([ @@ -45,8 +41,13 @@ fn main() { .success(); assert!(build_successful); - // Find all compiled binaries + // Get the target architecture for pre-cross-compilation of plugins + // and create and engine with the appropriate config + let target_triple = std::env::var("TARGET").unwrap().to_string(); + println!("cargo:rerun-if-env-changed=TARGET"); let engine = create_default_engine(&target_triple); + + // Find all compiled binaries let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target)) .expect("Could not find compiled plugins in target"); @@ -64,7 +65,7 @@ fn main() { if let Some(path) = is_wasm() { 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, &target_triple); + precompile(&out_path, &engine); } } } @@ -82,15 +83,14 @@ fn create_default_engine(target_triple: &str) -> Engine { Engine::new(&config).expect("Could not create precompilation engine") } -fn precompile(path: &Path, engine: &Engine, target_triple: &str) { +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(), - target_triple, )); let mut out_file = std::fs::File::create(out_path) .expect("Could not create output file for precompiled module"); diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 3d1cb3d170c547346d8bd8b659c698fab774cdf5..7a0646dda6d7e25f7ef2d258a3bf74941c420d3e 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -2,10 +2,6 @@ use std::process::Command; fn main() { println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.14"); - println!( - "cargo:rustc-env=TARGET_TRIPLE={}", - std::env::var("TARGET").unwrap() - ); let output = Command::new("npm") .current_dir("../../styles") diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index c37d61f801c2cf073f2af3defd702ab3a527bc09..bd9a9d005fd7643ab880786f92de2055a69a9380 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -11,11 +11,6 @@ use util::ResultExt; #[allow(dead_code)] pub async fn new_json(executor: Arc) -> Result { - let bytes = include_bytes!(concat!( - "../../../../plugins/bin/json_language.wasm.", - env!("TARGET_TRIPLE"), - )); - let plugin = PluginBuilder::new_default()? .host_function_async("command", |command: String| async move { let mut args = command.split(' '); @@ -27,7 +22,9 @@ pub async fn new_json(executor: Arc) -> Result { .log_err() .map(|output| output.stdout) })? - .init(PluginBinary::Precompiled(bytes)) + .init(PluginBinary::Precompiled(include_bytes!( + "../../../../plugins/bin/json_language.wasm.pre", + ))) .await?; PluginLspAdapter::new(plugin, executor).await From fd5cb02ea9a1eb3c6945a182752e650ba501c2bf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 Jul 2022 15:12:16 +0200 Subject: [PATCH 21/31] Truncate description in tab title when it is too long --- crates/editor/src/editor.rs | 1 + crates/editor/src/items.rs | 8 +++++++- crates/search/src/project_search.rs | 4 +--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b0373c0fcb4d40b4c3e71b34675ea3c6e5fe68a1..72ba6d60af5f22af5ff4368bef8c8d090a36c744 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -35,6 +35,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; +pub use items::MAX_TAB_TITLE_LEN; pub use language::{char_kind, CharKind}; use language::{ BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticSeverity, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f703acdfdbba709812baa3a22eca661af021f4ab..8f215076bb57fe8f16e1f6dcaf6cc9fb7870ee23 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -23,6 +23,7 @@ use util::TryFutureExt; use workspace::{FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, StatusItemView}; pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); +pub const MAX_TAB_TITLE_LEN: usize = 24; impl FollowableItem for Editor { fn from_state_proto( @@ -320,9 +321,14 @@ impl Item for Editor { ) .with_children(detail.and_then(|detail| { let path = path_for_buffer(&self.buffer, detail, false, cx)?; + let description = path.to_string_lossy(); Some( Label::new( - path.to_string_lossy().into(), + if description.len() > MAX_TAB_TITLE_LEN { + description[..MAX_TAB_TITLE_LEN].to_string() + "…" + } else { + description.into() + }, style.description.text.clone(), ) .contained() diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 5098222ae0f9a0aed5eb1052c276ce48975ffa85..622b84633ce76f5b35e1500979c8557a192f3cba 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -4,7 +4,7 @@ use crate::{ ToggleWholeWord, }; use collections::HashMap; -use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll}; +use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN}; use gpui::{ actions, elements::*, platform::CursorStyle, Action, AppContext, ElementBox, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, @@ -26,8 +26,6 @@ use workspace::{ actions!(project_search, [Deploy, SearchInNew, ToggleFocus]); -const MAX_TAB_TITLE_LEN: usize = 24; - #[derive(Default)] struct ActiveSearches(HashMap, WeakViewHandle>); From 52e4774e8a6965ceb5b82b264404a8e0392a9628 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 14 Jul 2022 15:13:12 +0200 Subject: [PATCH 22/31] Re-enable JSON plugin --- crates/zed/src/languages.rs | 15 ++- crates/zed/src/languages/json.rs | 106 -------------------- crates/zed/src/languages/language_plugin.rs | 1 - 3 files changed, 6 insertions(+), 116 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 8dc37a3afe3f5c47a46086305e960fb545bd6847..2937d05516c3184f2bdce292f130d8dd54e57310 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -3,12 +3,11 @@ pub use language::*; use lazy_static::lazy_static; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; -// use util::ResultExt; +use util::ResultExt; mod c; mod go; mod installation; -mod json; mod language_plugin; mod python; mod rust; @@ -34,7 +33,7 @@ lazy_static! { .collect(); } -pub async fn init(languages: Arc, _executor: Arc) { +pub async fn init(languages: Arc, executor: Arc) { for (name, grammar, lsp_adapter) in [ ( "c", @@ -54,12 +53,10 @@ pub async fn init(languages: Arc, _executor: Arc) ( "json", tree_sitter_json::language(), - Some(CachedLspAdapter::new(json::JsonLspAdapter).await), - // TODO: switch back to plugin - // match language_plugin::new_json(executor).await.log_err() { - // Some(lang) => Some(CachedLspAdapter::new(lang).await), - // None => None, - // }, + match language_plugin::new_json(executor).await.log_err() { + Some(lang) => Some(CachedLspAdapter::new(lang).await), + None => None, + }, ), ( "markdown", diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs deleted file mode 100644 index 7b6569d3362fdb1e551f194f45a1201ac8f4e7fe..0000000000000000000000000000000000000000 --- a/crates/zed/src/languages/json.rs +++ /dev/null @@ -1,106 +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 collections::HashMap; -use futures::StreamExt; -use language::{LanguageServerName, LspAdapter}; -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 LspAdapter 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 language_ids(&self) -> HashMap { - [("JSON".into(), "jsonc".into())].into_iter().collect() - } -} diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index bd9a9d005fd7643ab880786f92de2055a69a9380..8f87b03908856c1eefb57ba323fc2bbdf96f826b 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -9,7 +9,6 @@ use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; -#[allow(dead_code)] pub async fn new_json(executor: Arc) -> Result { let plugin = PluginBuilder::new_default()? .host_function_async("command", |command: String| async move { From 91fefae96ad8b14feec6db247e5b2a02788a771d Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Thu, 14 Jul 2022 16:05:10 +0200 Subject: [PATCH 23/31] Add retries to failing terminal test --- crates/terminal/src/terminal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 12c092d6e6ffd78fb326aa1eb5e79f534fb41f38..b0bdf5687226acd1aa499cc83a626509aeca43e3 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -468,7 +468,7 @@ mod tests { ///Basic integration test, can we get the terminal to show up, execute a command, //and produce noticable output? - #[gpui::test] + #[gpui::test(retries = 5)] async fn test_terminal(cx: &mut TestAppContext) { let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx)); From 49ef33090cc7c42e055b1b7b52bf53fb40171fd5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 Jul 2022 16:42:30 +0200 Subject: [PATCH 24/31] Add test for tab disambiguation --- crates/workspace/src/workspace.rs | 67 ++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d72704da0120f979081a72457bbd2690794b9177..3aa85c4a9577540092bee02ebed536fb0479d6fa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2701,11 +2701,62 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { #[cfg(test)] mod tests { + use std::cell::Cell; + use super::*; use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext}; use project::{FakeFs, Project, ProjectEntryId}; use serde_json::json; + #[gpui::test] + async fn test_tab_disambiguation(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + Settings::test_async(cx); + + let fs = FakeFs::new(cx.background()); + let project = Project::test(fs, [], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + + // Adding an item with no ambiguity renders the tab without detail. + let item1 = cx.add_view(window_id, |_| { + let mut item = TestItem::new(); + item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]); + item + }); + workspace.update(cx, |workspace, cx| { + workspace.add_item(Box::new(item1.clone()), cx); + }); + item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None)); + + // Adding an item that creates ambiguity increases the level of detail on + // both tabs. + let item2 = cx.add_view(window_id, |_| { + let mut item = TestItem::new(); + item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); + item + }); + workspace.update(cx, |workspace, cx| { + workspace.add_item(Box::new(item2.clone()), cx); + }); + item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); + item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); + + // Adding an item that creates ambiguity increases the level of detail only + // on the ambiguous tabs. In this case, the ambiguity can't be resolved so + // we stop at the highest detail available. + let item3 = cx.add_view(window_id, |_| { + let mut item = TestItem::new(); + item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); + item + }); + workspace.update(cx, |workspace, cx| { + workspace.add_item(Box::new(item3.clone()), cx); + }); + item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); + item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); + item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); + } + #[gpui::test] async fn test_tracking_active_path(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); @@ -3226,6 +3277,8 @@ mod tests { project_entry_ids: Vec, project_path: Option, nav_history: Option, + tab_descriptions: Option>, + tab_detail: Cell>, } enum TestItemEvent { @@ -3245,6 +3298,8 @@ mod tests { project_entry_ids: self.project_entry_ids.clone(), project_path: self.project_path.clone(), nav_history: None, + tab_descriptions: None, + tab_detail: Default::default(), } } } @@ -3262,6 +3317,8 @@ mod tests { project_path: None, is_singleton: true, nav_history: None, + tab_descriptions: None, + tab_detail: Default::default(), } } @@ -3292,7 +3349,15 @@ mod tests { } impl Item for TestItem { - fn tab_content(&self, _: Option, _: &theme::Tab, _: &AppContext) -> ElementBox { + fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option> { + self.tab_descriptions.as_ref().and_then(|descriptions| { + let description = *descriptions.get(detail).or(descriptions.last())?; + Some(description.into()) + }) + } + + fn tab_content(&self, detail: Option, _: &theme::Tab, _: &AppContext) -> ElementBox { + self.tab_detail.set(detail); Empty::new().boxed() } From d4ee372eabe670f1c7d94b3241cec45ea8a5395f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 Jul 2022 16:46:45 +0200 Subject: [PATCH 25/31] :art: --- crates/editor/src/items.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8f215076bb57fe8f16e1f6dcaf6cc9fb7870ee23..5bb8d4d0b2f0bc105608d0f672a7ee74be1fde17 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -580,24 +580,28 @@ impl StatusItemView for CursorPosition { fn path_for_buffer<'a>( buffer: &ModelHandle, - mut depth: usize, + mut height: usize, include_filename: bool, cx: &'a AppContext, ) -> Option> { let file = buffer.read(cx).as_singleton()?.read(cx).file()?; - - let mut path = file.path().as_ref(); - depth += 1; - while depth > 0 { - if let Some(parent) = path.parent() { - path = parent; - depth -= 1; + // Ensure we always render at least the filename. + height += 1; + + let mut prefix = file.path().as_ref(); + while height > 0 { + if let Some(parent) = prefix.parent() { + prefix = parent; + height -= 1; } else { break; } } - if depth > 0 { + // Here we could have just always used `full_path`, but that is very + // allocation-heavy and so we try to use a `Cow` if we haven't + // traversed all the way up to the worktree's root. + if height > 0 { let full_path = file.full_path(cx); if include_filename { Some(full_path.into()) @@ -605,7 +609,7 @@ fn path_for_buffer<'a>( Some(full_path.parent().unwrap().to_path_buf().into()) } } else { - let mut path = file.path().strip_prefix(path).unwrap(); + let mut path = file.path().strip_prefix(prefix).unwrap(); if !include_filename { path = path.parent().unwrap(); } From ee61671f927fa04c252f804cb202a42d926a1dcf Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 13:10:01 -0700 Subject: [PATCH 26/31] Tidied up the terminal theme --- styles/src/styleTree/terminal.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/styles/src/styleTree/terminal.ts b/styles/src/styleTree/terminal.ts index bc133f09c8a0564854120e1dd329de73e9f05353..1b82a591073857f463d816a43bdeb6121a88b337 100644 --- a/styles/src/styleTree/terminal.ts +++ b/styles/src/styleTree/terminal.ts @@ -1,7 +1,14 @@ import Theme from "../themes/common/theme"; -import { border, modalShadow } from "./components"; +import { border, modalShadow, player } from "./components"; export default function terminal(theme: Theme) { + /** + * Colors are controlled per-cell in the terminal grid. + * Cells can be set to any of these more 'theme-capable' colors + * or can be set directly with RGB values. + * Here are the common interpretations of these names: + * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + */ let colors = { black: theme.ramps.neutral(0).hex(), red: theme.ramps.red(0.5).hex(), @@ -11,7 +18,7 @@ export default function terminal(theme: Theme) { magenta: theme.ramps.magenta(0.5).hex(), cyan: theme.ramps.cyan(0.5).hex(), white: theme.ramps.neutral(7).hex(), - brightBlack: theme.ramps.neutral(2).hex(), + brightBlack: theme.ramps.neutral(4).hex(), brightRed: theme.ramps.red(0.25).hex(), brightGreen: theme.ramps.green(0.25).hex(), brightYellow: theme.ramps.yellow(0.25).hex(), @@ -19,10 +26,19 @@ export default function terminal(theme: Theme) { brightMagenta: theme.ramps.magenta(0.25).hex(), brightCyan: theme.ramps.cyan(0.25).hex(), brightWhite: theme.ramps.neutral(7).hex(), + /** + * Default color for characters + */ foreground: theme.ramps.neutral(7).hex(), + /** + * Default color for the rectangle background of a cell + */ background: theme.ramps.neutral(0).hex(), modalBackground: theme.ramps.neutral(1).hex(), - cursor: theme.ramps.neutral(7).hex(), + /** + * Default color for the cursor + */ + cursor: player(theme, 1).selection.cursor, dimBlack: theme.ramps.neutral(7).hex(), dimRed: theme.ramps.red(0.75).hex(), dimGreen: theme.ramps.green(0.75).hex(), From 0e257b400921d498ca20b9fa87e3a51de118cee2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 13:15:02 -0700 Subject: [PATCH 27/31] Increased test duration --- styles/package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/styles/package-lock.json b/styles/package-lock.json index 49304dc2fa98dfec942bf6687be56cefc13cdf80..2eb6d3a1bfaeaa206f0cc8a0a03efa0387d144c2 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From cf5c20c3a5cdfe824f4e890f73915caa179cad4b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 13:15:42 -0700 Subject: [PATCH 28/31] Remembered how to use a terminal --- crates/terminal/src/terminal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index b0bdf5687226acd1aa499cc83a626509aeca43e3..d18ea50f44e7730d1683f159dd565207d4818e7f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -479,7 +479,7 @@ mod tests { terminal.carriage_return(&Return, cx); }); - cx.set_condition_duration(Some(Duration::from_secs(2))); + cx.set_condition_duration(Some(Duration::from_secs(5))); terminal .condition(cx, |terminal, cx| { let term = terminal.connection.read(cx).term.clone(); From b5765bc8cd7f60b506a909d3e21bbfa63ef226d8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 15 Jul 2022 09:00:10 +0200 Subject: [PATCH 29/31] Illustrate `format_on_save` external command using prettier This gives us a chance to highlight a real-world scenario that a lot of our users will want to use, as well as showcasing the special `{buffer_path}` argument. --- assets/settings/default.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 414f83982ecdae4f2805234df0f5a7d4e917387b..855854bd36f704a42f8036f00aedaf4795f72a52 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -42,10 +42,10 @@ // 3. Format code using an external command: // "format_on_save": { // "external": { - // "command": "sed", - // "arguments": ["-e", "s/ *$//"] + // "command": "prettier", + // "arguments": ["--stdin-filepath", "{buffer_path}"] // } - // }, + // } "format_on_save": "language_server", // How to soft-wrap long lines of text. This setting can take // three values: From 42d68af073f26177cfa5474c34ab3fb28a89b1e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 15 Jul 2022 10:41:07 +0200 Subject: [PATCH 30/31] Add instructions to install redis and include it in Procfile --- Procfile | 1 + README.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/Procfile b/Procfile index a64b411ef3224a8b6bbf4596bff109b125462bf4..780479b95881761f774d743064f2238669b1ee56 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ web: cd ../zed.dev && PORT=3000 npx next dev collab: cd crates/collab && cargo run +redis: redis-server diff --git a/README.md b/README.md index 31cfdcbd7fb5002926a47e704c76ddd464e5a7e9..5da1623b040a2f8d1a113519b8c35d8fbcdf08ba 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ script/sqlx migrate run script/seed-db ``` +Install Redis: + +``` +brew install redis +``` + Run the web frontend and the collaboration server. ``` From 660289e0a45698bbd400d7b5d3258cda5496a7ef Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 15 Jul 2022 12:09:37 +0200 Subject: [PATCH 31/31] Ignore dump.rdb files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d3d0634a401a3fe5f8131ca5e1797289f1c01939..1ec8d6269c2cb5d7de73211296cec3027da87d29 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /crates/collab/static/styles.css /vendor/bin /assets/themes/*.json +dump.rdb