From 9d06b15d92e386690a78ce7eba34d31081e917d6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 8 Jul 2022 19:03:25 +0200 Subject: [PATCH 01/71] Fix spurious borrow checker error Co-Authored-By: Max Brunsfeld --- crates/language/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index ba8744624d..ac3759c257 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -443,7 +443,7 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { async fn search<'a>( outline: &'a Outline, - query: &str, + query: &'a str, cx: &'a gpui::TestAppContext, ) -> Vec<(&'a str, Vec)> { let matches = cx From 3fb1cd07260afa2ebcf0aadd1dd96a87b51adb5d Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 13 Jul 2022 16:31:47 +0200 Subject: [PATCH 02/71] 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 d1b1b58411..b323bdc34e 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 6f4bdab78e..690e6d385c 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 03/71] 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 8ac843cb02..4088041fe6 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 b323bdc34e..8803cb2fb7 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 690e6d385c..090dc6b313 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 04/71] 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 4088041fe6..8ac843cb02 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 8803cb2fb7..93ba175c18 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 b008a98c28..8665a108c9 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 2767d9c4b8..2748a3f3f7 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, +} + +/// 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 { - /// 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, - }) - } - /// 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() + /// 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_default() -> Result { + let default_ctx = 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) + .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 090dc6b313..e5513e97bf 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 05/71] 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 93ba175c18..8a0c5c57de 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 06/71] 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 e1cc1d61c2..82f5701f7f 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 0000000000..7b6569d336 --- /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 6f4bdab78e..0f8d503adc 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 07/71] 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 6c87bb055f..14016ae243 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 6e1e3b09bd..dfd4938742 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 e58f1fc341..baa346f7f0 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 56f664566e..99d60ed9a2 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 0000000000..1313ad346f --- /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 026144db64..a05a46fc6d 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 08/71] 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 1313ad346f..be083b2bce 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 09/71] 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 85a6cd1e19..39477bc927 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 be083b2bce..f30bc0a678 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 10/71] add show_completions_on_input setting to disable popping the completions menu automatically --- crates/editor/src/editor.rs | 362 ++++++++++----------- crates/editor/src/link_go_to_definition.rs | 86 +++-- crates/editor/src/test.rs | 58 +++- crates/settings/src/settings.rs | 5 + crates/util/src/test/marked_text.rs | 2 +- styles/package-lock.json | 1 + 6 files changed, 278 insertions(+), 236 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e58f1fc341..b6d59ab3f8 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()], + 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() }, - 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() - }), - ..Default::default() - }, - ..Default::default() - })) - .await; - - let text = " - 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); - }); - - 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"), - ], + cx, ) .await; - editor - .condition(&cx, |editor, _| editor.context_menu_visible()) - .await; - let apply_additional_edits = editor.update(cx, |editor, cx| { + cx.set_state(indoc! {" + one| + two + three"}); + cx.simulate_keystroke("."); + handle_completion_request( + &mut cx, + indoc! {" + one.|<> + two + three"}, + vec!["first_completion", "second_completion"], + ) + .await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + 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")), - ) - .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), - ]) - }); - - 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()); - }); - - 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"), - ], - ) - .await; - editor - .condition(&cx, |editor, _| editor.context_menu_visible()) - .await; - - editor.update(cx, |editor, cx| { - editor.handle_input(&Input("i".to_string()), cx); - }); - - 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"), - ], - ) - .await; - editor - .condition(&cx, |editor, _| editor.context_menu_visible()) - .await; - - let apply_additional_edits = editor.update(cx, |editor, cx| { - let apply_additional_edits = editor - .confirm_completion(&ConfirmCompletion::default(), cx) - .unwrap(); - assert_eq!( - editor.text(cx), - " + &mut cx, + Some(( + indoc! {" one.second_completion - two sixth_completion - three sixth_completion - additional edit - " - .unindent() - ); - apply_additional_edits + two + three<>"}, + "\nadditional edit", + )), + ) + .await; + apply_additional_edits.await.unwrap(); + cx.assert_editor_state(indoc! {" + one.second_completion| + two + three + additional edit"}); + + 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 cx, + indoc! {" + one.second_completion + two s + three + additional edit"}, + vec!["fourth_completion", "fifth_completion", "sixth_completion"], + ) + .await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + + cx.simulate_keystroke("i"); + + handle_completion_request( + &mut cx, + indoc! {" + one.second_completion + two si + three + additional edit"}, + vec!["fourth_completion", "fifth_completion", "sixth_completion"], + ) + .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(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(); - async fn handle_completion_request( - fake: &mut FakeLanguageServer, - path: &'static str, - position: Point, - completions: Vec<(Range, &'static str)>, + 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() + }); + cx.assert_editor_state("editor.close|"); + handle_resolve_completion_request(&mut cx, None).await; + apply_additional_edits.await.unwrap(); + + // 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 2e59a72402..f034df64b6 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 4e10f516c1..dd05a14bd6 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 snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); - 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.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); + 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 6073b1ef15..807587ac86 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 4529c8c803..2a5969c265 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 2eb6d3a1bf..49304dc2fa 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 11/71] 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 2417d0f5d4..414f83982e 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 807587ac86..63e75364e6 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 12/71] 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 6073b1ef15..2897b0ab83 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 82f5701f7f..8dc20bdbd1 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 d27a5b7c5b..a033be4dee 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 13/71] 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 14016ae243..e666405a11 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 0c5f3ddfdd..5535711266 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 79b7dcb596ce4c826b40eda75663f5c06d67db0b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 13 Jul 2022 16:32:25 -0700 Subject: [PATCH 14/71] Basic keybindings infra done --- assets/keymaps/default.json | 10 +- crates/terminal/Cargo.toml | 4 +- crates/terminal/src/connection.rs | 19 +- crates/terminal/src/connection/events.rs | 260 +++++++++++++++++++++++ crates/terminal/src/terminal_element.rs | 22 +- 5 files changed, 299 insertions(+), 16 deletions(-) create mode 100644 crates/terminal/src/connection/events.rs diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 0e13bae794..ae9eb3c57a 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", @@ -419,8 +418,13 @@ "down": "terminal::Down", "tab": "terminal::Tab", "cmd-v": "terminal::Paste", - "cmd-c": "terminal::Copy", - "ctrl-l": "terminal::Clear" + "cmd-c": "terminal::Copy" + } + }, + { + "context": "ModalTerminal", + "bindings": { + "shift-escape": "terminal::DeployModal" } } ] \ No newline at end of file diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index e3a458327d..876c28a273 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -27,6 +27,4 @@ dirs = "4.0.0" gpui = { path = "../gpui", features = ["test-support"] } client = { path = "../client", features = ["test-support"]} project = { path = "../project", features = ["test-support"]} -workspace = { path = "../workspace", features = ["test-support"] } - - +workspace = { path = "../workspace", features = ["test-support"] } \ No newline at end of file diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index 800791370c..a1326162f7 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -1,3 +1,5 @@ +mod events; + use alacritty_terminal::{ ansi::{ClearMode, Handler}, config::{Config, PtyConfig}, @@ -13,13 +15,15 @@ use futures::{channel::mpsc::unbounded, StreamExt}; use settings::Settings; use std::{collections::HashMap, path::PathBuf, sync::Arc}; -use gpui::{ClipboardItem, CursorStyle, Entity, ModelContext}; +use gpui::{ClipboardItem, CursorStyle, Entity, KeyDownEvent, ModelContext}; use crate::{ color_translation::{get_color_at_index, to_alac_rgb}, ZedListener, }; +use self::events::to_esc_str; + const DEFAULT_TITLE: &str = "Terminal"; ///Upward flowing events, for changing the title and such @@ -182,6 +186,19 @@ impl TerminalConnection { self.write_to_pty("\x0c".into()); self.term.lock().clear_screen(ClearMode::Saved); } + + pub fn try_keystroke(&mut self, key_down: &KeyDownEvent) -> bool { + let guard = self.term.lock(); + let mode = guard.mode(); + let esc = to_esc_str(key_down, mode); + drop(guard); + if esc.is_some() { + self.write_to_pty(esc.unwrap()); + true + } else { + false + } + } } impl Drop for TerminalConnection { diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/events.rs new file mode 100644 index 0000000000..ee61d69d54 --- /dev/null +++ b/crates/terminal/src/connection/events.rs @@ -0,0 +1,260 @@ +use alacritty_terminal::term::TermMode; +use gpui::{keymap::Keystroke, KeyDownEvent}; + +pub enum ModifierCombinations { + None, + Alt, + Ctrl, + Shift, + Other, +} + +impl ModifierCombinations { + fn new(ks: &Keystroke) -> Self { + match (ks.alt, ks.ctrl, ks.shift, ks.cmd) { + (false, false, false, false) => ModifierCombinations::None, + (true, false, false, false) => ModifierCombinations::Alt, + (false, true, false, false) => ModifierCombinations::Ctrl, + (false, false, true, false) => ModifierCombinations::Shift, + _ => ModifierCombinations::Other, + } + } +} + +pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { + let modifiers = ModifierCombinations::new(&event.keystroke); + + // Manual Bindings including modifiers + let manual_esc_str = match (event.keystroke.key.as_ref(), modifiers) { + ("l", ModifierCombinations::Ctrl) => Some("\x0c".to_string()), + ("tab", ModifierCombinations::Shift) => Some("\x1b[Z".to_string()), + ("backspace", ModifierCombinations::Alt) => Some("\x1b\x7f".to_string()), + ("backspace", ModifierCombinations::Shift) => Some("\x7f".to_string()), + ("home", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[1;2H".to_string()) + } + ("end", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[1;2F".to_string()) + } + ("pageup", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[5;2~".to_string()) + } + ("pagedown", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[6;2~".to_string()) + } + ("home", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOH".to_string()) + } + ("home", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[H".to_string()) + } + ("end", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOF".to_string()) + } + ("end", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[F".to_string()) + } + ("up", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOA".to_string()) + } + ("up", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[A".to_string()) + } + ("down", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOB".to_string()) + } + ("down", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[B".to_string()) + } + ("right", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOC".to_string()) + } + ("right", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[C".to_string()) + } + ("left", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOD".to_string()) + } + ("left", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[D".to_string()) + } + ("back", ModifierCombinations::None) => Some("\x7f".to_string()), + ("insert", ModifierCombinations::None) => Some("\x1b[2~".to_string()), + ("delete", ModifierCombinations::None) => Some("\x1b[3~".to_string()), + ("pageup", ModifierCombinations::None) => Some("\x1b[5~".to_string()), + ("pagedown", ModifierCombinations::None) => Some("\x1b[6~".to_string()), + ("f1", ModifierCombinations::None) => Some("\x1bOP".to_string()), + ("f2", ModifierCombinations::None) => Some("\x1bOQ".to_string()), + ("f3", ModifierCombinations::None) => Some("\x1bOR".to_string()), + ("f4", ModifierCombinations::None) => Some("\x1bOS".to_string()), + ("f5", ModifierCombinations::None) => Some("\x1b[15~".to_string()), + ("f6", ModifierCombinations::None) => Some("\x1b[17~".to_string()), + ("f7", ModifierCombinations::None) => Some("\x1b[18~".to_string()), + ("f8", ModifierCombinations::None) => Some("\x1b[19~".to_string()), + ("f9", ModifierCombinations::None) => Some("\x1b[20~".to_string()), + ("f10", ModifierCombinations::None) => Some("\x1b[21~".to_string()), + ("f11", ModifierCombinations::None) => Some("\x1b[23~".to_string()), + ("f12", ModifierCombinations::None) => Some("\x1b[24~".to_string()), + ("f13", ModifierCombinations::None) => Some("\x1b[25~".to_string()), + ("f14", ModifierCombinations::None) => Some("\x1b[26~".to_string()), + ("f15", ModifierCombinations::None) => Some("\x1b[28~".to_string()), + ("f16", ModifierCombinations::None) => Some("\x1b[29~".to_string()), + ("f17", ModifierCombinations::None) => Some("\x1b[31~".to_string()), + ("f18", ModifierCombinations::None) => Some("\x1b[32~".to_string()), + ("f19", ModifierCombinations::None) => Some("\x1b[33~".to_string()), + ("f20", ModifierCombinations::None) => Some("\x1b[34~".to_string()), + // NumpadEnter, Action::Esc("\n".into()); + _ => None, + }; + if manual_esc_str.is_some() { + return manual_esc_str; + } + + // Automated bindings applying modifiers + let modifier_code = modifier_code(&event.keystroke); + let modified_esc_str = match event.keystroke.key.as_ref() { + "up" => Some(format!("\x1b[1;{}A", modifier_code)), + "down" => Some(format!("\x1b[1;{}B", modifier_code)), + "right" => Some(format!("\x1b[1;{}C", modifier_code)), + "left" => Some(format!("\x1b[1;{}D", modifier_code)), + "f1" => Some(format!("\x1b[1;{}P", modifier_code)), + "f2" => Some(format!("\x1b[1;{}Q", modifier_code)), + "f3" => Some(format!("\x1b[1;{}R", modifier_code)), + "f4" => Some(format!("\x1b[1;{}S", modifier_code)), + "F5" => Some(format!("\x1b[15;{}~", modifier_code)), + "f6" => Some(format!("\x1b[17;{}~", modifier_code)), + "f7" => Some(format!("\x1b[18;{}~", modifier_code)), + "f8" => Some(format!("\x1b[19;{}~", modifier_code)), + "f9" => Some(format!("\x1b[20;{}~", modifier_code)), + "f10" => Some(format!("\x1b[21;{}~", modifier_code)), + "f11" => Some(format!("\x1b[23;{}~", modifier_code)), + "f12" => Some(format!("\x1b[24;{}~", modifier_code)), + "f13" => Some(format!("\x1b[25;{}~", modifier_code)), + "f14" => Some(format!("\x1b[26;{}~", modifier_code)), + "f15" => Some(format!("\x1b[28;{}~", modifier_code)), + "f16" => Some(format!("\x1b[29;{}~", modifier_code)), + "f17" => Some(format!("\x1b[31;{}~", modifier_code)), + "f18" => Some(format!("\x1b[32;{}~", modifier_code)), + "f19" => Some(format!("\x1b[33;{}~", modifier_code)), + "f20" => Some(format!("\x1b[34;{}~", modifier_code)), + _ if modifier_code == 2 => None, + "insert" => Some(format!("\x1b[2;{}~", modifier_code)), + "pageup" => Some(format!("\x1b[5;{}~", modifier_code)), + "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)), + "end" => Some(format!("\x1b[1;{}F", modifier_code)), + "home" => Some(format!("\x1b[1;{}H", modifier_code)), + _ => None, + }; + if modified_esc_str.is_some() { + return modified_esc_str; + } + + // Fallback to keystroke input sent directly + return event.input.clone(); +} + +/* +So, to match alacritty keyboard handling, we need to check APP_CURSOR, and ALT_SCREEN + +And we need to convert the strings that GPUI returns to keys + +And we need a way of easily declaring and matching a modifier pattern on those keys + +And we need to block writing the input to the pty if any of these match + +And I need to figure out how to express this in a cross platform way + +And a way of optionally interfacing this with actions for rebinding in defaults.json + +Design notes: +I would like terminal mode checking to be concealed behind the TerminalConnection in as many ways as possible. +Alacritty has a lot of stuff intermixed for it's input handling. TerminalConnection should be in charge +of anything that needs to conform to a standard that isn't handled by Term, e.g.: +- Reporting mouse events correctly. +- Reporting scrolls -> Depends on MOUSE_MODE, ALT_SCREEN, and ALTERNATE_SCROLL, etc. +- Correctly bracketing a paste +- Storing changed colors +- Focus change sequence + +Scrolling might be handled internally or externally, need a way to ask. Everything else should probably happen internally. + +Standards/OS compliance is in connection.rs. +This takes GPUI events and translates them to the correct terminal stuff +This means that standards compliance outside of connection should be kept to a minimum. Yes, this feels good. +Connection needs to be split up then, into a bunch of event handlers + +Punting on these by pushing them up to a scrolling element +(either on dispatch_event directly or a seperate scrollbar) + Home, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToTop; + End, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToBottom; + PageUp, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageUp; + PageDown, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageDown; + + + +NOTE, THE FOLLOWING HAS 2 BINDINGS: +K, ModifiersState::LOGO, Action::Esc("\x0c".into()); +K, ModifiersState::LOGO, Action::ClearHistory; => ctx.terminal_mut().clear_screen(ClearMode::Saved), + +*/ + +/// Code Modifiers +/// ---------+--------------------------- +/// 2 | Shift +/// 3 | Alt +/// 4 | Shift + Alt +/// 5 | Control +/// 6 | Shift + Control +/// 7 | Alt + Control +/// 8 | Shift + Alt + Control +/// ---------+--------------------------- +/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys +fn modifier_code(keystroke: &Keystroke) -> u32 { + let mut modifier_code = 0; + if keystroke.shift { + modifier_code |= 1; + } + if keystroke.alt { + modifier_code |= 1 << 1; + } + if keystroke.ctrl { + modifier_code |= 1 << 2; + } + modifier_code + 1 +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_match_alacritty_keybindings() { + // let bindings = alacritty::config::bindings::default_key_bindings(); + //TODO + } + + #[test] + fn test_modifier_code_calc() { + // Code Modifiers + // ---------+--------------------------- + // 2 | Shift + // 3 | Alt + // 4 | Shift + Alt + // 5 | Control + // 6 | Shift + Control + // 7 | Alt + Control + // 8 | Shift + Alt + Control + // ---------+--------------------------- + // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys + // assert_eq!(2, modifier_code(Keystroke::parse("shift-A").unwrap())); + assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap())); + assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap())); + assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap())); + assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap())); + assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap())); + assert_eq!( + 8, + modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap()) + ); + } +} diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 09dce20f97..e93a68fcb7 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -9,7 +9,7 @@ use alacritty_terminal::{ }, Term, }; -use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine, Input}; +use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, elements::*, @@ -389,14 +389,18 @@ impl Element for TerminalEl { cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32)); }) .is_some(), - Event::KeyDown(KeyDownEvent { - input: Some(input), .. - }) => cx - .is_parent_view_focused() - .then(|| { - cx.dispatch_action(Input(input.to_string())); - }) - .is_some(), + Event::KeyDown(e @ KeyDownEvent { .. }) => { + if !cx.is_parent_view_focused() { + return false; + } + + self.connection + .upgrade(cx.app) + .map(|connection| { + connection.update(cx.app, |connection, _| connection.try_keystroke(e)) + }) + .unwrap_or(false) + } _ => false, } } From f55b24ddeef03991f6e0b396a16f674d21d4d3f5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 13 Jul 2022 16:39:22 -0700 Subject: [PATCH 15/71] 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 0e13bae794..833fefd0f2 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 1130050690..708f96856b 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 49304dc2fa..2eb6d3a1bf 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 16/71] 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 6180d310df..4c90b8a002 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 a033be4dee..3a2bbb9be8 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 17/71] 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 e666405a11..72cb4d292d 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 5535711266..37bfbdee8a 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 18/71] 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 87b6ea7547..f5309ebb58 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 da62fe7e54..5ff2243bef 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 19/71] 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 ecc1b2df68..1e89ba0853 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 6adefcf62a..b0373c0fcb 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 0e3aca1447..f703acdfdb 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 5f069d223f..d5b85b0aee 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 0d56ac1979..ee24539287 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 dbd4b443a1..cc972b9bcd 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 e1acc6a771..5098222ae0 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 12c092d6e6..71587ef135 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 7936a9b6bb..2299bc3477 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 87b6ea7547..6862cc0028 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 da62fe7e54..d72704da01 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 36d47bed92..ef5b1fe69c 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 20/71] 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 21c1ab973d..a8c0a063a8 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 8a0c5c57de..49c3f89a80 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 7a0646dda6..3d1cb3d170 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 8dc20bdbd1..8dc37a3afe 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 4fbc63a667..c37d61f801 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 2eb6d3a1bf..49304dc2fa 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 21/71] Update docs --- crates/plugin_runtime/README.md | 30 ++++++++++++++++++++++++------ crates/plugin_runtime/build.rs | 2 -- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 8ac843cb02..012f156362 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: +The `.init` method takes a single argument containing the plugin binary. -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`. +1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). -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`). +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 49c3f89a80..dbfdebf9a8 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 22/71] 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 012f156362..38d1c0bb5d 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 dbfdebf9a8..0614659b5e 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 3d1cb3d170..7a0646dda6 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 c37d61f801..bd9a9d005f 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 23/71] 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 b0373c0fcb..72ba6d60af 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 f703acdfdb..8f215076bb 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 5098222ae0..622b84633c 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 24/71] 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 8dc37a3afe..2937d05516 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 7b6569d336..0000000000 --- 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 bd9a9d005f..8f87b03908 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 25/71] 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 12c092d6e6..b0bdf56872 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 26/71] 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 d72704da01..3aa85c4a95 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 27/71] :art: --- crates/editor/src/items.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8f215076bb..5bb8d4d0b2 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()?; + // Ensure we always render at least the filename. + height += 1; - let mut path = file.path().as_ref(); - depth += 1; - while depth > 0 { - if let Some(parent) = path.parent() { - path = parent; - depth -= 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 28/71] 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 bc133f09c8..1b82a59107 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 29/71] 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 49304dc2fa..2eb6d3a1bf 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 30/71] 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 b0bdf56872..d18ea50f44 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 2e749631fea33f2afdafc310efb389d7dc78c90c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 16:27:02 -0700 Subject: [PATCH 31/71] Checkpoint, have caret notation implemented --- crates/terminal/src/connection/events.rs | 69 +++++++++++++++++++++++- crates/terminal/src/terminal.rs | 2 +- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/events.rs index ee61d69d54..49e5770ea3 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/events.rs @@ -6,6 +6,10 @@ pub enum ModifierCombinations { Alt, Ctrl, Shift, + AltCtrl, + AltShift, + CtrlShift, + AltCtrlShift, Other, } @@ -16,6 +20,10 @@ impl ModifierCombinations { (true, false, false, false) => ModifierCombinations::Alt, (false, true, false, false) => ModifierCombinations::Ctrl, (false, false, true, false) => ModifierCombinations::Shift, + (true, true, false, false) => ModifierCombinations::AltCtrl, + (true, false, true, false) => ModifierCombinations::AltShift, + (false, true, true, false) => ModifierCombinations::CtrlShift, + (true, true, true, false) => ModifierCombinations::AltCtrlShift, _ => ModifierCombinations::Other, } } @@ -26,7 +34,6 @@ pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { // Manual Bindings including modifiers let manual_esc_str = match (event.keystroke.key.as_ref(), modifiers) { - ("l", ModifierCombinations::Ctrl) => Some("\x0c".to_string()), ("tab", ModifierCombinations::Shift) => Some("\x1b[Z".to_string()), ("backspace", ModifierCombinations::Alt) => Some("\x1b\x7f".to_string()), ("backspace", ModifierCombinations::Shift) => Some("\x7f".to_string()), @@ -104,6 +111,66 @@ pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { ("f19", ModifierCombinations::None) => Some("\x1b[33~".to_string()), ("f20", ModifierCombinations::None) => Some("\x1b[34~".to_string()), // NumpadEnter, Action::Esc("\n".into()); + //Make all mappings for caret notation keys! + ("a", ModifierCombinations::Ctrl) => Some("\x01".to_string()), //1 + ("A", ModifierCombinations::CtrlShift) => Some("\x01".to_string()), //1 + ("b", ModifierCombinations::Ctrl) => Some("\x02".to_string()), //2 + ("B", ModifierCombinations::CtrlShift) => Some("\x02".to_string()), //2 + ("c", ModifierCombinations::Ctrl) => Some("\x03".to_string()), //3 + ("C", ModifierCombinations::CtrlShift) => Some("\x03".to_string()), //3 + ("d", ModifierCombinations::Ctrl) => Some("\x04".to_string()), //4 + ("D", ModifierCombinations::CtrlShift) => Some("\x04".to_string()), //4 + ("e", ModifierCombinations::Ctrl) => Some("\x05".to_string()), //5 + ("E", ModifierCombinations::CtrlShift) => Some("\x05".to_string()), //5 + ("f", ModifierCombinations::Ctrl) => Some("\x06".to_string()), //6 + ("F", ModifierCombinations::CtrlShift) => Some("\x06".to_string()), //6 + ("g", ModifierCombinations::Ctrl) => Some("\x07".to_string()), //7 + ("G", ModifierCombinations::CtrlShift) => Some("\x07".to_string()), //7 + ("h", ModifierCombinations::Ctrl) => Some("\x08".to_string()), //8 + ("H", ModifierCombinations::CtrlShift) => Some("\x08".to_string()), //8 + ("i", ModifierCombinations::Ctrl) => Some("\x09".to_string()), //9 + ("I", ModifierCombinations::CtrlShift) => Some("\x09".to_string()), //9 + ("j", ModifierCombinations::Ctrl) => Some("\x0a".to_string()), //10 + ("J", ModifierCombinations::CtrlShift) => Some("\x0a".to_string()), //10 + ("k", ModifierCombinations::Ctrl) => Some("\x0b".to_string()), //11 + ("K", ModifierCombinations::CtrlShift) => Some("\x0b".to_string()), //11 + ("l", ModifierCombinations::Ctrl) => Some("\x0c".to_string()), //12 + ("L", ModifierCombinations::CtrlShift) => Some("\x0c".to_string()), //12 + ("m", ModifierCombinations::Ctrl) => Some("\x0d".to_string()), //13 + ("M", ModifierCombinations::CtrlShift) => Some("\x0d".to_string()), //13 + ("n", ModifierCombinations::Ctrl) => Some("\x0e".to_string()), //14 + ("N", ModifierCombinations::CtrlShift) => Some("\x0e".to_string()), //14 + ("o", ModifierCombinations::Ctrl) => Some("\x0f".to_string()), //15 + ("O", ModifierCombinations::CtrlShift) => Some("\x0f".to_string()), //15 + ("p", ModifierCombinations::Ctrl) => Some("\x10".to_string()), //16 + ("P", ModifierCombinations::CtrlShift) => Some("\x10".to_string()), //16 + ("q", ModifierCombinations::Ctrl) => Some("\x11".to_string()), //17 + ("Q", ModifierCombinations::CtrlShift) => Some("\x11".to_string()), //17 + ("r", ModifierCombinations::Ctrl) => Some("\x12".to_string()), //18 + ("R", ModifierCombinations::CtrlShift) => Some("\x12".to_string()), //18 + ("s", ModifierCombinations::Ctrl) => Some("\x13".to_string()), //19 + ("S", ModifierCombinations::CtrlShift) => Some("\x13".to_string()), //19 + ("t", ModifierCombinations::Ctrl) => Some("\x14".to_string()), //20 + ("T", ModifierCombinations::CtrlShift) => Some("\x14".to_string()), //20 + ("u", ModifierCombinations::Ctrl) => Some("\x15".to_string()), //21 + ("U", ModifierCombinations::CtrlShift) => Some("\x15".to_string()), //21 + ("v", ModifierCombinations::Ctrl) => Some("\x16".to_string()), //22 + ("V", ModifierCombinations::CtrlShift) => Some("\x16".to_string()), //22 + ("w", ModifierCombinations::Ctrl) => Some("\x17".to_string()), //23 + ("W", ModifierCombinations::CtrlShift) => Some("\x17".to_string()), //23 + ("x", ModifierCombinations::Ctrl) => Some("\x18".to_string()), //24 + ("X", ModifierCombinations::CtrlShift) => Some("\x18".to_string()), //24 + ("y", ModifierCombinations::Ctrl) => Some("\x19".to_string()), //25 + ("Y", ModifierCombinations::CtrlShift) => Some("\x19".to_string()), //25 + ("z", ModifierCombinations::Ctrl) => Some("\x1a".to_string()), //26 + ("Z", ModifierCombinations::CtrlShift) => Some("\x1a".to_string()), //26 + ("@", ModifierCombinations::Ctrl) => Some("\x00".to_string()), //0 + ("[", ModifierCombinations::Ctrl) => Some("\x1b".to_string()), //27 + ("\\", ModifierCombinations::Ctrl) => Some("\x1c".to_string()), //28 + ("]", ModifierCombinations::Ctrl) => Some("\x1d".to_string()), //29 + ("^", ModifierCombinations::Ctrl) => Some("\x1e".to_string()), //30 + ("_", ModifierCombinations::Ctrl) => Some("\x1f".to_string()), //31 + ("?", ModifierCombinations::Ctrl) => Some("\x7f".to_string()), //127 _ => None, }; if manual_esc_str.is_some() { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 12c092d6e6..fcf1e7db30 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -66,7 +66,7 @@ actions!( Paste, Deploy, Quit, - DeployModal, + DeployModal ] ); impl_internal_actions!(terminal, [ScrollTerminal]); From 98651c4b866986096a86b867532f8fa294b01bfe Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 17:21:30 -0700 Subject: [PATCH 32/71] New key mapping system in place and working --- assets/keymaps/default.json | 16 ++-- crates/terminal/src/connection.rs | 7 +- crates/terminal/src/connection/events.rs | 45 ++++++--- crates/terminal/src/terminal.rs | 116 ++++++----------------- crates/terminal/src/terminal_element.rs | 41 +++----- 5 files changed, 84 insertions(+), 141 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index ae9eb3c57a..11893847ad 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -407,18 +407,16 @@ { "context": "Terminal", "bindings": { - "ctrl-c": "terminal::Sigint", - "escape": "terminal::Escape", - "ctrl-d": "terminal::Quit", - "backspace": "terminal::Del", - "enter": "terminal::Return", - "left": "terminal::Left", - "right": "terminal::Right", + // Overrides for global bindings, remove at your own risk: "up": "terminal::Up", "down": "terminal::Down", - "tab": "terminal::Tab", + "escape": "terminal::Escape", + "enter": "terminal::Enter", + "ctrl-c": "terminal::CtrlC", + // Useful terminal actions: + "cmd-c": "terminal::Copy", "cmd-v": "terminal::Paste", - "cmd-c": "terminal::Copy" + "cmd-k": "terminal::Clear" } }, { diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index a1326162f7..7babe45d93 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -15,7 +15,7 @@ use futures::{channel::mpsc::unbounded, StreamExt}; use settings::Settings; use std::{collections::HashMap, path::PathBuf, sync::Arc}; -use gpui::{ClipboardItem, CursorStyle, Entity, KeyDownEvent, ModelContext}; +use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext}; use crate::{ color_translation::{get_color_at_index, to_alac_rgb}, @@ -187,10 +187,11 @@ impl TerminalConnection { self.term.lock().clear_screen(ClearMode::Saved); } - pub fn try_keystroke(&mut self, key_down: &KeyDownEvent) -> bool { + pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { let guard = self.term.lock(); let mode = guard.mode(); - let esc = to_esc_str(key_down, mode); + let esc = to_esc_str(keystroke, mode); + dbg!(&esc); drop(guard); if esc.is_some() { self.write_to_pty(esc.unwrap()); diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/events.rs index 49e5770ea3..cf0163e558 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/events.rs @@ -1,15 +1,12 @@ use alacritty_terminal::term::TermMode; -use gpui::{keymap::Keystroke, KeyDownEvent}; +use gpui::keymap::Keystroke; pub enum ModifierCombinations { None, Alt, Ctrl, Shift, - AltCtrl, - AltShift, CtrlShift, - AltCtrlShift, Other, } @@ -20,20 +17,24 @@ impl ModifierCombinations { (true, false, false, false) => ModifierCombinations::Alt, (false, true, false, false) => ModifierCombinations::Ctrl, (false, false, true, false) => ModifierCombinations::Shift, - (true, true, false, false) => ModifierCombinations::AltCtrl, - (true, false, true, false) => ModifierCombinations::AltShift, (false, true, true, false) => ModifierCombinations::CtrlShift, - (true, true, true, false) => ModifierCombinations::AltCtrlShift, _ => ModifierCombinations::Other, } } } -pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { - let modifiers = ModifierCombinations::new(&event.keystroke); +pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { + let modifiers = ModifierCombinations::new(&keystroke); // Manual Bindings including modifiers - let manual_esc_str = match (event.keystroke.key.as_ref(), modifiers) { + let manual_esc_str = match (keystroke.key.as_ref(), modifiers) { + //Basic special keys + ("space", ModifierCombinations::None) => Some(" ".to_string()), + ("tab", ModifierCombinations::None) => Some("\x09".to_string()), + ("escape", ModifierCombinations::None) => Some("\x1b".to_string()), + ("enter", ModifierCombinations::None) => Some("\x0d".to_string()), + ("backspace", ModifierCombinations::None) => Some("\x7f".to_string()), + //Interesting escape codes ("tab", ModifierCombinations::Shift) => Some("\x1b[Z".to_string()), ("backspace", ModifierCombinations::Alt) => Some("\x1b\x7f".to_string()), ("backspace", ModifierCombinations::Shift) => Some("\x7f".to_string()), @@ -111,7 +112,7 @@ pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { ("f19", ModifierCombinations::None) => Some("\x1b[33~".to_string()), ("f20", ModifierCombinations::None) => Some("\x1b[34~".to_string()), // NumpadEnter, Action::Esc("\n".into()); - //Make all mappings for caret notation keys! + //Mappings for caret notation keys ("a", ModifierCombinations::Ctrl) => Some("\x01".to_string()), //1 ("A", ModifierCombinations::CtrlShift) => Some("\x01".to_string()), //1 ("b", ModifierCombinations::Ctrl) => Some("\x02".to_string()), //2 @@ -178,8 +179,8 @@ pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { } // Automated bindings applying modifiers - let modifier_code = modifier_code(&event.keystroke); - let modified_esc_str = match event.keystroke.key.as_ref() { + let modifier_code = modifier_code(&keystroke); + let modified_esc_str = match keystroke.key.as_ref() { "up" => Some(format!("\x1b[1;{}A", modifier_code)), "down" => Some(format!("\x1b[1;{}B", modifier_code)), "right" => Some(format!("\x1b[1;{}C", modifier_code)), @@ -217,10 +218,26 @@ pub fn to_esc_str(event: &KeyDownEvent, mode: &TermMode) -> Option { } // Fallback to keystroke input sent directly - return event.input.clone(); + if keystroke.key.chars().count() == 1 { + dbg!("This should catch space", &keystroke.key); + return Some(keystroke.key.clone()); + } else { + None + } } /* +New keybindings test plan: + +Is the terminal still usable? YES! +Do ctrl-shift-[X] and ctrl-[x] do the same thing? I THINK SO +Does ctrl-l work? YES +Does tab work? YES +Do all the global overrides (up, down, enter, escape, ctrl-c) work? => YES +Space also doesn't work YES! + + + So, to match alacritty keyboard handling, we need to check APP_CURSOR, and ALT_SCREEN And we need to convert the strings that GPUI returns to keys diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fcf1e7db30..96db9aa034 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -5,7 +5,6 @@ pub mod terminal_element; use alacritty_terminal::{ event::{Event as AlacTermEvent, EventListener}, - grid::Scroll, term::SizeInfo, }; @@ -14,7 +13,7 @@ use dirs::home_dir; use editor::Input; use futures::channel::mpsc::UnboundedSender; use gpui::{ - actions, elements::*, impl_internal_actions, AppContext, ClipboardItem, Entity, ModelHandle, + actions, elements::*, keymap::Keystroke, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, }; use modal::deploy_modal; @@ -27,16 +26,6 @@ use workspace::{Item, Workspace}; use crate::terminal_element::TerminalEl; -//ASCII Control characters on a keyboard -const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c' -const TAB_CHAR: char = 9_u8 as char; -const CARRIAGE_RETURN_CHAR: char = 13_u8 as char; -const ESC_CHAR: char = 27_u8 as char; // == \x1b -const DEL_CHAR: char = 127_u8 as char; -const LEFT_SEQ: &str = "\x1b[D"; -const RIGHT_SEQ: &str = "\x1b[C"; -const UP_SEQ: &str = "\x1b[A"; -const DOWN_SEQ: &str = "\x1b[B"; const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space. const DEBUG_TERMINAL_HEIGHT: f32 = 200.; const DEBUG_CELL_WIDTH: f32 = 5.; @@ -52,44 +41,34 @@ pub struct ScrollTerminal(pub i32); actions!( terminal, [ - Sigint, - Escape, - Del, - Return, - Left, - Right, + Deploy, Up, Down, - Tab, + CtrlC, + Escape, + Enter, Clear, Copy, Paste, - Deploy, - Quit, DeployModal ] ); -impl_internal_actions!(terminal, [ScrollTerminal]); ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { - cx.add_action(Terminal::deploy); - cx.add_action(Terminal::send_sigint); - cx.add_action(Terminal::escape); - cx.add_action(Terminal::quit); - cx.add_action(Terminal::del); - cx.add_action(Terminal::carriage_return); - cx.add_action(Terminal::left); - cx.add_action(Terminal::right); + //Global binding overrrides + cx.add_action(Terminal::send_ctrl_c); cx.add_action(Terminal::up); cx.add_action(Terminal::down); - cx.add_action(Terminal::tab); + cx.add_action(Terminal::escape); + cx.add_action(Terminal::enter); + //Useful terminal actions + cx.add_action(Terminal::deploy); + cx.add_action(deploy_modal); cx.add_action(Terminal::copy); cx.add_action(Terminal::paste); - cx.add_action(Terminal::scroll_terminal); cx.add_action(Terminal::input); cx.add_action(Terminal::clear); - cx.add_action(deploy_modal); } ///A translation struct for Alacritty to communicate with us from their event loop @@ -168,15 +147,6 @@ impl Terminal { } } - ///Scroll the terminal. This locks the terminal - fn scroll_terminal(&mut self, scroll: &ScrollTerminal, cx: &mut ViewContext) { - self.connection - .read(cx) - .term - .lock() - .scroll_display(Scroll::Delta(scroll.0)); - } - fn input(&mut self, Input(text): &Input, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { //TODO: This is probably not encoding UTF8 correctly (see alacritty/src/input.rs:L825-837) @@ -200,11 +170,6 @@ impl Terminal { workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(wd, false, cx))), cx); } - ///Tell Zed to close us - fn quit(&mut self, _: &Quit, cx: &mut ViewContext) { - cx.emit(Event::CloseTerminal); - } - ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { let term = self.connection.read(cx).term.lock(); @@ -224,66 +189,39 @@ impl Terminal { } } - ///Send the `up` key + ///Synthesize the keyboard event corresponding to 'up' fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(UP_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("up").unwrap()); }); } - ///Send the `down` key + ///Synthesize the keyboard event corresponding to 'down' fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(DOWN_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("down").unwrap()); }); } - ///Send the `tab` key - fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { + ///Synthesize the keyboard event corresponding to 'ctrl-c' + fn send_ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(TAB_CHAR.to_string()); + connection.try_keystroke(&Keystroke::parse("ctrl-c").unwrap()); }); } - ///Send `SIGINT` (`ctrl-c`) - fn send_sigint(&mut self, _: &Sigint, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(ETX_CHAR.to_string()); - }); - } - - ///Send the `escape` key + ///Synthesize the keyboard event corresponding to 'escape' fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(ESC_CHAR.to_string()); + connection.try_keystroke(&Keystroke::parse("escape").unwrap()); }); } - ///Send the `delete` key. TODO: Difference between this and backspace? - fn del(&mut self, _: &Del, cx: &mut ViewContext) { + ///Synthesize the keyboard event corresponding to 'enter' + fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { + dbg!("Here!"); self.connection.update(cx, |connection, _| { - connection.write_to_pty(DEL_CHAR.to_string()); - }); - } - - ///Send a carriage return. TODO: May need to check the terminal mode. - fn carriage_return(&mut self, _: &Return, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(CARRIAGE_RETURN_CHAR.to_string()); - }); - } - - //Send the `left` key - fn left(&mut self, _: &Left, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(LEFT_SEQ.to_string()); - }); - } - - //Send the `right` key - fn right(&mut self, _: &Right, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(RIGHT_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("enter").unwrap()); }); } } @@ -476,7 +414,7 @@ mod tests { terminal.connection.update(cx, |connection, _| { connection.write_to_pty("expr 3 + 4".to_string()); }); - terminal.carriage_return(&Return, cx); + terminal.enter(&Enter, cx); }); cx.set_condition_duration(Some(Duration::from_secs(2))); @@ -501,7 +439,7 @@ mod tests { terminal.connection.update(cx, |connection, _| { connection.write_to_pty("expr 3 + 4".to_string()); }); - terminal.carriage_return(&Return, cx); + terminal.enter(&Enter, cx); }); terminal diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index e93a68fcb7..d4d2492bb8 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,5 +1,5 @@ use alacritty_terminal::{ - grid::{Dimensions, GridIterator, Indexed}, + grid::{Dimensions, GridIterator, Indexed, Scroll}, index::{Column as GridCol, Line as GridLine, Point, Side}, selection::{Selection, SelectionRange, SelectionType}, sync::FairMutex, @@ -31,9 +31,7 @@ use theme::TerminalStyle; use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; use std::{fmt::Debug, ops::Sub}; -use crate::{ - color_translation::convert_color, connection::TerminalConnection, ScrollTerminal, ZedListener, -}; +use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener}; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I @@ -359,25 +357,6 @@ impl Element for TerminalEl { _paint: &mut Self::PaintState, cx: &mut gpui::EventContext, ) -> bool { - //The problem: - //Depending on the terminal mode, we either send an escape sequence - //OR update our own data structures. - //e.g. scrolling. If we do smooth scrolling, then we need to check if - //we own scrolling and then if so, do our scrolling thing. - //Ok, so the terminal connection should have APIs for querying it semantically - //something like `should_handle_scroll()`. This means we need a handle to the connection. - //Actually, this is the only time that this app needs to talk to the outer world. - //TODO for scrolling rework: need a way of intercepting Home/End/PageUp etc. - //Sometimes going to scroll our own internal buffer, sometimes going to send ESC - // - //Same goes for key events - //Actually, we don't use the terminal at all in dispatch_event code, the view - //Handles it all. Check how the editor implements scrolling, is it view-level - //or element level? - - //Question: Can we continue dispatching to the view, so it can talk to the connection - //Or should we instead add a connection into here? - match event { Event::ScrollWheel(ScrollWheelEvent { delta, position, .. @@ -386,10 +365,18 @@ impl Element for TerminalEl { .then(|| { let vertical_scroll = (delta.y() / layout.line_height.0) * ALACRITTY_SCROLL_MULTIPLIER; - cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32)); + + if let Some(connection) = self.connection.upgrade(cx.app) { + connection.update(cx.app, |connection, _| { + connection + .term + .lock() + .scroll_display(Scroll::Delta(vertical_scroll.round() as i32)); + }) + } }) .is_some(), - Event::KeyDown(e @ KeyDownEvent { .. }) => { + Event::KeyDown(KeyDownEvent { keystroke, .. }) => { if !cx.is_parent_view_focused() { return false; } @@ -397,7 +384,9 @@ impl Element for TerminalEl { self.connection .upgrade(cx.app) .map(|connection| { - connection.update(cx.app, |connection, _| connection.try_keystroke(e)) + connection.update(cx.app, |connection, _| { + connection.try_keystroke(dbg!(keystroke)) + }) }) .unwrap_or(false) } From 1935208de6d11ea21139f83628fae94037326922 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 17:23:43 -0700 Subject: [PATCH 33/71] Clean up prints --- crates/terminal/src/connection.rs | 1 - crates/terminal/src/connection/events.rs | 2 +- crates/terminal/src/terminal.rs | 1 - crates/terminal/src/terminal_element.rs | 5 ++--- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index 7babe45d93..d17752d395 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -191,7 +191,6 @@ impl TerminalConnection { let guard = self.term.lock(); let mode = guard.mode(); let esc = to_esc_str(keystroke, mode); - dbg!(&esc); drop(guard); if esc.is_some() { self.write_to_pty(esc.unwrap()); diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/events.rs index cf0163e558..0adff38f86 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/events.rs @@ -219,7 +219,7 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { // Fallback to keystroke input sent directly if keystroke.key.chars().count() == 1 { - dbg!("This should catch space", &keystroke.key); + //TODO this might fail on unicode during internationalization return Some(keystroke.key.clone()); } else { None diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 96db9aa034..7963c40792 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -219,7 +219,6 @@ impl Terminal { ///Synthesize the keyboard event corresponding to 'enter' fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { - dbg!("Here!"); self.connection.update(cx, |connection, _| { connection.try_keystroke(&Keystroke::parse("enter").unwrap()); }); diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index d4d2492bb8..cfb881feb2 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -384,9 +384,8 @@ impl Element for TerminalEl { self.connection .upgrade(cx.app) .map(|connection| { - connection.update(cx.app, |connection, _| { - connection.try_keystroke(dbg!(keystroke)) - }) + connection + .update(cx.app, |connection, _| connection.try_keystroke(keystroke)) }) .unwrap_or(false) } From 8220b37c4fabc3d146097743aaf58f889c5419b8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 14 Jul 2022 17:25:17 -0700 Subject: [PATCH 34/71] Method rename --- crates/terminal/src/terminal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 7963c40792..2ed4567706 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -57,7 +57,7 @@ actions!( ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { //Global binding overrrides - cx.add_action(Terminal::send_ctrl_c); + cx.add_action(Terminal::ctrl_c); cx.add_action(Terminal::up); cx.add_action(Terminal::down); cx.add_action(Terminal::escape); @@ -204,7 +204,7 @@ impl Terminal { } ///Synthesize the keyboard event corresponding to 'ctrl-c' - fn send_ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { + fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { connection.try_keystroke(&Keystroke::parse("ctrl-c").unwrap()); }); From b5765bc8cd7f60b506a909d3e21bbfa63ef226d8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 15 Jul 2022 09:00:10 +0200 Subject: [PATCH 35/71] 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 414f83982e..855854bd36 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 f9995e1fcd0742c19241c3cc3807eda6c8ed308f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 00:26:04 -0700 Subject: [PATCH 36/71] Starting on tests --- Cargo.lock | 957 ++++++++++++++++++++++- crates/terminal/Cargo.toml | 3 +- crates/terminal/src/connection/events.rs | 2 +- 3 files changed, 926 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c87bb055f..02403aa49b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,41 @@ dependencies = [ "memchr", ] +[[package]] +name = "alacritty" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f293c6549baebc7f1ad0d39006bf12b7b0225cfb38687d6bd431e840b1b9f19" +dependencies = [ + "alacritty_config_derive", + "alacritty_terminal", + "bitflags", + "cocoa", + "copypasta", + "crossfont", + "dirs 3.0.2", + "embed-resource", + "fnv", + "gl_generator", + "glutin", + "libc", + "log", + "notify", + "objc", + "parking_lot 0.11.2", + "png", + "raw-window-handle", + "serde", + "serde_json", + "serde_yaml", + "structopt", + "unicode-width", + "wayland-client", + "winapi 0.3.9", + "x11-dl", + "xdg", +] + [[package]] name = "alacritty_config_derive" version = "0.1.0" @@ -86,7 +121,7 @@ dependencies = [ "mio-anonymous-pipes", "mio-extras", "miow 0.3.7", - "nix", + "nix 0.22.3", "parking_lot 0.11.2", "regex-automata", "serde", @@ -104,6 +139,12 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" +[[package]] +name = "android_glue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" + [[package]] name = "ansi_term" version = "0.12.1" @@ -680,6 +721,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log", + "nix 0.22.3", +] + [[package]] name = "cap-fs-ext" version = "0.24.4" @@ -782,6 +833,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "chat_panel" version = "0.1.0" @@ -897,7 +957,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap 3.2.8", - "core-foundation", + "core-foundation 0.9.3", "core-services", "dirs 3.0.2", "ipc-channel", @@ -932,6 +992,16 @@ dependencies = [ "util", ] +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi 0.3.9", +] + [[package]] name = "clock" version = "0.1.0" @@ -956,9 +1026,9 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "libc", "objc", ] @@ -970,9 +1040,9 @@ source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2f dependencies = [ "bitflags", "block", - "core-foundation", + "core-foundation 0.9.3", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] @@ -1105,29 +1175,71 @@ dependencies = [ "theme", ] +[[package]] +name = "copypasta" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types 0.3.2", + "libc", +] + [[package]] name = "core-graphics" version = "0.22.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.3", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", ] @@ -1137,8 +1249,8 @@ version = "0.1.1" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ "bitflags", - "core-foundation", - "foreign-types", + "core-foundation 0.9.3", + "foreign-types 0.3.2", "libc", ] @@ -1148,7 +1260,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b344b958cae90858bf6086f49599ecc5ec8698eacad0ea155509ba11fab347" dependencies = [ - "core-foundation", + "core-foundation 0.9.3", ] [[package]] @@ -1157,12 +1269,25 @@ version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "libc", ] +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -1377,6 +1502,27 @@ dependencies = [ "once_cell", ] +[[package]] +name = "crossfont" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1299695a4c6417b7e4a6957bd963478406e148b7b0351e2f2ce31296b0518251" +dependencies = [ + "cocoa", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", + "core-graphics 0.22.3", + "core-text", + "dwrote", + "foreign-types 0.5.0", + "freetype-rs", + "libc", + "log", + "pkg-config", + "servo-fontconfig", + "winapi 0.3.9", +] + [[package]] name = "crypto-common" version = "0.1.4" @@ -1407,6 +1553,12 @@ dependencies = [ "syn", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "curl" version = "0.4.43" @@ -1438,6 +1590,41 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-url" version = "0.1.1" @@ -1573,12 +1760,33 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + [[package]] name = "dotenv" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "dwrote" version = "0.11.0" @@ -1587,6 +1795,8 @@ checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", + "serde", + "serde_derive", "winapi 0.3.9", "wio", ] @@ -1649,6 +1859,19 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +[[package]] +name = "embed-resource" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" +dependencies = [ + "cc", + "rustc_version 0.4.0", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "encoding_rs" version = "0.8.31" @@ -1790,6 +2013,18 @@ dependencies = [ "workspace", ] +[[package]] +name = "filetime" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "windows-sys", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1831,8 +2066,8 @@ source = "git+https://github.com/zed-industries/font-kit?rev=8eaf7a918eafa28b0a3 dependencies = [ "bitflags", "byteorder", - "core-foundation", - "core-graphics", + "core-foundation 0.9.3", + "core-graphics 0.22.3", "core-text", "dirs-next", "dwrote", @@ -1855,7 +2090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1" dependencies = [ "log", - "memmap2", + "memmap2 0.2.3", "ttf-parser 0.12.3", ] @@ -1865,7 +2100,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1874,6 +2130,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -1894,6 +2156,17 @@ dependencies = [ "libc", ] +[[package]] +name = "freetype-rs" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" +dependencies = [ + "bitflags", + "freetype-sys", + "libc", +] + [[package]] name = "freetype-sys" version = "0.13.1" @@ -1916,16 +2189,35 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys 2.0.1", +] + [[package]] name = "fsevent" version = "2.0.2" dependencies = [ "bitflags", - "fsevent-sys", + "fsevent-sys 3.1.0", "parking_lot 0.11.2", "tempdir", ] +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + [[package]] name = "fsevent-sys" version = "3.1.0" @@ -2142,6 +2434,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glob" version = "0.3.0" @@ -2161,6 +2464,78 @@ dependencies = [ "regex", ] +[[package]] +name = "glutin" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00ea9dbe544bc8a657c4c4a798c2d16cd01b549820e47657297549d28371f6d2" +dependencies = [ + "android_glue", + "cgl", + "cocoa", + "core-foundation 0.9.3", + "glutin_egl_sys", + "glutin_emscripten_sys", + "glutin_gles2_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "lazy_static", + "libloading", + "log", + "objc", + "osmesa-sys", + "parking_lot 0.11.2", + "wayland-client", + "wayland-egl", + "winapi 0.3.9", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" +dependencies = [ + "gl_generator", + "winapi 0.3.9", +] + +[[package]] +name = "glutin_emscripten_sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" + +[[package]] +name = "glutin_gles2_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" +dependencies = [ + "gl_generator", + "objc", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" +dependencies = [ + "gl_generator", +] + [[package]] name = "go_to_line" version = "0.1.0" @@ -2186,15 +2561,15 @@ dependencies = [ "cc", "cocoa", "collections", - "core-foundation", - "core-graphics", + "core-foundation 0.9.3", + "core-graphics 0.22.3", "core-text", "ctor", "dhat", "env_logger", "etagere", "font-kit", - "foreign-types", + "foreign-types 0.3.2", "futures", "gpui_macros", "image", @@ -2481,6 +2856,12 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -2546,6 +2927,26 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" +[[package]] +name = "inotify" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -2553,6 +2954,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2672,6 +3076,12 @@ dependencies = [ "cc", ] +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.24" @@ -2728,6 +3138,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "kurbo" version = "0.8.3" @@ -2777,6 +3193,12 @@ dependencies = [ "util", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -3027,6 +3449,24 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -3052,7 +3492,7 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "foreign-types", + "foreign-types 0.3.2", "log", "objc", ] @@ -3224,6 +3664,59 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + [[package]] name = "net2" version = "0.2.37" @@ -3248,6 +3741,17 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nom" version = "7.1.1" @@ -3258,6 +3762,24 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "4.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" +dependencies = [ + "bitflags", + "filetime", + "fsevent 0.4.0", + "fsevent-sys 2.0.1", + "inotify", + "libc", + "mio 0.6.23", + "mio-extras", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -3339,6 +3861,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num_threads" version = "0.1.6" @@ -3358,6 +3901,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -3367,6 +3921,15 @@ dependencies = [ "cc", ] +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.28.4" @@ -3399,7 +3962,7 @@ checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ "bitflags", "cfg-if 1.0.0", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -3451,6 +4014,15 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +[[package]] +name = "osmesa-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" +dependencies = [ + "shared_library", +] + [[package]] name = "outline" version = "0.1.0" @@ -3564,7 +4136,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" dependencies = [ - "rustc_version", + "rustc_version 0.3.3", ] [[package]] @@ -3786,6 +4358,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3829,7 +4411,7 @@ dependencies = [ "client", "clock", "collections", - "fsevent", + "fsevent 2.0.2", "futures", "fuzzy", "gpui", @@ -4019,6 +4601,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.20" @@ -4127,6 +4718,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + [[package]] name = "rayon" version = "1.5.3" @@ -4441,7 +5041,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ - "semver", + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.12", ] [[package]] @@ -4583,6 +5192,12 @@ dependencies = [ "syn", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -4665,8 +5280,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", - "core-foundation", - "core-foundation-sys", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", "libc", "security-framework-sys", ] @@ -4677,7 +5292,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] @@ -4690,6 +5305,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" + [[package]] name = "semver-parser" version = "0.10.2" @@ -4897,6 +5518,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + [[package]] name = "shellexpand" version = "2.1.0" @@ -5016,6 +5647,53 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +[[package]] +name = "smithay-client-toolkit" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2 0.3.1", + "nix 0.22.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags", + "dlib", + "lazy_static", + "log", + "memmap2 0.5.5", + "nix 0.24.1", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit 0.16.0", + "wayland-client", +] + [[package]] name = "smol" version = "1.2.5" @@ -5213,6 +5891,30 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -5353,6 +6055,7 @@ dependencies = [ name = "terminal" version = "0.1.0" dependencies = [ + "alacritty", "alacritty_terminal", "client", "dirs 4.0.0", @@ -6142,7 +6845,7 @@ dependencies = [ "fontdb", "kurbo", "log", - "memmap2", + "memmap2 0.2.3", "pico-args", "rctree", "roxmltree", @@ -6255,6 +6958,26 @@ dependencies = [ "workspace", ] +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "vte" version = "0.10.1" @@ -6673,6 +7396,89 @@ dependencies = [ "wast 43.0.0", ] +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.22.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix 0.22.3", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix 0.22.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-egl" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83281d69ee162b59031c666385e93bde4039ec553b90c4191cdb128ceea29a3a" +dependencies = [ + "wayland-client", + "wayland-sys", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.58" @@ -6885,6 +7691,40 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio 0.8.4", + "ndk", + "ndk-glue", + "ndk-sys", + "objc", + "parking_lot 0.11.2", + "percent-encoding", + "raw-window-handle", + "serde", + "smithay-client-toolkit 0.15.4", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi 0.3.9", + "x11-dl", +] + [[package]] name = "winreg" version = "0.10.1" @@ -6961,6 +7801,55 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "x11-clipboard" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +dependencies = [ + "xcb", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xdg" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" +dependencies = [ + "dirs 4.0.0", +] + [[package]] name = "xml-rs" version = "0.8.4" @@ -7017,7 +7906,7 @@ dependencies = [ "editor", "env_logger", "file_finder", - "fsevent", + "fsevent 2.0.2", "futures", "fuzzy", "go_to_line", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 876c28a273..63894b57f0 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -27,4 +27,5 @@ dirs = "4.0.0" gpui = { path = "../gpui", features = ["test-support"] } client = { path = "../client", features = ["test-support"]} project = { path = "../project", features = ["test-support"]} -workspace = { path = "../workspace", features = ["test-support"] } \ No newline at end of file +workspace = { path = "../workspace", features = ["test-support"] } +alacritty = "0.10.1" \ No newline at end of file diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/events.rs index 0adff38f86..14a3b873c7 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/events.rs @@ -313,7 +313,7 @@ mod test { #[test] fn test_match_alacritty_keybindings() { - // let bindings = alacritty::config::bindings::default_key_bindings(); + let bindings = alacritty::config::bindings::default_key_bindings(); //TODO } From 42d68af073f26177cfa5474c34ab3fb28a89b1e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 15 Jul 2022 10:41:07 +0200 Subject: [PATCH 37/71] 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 a64b411ef3..780479b958 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 31cfdcbd7f..5da1623b04 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 38/71] Ignore dump.rdb files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d3d0634a40..1ec8d6269c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /crates/collab/static/styles.css /vendor/bin /assets/themes/*.json +dump.rdb From d121226cd6edb54aa8450ee037e7464a6124b119 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 15 Jul 2022 12:44:48 +0200 Subject: [PATCH 39/71] Write initial plan --- crates/plugin_runtime/OPAQUE.md | 178 ++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 crates/plugin_runtime/OPAQUE.md diff --git a/crates/plugin_runtime/OPAQUE.md b/crates/plugin_runtime/OPAQUE.md new file mode 100644 index 0000000000..6e140d796c --- /dev/null +++ b/crates/plugin_runtime/OPAQUE.md @@ -0,0 +1,178 @@ +# Opaque handles to resources + +Currently, Zed's plugin system only supports moving *data* (e.g. things you can serialize) across the boundary between guest-side plugin and host-side runtime. Resources, things you can't just copy, have been set aside for now. Given how important this is to Zed, I think it's about time we address this. + +Managing resources is very important to Zed, because a lot of what Zed does is exactly that—managing resources. Each open buffer you're editing is a resource, as is the language server you're querying, or the collaboration session you're currently in. Therefore, writing a plugin system with deep integration with Zed requires some mechanism to manage resources. + +The reason resources are problematic is because, unlike data, we can't pass resources across the ABI boundary. Wasm can't take references to host memory (and even if it could, that doesn't mean that it's a good idea). To add support for resources to plugins, we'd need three things: + +1. Some sort of way for the host-side runtime to hang onto **references** to a resource. If the plugin requests to modify a resource, but we don't even know where that resource is, that's kinda bad, isn't it? + +2. Some sort of way for the guest-side runtime to hang onto **handles** to a resource. We can't reference the resource directly from a plugin, but if a resource *has* been registered with the runtime, we can at least take a runtime-provided handle to that resource so that we may request that the runtime modify it in the future. + +3. Some sort of way to **modify the resources** we're holding onto. This requires two things: some way for a plugin to request a modification, and some for the runtime to apply that modification. Here I'm using 'modification' in the most general sense, which includes, e.g. reading or writing to the resource, i.e. calling a method on it. + +Luckily for us managing resources across boundaries is a problem that languages have had to deal with for eons. File descriptors referencing resources managed by the kernel is the quintessential definition of resource management, but this pattern is oft repeated in games, scripting languages, or surprise surprise, when writing plugins. + +To see what managing plugins could look like in Rust, we need look no further than Rhai. Rhai is a scripting language powered by a tree-walk interpreter written in Rust. It's pretty neat, but what we care about is not the language itself, but how it interfaces with Rust types. + +In its [guide](https://rhai.rs/book/rust/custom-types.html), Rhai claims the following: + +> Rhai works seamlessly with any Rust type, as long as it implements `Clone` as this allows the `Engine` to pass by value. + +This doesn't mean that the underlying resources themselves need to be copied: + +> \[Because Rhai works with types implementing `Clone`\] it is extremely easy to use Rhai with data types such as `Rc<...>`, `Arc<...>`, `Rc>`, `Arc>` etc. + +Given that we have to register a resource with our plugin runtime before we use it, requiring the resource to be behind a shared reference makes sense, so I think the `Clone` bound is reasonable. So how does `Rhai` represent types under the hood? + +> A custom type is stored in Rhai as a Rust trait object (specifically, a `dyn rhai::Variant`), with no restrictions other than being `Clone` (plus `Send + Sync` under the `sync` feature). + +I'd be interested to know how Rhai disambiguates between different types if everything's a trait object under the hood. (Spoilers, but I've done some digging, and it looks like we keep track of the name of the type using some `std::any::type_name` magic). + +Rhai actually exposes a pretty nice interface for working with native Rust types. We can register a type using `Engine::register_type::()`. Internally, this just grabs the string name of the type for future reference. + +> **Note**: Rhai uses strings, but I wonder if you could get away with something more compact using `TypeIds`. Maybe not, given that `TypeId`s are not deterministic across builds, and we'd need matching IDs both host-side and guest side. + +In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). + +With respect to Wasm plugins, I think an interface like this is fairly important, because we don't know whether the original plugin was written in Rust. (This may not be true now, but once we allow packaging and shipping plugins, it's important to maintain a consistent interface, because even Rust changes over time.) + +Once we've registered a type, we can begin using this type in functions. We can add new function using the standard `Engine::register_fn` function, which has the following signature: + +```rust +pub fn register_fn(&mut self, name: N, func: F) -> &mut Self +where + N: AsRef + Into, + F: RegisterNativeFunction, +``` + +This is quite complex, but under the hood it's fairly similar to our own `PluginBuilder::host_function` async method. Looking at `RegisterNativeFunction`, it seems as though this trait essentially provides methods that expose the `TypeID`s as well as type/param names of the arguments and return types of the function (surprise surprise). + +So once we register a function, what happens once we call it? Well, let me introduce you to my friend `Engine::call_native_fn`, whose type signature is too complex to list here. + +> **Note**: Finding this function took like 7 levels of indirection from `eval`. It's surprising how much shuffling of data Rhai does under the hood, I bet you could probably make it a lot faster. + +This takes and returns, like everything else in Rhai, an object of type `Dynamic`. We know that we can use native Rust types, so how does Rhai perform the conversion to and from `Dynamic`? + +The secret lies in `Dynamic::try_cast::(self)`. Like most dynamic scripting languages, Rhai uses a tagged `Union` to represent types. Remember `dyn Variant` from earlier? Rhai's `Union` has a variant, `Variant`, to hold the dynamic native types: + +```rust +/// Any type as a trait object. +#[allow(clippy::redundant_allocation)] +Variant(Box>, Tag, AccessMode), +``` + +Redundant allocations aside, To `try_cast` a `Dynamic` type to `T: Any`thing, we pattern match on `Union`. In the case of variant, we: + +```rust +Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), +``` + +Now Rhai can do this because it's implemented in Rust. In other words, unlike Wasm, Rhai scripts can, indirectly, hold references to places in host memory. For us to implement something like this for Wasm plugins, we'd have to keep track of a "`ResourcePool`"—alive for the duration of each function call—that we can check rust types into and out of. + + I think I've got what's going on now, let's stop talking about Rhai and discuss what this opaque object system would look like if we implemented it in Rust. + + # Design Sketch + +First things first, we'd have to generalize the arguments we can pass to functions host-side. Currently, we support anything that's `serde`able. We'd have to create a new trait, say `Value`, that has blanket implementations for both `serde` and `Clone` (or something like this; if a type is both `serde` and `clone`, we'd have to figure out a way to disambiguate). + + We'd also create a `ResourcePool` that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources, (e.g. `Clone` instead of `Serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. + + We'd probably also need a `Resource` trait that defines an associated handle for a resource. Something like this: + + ```rust + pub trait Resource { + type Handle: Serialize + DeserializeOwned + fn handle(index: u32) -> Self; + fn index(handle: Self) -> u32; + } + ``` + + Where a handle is just a dead-simple wrapper around a `u32`: + + ```rust + #[derive(Serialize, Deserialize)] + pub struct CoolHandle(u32); + ``` + + It's important that this handle be accessible *both* host-side and plugin side. I don't know if this means that we have another crate, like `plugin_handles`, that contains a bunch of u32 wrappers, or something else. Because a `Resource::Handle` is just a u32, it's trivially `serde`, and can cross the ABI boundary. + + So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. + + ```rust + // Implementations and attributes omitted + pub struct Rope { ... }; + pub struct RopeHandle(u32); + impl Resource for Arc> { ... } + + let builder: PluginBuilder = ...; + let builder = builder + .host_fn_async( + "append", + |(rope, string): (Arc>, &str)| async move { + rope.write().await.append(Rope::from(string)) + } + ) + // ... +``` + +He're we're providing a host function, `append` that can be called from Wasm. To import this function into a plugin, we'd do something like the following: + +```rust +use plugin::prelude::*; +use plugin_handles::RopeHandle; + +#[import] +pub fn append(rope: RopeHandle, string: &str); +``` + +This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? First, we'd define a plugin-side function as follows: + +```rust +// same file as above ... + +#[export] +pub fn append_newline(rope: RopeHandle){ + append(rope, "\n"); +} +``` + +Host-side, we'd treat this function like any other: + +```rust +pub struct NewlineAppenderPlugin { + append_newline: WasiFn>, ()>, + runtime: Arc>, +} +``` + +To call this function, we'd do the following: + +```rust +let plugin: NewlineAppenderPlugin = ...; +let rope = Arc::new(RwLock::new(Rope::from("Hello World"))); + +plugin.lock().await.call( + &plugin.append_newline, + rope.clone(), +).await?; + +// `rope` is now "Hello World\n" +``` + +So here's what calling `append_newline` would do, from the top: + +1. First, we'd create a new `ResourcePool`, and insert the `Arc>`, creating a `RopeHandle` in the process. (We could also reuse a resource pool across calls, but the idea is that the pool only keeps track of resources for the duration of the call). + +2. Then, we'd call the Wasm plugin function `append_newline`, passing in the `RopeHandle` we created, which easily crosses the ABI boundary. + +3. Next, in Wasm, we call the native imported function `append`. This sends the `RopeHandle` back over the boundary, to Rust. + +4. Looking in the `Plugin`'s `ResourcePool`, we'd convert the handle into an index, grab and downcast the `dyn Any` back into the type we need, and then call the async Rust callback with an `Arc>`. + +5. The Rust async callback actually acquires a lock and appends the newline. + +6. And from here on out we return up the callstack, through Wasm, to Rust all the way back to where we started. + +Using this approach, it should be possible to add fairly good support for resources to Wasm. I've only done a little rough prototyping, so we're bound to run into some issues along the way, but I think this should be a good first approximation. From 85cf703282ab498cee787990c296cefb4375052e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Fri, 15 Jul 2022 13:16:01 +0200 Subject: [PATCH 40/71] Fix typos, minor revisions --- crates/plugin_runtime/OPAQUE.md | 36 +++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/plugin_runtime/OPAQUE.md b/crates/plugin_runtime/OPAQUE.md index 6e140d796c..c99224edeb 100644 --- a/crates/plugin_runtime/OPAQUE.md +++ b/crates/plugin_runtime/OPAQUE.md @@ -12,9 +12,9 @@ The reason resources are problematic is because, unlike data, we can't pass reso 3. Some sort of way to **modify the resources** we're holding onto. This requires two things: some way for a plugin to request a modification, and some for the runtime to apply that modification. Here I'm using 'modification' in the most general sense, which includes, e.g. reading or writing to the resource, i.e. calling a method on it. -Luckily for us managing resources across boundaries is a problem that languages have had to deal with for eons. File descriptors referencing resources managed by the kernel is the quintessential definition of resource management, but this pattern is oft repeated in games, scripting languages, or surprise surprise, when writing plugins. +Luckily for us, managing resources across boundaries is a problem that languages have had to deal with for eons. File descriptors referencing resources managed by the kernel quintessentially defines of resource management, but this pattern is oft repeated in games, scripting languages, or surprise surprise, when writing plugins. -To see what managing plugins could look like in Rust, we need look no further than Rhai. Rhai is a scripting language powered by a tree-walk interpreter written in Rust. It's pretty neat, but what we care about is not the language itself, but how it interfaces with Rust types. +To see what managing resources in plugins could look like in Rust, we need look no further than Rhai. Rhai is a scripting language powered by a tree-walk interpreter written in Rust. It's pretty neat, but what we care about is not the language itself, but how it interfaces with Rust types. In its [guide](https://rhai.rs/book/rust/custom-types.html), Rhai claims the following: @@ -28,7 +28,7 @@ Given that we have to register a resource with our plugin runtime before we use > A custom type is stored in Rhai as a Rust trait object (specifically, a `dyn rhai::Variant`), with no restrictions other than being `Clone` (plus `Send + Sync` under the `sync` feature). -I'd be interested to know how Rhai disambiguates between different types if everything's a trait object under the hood. (Spoilers, but I've done some digging, and it looks like we keep track of the name of the type using some `std::any::type_name` magic). +I'd be interested to know how Rhai disambiguates between different types if everything's a trait object under the hood. Rhai actually exposes a pretty nice interface for working with native Rust types. We can register a type using `Engine::register_type::()`. Internally, this just grabs the string name of the type for future reference. @@ -36,7 +36,7 @@ Rhai actually exposes a pretty nice interface for working with native Rust types In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). -With respect to Wasm plugins, I think an interface like this is fairly important, because we don't know whether the original plugin was written in Rust. (This may not be true now, but once we allow packaging and shipping plugins, it's important to maintain a consistent interface, because even Rust changes over time.) +With respect to Wasm plugins, I think an interface like this is fairly important, because we don't know whether the original plugin was written in Rust. (This may not be true now, because we write all the plugins Zed uses, but once we allow packaging and shipping plugins, it's important to maintain a consistent interface, because even Rust changes over time.) Once we've registered a type, we can begin using this type in functions. We can add new function using the standard `Engine::register_fn` function, which has the following signature: @@ -47,15 +47,15 @@ where F: RegisterNativeFunction, ``` -This is quite complex, but under the hood it's fairly similar to our own `PluginBuilder::host_function` async method. Looking at `RegisterNativeFunction`, it seems as though this trait essentially provides methods that expose the `TypeID`s as well as type/param names of the arguments and return types of the function (surprise surprise). +This is quite complex, but under the hood it's fairly similar to our own `PluginBuilder::host_function` async method. Looking at `RegisterNativeFunction`, it seems as though this trait essentially provides methods that expose the `TypeID`s and type/param names of the arguments and return types of the function. -So once we register a function, what happens once we call it? Well, let me introduce you to my friend `Engine::call_native_fn`, whose type signature is too complex to list here. +So once we register a function, what happens when we call it? Well, let me introduce you to my friend `Engine::call_native_fn`, whose type signature is too complex to list here. > **Note**: Finding this function took like 7 levels of indirection from `eval`. It's surprising how much shuffling of data Rhai does under the hood, I bet you could probably make it a lot faster. This takes and returns, like everything else in Rhai, an object of type `Dynamic`. We know that we can use native Rust types, so how does Rhai perform the conversion to and from `Dynamic`? -The secret lies in `Dynamic::try_cast::(self)`. Like most dynamic scripting languages, Rhai uses a tagged `Union` to represent types. Remember `dyn Variant` from earlier? Rhai's `Union` has a variant, `Variant`, to hold the dynamic native types: +The secret lies in `Dynamic::try_cast::(self) -> Option`. Like most dynamic scripting languages, Rhai uses a tagged `Union` to represent types. Remember `dyn Variant` from earlier? Rhai's `Union` has a variant, `Variant`, to hold the dynamic native types: ```rust /// Any type as a trait object. @@ -71,19 +71,19 @@ Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), Now Rhai can do this because it's implemented in Rust. In other words, unlike Wasm, Rhai scripts can, indirectly, hold references to places in host memory. For us to implement something like this for Wasm plugins, we'd have to keep track of a "`ResourcePool`"—alive for the duration of each function call—that we can check rust types into and out of. - I think I've got what's going on now, let's stop talking about Rhai and discuss what this opaque object system would look like if we implemented it in Rust. + I think I've got a handle on how Rhai works now, so let's stop talking about Rhai and discuss what this opaque object system would look like if we implemented it in Rust. # Design Sketch -First things first, we'd have to generalize the arguments we can pass to functions host-side. Currently, we support anything that's `serde`able. We'd have to create a new trait, say `Value`, that has blanket implementations for both `serde` and `Clone` (or something like this; if a type is both `serde` and `clone`, we'd have to figure out a way to disambiguate). +First things first, we'd have to generalize the arguments we can pass to and return from functions host-side. Currently, we support anything that's `serde`able. We'd have to create a new trait, say `Value`, that has blanket implementations for both `serde` and `Clone` (or something like this; if a type is both `serde` and `clone`, we'd have to figure out a way to disambiguate). - We'd also create a `ResourcePool` that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources, (e.g. `Clone` instead of `Serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. + We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. We'd probably also need a `Resource` trait that defines an associated handle for a resource. Something like this: ```rust pub trait Resource { - type Handle: Serialize + DeserializeOwned + type Handle: Serialize + DeserializeOwned; fn handle(index: u32) -> Self; fn index(handle: Self) -> u32; } @@ -127,7 +127,9 @@ use plugin_handles::RopeHandle; pub fn append(rope: RopeHandle, string: &str); ``` -This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? First, we'd define a plugin-side function as follows: +This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only aquire resources to handles we're given, so we'd need to expose a fuction that takes a handle. + +To illustrate that point, here's an example. First, we'd define a plugin-side function as follows: ```rust // same file as above ... @@ -173,6 +175,14 @@ So here's what calling `append_newline` would do, from the top: 5. The Rust async callback actually acquires a lock and appends the newline. -6. And from here on out we return up the callstack, through Wasm, to Rust all the way back to where we started. +6. And from here on out we return up the callstack, through Wasm, to Rust all the way back to where we started. Right before we return, we clear out the `ResourcePool`, so that we're no longer holding onto the underlying resource. + +Throughout this entire chain of calls, the resource remain host-side. By temporarilty checking it into a `ResourcePool`, we're able to keep a reference to the resource that we can use, while avoiding copying the uncopyable resource. + +## Final Notes Using this approach, it should be possible to add fairly good support for resources to Wasm. I've only done a little rough prototyping, so we're bound to run into some issues along the way, but I think this should be a good first approximation. + +This next week, I'll try to get a production-ready version of this working, using the `Language` resource required by some Language Server Adapters. + +Hope this guide made sense! \ No newline at end of file From 90428255d92f4671197d3d34f9fa33116a4557da Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 10:36:37 -0700 Subject: [PATCH 41/71] Added some simple tests for the new keybindings --- Cargo.lock | 957 +---------------------- crates/terminal/Cargo.toml | 1 - crates/terminal/src/connection/events.rs | 347 ++++---- 3 files changed, 205 insertions(+), 1100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02403aa49b..6c87bb055f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,41 +59,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alacritty" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f293c6549baebc7f1ad0d39006bf12b7b0225cfb38687d6bd431e840b1b9f19" -dependencies = [ - "alacritty_config_derive", - "alacritty_terminal", - "bitflags", - "cocoa", - "copypasta", - "crossfont", - "dirs 3.0.2", - "embed-resource", - "fnv", - "gl_generator", - "glutin", - "libc", - "log", - "notify", - "objc", - "parking_lot 0.11.2", - "png", - "raw-window-handle", - "serde", - "serde_json", - "serde_yaml", - "structopt", - "unicode-width", - "wayland-client", - "winapi 0.3.9", - "x11-dl", - "xdg", -] - [[package]] name = "alacritty_config_derive" version = "0.1.0" @@ -121,7 +86,7 @@ dependencies = [ "mio-anonymous-pipes", "mio-extras", "miow 0.3.7", - "nix 0.22.3", + "nix", "parking_lot 0.11.2", "regex-automata", "serde", @@ -139,12 +104,6 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" -[[package]] -name = "android_glue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" - [[package]] name = "ansi_term" version = "0.12.1" @@ -721,16 +680,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" -[[package]] -name = "calloop" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" -dependencies = [ - "log", - "nix 0.22.3", -] - [[package]] name = "cap-fs-ext" version = "0.24.4" @@ -833,15 +782,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - [[package]] name = "chat_panel" version = "0.1.0" @@ -957,7 +897,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap 3.2.8", - "core-foundation 0.9.3", + "core-foundation", "core-services", "dirs 3.0.2", "ipc-channel", @@ -992,16 +932,6 @@ dependencies = [ "util", ] -[[package]] -name = "clipboard-win" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" -dependencies = [ - "lazy-bytes-cast", - "winapi 0.3.9", -] - [[package]] name = "clock" version = "0.1.0" @@ -1026,9 +956,9 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "core-foundation 0.9.3", - "core-graphics 0.22.3", - "foreign-types 0.3.2", + "core-foundation", + "core-graphics", + "foreign-types", "libc", "objc", ] @@ -1040,9 +970,9 @@ source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2f dependencies = [ "bitflags", "block", - "core-foundation 0.9.3", + "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "libc", "objc", ] @@ -1175,71 +1105,29 @@ dependencies = [ "theme", ] -[[package]] -name = "copypasta" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" -dependencies = [ - "clipboard-win", - "objc", - "objc-foundation", - "objc_id", - "smithay-clipboard", - "x11-clipboard", -] - -[[package]] -name = "core-foundation" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -dependencies = [ - "core-foundation-sys 0.7.0", - "libc", -] - [[package]] name = "core-foundation" version = "0.9.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "core-foundation-sys 0.8.3", + "core-foundation-sys", "libc", ] -[[package]] -name = "core-foundation-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" - [[package]] name = "core-foundation-sys" version = "0.8.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" -[[package]] -name = "core-graphics" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" -dependencies = [ - "bitflags", - "core-foundation 0.7.0", - "foreign-types 0.3.2", - "libc", -] - [[package]] name = "core-graphics" version = "0.22.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ "bitflags", - "core-foundation 0.9.3", + "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "libc", ] @@ -1249,8 +1137,8 @@ version = "0.1.1" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ "bitflags", - "core-foundation 0.9.3", - "foreign-types 0.3.2", + "core-foundation", + "foreign-types", "libc", ] @@ -1260,7 +1148,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b344b958cae90858bf6086f49599ecc5ec8698eacad0ea155509ba11fab347" dependencies = [ - "core-foundation 0.9.3", + "core-foundation", ] [[package]] @@ -1269,25 +1157,12 @@ version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ - "core-foundation 0.9.3", - "core-graphics 0.22.3", - "foreign-types 0.3.2", + "core-foundation", + "core-graphics", + "foreign-types", "libc", ] -[[package]] -name = "core-video-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" -dependencies = [ - "cfg-if 0.1.10", - "core-foundation-sys 0.7.0", - "core-graphics 0.19.2", - "libc", - "objc", -] - [[package]] name = "cpp_demangle" version = "0.3.5" @@ -1502,27 +1377,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "crossfont" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1299695a4c6417b7e4a6957bd963478406e148b7b0351e2f2ce31296b0518251" -dependencies = [ - "cocoa", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", - "core-graphics 0.22.3", - "core-text", - "dwrote", - "foreign-types 0.5.0", - "freetype-rs", - "libc", - "log", - "pkg-config", - "servo-fontconfig", - "winapi 0.3.9", -] - [[package]] name = "crypto-common" version = "0.1.4" @@ -1553,12 +1407,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "curl" version = "0.4.43" @@ -1590,41 +1438,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "data-url" version = "0.1.1" @@ -1760,33 +1573,12 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dlib" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" -dependencies = [ - "libloading", -] - [[package]] name = "dotenv" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - [[package]] name = "dwrote" version = "0.11.0" @@ -1795,8 +1587,6 @@ checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", - "serde", - "serde_derive", "winapi 0.3.9", "wio", ] @@ -1859,19 +1649,6 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" -[[package]] -name = "embed-resource" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" -dependencies = [ - "cc", - "rustc_version 0.4.0", - "toml", - "vswhom", - "winreg", -] - [[package]] name = "encoding_rs" version = "0.8.31" @@ -2013,18 +1790,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "filetime" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall", - "windows-sys", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -2066,8 +1831,8 @@ source = "git+https://github.com/zed-industries/font-kit?rev=8eaf7a918eafa28b0a3 dependencies = [ "bitflags", "byteorder", - "core-foundation 0.9.3", - "core-graphics 0.22.3", + "core-foundation", + "core-graphics", "core-text", "dirs-next", "dwrote", @@ -2090,7 +1855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1" dependencies = [ "log", - "memmap2 0.2.3", + "memmap2", "ttf-parser 0.12.3", ] @@ -2100,28 +1865,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "foreign-types-shared", ] [[package]] @@ -2130,12 +1874,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -2156,17 +1894,6 @@ dependencies = [ "libc", ] -[[package]] -name = "freetype-rs" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" -dependencies = [ - "bitflags", - "freetype-sys", - "libc", -] - [[package]] name = "freetype-sys" version = "0.13.1" @@ -2189,35 +1916,16 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "fsevent" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" -dependencies = [ - "bitflags", - "fsevent-sys 2.0.1", -] - [[package]] name = "fsevent" version = "2.0.2" dependencies = [ "bitflags", - "fsevent-sys 3.1.0", + "fsevent-sys", "parking_lot 0.11.2", "tempdir", ] -[[package]] -name = "fsevent-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" -dependencies = [ - "libc", -] - [[package]] name = "fsevent-sys" version = "3.1.0" @@ -2434,17 +2142,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - [[package]] name = "glob" version = "0.3.0" @@ -2464,78 +2161,6 @@ dependencies = [ "regex", ] -[[package]] -name = "glutin" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ea9dbe544bc8a657c4c4a798c2d16cd01b549820e47657297549d28371f6d2" -dependencies = [ - "android_glue", - "cgl", - "cocoa", - "core-foundation 0.9.3", - "glutin_egl_sys", - "glutin_emscripten_sys", - "glutin_gles2_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "lazy_static", - "libloading", - "log", - "objc", - "osmesa-sys", - "parking_lot 0.11.2", - "wayland-client", - "wayland-egl", - "winapi 0.3.9", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" -dependencies = [ - "gl_generator", - "winapi 0.3.9", -] - -[[package]] -name = "glutin_emscripten_sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" - -[[package]] -name = "glutin_gles2_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" -dependencies = [ - "gl_generator", - "objc", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" -dependencies = [ - "gl_generator", -] - [[package]] name = "go_to_line" version = "0.1.0" @@ -2561,15 +2186,15 @@ dependencies = [ "cc", "cocoa", "collections", - "core-foundation 0.9.3", - "core-graphics 0.22.3", + "core-foundation", + "core-graphics", "core-text", "ctor", "dhat", "env_logger", "etagere", "font-kit", - "foreign-types 0.3.2", + "foreign-types", "futures", "gpui_macros", "image", @@ -2856,12 +2481,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.2.3" @@ -2927,26 +2546,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" -[[package]] -name = "inotify" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" -dependencies = [ - "bitflags", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - [[package]] name = "instant" version = "0.1.12" @@ -2954,9 +2553,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -3076,12 +2672,6 @@ dependencies = [ "cc", ] -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "jobserver" version = "0.1.24" @@ -3138,12 +2728,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - [[package]] name = "kurbo" version = "0.8.3" @@ -3193,12 +2777,6 @@ dependencies = [ "util", ] -[[package]] -name = "lazy-bytes-cast" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3449,24 +3027,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memmap2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" -dependencies = [ - "libc", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -3492,7 +3052,7 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "foreign-types 0.3.2", + "foreign-types", "log", "objc", ] @@ -3664,59 +3224,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ndk" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" -dependencies = [ - "bitflags", - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-glue" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4" -dependencies = [ - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-macro", - "ndk-sys", -] - -[[package]] -name = "ndk-macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" -dependencies = [ - "darling", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ndk-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" - [[package]] name = "net2" version = "0.2.37" @@ -3741,17 +3248,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nix" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "libc", -] - [[package]] name = "nom" version = "7.1.1" @@ -3762,24 +3258,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "notify" -version = "4.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" -dependencies = [ - "bitflags", - "filetime", - "fsevent 0.4.0", - "fsevent-sys 2.0.1", - "inotify", - "libc", - "mio 0.6.23", - "mio-extras", - "walkdir", - "winapi 0.3.9", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -3861,27 +3339,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "num_threads" version = "0.1.6" @@ -3901,17 +3358,6 @@ dependencies = [ "objc_exception", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc_exception" version = "0.1.2" @@ -3921,15 +3367,6 @@ dependencies = [ "cc", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "object" version = "0.28.4" @@ -3962,7 +3399,7 @@ checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ "bitflags", "cfg-if 1.0.0", - "foreign-types 0.3.2", + "foreign-types", "libc", "once_cell", "openssl-macros", @@ -4014,15 +3451,6 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" -[[package]] -name = "osmesa-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" -dependencies = [ - "shared_library", -] - [[package]] name = "outline" version = "0.1.0" @@ -4136,7 +3564,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" dependencies = [ - "rustc_version 0.3.3", + "rustc_version", ] [[package]] @@ -4358,16 +3786,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" -[[package]] -name = "proc-macro-crate" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" -dependencies = [ - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4411,7 +3829,7 @@ dependencies = [ "client", "clock", "collections", - "fsevent 2.0.2", + "fsevent", "futures", "fuzzy", "gpui", @@ -4601,15 +4019,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "quick-xml" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.20" @@ -4718,15 +4127,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "raw-window-handle" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" -dependencies = [ - "cty", -] - [[package]] name = "rayon" version = "1.5.3" @@ -5041,16 +4441,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ - "semver 0.11.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.12", + "semver", ] [[package]] @@ -5192,12 +4583,6 @@ dependencies = [ "syn", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -5280,8 +4665,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", + "core-foundation", + "core-foundation-sys", "libc", "security-framework-sys", ] @@ -5292,7 +4677,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ - "core-foundation-sys 0.8.3", + "core-foundation-sys", "libc", ] @@ -5305,12 +4690,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" - [[package]] name = "semver-parser" version = "0.10.2" @@ -5518,16 +4897,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - [[package]] name = "shellexpand" version = "2.1.0" @@ -5647,53 +5016,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" -[[package]] -name = "smithay-client-toolkit" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" -dependencies = [ - "bitflags", - "calloop", - "dlib", - "lazy_static", - "log", - "memmap2 0.3.1", - "nix 0.22.3", - "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" -dependencies = [ - "bitflags", - "dlib", - "lazy_static", - "log", - "memmap2 0.5.5", - "nix 0.24.1", - "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", -] - -[[package]] -name = "smithay-clipboard" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" -dependencies = [ - "smithay-client-toolkit 0.16.0", - "wayland-client", -] - [[package]] name = "smol" version = "1.2.5" @@ -5891,30 +5213,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "subtle" version = "2.4.1" @@ -6055,7 +5353,6 @@ dependencies = [ name = "terminal" version = "0.1.0" dependencies = [ - "alacritty", "alacritty_terminal", "client", "dirs 4.0.0", @@ -6845,7 +6142,7 @@ dependencies = [ "fontdb", "kurbo", "log", - "memmap2 0.2.3", + "memmap2", "pico-args", "rctree", "roxmltree", @@ -6958,26 +6255,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "vte" version = "0.10.1" @@ -7396,89 +6673,6 @@ dependencies = [ "wast 43.0.0", ] -[[package]] -name = "wayland-client" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix 0.22.3", - "scoped-tls", - "wayland-commons", - "wayland-scanner", - "wayland-sys", -] - -[[package]] -name = "wayland-commons" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" -dependencies = [ - "nix 0.22.3", - "once_cell", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-cursor" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" -dependencies = [ - "nix 0.22.3", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-egl" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83281d69ee162b59031c666385e93bde4039ec553b90c4191cdb128ceea29a3a" -dependencies = [ - "wayland-client", - "wayland-sys", -] - -[[package]] -name = "wayland-protocols" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" -dependencies = [ - "bitflags", - "wayland-client", - "wayland-commons", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", -] - -[[package]] -name = "wayland-sys" -version = "0.29.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" -dependencies = [ - "dlib", - "lazy_static", - "pkg-config", -] - [[package]] name = "web-sys" version = "0.3.58" @@ -7691,40 +6885,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" -[[package]] -name = "winit" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" -dependencies = [ - "bitflags", - "cocoa", - "core-foundation 0.9.3", - "core-graphics 0.22.3", - "core-video-sys", - "dispatch", - "instant", - "lazy_static", - "libc", - "log", - "mio 0.8.4", - "ndk", - "ndk-glue", - "ndk-sys", - "objc", - "parking_lot 0.11.2", - "percent-encoding", - "raw-window-handle", - "serde", - "smithay-client-toolkit 0.15.4", - "wasm-bindgen", - "wayland-client", - "wayland-protocols", - "web-sys", - "winapi 0.3.9", - "x11-dl", -] - [[package]] name = "winreg" version = "0.10.1" @@ -7801,55 +6961,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "x11-clipboard" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" -dependencies = [ - "xcb", -] - -[[package]] -name = "x11-dl" -version = "2.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" -dependencies = [ - "lazy_static", - "libc", - "pkg-config", -] - -[[package]] -name = "xcb" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" -dependencies = [ - "libc", - "log", - "quick-xml", -] - -[[package]] -name = "xcursor" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" -dependencies = [ - "nom", -] - -[[package]] -name = "xdg" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" -dependencies = [ - "dirs 4.0.0", -] - [[package]] name = "xml-rs" version = "0.8.4" @@ -7906,7 +7017,7 @@ dependencies = [ "editor", "env_logger", "file_finder", - "fsevent 2.0.2", + "fsevent", "futures", "fuzzy", "go_to_line", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 63894b57f0..ee67e644b0 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -28,4 +28,3 @@ gpui = { path = "../gpui", features = ["test-support"] } client = { path = "../client", features = ["test-support"]} project = { path = "../project", features = ["test-support"]} workspace = { path = "../workspace", features = ["test-support"] } -alacritty = "0.10.1" \ No newline at end of file diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/events.rs index 14a3b873c7..ae14e296b5 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/events.rs @@ -1,7 +1,20 @@ use alacritty_terminal::term::TermMode; use gpui::keymap::Keystroke; -pub enum ModifierCombinations { +/* +Design notes: +I would like terminal mode checking to be concealed behind the TerminalConnection in as many ways as possible. +Alacritty has a lot of stuff intermixed for it's input handling. TerminalConnection should be in charge +of anything that needs to conform to a standard that isn't handled by Term, e.g.: +- Reporting mouse events correctly. +- Reporting scrolls -> Depends on MOUSE_MODE, ALT_SCREEN, and ALTERNATE_SCROLL, etc. +- Correctly bracketing a paste +- Storing changed colors +- Focus change sequence +*/ + +#[derive(Debug)] +pub enum Modifiers { None, Alt, Ctrl, @@ -10,168 +23,168 @@ pub enum ModifierCombinations { Other, } -impl ModifierCombinations { +impl Modifiers { fn new(ks: &Keystroke) -> Self { match (ks.alt, ks.ctrl, ks.shift, ks.cmd) { - (false, false, false, false) => ModifierCombinations::None, - (true, false, false, false) => ModifierCombinations::Alt, - (false, true, false, false) => ModifierCombinations::Ctrl, - (false, false, true, false) => ModifierCombinations::Shift, - (false, true, true, false) => ModifierCombinations::CtrlShift, - _ => ModifierCombinations::Other, + (false, false, false, false) => Modifiers::None, + (true, false, false, false) => Modifiers::Alt, + (false, true, false, false) => Modifiers::Ctrl, + (false, false, true, false) => Modifiers::Shift, + (false, true, true, false) => Modifiers::CtrlShift, + _ => Modifiers::Other, } } } pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { - let modifiers = ModifierCombinations::new(&keystroke); + let modifiers = Modifiers::new(&keystroke); // Manual Bindings including modifiers let manual_esc_str = match (keystroke.key.as_ref(), modifiers) { //Basic special keys - ("space", ModifierCombinations::None) => Some(" ".to_string()), - ("tab", ModifierCombinations::None) => Some("\x09".to_string()), - ("escape", ModifierCombinations::None) => Some("\x1b".to_string()), - ("enter", ModifierCombinations::None) => Some("\x0d".to_string()), - ("backspace", ModifierCombinations::None) => Some("\x7f".to_string()), + ("space", Modifiers::None) => Some(" ".to_string()), + ("tab", Modifiers::None) => Some("\x09".to_string()), + ("escape", Modifiers::None) => Some("\x1b".to_string()), + ("enter", Modifiers::None) => Some("\x0d".to_string()), + ("backspace", Modifiers::None) => Some("\x7f".to_string()), //Interesting escape codes - ("tab", ModifierCombinations::Shift) => Some("\x1b[Z".to_string()), - ("backspace", ModifierCombinations::Alt) => Some("\x1b\x7f".to_string()), - ("backspace", ModifierCombinations::Shift) => Some("\x7f".to_string()), - ("home", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + ("tab", Modifiers::Shift) => Some("\x1b[Z".to_string()), + ("backspace", Modifiers::Alt) => Some("\x1b\x7f".to_string()), + ("backspace", Modifiers::Shift) => Some("\x7f".to_string()), + ("home", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { Some("\x1b[1;2H".to_string()) } - ("end", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + ("end", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { Some("\x1b[1;2F".to_string()) } - ("pageup", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + ("pageup", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { Some("\x1b[5;2~".to_string()) } - ("pagedown", ModifierCombinations::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + ("pagedown", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { Some("\x1b[6;2~".to_string()) } - ("home", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + ("home", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { Some("\x1bOH".to_string()) } - ("home", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + ("home", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { Some("\x1b[H".to_string()) } - ("end", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + ("end", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { Some("\x1bOF".to_string()) } - ("end", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + ("end", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { Some("\x1b[F".to_string()) } - ("up", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + ("up", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { Some("\x1bOA".to_string()) } - ("up", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + ("up", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { Some("\x1b[A".to_string()) } - ("down", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + ("down", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { Some("\x1bOB".to_string()) } - ("down", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + ("down", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { Some("\x1b[B".to_string()) } - ("right", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + ("right", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { Some("\x1bOC".to_string()) } - ("right", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + ("right", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { Some("\x1b[C".to_string()) } - ("left", ModifierCombinations::None) if mode.contains(TermMode::APP_CURSOR) => { + ("left", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { Some("\x1bOD".to_string()) } - ("left", ModifierCombinations::None) if !mode.contains(TermMode::APP_CURSOR) => { + ("left", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { Some("\x1b[D".to_string()) } - ("back", ModifierCombinations::None) => Some("\x7f".to_string()), - ("insert", ModifierCombinations::None) => Some("\x1b[2~".to_string()), - ("delete", ModifierCombinations::None) => Some("\x1b[3~".to_string()), - ("pageup", ModifierCombinations::None) => Some("\x1b[5~".to_string()), - ("pagedown", ModifierCombinations::None) => Some("\x1b[6~".to_string()), - ("f1", ModifierCombinations::None) => Some("\x1bOP".to_string()), - ("f2", ModifierCombinations::None) => Some("\x1bOQ".to_string()), - ("f3", ModifierCombinations::None) => Some("\x1bOR".to_string()), - ("f4", ModifierCombinations::None) => Some("\x1bOS".to_string()), - ("f5", ModifierCombinations::None) => Some("\x1b[15~".to_string()), - ("f6", ModifierCombinations::None) => Some("\x1b[17~".to_string()), - ("f7", ModifierCombinations::None) => Some("\x1b[18~".to_string()), - ("f8", ModifierCombinations::None) => Some("\x1b[19~".to_string()), - ("f9", ModifierCombinations::None) => Some("\x1b[20~".to_string()), - ("f10", ModifierCombinations::None) => Some("\x1b[21~".to_string()), - ("f11", ModifierCombinations::None) => Some("\x1b[23~".to_string()), - ("f12", ModifierCombinations::None) => Some("\x1b[24~".to_string()), - ("f13", ModifierCombinations::None) => Some("\x1b[25~".to_string()), - ("f14", ModifierCombinations::None) => Some("\x1b[26~".to_string()), - ("f15", ModifierCombinations::None) => Some("\x1b[28~".to_string()), - ("f16", ModifierCombinations::None) => Some("\x1b[29~".to_string()), - ("f17", ModifierCombinations::None) => Some("\x1b[31~".to_string()), - ("f18", ModifierCombinations::None) => Some("\x1b[32~".to_string()), - ("f19", ModifierCombinations::None) => Some("\x1b[33~".to_string()), - ("f20", ModifierCombinations::None) => Some("\x1b[34~".to_string()), + ("back", Modifiers::None) => Some("\x7f".to_string()), + ("insert", Modifiers::None) => Some("\x1b[2~".to_string()), + ("delete", Modifiers::None) => Some("\x1b[3~".to_string()), + ("pageup", Modifiers::None) => Some("\x1b[5~".to_string()), + ("pagedown", Modifiers::None) => Some("\x1b[6~".to_string()), + ("f1", Modifiers::None) => Some("\x1bOP".to_string()), + ("f2", Modifiers::None) => Some("\x1bOQ".to_string()), + ("f3", Modifiers::None) => Some("\x1bOR".to_string()), + ("f4", Modifiers::None) => Some("\x1bOS".to_string()), + ("f5", Modifiers::None) => Some("\x1b[15~".to_string()), + ("f6", Modifiers::None) => Some("\x1b[17~".to_string()), + ("f7", Modifiers::None) => Some("\x1b[18~".to_string()), + ("f8", Modifiers::None) => Some("\x1b[19~".to_string()), + ("f9", Modifiers::None) => Some("\x1b[20~".to_string()), + ("f10", Modifiers::None) => Some("\x1b[21~".to_string()), + ("f11", Modifiers::None) => Some("\x1b[23~".to_string()), + ("f12", Modifiers::None) => Some("\x1b[24~".to_string()), + ("f13", Modifiers::None) => Some("\x1b[25~".to_string()), + ("f14", Modifiers::None) => Some("\x1b[26~".to_string()), + ("f15", Modifiers::None) => Some("\x1b[28~".to_string()), + ("f16", Modifiers::None) => Some("\x1b[29~".to_string()), + ("f17", Modifiers::None) => Some("\x1b[31~".to_string()), + ("f18", Modifiers::None) => Some("\x1b[32~".to_string()), + ("f19", Modifiers::None) => Some("\x1b[33~".to_string()), + ("f20", Modifiers::None) => Some("\x1b[34~".to_string()), // NumpadEnter, Action::Esc("\n".into()); //Mappings for caret notation keys - ("a", ModifierCombinations::Ctrl) => Some("\x01".to_string()), //1 - ("A", ModifierCombinations::CtrlShift) => Some("\x01".to_string()), //1 - ("b", ModifierCombinations::Ctrl) => Some("\x02".to_string()), //2 - ("B", ModifierCombinations::CtrlShift) => Some("\x02".to_string()), //2 - ("c", ModifierCombinations::Ctrl) => Some("\x03".to_string()), //3 - ("C", ModifierCombinations::CtrlShift) => Some("\x03".to_string()), //3 - ("d", ModifierCombinations::Ctrl) => Some("\x04".to_string()), //4 - ("D", ModifierCombinations::CtrlShift) => Some("\x04".to_string()), //4 - ("e", ModifierCombinations::Ctrl) => Some("\x05".to_string()), //5 - ("E", ModifierCombinations::CtrlShift) => Some("\x05".to_string()), //5 - ("f", ModifierCombinations::Ctrl) => Some("\x06".to_string()), //6 - ("F", ModifierCombinations::CtrlShift) => Some("\x06".to_string()), //6 - ("g", ModifierCombinations::Ctrl) => Some("\x07".to_string()), //7 - ("G", ModifierCombinations::CtrlShift) => Some("\x07".to_string()), //7 - ("h", ModifierCombinations::Ctrl) => Some("\x08".to_string()), //8 - ("H", ModifierCombinations::CtrlShift) => Some("\x08".to_string()), //8 - ("i", ModifierCombinations::Ctrl) => Some("\x09".to_string()), //9 - ("I", ModifierCombinations::CtrlShift) => Some("\x09".to_string()), //9 - ("j", ModifierCombinations::Ctrl) => Some("\x0a".to_string()), //10 - ("J", ModifierCombinations::CtrlShift) => Some("\x0a".to_string()), //10 - ("k", ModifierCombinations::Ctrl) => Some("\x0b".to_string()), //11 - ("K", ModifierCombinations::CtrlShift) => Some("\x0b".to_string()), //11 - ("l", ModifierCombinations::Ctrl) => Some("\x0c".to_string()), //12 - ("L", ModifierCombinations::CtrlShift) => Some("\x0c".to_string()), //12 - ("m", ModifierCombinations::Ctrl) => Some("\x0d".to_string()), //13 - ("M", ModifierCombinations::CtrlShift) => Some("\x0d".to_string()), //13 - ("n", ModifierCombinations::Ctrl) => Some("\x0e".to_string()), //14 - ("N", ModifierCombinations::CtrlShift) => Some("\x0e".to_string()), //14 - ("o", ModifierCombinations::Ctrl) => Some("\x0f".to_string()), //15 - ("O", ModifierCombinations::CtrlShift) => Some("\x0f".to_string()), //15 - ("p", ModifierCombinations::Ctrl) => Some("\x10".to_string()), //16 - ("P", ModifierCombinations::CtrlShift) => Some("\x10".to_string()), //16 - ("q", ModifierCombinations::Ctrl) => Some("\x11".to_string()), //17 - ("Q", ModifierCombinations::CtrlShift) => Some("\x11".to_string()), //17 - ("r", ModifierCombinations::Ctrl) => Some("\x12".to_string()), //18 - ("R", ModifierCombinations::CtrlShift) => Some("\x12".to_string()), //18 - ("s", ModifierCombinations::Ctrl) => Some("\x13".to_string()), //19 - ("S", ModifierCombinations::CtrlShift) => Some("\x13".to_string()), //19 - ("t", ModifierCombinations::Ctrl) => Some("\x14".to_string()), //20 - ("T", ModifierCombinations::CtrlShift) => Some("\x14".to_string()), //20 - ("u", ModifierCombinations::Ctrl) => Some("\x15".to_string()), //21 - ("U", ModifierCombinations::CtrlShift) => Some("\x15".to_string()), //21 - ("v", ModifierCombinations::Ctrl) => Some("\x16".to_string()), //22 - ("V", ModifierCombinations::CtrlShift) => Some("\x16".to_string()), //22 - ("w", ModifierCombinations::Ctrl) => Some("\x17".to_string()), //23 - ("W", ModifierCombinations::CtrlShift) => Some("\x17".to_string()), //23 - ("x", ModifierCombinations::Ctrl) => Some("\x18".to_string()), //24 - ("X", ModifierCombinations::CtrlShift) => Some("\x18".to_string()), //24 - ("y", ModifierCombinations::Ctrl) => Some("\x19".to_string()), //25 - ("Y", ModifierCombinations::CtrlShift) => Some("\x19".to_string()), //25 - ("z", ModifierCombinations::Ctrl) => Some("\x1a".to_string()), //26 - ("Z", ModifierCombinations::CtrlShift) => Some("\x1a".to_string()), //26 - ("@", ModifierCombinations::Ctrl) => Some("\x00".to_string()), //0 - ("[", ModifierCombinations::Ctrl) => Some("\x1b".to_string()), //27 - ("\\", ModifierCombinations::Ctrl) => Some("\x1c".to_string()), //28 - ("]", ModifierCombinations::Ctrl) => Some("\x1d".to_string()), //29 - ("^", ModifierCombinations::Ctrl) => Some("\x1e".to_string()), //30 - ("_", ModifierCombinations::Ctrl) => Some("\x1f".to_string()), //31 - ("?", ModifierCombinations::Ctrl) => Some("\x7f".to_string()), //127 + ("a", Modifiers::Ctrl) => Some("\x01".to_string()), //1 + ("A", Modifiers::CtrlShift) => Some("\x01".to_string()), //1 + ("b", Modifiers::Ctrl) => Some("\x02".to_string()), //2 + ("B", Modifiers::CtrlShift) => Some("\x02".to_string()), //2 + ("c", Modifiers::Ctrl) => Some("\x03".to_string()), //3 + ("C", Modifiers::CtrlShift) => Some("\x03".to_string()), //3 + ("d", Modifiers::Ctrl) => Some("\x04".to_string()), //4 + ("D", Modifiers::CtrlShift) => Some("\x04".to_string()), //4 + ("e", Modifiers::Ctrl) => Some("\x05".to_string()), //5 + ("E", Modifiers::CtrlShift) => Some("\x05".to_string()), //5 + ("f", Modifiers::Ctrl) => Some("\x06".to_string()), //6 + ("F", Modifiers::CtrlShift) => Some("\x06".to_string()), //6 + ("g", Modifiers::Ctrl) => Some("\x07".to_string()), //7 + ("G", Modifiers::CtrlShift) => Some("\x07".to_string()), //7 + ("h", Modifiers::Ctrl) => Some("\x08".to_string()), //8 + ("H", Modifiers::CtrlShift) => Some("\x08".to_string()), //8 + ("i", Modifiers::Ctrl) => Some("\x09".to_string()), //9 + ("I", Modifiers::CtrlShift) => Some("\x09".to_string()), //9 + ("j", Modifiers::Ctrl) => Some("\x0a".to_string()), //10 + ("J", Modifiers::CtrlShift) => Some("\x0a".to_string()), //10 + ("k", Modifiers::Ctrl) => Some("\x0b".to_string()), //11 + ("K", Modifiers::CtrlShift) => Some("\x0b".to_string()), //11 + ("l", Modifiers::Ctrl) => Some("\x0c".to_string()), //12 + ("L", Modifiers::CtrlShift) => Some("\x0c".to_string()), //12 + ("m", Modifiers::Ctrl) => Some("\x0d".to_string()), //13 + ("M", Modifiers::CtrlShift) => Some("\x0d".to_string()), //13 + ("n", Modifiers::Ctrl) => Some("\x0e".to_string()), //14 + ("N", Modifiers::CtrlShift) => Some("\x0e".to_string()), //14 + ("o", Modifiers::Ctrl) => Some("\x0f".to_string()), //15 + ("O", Modifiers::CtrlShift) => Some("\x0f".to_string()), //15 + ("p", Modifiers::Ctrl) => Some("\x10".to_string()), //16 + ("P", Modifiers::CtrlShift) => Some("\x10".to_string()), //16 + ("q", Modifiers::Ctrl) => Some("\x11".to_string()), //17 + ("Q", Modifiers::CtrlShift) => Some("\x11".to_string()), //17 + ("r", Modifiers::Ctrl) => Some("\x12".to_string()), //18 + ("R", Modifiers::CtrlShift) => Some("\x12".to_string()), //18 + ("s", Modifiers::Ctrl) => Some("\x13".to_string()), //19 + ("S", Modifiers::CtrlShift) => Some("\x13".to_string()), //19 + ("t", Modifiers::Ctrl) => Some("\x14".to_string()), //20 + ("T", Modifiers::CtrlShift) => Some("\x14".to_string()), //20 + ("u", Modifiers::Ctrl) => Some("\x15".to_string()), //21 + ("U", Modifiers::CtrlShift) => Some("\x15".to_string()), //21 + ("v", Modifiers::Ctrl) => Some("\x16".to_string()), //22 + ("V", Modifiers::CtrlShift) => Some("\x16".to_string()), //22 + ("w", Modifiers::Ctrl) => Some("\x17".to_string()), //23 + ("W", Modifiers::CtrlShift) => Some("\x17".to_string()), //23 + ("x", Modifiers::Ctrl) => Some("\x18".to_string()), //24 + ("X", Modifiers::CtrlShift) => Some("\x18".to_string()), //24 + ("y", Modifiers::Ctrl) => Some("\x19".to_string()), //25 + ("Y", Modifiers::CtrlShift) => Some("\x19".to_string()), //25 + ("z", Modifiers::Ctrl) => Some("\x1a".to_string()), //26 + ("Z", Modifiers::CtrlShift) => Some("\x1a".to_string()), //26 + ("@", Modifiers::Ctrl) => Some("\x00".to_string()), //0 + ("[", Modifiers::Ctrl) => Some("\x1b".to_string()), //27 + ("\\", Modifiers::Ctrl) => Some("\x1c".to_string()), //28 + ("]", Modifiers::Ctrl) => Some("\x1d".to_string()), //29 + ("^", Modifiers::Ctrl) => Some("\x1e".to_string()), //30 + ("_", Modifiers::Ctrl) => Some("\x1f".to_string()), //31 + ("?", Modifiers::Ctrl) => Some("\x7f".to_string()), //127 _ => None, }; if manual_esc_str.is_some() { @@ -226,62 +239,6 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { } } -/* -New keybindings test plan: - -Is the terminal still usable? YES! -Do ctrl-shift-[X] and ctrl-[x] do the same thing? I THINK SO -Does ctrl-l work? YES -Does tab work? YES -Do all the global overrides (up, down, enter, escape, ctrl-c) work? => YES -Space also doesn't work YES! - - - -So, to match alacritty keyboard handling, we need to check APP_CURSOR, and ALT_SCREEN - -And we need to convert the strings that GPUI returns to keys - -And we need a way of easily declaring and matching a modifier pattern on those keys - -And we need to block writing the input to the pty if any of these match - -And I need to figure out how to express this in a cross platform way - -And a way of optionally interfacing this with actions for rebinding in defaults.json - -Design notes: -I would like terminal mode checking to be concealed behind the TerminalConnection in as many ways as possible. -Alacritty has a lot of stuff intermixed for it's input handling. TerminalConnection should be in charge -of anything that needs to conform to a standard that isn't handled by Term, e.g.: -- Reporting mouse events correctly. -- Reporting scrolls -> Depends on MOUSE_MODE, ALT_SCREEN, and ALTERNATE_SCROLL, etc. -- Correctly bracketing a paste -- Storing changed colors -- Focus change sequence - -Scrolling might be handled internally or externally, need a way to ask. Everything else should probably happen internally. - -Standards/OS compliance is in connection.rs. -This takes GPUI events and translates them to the correct terminal stuff -This means that standards compliance outside of connection should be kept to a minimum. Yes, this feels good. -Connection needs to be split up then, into a bunch of event handlers - -Punting on these by pushing them up to a scrolling element -(either on dispatch_event directly or a seperate scrollbar) - Home, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToTop; - End, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToBottom; - PageUp, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageUp; - PageDown, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageDown; - - - -NOTE, THE FOLLOWING HAS 2 BINDINGS: -K, ModifiersState::LOGO, Action::Esc("\x0c".into()); -K, ModifiersState::LOGO, Action::ClearHistory; => ctx.terminal_mut().clear_screen(ClearMode::Saved), - -*/ - /// Code Modifiers /// ---------+--------------------------- /// 2 | Shift @@ -312,9 +269,47 @@ mod test { use super::*; #[test] - fn test_match_alacritty_keybindings() { - let bindings = alacritty::config::bindings::default_key_bindings(); - //TODO + fn test_application_mode() { + let app_cursor = TermMode::APP_CURSOR; + let none = TermMode::NONE; + + let up = Keystroke::parse("up").unwrap(); + let down = Keystroke::parse("down").unwrap(); + let left = Keystroke::parse("left").unwrap(); + let right = Keystroke::parse("right").unwrap(); + + assert_eq!(to_esc_str(&up, &none), Some("\x1b[A".to_string())); + assert_eq!(to_esc_str(&down, &none), Some("\x1b[B".to_string())); + assert_eq!(to_esc_str(&right, &none), Some("\x1b[C".to_string())); + assert_eq!(to_esc_str(&left, &none), Some("\x1b[D".to_string())); + + assert_eq!(to_esc_str(&up, &app_cursor), Some("\x1bOA".to_string())); + assert_eq!(to_esc_str(&down, &app_cursor), Some("\x1bOB".to_string())); + assert_eq!(to_esc_str(&right, &app_cursor), Some("\x1bOC".to_string())); + assert_eq!(to_esc_str(&left, &app_cursor), Some("\x1bOD".to_string())); + } + + #[test] + fn test_ctrl_codes() { + let letters_lower = 'a'..='z'; + let letters_upper = 'A'..='Z'; + let mode = TermMode::ANY; + + for (lower, upper) in letters_lower.zip(letters_upper) { + assert_eq!( + to_esc_str( + &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(), + &mode + ), + to_esc_str( + &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(), + &mode + ), + "On letter: {}/{}", + lower, + upper + ) + } } #[test] From 3e864116277dc2bccc55e359aaef17a84f230774 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 11:20:54 -0700 Subject: [PATCH 42/71] Finished new keybindings system for now --- crates/terminal/src/connection.rs | 4 +- .../connection/{events.rs => keymappings.rs} | 197 ++++++++++++++---- 2 files changed, 153 insertions(+), 48 deletions(-) rename crates/terminal/src/connection/{events.rs => keymappings.rs} (67%) diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index d17752d395..8f48e4ba8b 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -1,4 +1,4 @@ -mod events; +mod keymappings; use alacritty_terminal::{ ansi::{ClearMode, Handler}, @@ -22,7 +22,7 @@ use crate::{ ZedListener, }; -use self::events::to_esc_str; +use self::keymappings::to_esc_str; const DEFAULT_TITLE: &str = "Terminal"; diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/keymappings.rs similarity index 67% rename from crates/terminal/src/connection/events.rs rename to crates/terminal/src/connection/keymappings.rs index ae14e296b5..a4d429843b 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/keymappings.rs @@ -2,12 +2,9 @@ use alacritty_terminal::term::TermMode; use gpui::keymap::Keystroke; /* -Design notes: -I would like terminal mode checking to be concealed behind the TerminalConnection in as many ways as possible. -Alacritty has a lot of stuff intermixed for it's input handling. TerminalConnection should be in charge -of anything that needs to conform to a standard that isn't handled by Term, e.g.: +Connection events still to do: - Reporting mouse events correctly. -- Reporting scrolls -> Depends on MOUSE_MODE, ALT_SCREEN, and ALTERNATE_SCROLL, etc. +- Reporting scrolls - Correctly bracketing a paste - Storing changed colors - Focus change sequence @@ -34,13 +31,24 @@ impl Modifiers { _ => Modifiers::Other, } } + + fn any(&self) -> bool { + match &self { + Modifiers::None => false, + Modifiers::Alt => true, + Modifiers::Ctrl => true, + Modifiers::Shift => true, + Modifiers::CtrlShift => true, + Modifiers::Other => true, + } + } } pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { let modifiers = Modifiers::new(&keystroke); // Manual Bindings including modifiers - let manual_esc_str = match (keystroke.key.as_ref(), modifiers) { + let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) { //Basic special keys ("space", Modifiers::None) => Some(" ".to_string()), ("tab", Modifiers::None) => Some("\x09".to_string()), @@ -192,53 +200,95 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { } // Automated bindings applying modifiers - let modifier_code = modifier_code(&keystroke); - let modified_esc_str = match keystroke.key.as_ref() { - "up" => Some(format!("\x1b[1;{}A", modifier_code)), - "down" => Some(format!("\x1b[1;{}B", modifier_code)), - "right" => Some(format!("\x1b[1;{}C", modifier_code)), - "left" => Some(format!("\x1b[1;{}D", modifier_code)), - "f1" => Some(format!("\x1b[1;{}P", modifier_code)), - "f2" => Some(format!("\x1b[1;{}Q", modifier_code)), - "f3" => Some(format!("\x1b[1;{}R", modifier_code)), - "f4" => Some(format!("\x1b[1;{}S", modifier_code)), - "F5" => Some(format!("\x1b[15;{}~", modifier_code)), - "f6" => Some(format!("\x1b[17;{}~", modifier_code)), - "f7" => Some(format!("\x1b[18;{}~", modifier_code)), - "f8" => Some(format!("\x1b[19;{}~", modifier_code)), - "f9" => Some(format!("\x1b[20;{}~", modifier_code)), - "f10" => Some(format!("\x1b[21;{}~", modifier_code)), - "f11" => Some(format!("\x1b[23;{}~", modifier_code)), - "f12" => Some(format!("\x1b[24;{}~", modifier_code)), - "f13" => Some(format!("\x1b[25;{}~", modifier_code)), - "f14" => Some(format!("\x1b[26;{}~", modifier_code)), - "f15" => Some(format!("\x1b[28;{}~", modifier_code)), - "f16" => Some(format!("\x1b[29;{}~", modifier_code)), - "f17" => Some(format!("\x1b[31;{}~", modifier_code)), - "f18" => Some(format!("\x1b[32;{}~", modifier_code)), - "f19" => Some(format!("\x1b[33;{}~", modifier_code)), - "f20" => Some(format!("\x1b[34;{}~", modifier_code)), - _ if modifier_code == 2 => None, - "insert" => Some(format!("\x1b[2;{}~", modifier_code)), - "pageup" => Some(format!("\x1b[5;{}~", modifier_code)), - "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)), - "end" => Some(format!("\x1b[1;{}F", modifier_code)), - "home" => Some(format!("\x1b[1;{}H", modifier_code)), - _ => None, - }; - if modified_esc_str.is_some() { - return modified_esc_str; + if modifiers.any() { + let modifier_code = modifier_code(&keystroke); + let modified_esc_str = match keystroke.key.as_ref() { + "up" => Some(format!("\x1b[1;{}A", modifier_code)), + "down" => Some(format!("\x1b[1;{}B", modifier_code)), + "right" => Some(format!("\x1b[1;{}C", modifier_code)), + "left" => Some(format!("\x1b[1;{}D", modifier_code)), + "f1" => Some(format!("\x1b[1;{}P", modifier_code)), + "f2" => Some(format!("\x1b[1;{}Q", modifier_code)), + "f3" => Some(format!("\x1b[1;{}R", modifier_code)), + "f4" => Some(format!("\x1b[1;{}S", modifier_code)), + "F5" => Some(format!("\x1b[15;{}~", modifier_code)), + "f6" => Some(format!("\x1b[17;{}~", modifier_code)), + "f7" => Some(format!("\x1b[18;{}~", modifier_code)), + "f8" => Some(format!("\x1b[19;{}~", modifier_code)), + "f9" => Some(format!("\x1b[20;{}~", modifier_code)), + "f10" => Some(format!("\x1b[21;{}~", modifier_code)), + "f11" => Some(format!("\x1b[23;{}~", modifier_code)), + "f12" => Some(format!("\x1b[24;{}~", modifier_code)), + "f13" => Some(format!("\x1b[25;{}~", modifier_code)), + "f14" => Some(format!("\x1b[26;{}~", modifier_code)), + "f15" => Some(format!("\x1b[28;{}~", modifier_code)), + "f16" => Some(format!("\x1b[29;{}~", modifier_code)), + "f17" => Some(format!("\x1b[31;{}~", modifier_code)), + "f18" => Some(format!("\x1b[32;{}~", modifier_code)), + "f19" => Some(format!("\x1b[33;{}~", modifier_code)), + "f20" => Some(format!("\x1b[34;{}~", modifier_code)), + _ if modifier_code == 2 => None, + "insert" => Some(format!("\x1b[2;{}~", modifier_code)), + "pageup" => Some(format!("\x1b[5;{}~", modifier_code)), + "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)), + "end" => Some(format!("\x1b[1;{}F", modifier_code)), + "home" => Some(format!("\x1b[1;{}H", modifier_code)), + _ => None, + }; + if modified_esc_str.is_some() { + return modified_esc_str; + } } - // Fallback to keystroke input sent directly - if keystroke.key.chars().count() == 1 { - //TODO this might fail on unicode during internationalization + //Fallback to sending the keystroke input directly + //Skin colors in utf8 are implemented as a seperate, invisible character + //that modifies the associated emoji. Some languages may have similarly + //implemented modifiers, e.g. certain diacritics that can be typed as a single character. + //This means that we need to assume some user input can result in multi-byte, + //multi-char strings. This is somewhat difficult, as GPUI normalizes all + //keys into a string representation. Hence, the check here to filter out GPUI + //keys that weren't captured above. + if !matches_gpui_key_str(&keystroke.key) { return Some(keystroke.key.clone()); } else { None } } +///Checks if the given string matches a GPUI key string. +///Table made from reading the source at gpui/src/platform/mac/event.rs +fn matches_gpui_key_str(str: &str) -> bool { + match str { + "backspace" => true, + "up" => true, + "down" => true, + "left" => true, + "right" => true, + "pageup" => true, + "pagedown" => true, + "home" => true, + "end" => true, + "delete" => true, + "enter" => true, + "escape" => true, + "tab" => true, + "f1" => true, + "f2" => true, + "f3" => true, + "f4" => true, + "f5" => true, + "f6" => true, + "f7" => true, + "f8" => true, + "f9" => true, + "f10" => true, + "f11" => true, + "f12" => true, + "space" => true, + _ => false, + } +} + /// Code Modifiers /// ---------+--------------------------- /// 2 | Shift @@ -268,6 +318,61 @@ fn modifier_code(keystroke: &Keystroke) -> u32 { mod test { use super::*; + #[test] + fn test_scroll_keys() { + //These keys should be handled by the scrolling element directly + //Need to signify this by returning 'None' + let shift_pageup = Keystroke::parse("shift-pageup").unwrap(); + let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap(); + let shift_home = Keystroke::parse("shift-home").unwrap(); + let shift_end = Keystroke::parse("shift-end").unwrap(); + + let none = TermMode::NONE; + assert_eq!(to_esc_str(&shift_pageup, &none), None); + assert_eq!(to_esc_str(&shift_pagedown, &none), None); + assert_eq!(to_esc_str(&shift_home, &none), None); + assert_eq!(to_esc_str(&shift_end, &none), None); + + let alt_screen = TermMode::ALT_SCREEN; + assert_eq!( + to_esc_str(&shift_pageup, &alt_screen), + Some("\x1b[5;2~".to_string()) + ); + assert_eq!( + to_esc_str(&shift_pagedown, &alt_screen), + Some("\x1b[6;2~".to_string()) + ); + assert_eq!( + to_esc_str(&shift_home, &alt_screen), + Some("\x1b[1;2H".to_string()) + ); + assert_eq!( + to_esc_str(&shift_end, &alt_screen), + Some("\x1b[1;2F".to_string()) + ); + + let pageup = Keystroke::parse("pageup").unwrap(); + let pagedown = Keystroke::parse("pagedown").unwrap(); + let any = TermMode::ANY; + + assert_eq!(to_esc_str(&pageup, &any), Some("\x1b[5~".to_string())); + assert_eq!(to_esc_str(&pagedown, &any), Some("\x1b[6~".to_string())); + } + + #[test] + fn test_multi_char_fallthrough() { + let ks = Keystroke { + ctrl: false, + alt: false, + shift: false, + cmd: false, + + key: "🖖🏻".to_string(), //2 char string + }; + + assert_eq!(to_esc_str(&ks, &TermMode::NONE), Some("🖖🏻".to_string())); + } + #[test] fn test_application_mode() { let app_cursor = TermMode::APP_CURSOR; @@ -325,7 +430,7 @@ mod test { // 8 | Shift + Alt + Control // ---------+--------------------------- // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys - // assert_eq!(2, modifier_code(Keystroke::parse("shift-A").unwrap())); + assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap())); assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap())); assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap())); assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap())); From 82828589417c3f30ebb6839e797ff7404259adfd Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 11:30:14 -0700 Subject: [PATCH 43/71] Updated copy integration test to match retries on terminal integration test --- crates/terminal/src/terminal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c2d8fc1bab..9f5ab4ce7f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -496,11 +496,11 @@ mod tests { } /// Integration test for selections, clipboard, and terminal execution - #[gpui::test] + #[gpui::test(retries = 5)] async fn test_copy(cx: &mut TestAppContext) { let mut result_line: i32 = 0; let terminal = cx.add_view(Default::default(), |cx| Terminal::new(None, false, cx)); - cx.set_condition_duration(Some(Duration::from_secs(2))); + cx.set_condition_duration(Some(Duration::from_secs(5))); terminal.update(cx, |terminal, cx| { terminal.connection.update(cx, |connection, _| { From e156675640302d2ba8a86ea2179703162dcf15bd Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 12:15:55 -0700 Subject: [PATCH 44/71] Revamped testing in terminals --- crates/terminal/src/terminal.rs | 85 ++++++------------- .../src/tests/terminal_test_context.rs | 82 ++++++++++++++++++ 2 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 crates/terminal/src/tests/terminal_test_context.rs diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 9f5ab4ce7f..2ae3faf103 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -458,85 +458,59 @@ fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option) -> String { - let lines = grid_iterator.group_by(|i| i.point.line.0); - lines - .into_iter() - .map(|(_, line)| line.map(|i| i.c).collect::()) - .collect::>() - .join("\n") - } } diff --git a/crates/terminal/src/tests/terminal_test_context.rs b/crates/terminal/src/tests/terminal_test_context.rs new file mode 100644 index 0000000000..ab0809c3a1 --- /dev/null +++ b/crates/terminal/src/tests/terminal_test_context.rs @@ -0,0 +1,82 @@ +use std::time::Duration; + +use alacritty_terminal::term::SizeInfo; +use gpui::{AppContext, ModelContext, ModelHandle, ReadModelWith, TestAppContext}; +use itertools::Itertools; + +use crate::{ + connection::TerminalConnection, DEBUG_CELL_WIDTH, DEBUG_LINE_HEIGHT, DEBUG_TERMINAL_HEIGHT, + DEBUG_TERMINAL_WIDTH, +}; + +pub struct TerminalTestContext<'a> { + pub cx: &'a mut TestAppContext, + pub connection: ModelHandle, +} + +impl<'a> TerminalTestContext<'a> { + pub fn new(cx: &'a mut TestAppContext) -> Self { + cx.set_condition_duration(Some(Duration::from_secs(5))); + + let size_info = SizeInfo::new( + DEBUG_TERMINAL_WIDTH, + DEBUG_TERMINAL_HEIGHT, + DEBUG_CELL_WIDTH, + DEBUG_LINE_HEIGHT, + 0., + 0., + false, + ); + + let connection = cx.add_model(|cx| TerminalConnection::new(None, size_info, cx)); + + TerminalTestContext { cx, connection } + } + + pub async fn execute_and_wait(&mut self, command: &str, f: F) -> String + where + F: Fn(String, &AppContext) -> bool, + { + let command = command.to_string(); + self.connection.update(self.cx, |connection, _| { + connection.write_to_pty(command); + connection.write_to_pty("\r".to_string()); + }); + + self.connection + .condition(self.cx, |conn, cx| { + let content = Self::grid_as_str(conn); + f(content, cx) + }) + .await; + + self.cx + .read_model_with(&self.connection, &mut |conn, _: &AppContext| { + Self::grid_as_str(conn) + }) + } + + pub fn update_connection(&mut self, f: F) -> S + where + F: FnOnce(&mut TerminalConnection, &mut ModelContext) -> S, + { + self.connection.update(self.cx, |conn, cx| f(conn, cx)) + } + + fn grid_as_str(connection: &TerminalConnection) -> String { + let term = connection.term.lock(); + let grid_iterator = term.renderable_content().display_iter; + let lines = grid_iterator.group_by(|i| i.point.line.0); + lines + .into_iter() + .map(|(_, line)| line.map(|i| i.c).collect::()) + .collect::>() + .join("\n") + } +} + +impl<'a> Drop for TerminalTestContext<'a> { + fn drop(&mut self) { + self.cx.set_condition_duration(None); + } +} From 1363d2c502fee340ea4f4a89b6dc372e8c2d9fcc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Jul 2022 14:58:08 -0700 Subject: [PATCH 45/71] Add admin API for counting users with a given amount of activity --- crates/collab/src/api.rs | 28 ++++++++++++++- crates/collab/src/db.rs | 78 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index d20748609a..6ecb43f7fd 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -17,7 +17,7 @@ use axum::{ use axum_extra::response::ErasedJson; use serde::{Deserialize, Serialize}; use serde_json::json; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use time::OffsetDateTime; use tower::ServiceBuilder; use tracing::instrument; @@ -43,6 +43,7 @@ pub fn routes(rpc_server: &Arc, state: Arc) -> Router, + Extension(app): Extension>, +) -> Result { + let durations_in_minutes = [10, 60, 4 * 60, 8 * 60]; + + let mut user_sets = Vec::with_capacity(durations_in_minutes.len()); + for duration in durations_in_minutes { + user_sets.push(ActiveUserSet { + active_time_in_minutes: duration, + user_count: app + .db + .get_active_user_count(params.start..params.end, Duration::from_secs(duration * 60)) + .await?, + }) + } + Ok(ErasedJson::pretty(user_sets)) +} + #[derive(Deserialize)] struct GetProjectMetadataParams { project_id: u64, diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index e7ef0d5797..967bd2ee99 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -69,6 +69,14 @@ pub trait Db: Send + Sync { active_projects: &[(UserId, ProjectId)], ) -> Result<()>; + /// Get the number of users who have been active in the given + /// time period for at least the given time duration. + async fn get_active_user_count( + &self, + time_period: Range, + min_duration: Duration, + ) -> Result; + /// Get the users that have been most active during the given time period, /// along with the amount of time they have been active in each project. async fn get_top_users_activity_summary( @@ -593,6 +601,40 @@ impl Db for PostgresDb { Ok(()) } + async fn get_active_user_count( + &self, + time_period: Range, + min_duration: Duration, + ) -> Result { + let query = " + WITH + project_durations AS ( + SELECT user_id, project_id, SUM(duration_millis) AS project_duration + FROM project_activity_periods + WHERE $1 < ended_at AND ended_at <= $2 + GROUP BY user_id, project_id + ), + user_durations AS ( + SELECT user_id, SUM(project_duration) as total_duration + FROM project_durations + GROUP BY user_id + ORDER BY total_duration DESC + LIMIT $3 + ) + SELECT count(user_durations.user_id) + FROM user_durations + WHERE user_durations.total_duration >= $3 + "; + + let count: i64 = sqlx::query_scalar(query) + .bind(time_period.start) + .bind(time_period.end) + .bind(min_duration.as_millis() as i64) + .fetch_one(&self.pool) + .await?; + Ok(count as usize) + } + async fn get_top_users_activity_summary( &self, time_period: Range, @@ -1544,7 +1586,7 @@ pub mod tests { } #[tokio::test(flavor = "multi_thread")] - async fn test_project_activity() { + async fn test_user_activity() { let test_db = TestDb::postgres().await; let db = test_db.db(); @@ -1641,6 +1683,32 @@ pub mod tests { }, ] ); + + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(56)) + .await + .unwrap(), + 0 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(54)) + .await + .unwrap(), + 1 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(30)) + .await + .unwrap(), + 2 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(10)) + .await + .unwrap(), + 3 + ); + assert_eq!( db.get_user_activity_timeline(t3..t6, user_1).await.unwrap(), &[ @@ -2477,6 +2545,14 @@ pub mod tests { unimplemented!() } + async fn get_active_user_count( + &self, + _time_period: Range, + _min_duration: Duration, + ) -> Result { + unimplemented!() + } + async fn get_top_users_activity_summary( &self, _time_period: Range, From 69146fb318641a212981de051451a06cba734273 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Jul 2022 16:30:13 -0700 Subject: [PATCH 46/71] Allow the web client to specify activity bucket durations --- crates/collab/src/api.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 6ecb43f7fd..5cc8b58cfb 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -299,6 +299,13 @@ async fn get_user_activity_timeline( Ok(ErasedJson::pretty(summary)) } +#[derive(Deserialize)] +struct ActiveUserCountParams { + #[serde(flatten)] + period: TimePeriodParams, + durations_in_minutes: String, +} + #[derive(Serialize)] struct ActiveUserSet { active_time_in_minutes: u64, @@ -306,18 +313,23 @@ struct ActiveUserSet { } async fn get_active_user_counts( - Query(params): Query, + Query(params): Query, Extension(app): Extension>, ) -> Result { - let durations_in_minutes = [10, 60, 4 * 60, 8 * 60]; - - let mut user_sets = Vec::with_capacity(durations_in_minutes.len()); + let durations_in_minutes = params.durations_in_minutes.split(','); + let mut user_sets = Vec::new(); for duration in durations_in_minutes { + let duration = duration + .parse() + .map_err(|_| anyhow!("invalid duration: {duration}"))?; user_sets.push(ActiveUserSet { active_time_in_minutes: duration, user_count: app .db - .get_active_user_count(params.start..params.end, Duration::from_secs(duration * 60)) + .get_active_user_count( + params.period.start..params.period.end, + Duration::from_secs(duration * 60), + ) .await?, }) } From eb23f13ac2e9c2f48c9df190656e40442bb277ab Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Jul 2022 17:15:32 -0700 Subject: [PATCH 47/71] Update rust in collab Dockerfiles --- Dockerfile | 2 +- Dockerfile.migrator | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cbe163c2d4..6bfd49be55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.2 -FROM rust:1.58-bullseye as builder +FROM rust:1.62-bullseye as builder WORKDIR app COPY . . diff --git a/Dockerfile.migrator b/Dockerfile.migrator index 69616f52d7..b6393ed1b5 100644 --- a/Dockerfile.migrator +++ b/Dockerfile.migrator @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.2 -FROM rust:1.58-bullseye as builder +FROM rust:1.62-bullseye as builder WORKDIR app RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=./target \ From dc120c1e0512c6eff8a5d9459ca8a6418642aa35 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 18:27:10 -0700 Subject: [PATCH 48/71] Added settings for common terminal configurations --- Cargo.lock | 1 + assets/settings/default.json | 51 ++++- crates/settings/src/settings.rs | 51 ++++- crates/terminal/Cargo.toml | 1 + crates/terminal/src/connection.rs | 28 ++- crates/terminal/src/terminal.rs | 178 +++++++++++------- crates/terminal/src/terminal_element.rs | 35 +++- .../src/tests/terminal_test_context.rs | 12 +- crates/zed/src/settings_file.rs | 19 +- crates/zed/src/zed.rs | 7 + 10 files changed, 283 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72cb4d292d..ebf1b41829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5365,6 +5365,7 @@ dependencies = [ "ordered-float", "project", "settings", + "shellexpand", "smallvec", "theme", "util", diff --git a/assets/settings/default.json b/assets/settings/default.json index 855854bd36..3e6d9875fc 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -54,7 +54,7 @@ // "soft_wrap": "none", // 2. Soft wrap lines that overflow the editor: // "soft_wrap": "editor_width", - // 2. Soft wrap lines at the preferred line length + // 3. 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 @@ -65,6 +65,55 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, + // Settings specific to the terminal + "terminal": { + // What shell to use when opening a terminal. May take 3 values: + // 1. Use the system's terminal configuration (e.g. $TERM). Note this is not + // A substitution, only an exact text match + // "shell": "system" + // 2. A program: + // "shell": { + // "program": "sh" + // } + // 3. A program with arguments: + // "shell": { + // "with_arguments": { + // "program": "/bin/bash", + // "arguments": ["--login"] + // } + // } + "shell": "system", + // What working directory to use when launching the terminal. + // May take 4 values: + // 1. Use the current file's project directory. + // "working_directory": "current_project_directory" + // 2. Use the first project in this workspace's directory + // "working_directory": "first_project_directory" + // 3. Always use this platform's home directory (if we can find it) + // "working_directory": "always_home" + // 4. Always use a specific directory. This value will be shell expanded. + // If this path is not a valid directory the terminal will default to + // this platform's home directory (if we can find it) + // "working_directory": { + // "always": { + // "directory": "~/zed/projects/" + // } + // } + // + // + "working_directory": "current_project_directory", + //Any key-value pairs added to this list will be added to the terminal's + //enviroment. Use `:` to seperate multiple values, not multiple list items + "env": [ + //["KEY", "value1:value2"] + ] + //Set the terminal's font size. If this option is not listed, + //the terminal will default to matching the buffer's font size. + //"font_size": "15" + //Set the terminal's font family. If this option is not listed, + //the terminal will default to matching the buffer's font family. + //"font_family": "Zed Mono" + }, // Different settings for specific languages. "languages": { "Plain Text": { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index c73129a347..76cc653ff8 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -22,14 +22,16 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub struct Settings { pub projects_online_by_default: bool, pub buffer_font_family: FamilyId, - pub buffer_font_size: f32, pub default_buffer_font_size: f32, + pub 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, pub editor_overrides: EditorSettings, + pub terminal_defaults: TerminalSettings, + pub terminal_overrides: TerminalSettings, pub language_defaults: HashMap, EditorSettings>, pub language_overrides: HashMap, EditorSettings>, pub theme: Arc, @@ -73,6 +75,38 @@ pub enum Autosave { OnWindowChange, } +#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] +pub struct TerminalSettings { + pub shell: Option, + pub working_directory: Option, + pub font_size: Option, + pub font_family: Option, + pub env: Option>, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Shell { + System, + Program(String), + WithArguments { program: String, args: Vec }, +} + +impl Default for Shell { + fn default() -> Self { + Shell::System + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WorkingDirectory { + CurrentProjectDirectory, + FirstProjectDirectory, + AlwaysHome, + Always { directory: String }, +} + #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct SettingsFileContent { #[serde(default)] @@ -92,6 +126,8 @@ pub struct SettingsFileContent { #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] + pub terminal: TerminalSettings, + #[serde(default)] #[serde(alias = "language_overrides")] pub languages: HashMap, EditorSettings>, #[serde(default)] @@ -133,8 +169,10 @@ impl Settings { format_on_save: required(defaults.editor.format_on_save), enable_language_server: required(defaults.editor.enable_language_server), }, - language_defaults: defaults.languages, editor_overrides: Default::default(), + terminal_defaults: Default::default(), + terminal_overrides: Default::default(), + language_defaults: defaults.languages, language_overrides: Default::default(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), } @@ -171,7 +209,14 @@ impl Settings { merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); + // Ensure terminal font is loaded, so we can request it in terminal_element layout + if let Some(terminal_font) = &data.terminal.font_family { + font_cache.load_family(&[terminal_font]).log_err(); + } + self.editor_overrides = data.editor; + self.terminal_defaults.font_size = data.terminal.font_size; + self.terminal_overrides = data.terminal; self.language_overrides = data.languages; } @@ -239,6 +284,8 @@ impl Settings { enable_language_server: Some(true), }, editor_overrides: Default::default(), + terminal_defaults: Default::default(), + terminal_overrides: Default::default(), language_defaults: Default::default(), language_overrides: Default::default(), projects_online_by_default: true, diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index ee67e644b0..09a3fb171f 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -22,6 +22,7 @@ futures = "0.3" ordered-float = "2.1.1" itertools = "0.10" dirs = "4.0.0" +shellexpand = "2.1.0" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index 8f48e4ba8b..5941dc506e 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -2,7 +2,7 @@ mod keymappings; use alacritty_terminal::{ ansi::{ClearMode, Handler}, - config::{Config, PtyConfig}, + config::{Config, Program, PtyConfig}, event::{Event as AlacTermEvent, Notify}, event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, @@ -12,7 +12,7 @@ use alacritty_terminal::{ Term, }; use futures::{channel::mpsc::unbounded, StreamExt}; -use settings::Settings; +use settings::{Settings, Shell}; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext}; @@ -46,16 +46,32 @@ pub struct TerminalConnection { impl TerminalConnection { pub fn new( working_directory: Option, + shell: Option, + env_vars: Option>, initial_size: SizeInfo, cx: &mut ModelContext, ) -> TerminalConnection { - let pty_config = PtyConfig { - shell: None, //Use the users default shell - working_directory: working_directory.clone(), - hold: false, + let pty_config = { + let shell = shell.and_then(|shell| match shell { + Shell::System => None, + Shell::Program(program) => Some(Program::Just(program)), + Shell::WithArguments { program, args } => Some(Program::WithArgs { program, args }), + }); + + PtyConfig { + shell, + working_directory: working_directory.clone(), + hold: false, + } }; let mut env: HashMap = HashMap::new(); + if let Some(envs) = env_vars { + for (var, val) in envs { + env.insert(var, val); + } + } + //TODO: Properly set the current locale, env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index e1bb823b1b..c94e28d6e9 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -18,10 +18,10 @@ use gpui::{ }; use modal::deploy_modal; -use project::{Project, ProjectPath}; -use settings::Settings; +use project::{LocalWorktree, Project, ProjectPath}; +use settings::{Settings, WorkingDirectory}; use smallvec::SmallVec; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use workspace::{Item, Workspace}; use crate::terminal_element::TerminalEl; @@ -110,8 +110,15 @@ impl Terminal { false, ); - let connection = - cx.add_model(|cx| TerminalConnection::new(working_directory, size_info, cx)); + let (shell, envs) = { + let settings = cx.global::(); + let shell = settings.terminal_overrides.shell.clone(); + let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap. + (shell, envs) + }; + + let connection = cx + .add_model(|cx| TerminalConnection::new(working_directory, shell, envs, size_info, cx)); Terminal::from_connection(connection, modal, cx) } @@ -371,12 +378,41 @@ impl Item for Terminal { } } +///Get's the working directory for the given workspace, respecting the user's settings. +fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option { + let wd_setting = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + let res = match wd_setting { + WorkingDirectory::CurrentProjectDirectory => current_project_directory(workspace, cx), + WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx), + WorkingDirectory::AlwaysHome => None, + WorkingDirectory::Always { directory } => shellexpand::full(&directory) + .ok() + .map(|dir| Path::new(&dir.to_string()).to_path_buf()) + .filter(|dir| dir.is_dir()), + }; + res.or_else(|| home_dir()) +} + +///Get's the first project's home directory, or the home directory +fn first_project_directory(workspace: &Workspace, cx: &AppContext) -> Option { + workspace + .worktrees(cx) + .next() + .and_then(|worktree_handle| worktree_handle.read(cx).as_local()) + .and_then(get_path_from_wt) +} + ///Gets the intuitively correct working directory from the given workspace ///If there is an active entry for this project, returns that entry's worktree root. ///If there's no active entry but there is a worktree, returns that worktrees root. ///If either of these roots are files, or if there are any other query failures, /// returns the user's home directory -fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option { +fn current_project_directory(workspace: &Workspace, cx: &AppContext) -> Option { let project = workspace.project().read(cx); project @@ -384,12 +420,13 @@ fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option Option { + wt.root_entry() + .filter(|re| re.is_dir()) + .map(|_| wt.abs_path().to_path_buf()) } #[cfg(test)] @@ -398,10 +435,6 @@ mod tests { use crate::tests::terminal_test_context::TerminalTestContext; use super::*; - use alacritty_terminal::{ - index::{Column, Line, Point, Side}, - selection::{Selection, SelectionType}, - }; use gpui::TestAppContext; use std::path::Path; @@ -419,37 +452,6 @@ mod tests { .await; } - /// TODO: I don't think this is actually testing anything anymore. - ///Integration test for selections, clipboard, and terminal execution - #[gpui::test(retries = 5)] - async fn test_copy(cx: &mut TestAppContext) { - - let mut cx = TerminalTestContext::new(cx); - let grid_content = cx - .execute_and_wait("expr 3 + 4", |content, _cx| content.contains("7")) - .await; - - //Get the position of the result - let idx = grid_content.chars().position(|c| c == '7').unwrap(); - let result_line = grid_content - .chars() - .take(idx) - .filter(|c| *c == '\n') - .count() as i32; - - let copy_res = cx.update_connection(|connection, _cx| { - let mut term = connection.term.lock(); - term.selection = Some(Selection::new( - SelectionType::Semantic, - Point::new(Line(result_line), Column(0)), - Side::Right, - )); - term.selection_to_string() - }); - - assert_eq!(copy_res.unwrap(), "7"); - } - ///Working directory calculation tests ///No Worktrees in project -> home_dir() @@ -469,8 +471,10 @@ mod tests { assert!(active_entry.is_none()); assert!(workspace.worktrees(cx).next().is_none()); - let res = get_wd_for_workspace(workspace, cx); - assert_eq!(res, home_dir()) + let res = current_project_directory(workspace, cx); + assert_eq!(res, None); + let res = first_project_directory(workspace, cx); + assert_eq!(res, None); }); } @@ -507,8 +511,10 @@ mod tests { assert!(active_entry.is_none()); assert!(workspace.worktrees(cx).next().is_some()); - let res = get_wd_for_workspace(workspace, cx); - assert_eq!(res, home_dir()) + let res = current_project_directory(workspace, cx); + assert_eq!(res, None); + let res = first_project_directory(workspace, cx); + assert_eq!(res, None); }); } @@ -543,7 +549,9 @@ mod tests { assert!(active_entry.is_none()); assert!(workspace.worktrees(cx).next().is_some()); - let res = get_wd_for_workspace(workspace, cx); + let res = current_project_directory(workspace, cx); + assert_eq!(res, Some((Path::new("/root/")).to_path_buf())); + let res = first_project_directory(workspace, cx); assert_eq!(res, Some((Path::new("/root/")).to_path_buf())); }); } @@ -555,17 +563,32 @@ mod tests { let params = cx.update(AppState::test); let project = Project::test(params.fs.clone(), [], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); - let (wt, _) = project + let (wt1, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root.txt", true, cx) + project.find_or_create_local_worktree("/root1/", true, cx) + }) + .await + .unwrap(); + + let (wt2, _) = project + .update(cx, |project, cx| { + project.find_or_create_local_worktree("/root2.txt", true, cx) }) .await .unwrap(); //Setup root - let entry = cx + let _ = cx .update(|cx| { - wt.update(cx, |wt, cx| { + wt1.update(cx, |wt, cx| { + wt.as_local().unwrap().create_entry(Path::new(""), true, cx) + }) + }) + .await + .unwrap(); + let entry2 = cx + .update(|cx| { + wt2.update(cx, |wt, cx| { wt.as_local() .unwrap() .create_entry(Path::new(""), false, cx) @@ -576,8 +599,8 @@ mod tests { cx.update(|cx| { let p = ProjectPath { - worktree_id: wt.read(cx).id(), - path: entry.path, + worktree_id: wt2.read(cx).id(), + path: entry2.path, }; project.update(cx, |project, cx| project.set_active_path(Some(p), cx)); }); @@ -589,8 +612,10 @@ mod tests { assert!(active_entry.is_some()); - let res = get_wd_for_workspace(workspace, cx); - assert_eq!(res, home_dir()); + let res = current_project_directory(workspace, cx); + assert_eq!(res, None); + let res = first_project_directory(workspace, cx); + assert_eq!(res, Some((Path::new("/root1/")).to_path_buf())); }); } @@ -601,17 +626,32 @@ mod tests { let params = cx.update(AppState::test); let project = Project::test(params.fs.clone(), [], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); - let (wt, _) = project + let (wt1, _) = project .update(cx, |project, cx| { - project.find_or_create_local_worktree("/root/", true, cx) + project.find_or_create_local_worktree("/root1/", true, cx) + }) + .await + .unwrap(); + + let (wt2, _) = project + .update(cx, |project, cx| { + project.find_or_create_local_worktree("/root2/", true, cx) }) .await .unwrap(); //Setup root - let entry = cx + let _ = cx .update(|cx| { - wt.update(cx, |wt, cx| { + wt1.update(cx, |wt, cx| { + wt.as_local().unwrap().create_entry(Path::new(""), true, cx) + }) + }) + .await + .unwrap(); + let entry2 = cx + .update(|cx| { + wt2.update(cx, |wt, cx| { wt.as_local().unwrap().create_entry(Path::new(""), true, cx) }) }) @@ -620,8 +660,8 @@ mod tests { cx.update(|cx| { let p = ProjectPath { - worktree_id: wt.read(cx).id(), - path: entry.path, + worktree_id: wt2.read(cx).id(), + path: entry2.path, }; project.update(cx, |project, cx| project.set_active_path(Some(p), cx)); }); @@ -633,8 +673,10 @@ mod tests { assert!(active_entry.is_some()); - let res = get_wd_for_workspace(workspace, cx); - assert_eq!(res, Some((Path::new("/root/")).to_path_buf())); + let res = current_project_directory(workspace, cx); + assert_eq!(res, Some((Path::new("/root2/")).to_path_buf())); + let res = first_project_directory(workspace, cx); + assert_eq!(res, Some((Path::new("/root1/")).to_path_buf())); }); } } diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index cfb881feb2..18956ded81 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -27,6 +27,7 @@ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; use theme::TerminalStyle; +use util::ResultExt; use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; use std::{fmt::Debug, ops::Sub}; @@ -256,10 +257,11 @@ impl Element for TerminalEl { for layout_line in &layout.layout_lines { for layout_cell in &layout_line.cells { let position = vec2f( - origin.x() + layout_cell.point.column as f32 * layout.em_width.0, + (origin.x() + layout_cell.point.column as f32 * layout.em_width.0) + .floor(), origin.y() + layout_cell.point.line as f32 * layout.line_height.0, ); - let size = vec2f(layout.em_width.0, layout.line_height.0); + let size = vec2f(layout.em_width.0.ceil(), layout.line_height.0); cx.scene.push_quad(Quad { bounds: RectF::new(position, size), @@ -318,7 +320,7 @@ impl Element for TerminalEl { //Don't actually know the start_x for a line, until here: let cell_origin = vec2f( - origin.x() + point.column as f32 * layout.em_width.0, + (origin.x() + point.column as f32 * layout.em_width.0).floor(), origin.y() + point.line as f32 * layout.line_height.0, ); @@ -420,14 +422,33 @@ pub fn mouse_to_cell_data( ///Configures a text style from the current settings. fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { + // Pull the font family from settings properly overriding + let family_id = settings + .terminal_overrides + .font_family + .as_ref() + .and_then(|family_name| dbg!(font_cache.load_family(&[family_name]).log_err())) + .or_else(|| { + settings + .terminal_defaults + .font_family + .as_ref() + .and_then(|family_name| font_cache.load_family(&[family_name]).log_err()) + }) + .unwrap_or(settings.buffer_font_family); + TextStyle { color: settings.theme.editor.text_color, - font_family_id: settings.buffer_font_family, - font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(), + font_family_id: family_id, + font_family_name: font_cache.family_name(family_id).unwrap(), font_id: font_cache - .select_font(settings.buffer_font_family, &Default::default()) + .select_font(family_id, &Default::default()) .unwrap(), - font_size: settings.buffer_font_size, + font_size: settings + .terminal_overrides + .font_size + .or(settings.terminal_defaults.font_size) + .unwrap_or(settings.buffer_font_size), font_properties: Default::default(), underline: Default::default(), } diff --git a/crates/terminal/src/tests/terminal_test_context.rs b/crates/terminal/src/tests/terminal_test_context.rs index ab0809c3a1..b5696aff13 100644 --- a/crates/terminal/src/tests/terminal_test_context.rs +++ b/crates/terminal/src/tests/terminal_test_context.rs @@ -1,7 +1,7 @@ use std::time::Duration; use alacritty_terminal::term::SizeInfo; -use gpui::{AppContext, ModelContext, ModelHandle, ReadModelWith, TestAppContext}; +use gpui::{AppContext, ModelHandle, ReadModelWith, TestAppContext}; use itertools::Itertools; use crate::{ @@ -28,7 +28,8 @@ impl<'a> TerminalTestContext<'a> { false, ); - let connection = cx.add_model(|cx| TerminalConnection::new(None, size_info, cx)); + let connection = + cx.add_model(|cx| TerminalConnection::new(None, None, None, size_info, cx)); TerminalTestContext { cx, connection } } @@ -56,13 +57,6 @@ impl<'a> TerminalTestContext<'a> { }) } - pub fn update_connection(&mut self, f: F) -> S - where - F: FnOnce(&mut TerminalConnection, &mut ModelContext) -> S, - { - self.connection.update(self.cx, |conn, cx| f(conn, cx)) - } - fn grid_as_str(connection: &TerminalConnection) -> String { let term = connection.term.lock(); let grid_iterator = term.renderable_content().display_iter; diff --git a/crates/zed/src/settings_file.rs b/crates/zed/src/settings_file.rs index 9fb0ee0da2..b8cdde8b2f 100644 --- a/crates/zed/src/settings_file.rs +++ b/crates/zed/src/settings_file.rs @@ -39,15 +39,20 @@ where Self(rx) } + ///Loads the given watched JSON file. In the special case that the file is + ///empty (ignoring whitespace) or is not a file, this will return T::default() async fn load(fs: Arc, path: &Path) -> Option { - if fs.is_file(&path).await { - fs.load(&path) - .await - .log_err() - .and_then(|data| parse_json_with_comments(&data).log_err()) - } else { - Some(T::default()) + if !fs.is_file(&path).await { + return Some(T::default()); } + + fs.load(&path).await.log_err().and_then(|data| { + if data.trim().is_empty() { + Some(T::default()) + } else { + parse_json_with_comments(&data).log_err() + } + }) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3a2bbb9be8..a7420c59f0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -79,18 +79,25 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { cx.update_global::(|settings, cx| { settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE); + if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() { + *terminal_font_size = (*terminal_font_size + 1.0).max(MIN_FONT_SIZE); + } cx.refresh_windows(); }); }); cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { cx.update_global::(|settings, cx| { settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE); + if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() { + *terminal_font_size = (*terminal_font_size - 1.0).max(MIN_FONT_SIZE); + } cx.refresh_windows(); }); }); cx.add_global_action(move |_: &ResetBufferFontSize, cx| { cx.update_global::(|settings, cx| { settings.buffer_font_size = settings.default_buffer_font_size; + settings.terminal_overrides.font_size = settings.terminal_defaults.font_size; cx.refresh_windows(); }); }); From 523e565b9b4c4c478fea2e7effe3ab1d9b201bcf Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 18:38:17 -0700 Subject: [PATCH 49/71] bad patch for panic on mis configured shell --- crates/terminal/src/connection.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index 5941dc506e..6eed882e2d 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -91,7 +91,20 @@ impl TerminalConnection { let term = Arc::new(FairMutex::new(term)); //Setup the pty... - let pty = tty::new(&pty_config, &initial_size, None).expect("Could not create tty"); + let pty = { + if let Some(pty) = tty::new(&pty_config, &initial_size, None).ok() { + pty + } else { + let pty_config = PtyConfig { + shell: None, + working_directory: working_directory.clone(), + ..Default::default() + }; + + tty::new(&pty_config, &initial_size, None) + .expect("Failed with default shell too :(") + } + }; //And connect them together let event_loop = EventLoop::new( From 6675c96c8a2f1caddd0540411ca84339fd731c1d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 18:42:07 -0700 Subject: [PATCH 50/71] Removed some old dbgs and comments --- assets/settings/default.json | 7 +++---- crates/terminal/src/terminal_element.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 3e6d9875fc..73c73636f6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -68,8 +68,7 @@ // Settings specific to the terminal "terminal": { // What shell to use when opening a terminal. May take 3 values: - // 1. Use the system's terminal configuration (e.g. $TERM). Note this is not - // A substitution, only an exact text match + // 1. Use the system's default terminal configuration (e.g. $TERM). // "shell": "system" // 2. A program: // "shell": { @@ -107,10 +106,10 @@ "env": [ //["KEY", "value1:value2"] ] - //Set the terminal's font size. If this option is not listed, + //Set the terminal's font size. If this option is not included, //the terminal will default to matching the buffer's font size. //"font_size": "15" - //Set the terminal's font family. If this option is not listed, + //Set the terminal's font family. If this option is not included, //the terminal will default to matching the buffer's font family. //"font_family": "Zed Mono" }, diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 18956ded81..3406b4b9e1 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -427,7 +427,7 @@ fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { .terminal_overrides .font_family .as_ref() - .and_then(|family_name| dbg!(font_cache.load_family(&[family_name]).log_err())) + .and_then(|family_name| font_cache.load_family(&[family_name]).log_err()) .or_else(|| { settings .terminal_defaults From 8349ead6b24c886a7ffd75f49408d45e987d0abf Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Jul 2022 19:01:08 -0700 Subject: [PATCH 51/71] Add bracketed paste support --- crates/terminal/src/connection.rs | 13 ++++++++++++- crates/terminal/src/terminal.rs | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index 6eed882e2d..0e051da17c 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -7,7 +7,7 @@ use alacritty_terminal::{ event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, sync::FairMutex, - term::SizeInfo, + term::{SizeInfo, TermMode}, tty::{self, setup_env}, Term, }; @@ -228,6 +228,17 @@ impl TerminalConnection { false } } + + ///Paste text into the terminal + pub fn paste(&mut self, text: &str) { + if self.term.lock().mode().contains(TermMode::BRACKETED_PASTE) { + self.write_to_pty("\x1b[200~".to_string()); + self.write_to_pty(text.replace('\x1b', "").to_string()); + self.write_to_pty("\x1b[201~".to_string()); + } else { + self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r")); + } + } } impl Drop for TerminalConnection { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c94e28d6e9..26880258da 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -191,7 +191,7 @@ impl Terminal { fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { if let Some(item) = cx.read_from_clipboard() { self.connection.update(cx, |connection, _| { - connection.write_to_pty(item.text().to_owned()); + connection.paste(item.text()); }) } } From af917de68421d9ba792ed3a342f18b78cdbf1203 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 16 Jul 2022 08:40:44 +0200 Subject: [PATCH 52/71] Handle inserting multiple tabs on the same row This also fixes a panic when tabbing on a row that contained multi-byte characters. --- crates/editor/src/editor.rs | 50 ++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 72ba6d60af..846c05a412 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2909,9 +2909,17 @@ impl Editor { if selections.iter().all(|s| s.is_empty()) { self.transact(cx, |this, cx| { this.buffer.update(cx, |buffer, cx| { + let mut prev_cursor_row = 0; + let mut row_delta = 0; for selection in &mut selections { - let language_name = - buffer.language_at(selection.start, cx).map(|l| l.name()); + let mut cursor = selection.start; + if cursor.row != prev_cursor_row { + row_delta = 0; + prev_cursor_row = cursor.row; + } + cursor.column += row_delta; + + let language_name = buffer.language_at(cursor, cx).map(|l| l.name()); let settings = cx.global::(); let tab_size = if settings.hard_tabs(language_name.as_deref()) { IndentSize::tab() @@ -2919,21 +2927,18 @@ impl Editor { let tab_size = settings.tab_size(language_name.as_deref()).get(); let char_column = buffer .read(cx) - .text_for_range(Point::new(selection.start.row, 0)..selection.start) + .text_for_range(Point::new(cursor.row, 0)..cursor) .flat_map(str::chars) .count(); let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); IndentSize::spaces(chars_to_next_tab_stop) }; - buffer.edit( - [( - selection.start..selection.start, - tab_size.chars().collect::(), - )], - cx, - ); - selection.start.column += tab_size.len; - selection.end = selection.start; + buffer.edit([(cursor..cursor, tab_size.chars().collect::())], cx); + cursor.column += tab_size.len; + selection.start = cursor; + selection.end = cursor; + + row_delta += tab_size.len; } }); this.change_selections(Some(Autoscroll::Fit), cx, |s| { @@ -7566,6 +7571,27 @@ mod tests { }); } + #[gpui::test] + async fn test_tab(cx: &mut gpui::TestAppContext) { + let mut cx = EditorTestContext::new(cx).await; + cx.update(|cx| { + cx.update_global::(|settings, _| { + settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap()); + }); + }); + cx.set_state(indoc! {" + |ab|c + |🏀|🏀|efg + d| + "}); + cx.update_editor(|e, cx| e.tab(&Tab, cx)); + cx.assert_editor_state(indoc! {" + |ab |c + |🏀 |🏀 |efg + d | + "}); + } + #[gpui::test] async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx).await; From 8add81350eeb37c8929a4aff78931b96276c71f2 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Sun, 17 Jul 2022 23:19:32 -0700 Subject: [PATCH 53/71] Rework presenter and MouseRegion to use Handler hashmap rather than individual fields --- .../src/activity_indicator.rs | 6 +- crates/auto_update/src/update_notification.rs | 8 +- crates/chat_panel/src/chat_panel.rs | 6 +- crates/contacts_panel/src/contacts_panel.rs | 29 +- crates/contacts_panel/src/notifications.rs | 10 +- crates/context_menu/src/context_menu.rs | 10 +- crates/diagnostics/src/items.rs | 10 +- crates/editor/src/editor.rs | 10 +- crates/editor/src/element.rs | 12 +- crates/gpui/src/app.rs | 4 +- crates/gpui/src/elements/event_handler.rs | 28 +- .../gpui/src/elements/mouse_event_handler.rs | 93 ++----- crates/gpui/src/elements/tooltip.rs | 6 +- crates/gpui/src/platform/event.rs | 40 ++- crates/gpui/src/platform/mac/event.rs | 8 +- crates/gpui/src/platform/mac/window.rs | 4 +- crates/gpui/src/presenter.rs | 209 ++++++--------- crates/gpui/src/scene.rs | 250 ++++++++++++++++-- crates/gpui/src/views/select.rs | 10 +- crates/picker/src/picker.rs | 8 +- crates/project_panel/src/project_panel.rs | 54 ++-- crates/search/src/buffer_search.rs | 12 +- crates/search/src/project_search.rs | 12 +- crates/terminal/src/terminal_element.rs | 119 +++++---- crates/workspace/src/pane.rs | 20 +- crates/workspace/src/sidebar.rs | 38 +-- crates/workspace/src/toolbar.rs | 6 +- crates/workspace/src/workspace.rs | 10 +- crates/zed/src/feedback.rs | 4 +- styles/package-lock.json | 1 + 30 files changed, 616 insertions(+), 421 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 8bc84f911c..8de3ceda3c 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -3,7 +3,7 @@ use editor::Editor; use futures::StreamExt; use gpui::{ actions, elements::*, platform::CursorStyle, Action, AppContext, Entity, ModelHandle, - MutableAppContext, RenderContext, View, ViewContext, ViewHandle, + MouseButton, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use project::{LanguageServerProgress, Project}; @@ -317,7 +317,9 @@ impl View for ActivityIndicator { if let Some(action) = action { element = element .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())); + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(action.boxed_clone()) + }); } element.boxed() diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index e9c73ef4bc..7863fe900d 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -2,7 +2,7 @@ use crate::ViewReleaseNotes; use gpui::{ elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, platform::{AppVersion, CursorStyle}, - Element, Entity, View, ViewContext, + Element, Entity, MouseButton, View, ViewContext, }; use menu::Cancel; use settings::Settings; @@ -62,7 +62,7 @@ impl View for UpdateNotification { .boxed() }) .with_padding(Padding::uniform(5.)) - .on_click(move |_, _, cx| cx.dispatch_action(Cancel)) + .on_click(MouseButton::Left, move |_, cx| cx.dispatch_action(Cancel)) .aligned() .constrained() .with_height(cx.font_cache().line_height(theme.message.text.font_size)) @@ -84,7 +84,9 @@ impl View for UpdateNotification { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, _, cx| cx.dispatch_action(ViewReleaseNotes)) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(ViewReleaseNotes) + }) .boxed() } } diff --git a/crates/chat_panel/src/chat_panel.rs b/crates/chat_panel/src/chat_panel.rs index a8db280bf8..fa913971df 100644 --- a/crates/chat_panel/src/chat_panel.rs +++ b/crates/chat_panel/src/chat_panel.rs @@ -8,8 +8,8 @@ use gpui::{ elements::*, platform::CursorStyle, views::{ItemType, Select, SelectStyle}, - AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, - ViewContext, ViewHandle, + AppContext, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, + Task, View, ViewContext, ViewHandle, }; use menu::Confirm; use postage::prelude::Stream; @@ -320,7 +320,7 @@ impl ChatPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { let rpc = rpc.clone(); let this = this.clone(); cx.spawn(|mut cx| async move { diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index f4010a1278..9fc8f5f22d 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -13,8 +13,9 @@ use gpui::{ geometry::{rect::RectF, vector::vec2f}, impl_actions, impl_internal_actions, platform::CursorStyle, - AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext, - RenderContext, Subscription, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, + AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, + MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakModelHandle, + WeakViewHandle, }; use join_project_notification::JoinProjectNotification; use menu::{Confirm, SelectNext, SelectPrev}; @@ -310,7 +311,9 @@ impl ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| cx.dispatch_action(ToggleExpanded(section))) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleExpanded(section)) + }) .boxed() } @@ -445,7 +448,7 @@ impl ContactsPanel { Some( button .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(ToggleProjectOnline { project: Some(open_project.clone()), }) @@ -499,7 +502,7 @@ impl ContactsPanel { } else { CursorStyle::Arrow }) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { if !is_host { cx.dispatch_global_action(JoinProject { contact: contact.clone(), @@ -563,7 +566,7 @@ impl ContactsPanel { } else { button .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { let project = project_handle.upgrade(cx.deref_mut()); cx.dispatch_action(ToggleProjectOnline { project }) }) @@ -646,7 +649,7 @@ impl ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(RespondToContactRequest { user_id, accept: false, @@ -668,7 +671,7 @@ impl ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(RespondToContactRequest { user_id, accept: true, @@ -691,7 +694,9 @@ impl ContactsPanel { }) .with_padding(Padding::uniform(2.)) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| cx.dispatch_action(RemoveContact(user_id))) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(RemoveContact(user_id)) + }) .flex_float() .boxed(), ); @@ -1078,7 +1083,9 @@ impl View for ContactsPanel { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, _, cx| cx.dispatch_action(contact_finder::Toggle)) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(contact_finder::Toggle) + }) .boxed(), ) .constrained() @@ -1126,7 +1133,7 @@ impl View for ContactsPanel { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { cx.write_to_clipboard(ClipboardItem::new( info.url.to_string(), )); diff --git a/crates/contacts_panel/src/notifications.rs b/crates/contacts_panel/src/notifications.rs index c02fd73b8f..a80dafdd22 100644 --- a/crates/contacts_panel/src/notifications.rs +++ b/crates/contacts_panel/src/notifications.rs @@ -3,7 +3,7 @@ use client::User; use gpui::{ elements::{Flex, Image, Label, MouseEventHandler, Padding, ParentElement, Text}, platform::CursorStyle, - Action, Element, ElementBox, RenderContext, View, + Action, Element, ElementBox, MouseButton, RenderContext, View, }; use settings::Settings; use std::sync::Arc; @@ -61,7 +61,9 @@ pub fn render_user_notification( }) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(5.)) - .on_click(move |_, _, cx| cx.dispatch_any_action(dismiss_action.boxed_clone())) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(dismiss_action.boxed_clone()) + }) .aligned() .constrained() .with_height( @@ -96,7 +98,9 @@ pub fn render_user_notification( .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(action.boxed_clone()) + }) .boxed() }, )) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 39477bc927..7bfe3bb2e8 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -1,7 +1,7 @@ use gpui::{ elements::*, geometry::vector::Vector2F, impl_internal_actions, keymap, platform::CursorStyle, - Action, AppContext, Axis, Entity, MutableAppContext, RenderContext, SizeConstraint, - Subscription, View, ViewContext, + Action, AppContext, Axis, Entity, MouseButton, MutableAppContext, RenderContext, + SizeConstraint, Subscription, View, ViewContext, }; use menu::*; use settings::Settings; @@ -337,7 +337,7 @@ impl ContextMenu { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(Clicked); cx.dispatch_any_action(action.boxed_clone()); }) @@ -355,7 +355,7 @@ impl ContextMenu { .with_style(style.container) .boxed() }) - .on_mouse_down_out(|_, cx| cx.dispatch_action(Cancel)) - .on_right_mouse_down_out(|_, cx| cx.dispatch_action(Cancel)) + .on_mouse_down_out(MouseButton::Left, |_, cx| cx.dispatch_action(Cancel)) + .on_mouse_down_out(MouseButton::Right, |_, cx| cx.dispatch_action(Cancel)) } } diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index e2961ff3bd..b7c4b2a22d 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -1,8 +1,8 @@ use collections::HashSet; use editor::{Editor, GoToNextDiagnostic}; use gpui::{ - elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, MutableAppContext, - RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, + elements::*, platform::CursorStyle, serde_json, Entity, ModelHandle, MouseButton, + MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::Diagnostic; use project::Project; @@ -161,7 +161,7 @@ impl View for DiagnosticIndicator { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, _, cx| cx.dispatch_action(crate::Deploy)) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Deploy)) .with_tooltip::( 0, "Project Diagnostics".to_string(), @@ -201,7 +201,9 @@ impl View for DiagnosticIndicator { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, _, cx| cx.dispatch_action(GoToNextDiagnostic)) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(GoToNextDiagnostic) + }) .boxed(), ); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 72ba6d60af..321c94e2bd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -30,8 +30,8 @@ use gpui::{ impl_actions, impl_internal_actions, platform::CursorStyle, text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, - ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -707,7 +707,7 @@ impl CompletionsMenu { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(move |_, cx| { + .on_mouse_down(MouseButton::Left, move |_, cx| { cx.dispatch_action(ConfirmCompletion { item_ix: Some(item_ix), }); @@ -840,7 +840,7 @@ impl CodeActionsMenu { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(move |_, cx| { + .on_mouse_down(MouseButton::Left, move |_, cx| { cx.dispatch_action(ConfirmCodeAction { item_ix: Some(item_ix), }); @@ -2674,7 +2674,7 @@ impl Editor { }) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(3.)) - .on_mouse_down(|_, cx| { + .on_mouse_down(MouseButton::Left, |_, cx| { cx.dispatch_action(ToggleCodeActions { deployed_from_indicator: true, }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 99d60ed9a2..013de1e7df 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -25,7 +25,7 @@ use gpui::{ platform::CursorStyle, text_layout::{self, Line, RunStyle, TextLayoutCache}, AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, KeyDownEvent, - LayoutContext, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, + LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext, WeakViewHandle, }; @@ -968,7 +968,9 @@ impl EditorElement { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| cx.dispatch_action(jump_action.clone())) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(jump_action.clone()) + }) .with_tooltip::( *key, "Jump to Buffer".to_string(), @@ -1483,7 +1485,7 @@ impl Element for EditorElement { } match event { - Event::MouseDown(MouseEvent { + Event::MouseDown(MouseButtonEvent { button: MouseButton::Left, position, cmd, @@ -1501,12 +1503,12 @@ impl Element for EditorElement { paint, cx, ), - Event::MouseDown(MouseEvent { + Event::MouseDown(MouseButtonEvent { button: MouseButton::Right, position, .. }) => self.mouse_right_down(*position, layout, paint, cx), - Event::MouseUp(MouseEvent { + Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, position, .. diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 075339bba1..2cd6687bcb 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -5401,7 +5401,7 @@ impl RefCounts { #[cfg(test)] mod tests { use super::*; - use crate::{actions, elements::*, impl_actions, MouseButton, MouseEvent}; + use crate::{actions, elements::*, impl_actions, MouseButton, MouseButtonEvent}; use serde::Deserialize; use smol::future::poll_once; use std::{ @@ -5754,7 +5754,7 @@ mod tests { let presenter = cx.presenters_and_platform_windows[&window_id].0.clone(); // Ensure window's root element is in a valid lifecycle state. presenter.borrow_mut().dispatch_event( - Event::MouseDown(MouseEvent { + Event::MouseDown(MouseButtonEvent { position: Default::default(), button: MouseButton::Left, ctrl: false, diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 1fec838788..55e4928ad8 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -1,11 +1,11 @@ use crate::{ geometry::vector::Vector2F, CursorRegion, DebugContext, Element, ElementBox, Event, - EventContext, LayoutContext, MouseButton, MouseEvent, MouseRegion, NavigationDirection, + EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseRegion, NavigationDirection, PaintContext, SizeConstraint, }; use pathfinder_geometry::rect::RectF; use serde_json::json; -use std::{any::TypeId, rc::Rc}; +use std::any::TypeId; pub struct EventHandler { child: ElementBox, @@ -82,19 +82,11 @@ impl Element for EventHandler { bounds: visible_bounds, style: Default::default(), }); - cx.scene.push_mouse_region(MouseRegion { - view_id: cx.current_view_id(), - discriminant: Some(discriminant), - bounds: visible_bounds, - hover: Some(Rc::new(|_, _, _| {})), - mouse_down: Some(Rc::new(|_, _| {})), - click: Some(Rc::new(|_, _, _| {})), - right_mouse_down: Some(Rc::new(|_, _| {})), - right_click: Some(Rc::new(|_, _, _| {})), - drag: Some(Rc::new(|_, _, _| {})), - mouse_down_out: Some(Rc::new(|_, _| {})), - right_mouse_down_out: Some(Rc::new(|_, _| {})), - }); + cx.scene.push_mouse_region(MouseRegion::handle_all( + cx.current_view_id(), + Some(discriminant), + visible_bounds, + )); cx.scene.pop_stacking_context(); } self.child.paint(bounds.origin(), visible_bounds, cx); @@ -117,7 +109,7 @@ impl Element for EventHandler { true } else { match event { - Event::MouseDown(MouseEvent { + Event::MouseDown(MouseButtonEvent { button: MouseButton::Left, position, .. @@ -129,7 +121,7 @@ impl Element for EventHandler { } false } - Event::MouseDown(MouseEvent { + Event::MouseDown(MouseButtonEvent { button: MouseButton::Right, position, .. @@ -141,7 +133,7 @@ impl Element for EventHandler { } false } - Event::MouseDown(MouseEvent { + Event::MouseDown(MouseButtonEvent { button: MouseButton::Navigate(direction), position, .. diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 832aafaa9e..3a8a5f6d63 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, rc::Rc}; +use std::any::TypeId; use super::Padding; use crate::{ @@ -8,24 +8,16 @@ use crate::{ }, platform::CursorStyle, scene::CursorRegion, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion, MouseState, - PaintContext, RenderContext, SizeConstraint, View, + DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton, + MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext, RenderContext, + SizeConstraint, View, }; use serde_json::json; pub struct MouseEventHandler { child: ElementBox, - tag: TypeId, - id: usize, cursor_style: Option, - mouse_down: Option>, - click: Option>, - right_mouse_down: Option>, - right_click: Option>, - mouse_down_out: Option>, - right_mouse_down_out: Option>, - drag: Option>, - hover: Option>, + region: MouseRegion, padding: Padding, } @@ -37,18 +29,9 @@ impl MouseEventHandler { F: FnOnce(MouseState, &mut RenderContext) -> ElementBox, { Self { - id, - tag: TypeId::of::(), child: render_child(cx.mouse_state::(id), cx), cursor_style: None, - mouse_down: None, - click: None, - right_mouse_down: None, - right_click: None, - mouse_down_out: None, - right_mouse_down_out: None, - drag: None, - hover: None, + region: MouseRegion::new(0, Some((TypeId::of::(), id)), Default::default()), padding: Default::default(), } } @@ -60,65 +43,45 @@ impl MouseEventHandler { pub fn on_mouse_down( mut self, - handler: impl Fn(Vector2F, &mut EventContext) + 'static, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, ) -> Self { - self.mouse_down = Some(Rc::new(handler)); + self.region = self.region.on_down(button, handler); self } pub fn on_click( mut self, - handler: impl Fn(Vector2F, usize, &mut EventContext) + 'static, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, ) -> Self { - self.click = Some(Rc::new(handler)); - self - } - - pub fn on_right_mouse_down( - mut self, - handler: impl Fn(Vector2F, &mut EventContext) + 'static, - ) -> Self { - self.right_mouse_down = Some(Rc::new(handler)); - self - } - - pub fn on_right_click( - mut self, - handler: impl Fn(Vector2F, usize, &mut EventContext) + 'static, - ) -> Self { - self.right_click = Some(Rc::new(handler)); + self.region = self.region.on_click(button, handler); self } pub fn on_mouse_down_out( mut self, - handler: impl Fn(Vector2F, &mut EventContext) + 'static, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, ) -> Self { - self.mouse_down_out = Some(Rc::new(handler)); - self - } - - pub fn on_right_mouse_down_out( - mut self, - handler: impl Fn(Vector2F, &mut EventContext) + 'static, - ) -> Self { - self.right_mouse_down_out = Some(Rc::new(handler)); + self.region = self.region.on_down_out(button, handler); self } pub fn on_drag( mut self, - handler: impl Fn(Vector2F, Vector2F, &mut EventContext) + 'static, + button: MouseButton, + handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, ) -> Self { - self.drag = Some(Rc::new(handler)); + self.region = self.region.on_drag(button, handler); self } pub fn on_hover( mut self, - handler: impl Fn(Vector2F, bool, &mut EventContext) + 'static, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, ) -> Self { - self.hover = Some(Rc::new(handler)); + self.region = self.region.on_hover(handler); self } @@ -163,19 +126,9 @@ impl Element for MouseEventHandler { }); } - cx.scene.push_mouse_region(MouseRegion { - view_id: cx.current_view_id(), - discriminant: Some((self.tag, self.id)), - bounds: hit_bounds, - hover: self.hover.clone(), - click: self.click.clone(), - mouse_down: self.mouse_down.clone(), - right_click: self.right_click.clone(), - right_mouse_down: self.right_mouse_down.clone(), - mouse_down_out: self.mouse_down_out.clone(), - right_mouse_down_out: self.right_mouse_down_out.clone(), - drag: self.drag.clone(), - }); + self.region.view_id = cx.current_view_id(); + self.region.bounds = hit_bounds; + cx.scene.push_mouse_region(self.region.clone()); self.child.paint(bounds.origin(), visible_bounds, cx); } diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index 9a65b2661d..572d142521 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -6,8 +6,8 @@ use crate::{ fonts::TextStyle, geometry::{rect::RectF, vector::Vector2F}, json::json, - Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint, - Task, View, + Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext, + SizeConstraint, Task, View, }; use serde::Deserialize; use std::{ @@ -91,7 +91,7 @@ impl Tooltip { }; let child = MouseEventHandler::new::, _, _>(id, cx, |_, _| child) - .on_hover(move |position, hover, cx| { + .on_hover(move |hover, MouseMovedEvent { position, .. }, cx| { let window_id = cx.window_id(); if let Some(view_id) = cx.view_id() { if hover { diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 90b5d21fc2..57e945175b 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -21,20 +21,26 @@ pub struct ModifiersChangedEvent { pub cmd: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct ScrollWheelEvent { pub position: Vector2F, pub delta: Vector2F, pub precise: bool, } -#[derive(Copy, Clone, Debug)] +#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum NavigationDirection { Back, Forward, } -#[derive(Copy, Clone, Debug)] +impl Default for NavigationDirection { + fn default() -> Self { + Self::Back + } +} + +#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, Right, @@ -42,8 +48,26 @@ pub enum MouseButton { Navigate(NavigationDirection), } -#[derive(Clone, Debug)] -pub struct MouseEvent { +impl MouseButton { + pub fn all() -> Vec { + vec![ + MouseButton::Left, + MouseButton::Right, + MouseButton::Middle, + MouseButton::Navigate(NavigationDirection::Back), + MouseButton::Navigate(NavigationDirection::Forward), + ] + } +} + +impl Default for MouseButton { + fn default() -> Self { + Self::Left + } +} + +#[derive(Clone, Debug, Default)] +pub struct MouseButtonEvent { pub button: MouseButton, pub position: Vector2F, pub ctrl: bool, @@ -53,7 +77,7 @@ pub struct MouseEvent { pub click_count: usize, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct MouseMovedEvent { pub position: Vector2F, pub pressed_button: Option, @@ -68,8 +92,8 @@ pub enum Event { KeyDown(KeyDownEvent), KeyUp(KeyUpEvent), ModifiersChanged(ModifiersChangedEvent), - MouseDown(MouseEvent), - MouseUp(MouseEvent), + MouseDown(MouseButtonEvent), + MouseUp(MouseButtonEvent), MouseMoved(MouseMovedEvent), ScrollWheel(ScrollWheelEvent), } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 5e23859675..209d8de766 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -2,8 +2,8 @@ use crate::{ geometry::vector::vec2f, keymap::Keystroke, platform::{Event, NavigationDirection}, - KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, - ScrollWheelEvent, + KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent, + MouseMovedEvent, ScrollWheelEvent, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventType}, @@ -126,7 +126,7 @@ impl Event { let modifiers = native_event.modifierFlags(); window_height.map(|window_height| { - Self::MouseDown(MouseEvent { + Self::MouseDown(MouseButtonEvent { button, position: vec2f( native_event.locationInWindow().x as f32, @@ -155,7 +155,7 @@ impl Event { window_height.map(|window_height| { let modifiers = native_event.modifierFlags(); - Self::MouseUp(MouseEvent { + Self::MouseUp(MouseButtonEvent { button, position: vec2f( native_event.locationInWindow().x as f32, diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 43ce09ffff..2680d9aa46 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -6,7 +6,7 @@ use crate::{ }, keymap::Keystroke, platform::{self, Event, WindowBounds, WindowContext}, - KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, Scene, + KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, Scene, }; use block::ConcreteBlock; use cocoa::{ @@ -635,7 +635,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { )) .detach(); } - Event::MouseUp(MouseEvent { + Event::MouseUp(MouseButtonEvent { button: MouseButton::Left, .. }) => { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 86a8c4cf30..243863149d 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -6,10 +6,10 @@ use crate::{ json::{self, ToJson}, keymap::Keystroke, platform::{CursorStyle, Event}, - scene::CursorRegion, + scene::{CursorRegion, MouseRegionEvent}, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, - FontSystem, ModelHandle, MouseButton, MouseEvent, MouseMovedEvent, MouseRegion, MouseRegionId, + FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle, }; @@ -230,105 +230,58 @@ impl Presenter { let mut mouse_down_out_handlers = Vec::new(); let mut mouse_down_region = None; let mut clicked_region = None; - let mut right_mouse_down_region = None; - let mut right_clicked_region = None; let mut dragged_region = None; - match event { - Event::MouseDown(MouseEvent { - position, - button: MouseButton::Left, - .. - }) => { + match &event { + Event::MouseDown( + e @ MouseButtonEvent { + position, button, .. + }, + ) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(position) { + if region.bounds.contains_point(*position) { if !hit { hit = true; invalidated_views.push(region.view_id); - mouse_down_region = Some((region.clone(), position)); + mouse_down_region = + Some((region.clone(), MouseRegionEvent::Down(e.clone()))); self.clicked_region = Some(region.clone()); - self.prev_drag_position = Some(position); + self.prev_drag_position = Some(*position); } - } else if let Some(handler) = region.mouse_down_out.clone() { - mouse_down_out_handlers.push((handler, region.view_id, position)); + } else if let Some(handler) = region + .handlers + .get(&(MouseRegionEvent::down_out_disc(), Some(*button))) + .cloned() + { + mouse_down_out_handlers.push(( + handler, + region.view_id, + MouseRegionEvent::DownOut(e.clone()), + )); } } } - Event::MouseUp(MouseEvent { - position, - click_count, - button: MouseButton::Left, - .. - }) => { + Event::MouseUp(e @ MouseButtonEvent { position, .. }) => { self.prev_drag_position.take(); if let Some(region) = self.clicked_region.take() { invalidated_views.push(region.view_id); - if region.bounds.contains_point(position) { - clicked_region = Some((region, position, click_count)); + if region.bounds.contains_point(*position) { + clicked_region = Some((region, MouseRegionEvent::Click(e.clone()))); } } } - Event::MouseDown(MouseEvent { - position, - button: MouseButton::Right, - .. - }) => { - let mut hit = false; - for (region, _) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(position) { - if !hit { - hit = true; - invalidated_views.push(region.view_id); - right_mouse_down_region = Some((region.clone(), position)); - self.right_clicked_region = Some(region.clone()); - } - } else if let Some(handler) = region.right_mouse_down_out.clone() { - mouse_down_out_handlers.push((handler, region.view_id, position)); - } - } - } - Event::MouseUp(MouseEvent { - position, - click_count, - button: MouseButton::Right, - .. - }) => { - if let Some(region) = self.right_clicked_region.take() { - invalidated_views.push(region.view_id); - if region.bounds.contains_point(position) { - right_clicked_region = Some((region, position, click_count)); - } - } - } - Event::MouseMoved(MouseMovedEvent { - pressed_button, - position, - shift, - ctrl, - alt, - cmd, - .. - }) => { - if let Some(MouseButton::Left) = pressed_button { - if let Some((clicked_region, prev_drag_position)) = self - .clicked_region - .as_ref() - .zip(self.prev_drag_position.as_mut()) - { - dragged_region = - Some((clicked_region.clone(), *prev_drag_position, position)); - *prev_drag_position = position; - } - - self.last_mouse_moved_event = Some(Event::MouseMoved(MouseMovedEvent { - position, - pressed_button: Some(MouseButton::Left), - shift, - ctrl, - alt, - cmd, - })); + Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => { + if let Some((clicked_region, prev_drag_position)) = self + .clicked_region + .as_ref() + .zip(self.prev_drag_position.as_mut()) + { + dragged_region = Some(( + clicked_region.clone(), + MouseRegionEvent::Drag(*prev_drag_position, e.clone()), + )); + *prev_drag_position = *position; } self.last_mouse_moved_event = Some(event.clone()); @@ -339,51 +292,39 @@ impl Presenter { let (mut handled, mut event_cx) = self.handle_hover_events(&event, &mut invalidated_views, cx); - for (handler, view_id, position) in mouse_down_out_handlers { - event_cx.with_current_view(view_id, |event_cx| handler(position, event_cx)) + for (handler, view_id, region_event) in mouse_down_out_handlers { + event_cx.with_current_view(view_id, |event_cx| handler(region_event, event_cx)) } - if let Some((mouse_down_region, position)) = mouse_down_region { + if let Some((mouse_down_region, region_event)) = mouse_down_region { handled = true; - if let Some(mouse_down_callback) = mouse_down_region.mouse_down { + if let Some(mouse_down_callback) = + mouse_down_region.handlers.get(®ion_event.handler_key()) + { event_cx.with_current_view(mouse_down_region.view_id, |event_cx| { - mouse_down_callback(position, event_cx); + mouse_down_callback(region_event, event_cx); }) } } - if let Some((clicked_region, position, click_count)) = clicked_region { + if let Some((clicked_region, region_event)) = clicked_region { handled = true; - if let Some(click_callback) = clicked_region.click { + if let Some(click_callback) = + clicked_region.handlers.get(®ion_event.handler_key()) + { event_cx.with_current_view(clicked_region.view_id, |event_cx| { - click_callback(position, click_count, event_cx); + click_callback(region_event, event_cx); }) } } - if let Some((right_mouse_down_region, position)) = right_mouse_down_region { + if let Some((dragged_region, region_event)) = dragged_region { handled = true; - if let Some(right_mouse_down_callback) = right_mouse_down_region.right_mouse_down { - event_cx.with_current_view(right_mouse_down_region.view_id, |event_cx| { - right_mouse_down_callback(position, event_cx); - }) - } - } - - if let Some((right_clicked_region, position, click_count)) = right_clicked_region { - handled = true; - if let Some(right_click_callback) = right_clicked_region.right_click { - event_cx.with_current_view(right_clicked_region.view_id, |event_cx| { - right_click_callback(position, click_count, event_cx); - }) - } - } - - if let Some((dragged_region, prev_position, position)) = dragged_region { - handled = true; - if let Some(drag_callback) = dragged_region.drag { + if let Some(drag_callback) = + dragged_region.handlers.get(®ion_event.handler_key()) + { event_cx.with_current_view(dragged_region.view_id, |event_cx| { - drag_callback(prev_position, position, event_cx); + drag_callback(region_event, event_cx); }) } } @@ -420,14 +361,17 @@ impl Presenter { invalidated_views: &mut Vec, cx: &'a mut MutableAppContext, ) -> (bool, EventContext<'a>) { - let mut unhovered_regions = Vec::new(); - let mut hovered_regions = Vec::new(); + let mut hover_regions = Vec::new(); + // let mut unhovered_regions = Vec::new(); + // let mut hovered_regions = Vec::new(); - if let Event::MouseMoved(MouseMovedEvent { - position, - pressed_button, - .. - }) = event + if let Event::MouseMoved( + e @ MouseMovedEvent { + position, + pressed_button, + .. + }, + ) = event { if let None = pressed_button { let mut style_to_assign = CursorStyle::Arrow; @@ -448,7 +392,10 @@ impl Presenter { if let Some(region_id) = region.id() { if !self.hovered_region_ids.contains(®ion_id) { invalidated_views.push(region.view_id); - hovered_regions.push((region.clone(), position)); + hover_regions.push(( + region.clone(), + MouseRegionEvent::Hover(true, e.clone()), + )); self.hovered_region_ids.insert(region_id); } } @@ -456,7 +403,10 @@ impl Presenter { if let Some(region_id) = region.id() { if self.hovered_region_ids.contains(®ion_id) { invalidated_views.push(region.view_id); - unhovered_regions.push((region.clone(), position)); + hover_regions.push(( + region.clone(), + MouseRegionEvent::Hover(false, e.clone()), + )); self.hovered_region_ids.remove(®ion_id); } } @@ -468,20 +418,11 @@ impl Presenter { let mut event_cx = self.build_event_context(cx); let mut handled = false; - for (unhovered_region, position) in unhovered_regions { + for (hover_region, region_event) in hover_regions { handled = true; - if let Some(hover_callback) = unhovered_region.hover { - event_cx.with_current_view(unhovered_region.view_id, |event_cx| { - hover_callback(*position, false, event_cx); - }) - } - } - - for (hovered_region, position) in hovered_regions { - handled = true; - if let Some(hover_callback) = hovered_region.hover { - event_cx.with_current_view(hovered_region.view_id, |event_cx| { - hover_callback(*position, true, event_cx); + if let Some(hover_callback) = hover_region.handlers.get(®ion_event.handler_key()) { + event_cx.with_current_view(hover_region.view_id, |event_cx| { + hover_callback(region_event, event_cx); }) } } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 769eabe7e5..f5577c5e5d 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,6 +1,7 @@ +use collections::HashMap; use serde::Deserialize; use serde_json::json; -use std::{any::TypeId, borrow::Cow, rc::Rc, sync::Arc}; +use std::{any::TypeId, borrow::Cow, mem::Discriminant, rc::Rc, sync::Arc}; use crate::{ color::Color, @@ -8,7 +9,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, platform::CursorStyle, - EventContext, ImageData, MouseEvent, MouseMovedEvent, ScrollWheelEvent, + EventContext, ImageData, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent, }; pub struct Scene { @@ -44,30 +45,247 @@ pub struct CursorRegion { pub style: CursorStyle, } +#[derive(Debug)] pub enum MouseRegionEvent { - Moved(MouseMovedEvent), - Hover(MouseEvent), - Down(MouseEvent), - Up(MouseEvent), - Click(MouseEvent), - DownOut(MouseEvent), + Move(MouseMovedEvent), + Drag(Vector2F, MouseMovedEvent), + Hover(bool, MouseMovedEvent), + Down(MouseButtonEvent), + Up(MouseButtonEvent), + Click(MouseButtonEvent), + DownOut(MouseButtonEvent), ScrollWheel(ScrollWheelEvent), } +impl MouseRegionEvent { + pub fn move_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Move(Default::default())) + } + pub fn drag_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Drag( + Default::default(), + Default::default(), + )) + } + pub fn hover_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Hover( + Default::default(), + Default::default(), + )) + } + pub fn down_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Down(Default::default())) + } + pub fn up_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Up(Default::default())) + } + pub fn click_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Click(Default::default())) + } + pub fn down_out_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::DownOut(Default::default())) + } + pub fn scroll_wheel_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) + } + + pub fn handler_key(&self) -> (Discriminant, Option) { + match self { + MouseRegionEvent::Move(_) => (Self::move_disc(), None), + MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => { + (Self::drag_disc(), *pressed_button) + } + MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None), + MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => { + (Self::down_disc(), Some(*button)) + } + MouseRegionEvent::Up(MouseButtonEvent { button, .. }) => { + (Self::up_disc(), Some(*button)) + } + MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => { + (Self::click_disc(), Some(*button)) + } + MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => { + (Self::down_out_disc(), Some(*button)) + } + MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), + } + } +} + #[derive(Clone, Default)] pub struct MouseRegion { pub view_id: usize, pub discriminant: Option<(TypeId, usize)>, pub bounds: RectF, + pub handlers: HashMap< + (Discriminant, Option), + Rc, + >, +} - pub hover: Option>, - pub mouse_down: Option>, - pub click: Option>, - pub right_mouse_down: Option>, - pub right_click: Option>, - pub drag: Option>, - pub mouse_down_out: Option>, - pub right_mouse_down_out: Option>, +impl MouseRegion { + pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self { + Self { + view_id, + discriminant, + bounds, + handlers: Default::default(), + } + } + + pub fn handle_all( + view_id: usize, + discriminant: Option<(TypeId, usize)>, + bounds: RectF, + ) -> Self { + let mut handlers: HashMap< + (Discriminant, Option), + Rc, + > = Default::default(); + handlers.insert((MouseRegionEvent::move_disc(), None), Rc::new(|_, _| {})); + handlers.insert((MouseRegionEvent::hover_disc(), None), Rc::new(|_, _| {})); + for button in MouseButton::all() { + handlers.insert( + (MouseRegionEvent::drag_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + handlers.insert( + (MouseRegionEvent::down_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + handlers.insert( + (MouseRegionEvent::up_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + handlers.insert( + (MouseRegionEvent::click_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + handlers.insert( + (MouseRegionEvent::down_out_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + } + handlers.insert( + (MouseRegionEvent::scroll_wheel_disc(), None), + Rc::new(|_, _| {}), + ); + + Self { + view_id, + discriminant, + bounds, + handlers, + } + } + + pub fn on_down( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers.insert((MouseRegionEvent::down_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Down(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}", + region_event); + } + })); + self + } + + pub fn on_up( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers.insert((MouseRegionEvent::up_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Up(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}", + region_event); + } + })); + self + } + + pub fn on_click( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers.insert((MouseRegionEvent::click_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Click(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}", + region_event); + } + })); + self + } + + pub fn on_down_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers.insert((MouseRegionEvent::down_out_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::DownOut(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}", + region_event); + } + })); + self + } + + pub fn on_drag( + mut self, + button: MouseButton, + handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers.insert((MouseRegionEvent::drag_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event { + handler(prev_drag_position, mouse_moved_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}", + region_event); + } + })); + self + } + + pub fn on_hover( + mut self, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers.insert((MouseRegionEvent::hover_disc(), None), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Hover(hover, mouse_moved_event) = region_event { + handler(hover, mouse_moved_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}", + region_event); + } + })); + self + } } #[derive(Copy, Clone, Eq, PartialEq, Hash)] diff --git a/crates/gpui/src/views/select.rs b/crates/gpui/src/views/select.rs index 21527a1f2c..1b57caf476 100644 --- a/crates/gpui/src/views/select.rs +++ b/crates/gpui/src/views/select.rs @@ -1,8 +1,8 @@ use serde::Deserialize; use crate::{ - actions, elements::*, impl_actions, AppContext, Entity, MutableAppContext, RenderContext, View, - ViewContext, WeakViewHandle, + actions, elements::*, impl_actions, AppContext, Entity, MouseButton, MutableAppContext, + RenderContext, View, ViewContext, WeakViewHandle, }; pub struct Select { @@ -119,7 +119,9 @@ impl View for Select { .with_style(style.header) .boxed() }) - .on_click(move |_, _, cx| cx.dispatch_action(ToggleSelect)) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleSelect) + }) .boxed(), ); if self.is_open { @@ -151,7 +153,7 @@ impl View for Select { ) }, ) - .on_click(move |_, _, cx| { + .on_click(MouseButton::Left, move |_, cx| { cx.dispatch_action(SelectItem(ix)) }) .boxed() diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 8f8e5d26b9..0b30583053 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -7,8 +7,8 @@ use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap, platform::CursorStyle, - AppContext, Axis, Element, ElementBox, Entity, MouseState, MutableAppContext, RenderContext, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState, MutableAppContext, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev}; use settings::Settings; @@ -90,7 +90,9 @@ impl View for Picker { .read(cx) .render_match(ix, state, ix == selected_ix, cx) }) - .on_mouse_down(move |_, cx| cx.dispatch_action(SelectIndex(ix))) + .on_mouse_down(MouseButton::Left, move |_, cx| { + cx.dispatch_action(SelectIndex(ix)) + }) .with_cursor_style(CursorStyle::PointingHand) .boxed() })); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index eaff23a476..8d9443de4f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -11,8 +11,9 @@ use gpui::{ geometry::vector::Vector2F, impl_internal_actions, keymap, platform::CursorStyle, - AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext, - PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, + AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, + MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, + ViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -1054,19 +1055,25 @@ impl ProjectPanel { .with_padding_left(padding) .boxed() }) - .on_click(move |_, click_count, cx| { - if kind == EntryKind::Dir { - cx.dispatch_action(ToggleExpanded(entry_id)) - } else { - cx.dispatch_action(Open { - entry_id, - change_focus: click_count > 1, - }) - } - }) - .on_right_mouse_down(move |position, cx| { - cx.dispatch_action(DeployContextMenu { entry_id, position }) - }) + .on_click( + MouseButton::Left, + move |MouseButtonEvent { click_count, .. }, cx| { + if kind == EntryKind::Dir { + cx.dispatch_action(ToggleExpanded(entry_id)) + } else { + cx.dispatch_action(Open { + entry_id, + change_focus: click_count > 1, + }) + } + }, + ) + .on_mouse_down( + MouseButton::Right, + move |MouseButtonEvent { position, .. }, cx| { + cx.dispatch_action(DeployContextMenu { entry_id, position }) + }, + ) .with_cursor_style(CursorStyle::PointingHand) .boxed() } @@ -1113,13 +1120,16 @@ impl View for ProjectPanel { .expanded() .boxed() }) - .on_right_mouse_down(move |position, cx| { - // When deploying the context menu anywhere below the last project entry, - // act as if the user clicked the root of the last worktree. - if let Some(entry_id) = last_worktree_root_id { - cx.dispatch_action(DeployContextMenu { entry_id, position }) - } - }) + .on_mouse_down( + MouseButton::Right, + move |MouseButtonEvent { position, .. }, cx| { + // When deploying the context menu anywhere below the last project entry, + // act as if the user clicked the root of the last worktree. + if let Some(entry_id) = last_worktree_root_id { + cx.dispatch_action(DeployContextMenu { entry_id, position }) + } + }, + ) .boxed(), ) .with_child(ChildView::new(&self.context_menu).boxed()) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 5303c20e89..529da6f7b6 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -7,8 +7,8 @@ use collections::HashMap; use editor::{Anchor, Autoscroll, Editor}; use gpui::{ actions, elements::*, impl_actions, platform::CursorStyle, Action, AppContext, Entity, - MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, - WeakViewHandle, + MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use language::OffsetRangeExt; use project::search::SearchQuery; @@ -285,7 +285,9 @@ impl BufferSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |_, _, cx| cx.dispatch_any_action(option.to_toggle_action())) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(option.to_toggle_action()) + }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( option as usize, @@ -330,9 +332,9 @@ impl BufferSearchBar { .with_style(style.container) .boxed() }) - .on_click({ + .on_click(MouseButton::Left, { let action = action.boxed_clone(); - move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()) + move |_, cx| cx.dispatch_any_action(action.boxed_clone()) }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 622b84633c..405dd2d334 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,8 +7,8 @@ use collections::HashMap; 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, - ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, + ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, + View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, }; use menu::Confirm; use project::{search::SearchQuery, Project}; @@ -735,9 +735,9 @@ impl ProjectSearchBar { .with_style(style.container) .boxed() }) - .on_click({ + .on_click(MouseButton::Left, { let action = action.boxed_clone(); - move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()) + move |_, cx| cx.dispatch_any_action(action.boxed_clone()) }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( @@ -770,7 +770,9 @@ impl ProjectSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |_, _, cx| cx.dispatch_any_action(option.to_toggle_action())) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(option.to_toggle_action()) + }) .with_cursor_style(CursorStyle::PointingHand) .with_tooltip::( option as usize, diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 3406b4b9e1..f1ba70cab9 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -20,8 +20,8 @@ use gpui::{ }, json::json, text_layout::{Line, RunStyle}, - Event, FontCache, KeyDownEvent, MouseRegion, PaintContext, Quad, ScrollWheelEvent, - SizeConstraint, TextLayoutCache, WeakModelHandle, + Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, + PaintContext, Quad, ScrollWheelEvent, SizeConstraint, TextLayoutCache, WeakModelHandle, }; use itertools::Itertools; use ordered_float::OrderedFloat; @@ -29,7 +29,7 @@ use settings::Settings; use theme::TerminalStyle; use util::ResultExt; -use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; +use std::{cmp::min, ops::Range, sync::Arc}; use std::{fmt::Debug, ops::Sub}; use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener}; @@ -594,63 +594,76 @@ fn attach_mouse_handlers( let drag_mutex = terminal_mutex.clone(); let mouse_down_mutex = terminal_mutex.clone(); - cx.scene.push_mouse_region(MouseRegion { - view_id, - mouse_down: Some(Rc::new(move |pos, _| { - let mut term = mouse_down_mutex.lock(); - let (point, side) = mouse_to_cell_data( - pos, - origin, - cur_size, - term.renderable_content().display_offset, - ); - term.selection = Some(Selection::new(SelectionType::Simple, point, side)) - })), - click: Some(Rc::new(move |pos, click_count, cx| { - let mut term = click_mutex.lock(); + cx.scene.push_mouse_region( + MouseRegion::new(view_id, None, visible_bounds) + .on_down( + MouseButton::Left, + move |MouseButtonEvent { position, .. }, _| { + let mut term = mouse_down_mutex.lock(); - let (point, side) = mouse_to_cell_data( - pos, - origin, - cur_size, - term.renderable_content().display_offset, - ); + let (point, side) = mouse_to_cell_data( + position, + origin, + cur_size, + term.renderable_content().display_offset, + ); + term.selection = Some(Selection::new(SelectionType::Simple, point, side)) + }, + ) + .on_click( + MouseButton::Left, + move |MouseButtonEvent { + position, + click_count, + .. + }, + cx| { + let mut term = click_mutex.lock(); - let selection_type = match click_count { - 0 => return, //This is a release - 1 => Some(SelectionType::Simple), - 2 => Some(SelectionType::Semantic), - 3 => Some(SelectionType::Lines), - _ => None, - }; + let (point, side) = mouse_to_cell_data( + position, + origin, + cur_size, + term.renderable_content().display_offset, + ); - let selection = - selection_type.map(|selection_type| Selection::new(selection_type, point, side)); + let selection_type = match click_count { + 0 => return, //This is a release + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; - term.selection = selection; - cx.focus_parent_view(); - cx.notify(); - })), - bounds: visible_bounds, - drag: Some(Rc::new(move |_delta, pos, cx| { - let mut term = drag_mutex.lock(); + let selection = selection_type + .map(|selection_type| Selection::new(selection_type, point, side)); - let (point, side) = mouse_to_cell_data( - pos, - origin, - cur_size, - term.renderable_content().display_offset, - ); + term.selection = selection; + cx.focus_parent_view(); + cx.notify(); + }, + ) + .on_drag( + MouseButton::Left, + move |_, MouseMovedEvent { position, .. }, cx| { + let mut term = drag_mutex.lock(); - if let Some(mut selection) = term.selection.take() { - selection.update(point, side); - term.selection = Some(selection); - } + let (point, side) = mouse_to_cell_data( + position, + origin, + cur_size, + term.renderable_content().display_offset, + ); - cx.notify(); - })), - ..Default::default() - }); + if let Some(mut selection) = term.selection.take() { + selection.update(point, side); + term.selection = Some(selection); + } + + cx.notify(); + }, + ), + ); } ///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 4b231e03e7..657a9f0542 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -13,8 +13,9 @@ use gpui::{ }, impl_actions, impl_internal_actions, platform::{CursorStyle, NavigationDirection}, - AppContext, AsyncAppContext, Entity, ModelHandle, MutableAppContext, PromptLevel, Quad, - RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, + AppContext, AsyncAppContext, Entity, ModelHandle, MouseButton, MouseButtonEvent, + MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, ViewContext, ViewHandle, + WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -955,9 +956,9 @@ impl Pane { ) .with_padding(Padding::uniform(4.)) .with_cursor_style(CursorStyle::PointingHand) - .on_click({ + .on_click(MouseButton::Left, { let pane = pane.clone(); - move |_, _, cx| { + move |_, cx| { cx.dispatch_action(CloseItem { item_id, pane: pane.clone(), @@ -978,7 +979,7 @@ impl Pane { .with_style(style.container) .boxed() }) - .on_mouse_down(move |_, cx| { + .on_mouse_down(MouseButton::Left, move |_, cx| { cx.dispatch_action(ActivateItem(ix)); }) .boxed() @@ -1079,9 +1080,12 @@ impl View for Pane { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(|position, cx| { - cx.dispatch_action(DeploySplitMenu { position }); - }) + .on_mouse_down( + MouseButton::Left, + |MouseButtonEvent { position, .. }, cx| { + cx.dispatch_action(DeploySplitMenu { position }); + }, + ) .boxed(), ) .constrained() diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index a500f99492..cd4a009f2c 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -1,7 +1,7 @@ use crate::StatusItemView; use gpui::{ elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity, - RenderContext, Subscription, View, ViewContext, ViewHandle, + MouseButton, MouseMovedEvent, RenderContext, Subscription, View, ViewContext, ViewHandle, }; use serde::Deserialize; use settings::Settings; @@ -187,19 +187,27 @@ impl Sidebar { ..Default::default() }) .with_cursor_style(CursorStyle::ResizeLeftRight) - .on_mouse_down(|_, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(move |old_position, new_position, cx| { - let delta = new_position.x() - old_position.x(); - let prev_width = *actual_width.borrow(); - *custom_width.borrow_mut() = 0f32 - .max(match side { - Side::Left => prev_width + delta, - Side::Right => prev_width - delta, - }) - .round(); + .on_mouse_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere + .on_drag( + MouseButton::Left, + move |old_position, + MouseMovedEvent { + position: new_position, + .. + }, + cx| { + let delta = new_position.x() - old_position.x(); + let prev_width = *actual_width.borrow(); + *custom_width.borrow_mut() = 0f32 + .max(match side { + Side::Left => prev_width + delta, + Side::Right => prev_width - delta, + }) + .round(); - cx.notify(); - }) + cx.notify(); + }, + ) .boxed() } } @@ -314,9 +322,9 @@ impl View for SidebarButtons { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click({ + .on_click(MouseButton::Left, { let action = action.clone(); - move |_, _, cx| cx.dispatch_action(action.clone()) + move |_, cx| cx.dispatch_action(action.clone()) }) .with_tooltip::( ix, diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 636df9a039..6d8eefcc76 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -1,7 +1,7 @@ use crate::{ItemHandle, Pane}; use gpui::{ elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity, - MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, + MouseButton, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; @@ -191,7 +191,9 @@ fn nav_button( } else { CursorStyle::default() }) - .on_click(move |_, _, cx| cx.dispatch_action(action.clone())) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(action.clone()) + }) .with_tooltip::( 0, action_name.to_string(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f6c185a19f..3a30e0cee3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -21,8 +21,8 @@ use gpui::{ json::{self, ToJson}, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, Entity, ImageData, - ModelContext, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::LanguageRegistry; use log::error; @@ -1980,7 +1980,7 @@ impl Workspace { .with_style(style.container) .boxed() }) - .on_click(|_, _, cx| cx.dispatch_action(Authenticate)) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate)) .with_cursor_style(CursorStyle::PointingHand) .aligned() .boxed(), @@ -2031,7 +2031,9 @@ impl Workspace { if let Some((peer_id, peer_github_login)) = peer { MouseEventHandler::new::(replica_id.into(), cx, move |_, _| content) .with_cursor_style(CursorStyle::PointingHand) - .on_click(move |_, _, cx| cx.dispatch_action(ToggleFollow(peer_id))) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(ToggleFollow(peer_id)) + }) .with_tooltip::( peer_id.0 as usize, if is_followed { diff --git a/crates/zed/src/feedback.rs b/crates/zed/src/feedback.rs index 9e7564f411..f4b67a12bd 100644 --- a/crates/zed/src/feedback.rs +++ b/crates/zed/src/feedback.rs @@ -2,7 +2,7 @@ use crate::OpenBrowser; use gpui::{ elements::{MouseEventHandler, Text}, platform::CursorStyle, - Element, Entity, RenderContext, View, + Element, Entity, MouseButton, RenderContext, View, }; use settings::Settings; use workspace::StatusItemView; @@ -32,7 +32,7 @@ impl View for FeedbackLink { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, _, cx| { + .on_click(MouseButton::Left, |_, cx| { cx.dispatch_action(OpenBrowser { url: NEW_ISSUE_URL.into(), }) diff --git a/styles/package-lock.json b/styles/package-lock.json index 2eb6d3a1bf..49304dc2fa 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 2cbb0ae8431b7815d3c4979cacd136ccc69e130b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 08:58:09 +0200 Subject: [PATCH 54/71] Expose max number of project collaborators in Db::get_top_user_activity --- crates/collab/src/db.rs | 58 ++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 967bd2ee99..b771eb3d97 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -654,16 +654,22 @@ impl Db for PostgresDb { GROUP BY user_id ORDER BY total_duration DESC LIMIT $3 + ), + project_collaborators as ( + SELECT project_id, COUNT(DISTINCT user_id) as project_collaborators + FROM project_durations + GROUP BY project_id ) - SELECT user_durations.user_id, users.github_login, project_id, project_duration - FROM user_durations, project_durations, users + SELECT user_durations.user_id, users.github_login, project_durations.project_id, project_duration, project_collaborators + FROM user_durations, project_durations, project_collaborators, users WHERE user_durations.user_id = project_durations.user_id AND - user_durations.user_id = users.id + user_durations.user_id = users.id AND + project_durations.project_id = project_collaborators.project_id ORDER BY total_duration DESC, user_id ASC "; - let mut rows = sqlx::query_as::<_, (UserId, String, ProjectId, i64)>(query) + let mut rows = sqlx::query_as::<_, (UserId, String, ProjectId, i64, i64)>(query) .bind(time_period.start) .bind(time_period.end) .bind(max_user_count as i32) @@ -671,18 +677,23 @@ impl Db for PostgresDb { let mut result = Vec::::new(); while let Some(row) = rows.next().await { - let (user_id, github_login, project_id, duration_millis) = row?; + let (user_id, github_login, project_id, duration_millis, project_collaborators) = row?; let project_id = project_id; let duration = Duration::from_millis(duration_millis as u64); + let project_activity = ProjectActivitySummary { + id: project_id, + duration, + max_collaborators: project_collaborators as usize, + }; if let Some(last_summary) = result.last_mut() { if last_summary.id == user_id { - last_summary.project_activity.push((project_id, duration)); + last_summary.project_activity.push(project_activity); continue; } } result.push(UserActivitySummary { id: user_id, - project_activity: vec![(project_id, duration)], + project_activity: vec![project_activity], github_login, }); } @@ -1314,7 +1325,14 @@ pub struct Project { pub struct UserActivitySummary { pub id: UserId, pub github_login: String, - pub project_activity: Vec<(ProjectId, Duration)>, + pub project_activity: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct ProjectActivitySummary { + id: ProjectId, + duration: Duration, + max_collaborators: usize, } #[derive(Clone, Debug, PartialEq, Serialize)] @@ -1667,19 +1685,35 @@ pub mod tests { id: user_1, github_login: "user_1".to_string(), project_activity: vec![ - (project_1, Duration::from_secs(25)), - (project_2, Duration::from_secs(30)), + ProjectActivitySummary { + id: project_1, + duration: Duration::from_secs(25), + max_collaborators: 2 + }, + ProjectActivitySummary { + id: project_2, + duration: Duration::from_secs(30), + max_collaborators: 2 + } ] }, UserActivitySummary { id: user_2, github_login: "user_2".to_string(), - project_activity: vec![(project_2, Duration::from_secs(50))] + project_activity: vec![ProjectActivitySummary { + id: project_2, + duration: Duration::from_secs(50), + max_collaborators: 2 + }] }, UserActivitySummary { id: user_3, github_login: "user_3".to_string(), - project_activity: vec![(project_1, Duration::from_secs(15))] + project_activity: vec![ProjectActivitySummary { + id: project_1, + duration: Duration::from_secs(15), + max_collaborators: 2 + }] }, ] ); From 46646830698dc61e6c414b23ca4ddceb1450b072 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 08:59:35 +0200 Subject: [PATCH 55/71] Remove Redis --- .gitignore | 1 - Procfile | 1 - README.md | 6 ------ 3 files changed, 8 deletions(-) diff --git a/.gitignore b/.gitignore index 1ec8d6269c..d3d0634a40 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,3 @@ /crates/collab/static/styles.css /vendor/bin /assets/themes/*.json -dump.rdb diff --git a/Procfile b/Procfile index 780479b958..a64b411ef3 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,2 @@ 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 5da1623b04..31cfdcbd7f 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,6 @@ script/sqlx migrate run script/seed-db ``` -Install Redis: - -``` -brew install redis -``` - Run the web frontend and the collaboration server. ``` From 316a534a1675d49079e3cb04c705532dc88ef2f2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 10:16:24 +0200 Subject: [PATCH 56/71] Allow querying active user counts for people that have collaborated --- crates/collab/src/api.rs | 3 ++ crates/collab/src/db.rs | 111 +++++++++++++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 16 deletions(-) diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 5cc8b58cfb..f3209cab73 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -304,6 +304,8 @@ struct ActiveUserCountParams { #[serde(flatten)] period: TimePeriodParams, durations_in_minutes: String, + #[serde(default)] + only_collaborative: bool, } #[derive(Serialize)] @@ -329,6 +331,7 @@ async fn get_active_user_counts( .get_active_user_count( params.period.start..params.period.end, Duration::from_secs(duration * 60), + params.only_collaborative, ) .await?, }) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index b771eb3d97..f45a544728 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -75,6 +75,7 @@ pub trait Db: Send + Sync { &self, time_period: Range, min_duration: Duration, + only_collaborative: bool, ) -> Result; /// Get the users that have been most active during the given time period, @@ -605,15 +606,48 @@ impl Db for PostgresDb { &self, time_period: Range, min_duration: Duration, + only_collaborative: bool, ) -> Result { - let query = " - WITH - project_durations AS ( - SELECT user_id, project_id, SUM(duration_millis) AS project_duration - FROM project_activity_periods - WHERE $1 < ended_at AND ended_at <= $2 - GROUP BY user_id, project_id - ), + let mut with_clause = String::new(); + with_clause.push_str("WITH\n"); + with_clause.push_str( + " + project_durations AS ( + SELECT user_id, project_id, SUM(duration_millis) AS project_duration + FROM project_activity_periods + WHERE $1 < ended_at AND ended_at <= $2 + GROUP BY user_id, project_id + ), + ", + ); + with_clause.push_str( + " + project_collaborators as ( + SELECT project_id, COUNT(DISTINCT user_id) as max_collaborators + FROM project_durations + GROUP BY project_id + ), + ", + ); + + if only_collaborative { + with_clause.push_str( + " + user_durations AS ( + SELECT user_id, SUM(project_duration) as total_duration + FROM project_durations, project_collaborators + WHERE + project_durations.project_id = project_collaborators.project_id AND + max_collaborators > 1 + GROUP BY user_id + ORDER BY total_duration DESC + LIMIT $3 + ) + ", + ); + } else { + with_clause.push_str( + " user_durations AS ( SELECT user_id, SUM(project_duration) as total_duration FROM project_durations @@ -621,12 +655,20 @@ impl Db for PostgresDb { ORDER BY total_duration DESC LIMIT $3 ) + ", + ); + } + + let query = format!( + " + {with_clause} SELECT count(user_durations.user_id) FROM user_durations WHERE user_durations.total_duration >= $3 - "; + " + ); - let count: i64 = sqlx::query_scalar(query) + let count: i64 = sqlx::query_scalar(&query) .bind(time_period.start) .bind(time_period.end) .bind(min_duration.as_millis() as i64) @@ -656,11 +698,11 @@ impl Db for PostgresDb { LIMIT $3 ), project_collaborators as ( - SELECT project_id, COUNT(DISTINCT user_id) as project_collaborators + SELECT project_id, COUNT(DISTINCT user_id) as max_collaborators FROM project_durations GROUP BY project_id ) - SELECT user_durations.user_id, users.github_login, project_durations.project_id, project_duration, project_collaborators + SELECT user_durations.user_id, users.github_login, project_durations.project_id, project_duration, max_collaborators FROM user_durations, project_durations, project_collaborators, users WHERE user_durations.user_id = project_durations.user_id AND @@ -1719,29 +1761,65 @@ pub mod tests { ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(56)) + db.get_active_user_count(t0..t6, Duration::from_secs(56), false) .await .unwrap(), 0 ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(54)) + db.get_active_user_count(t0..t6, Duration::from_secs(56), true) + .await + .unwrap(), + 0 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(54), false) .await .unwrap(), 1 ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(30)) + db.get_active_user_count(t0..t6, Duration::from_secs(54), true) + .await + .unwrap(), + 1 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(30), false) .await .unwrap(), 2 ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(10)) + db.get_active_user_count(t0..t6, Duration::from_secs(30), true) + .await + .unwrap(), + 2 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(10), false) .await .unwrap(), 3 ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(10), true) + .await + .unwrap(), + 3 + ); + assert_eq!( + db.get_active_user_count(t0..t1, Duration::from_secs(5), false) + .await + .unwrap(), + 1 + ); + assert_eq!( + db.get_active_user_count(t0..t1, Duration::from_secs(5), true) + .await + .unwrap(), + 0 + ); assert_eq!( db.get_user_activity_timeline(t3..t6, user_1).await.unwrap(), @@ -2583,6 +2661,7 @@ pub mod tests { &self, _time_period: Range, _min_duration: Duration, + _only_collaborative: bool, ) -> Result { unimplemented!() } From f6c1393dfd27a1b6edcbd996764972143064dc10 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Jul 2022 18:07:55 -0700 Subject: [PATCH 57/71] Restructure FakeFs to let it model symlinks Instead of storing paths, model the FS more directly as nodes --- crates/project/src/fs.rs | 823 +++++++++++++++++++++------------ crates/project/src/worktree.rs | 11 +- 2 files changed, 528 insertions(+), 306 deletions(-) diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index 5c52801611..b6525f5497 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use fsevent::EventStream; -use futures::{Stream, StreamExt}; +use futures::{future::BoxFuture, Stream, StreamExt}; use language::LineEnding; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::{ @@ -12,11 +12,18 @@ use std::{ }; use text::Rope; +#[cfg(any(test, feature = "test-support"))] +use collections::{btree_map, BTreeMap}; +#[cfg(any(test, feature = "test-support"))] +use futures::lock::Mutex; +#[cfg(any(test, feature = "test-support"))] +use std::sync::{Arc, Weak}; + #[async_trait::async_trait] pub trait Fs: Send + Sync { async fn create_dir(&self, path: &Path) -> Result<()>; async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>; - async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>; + async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>; async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>; async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>; async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>; @@ -92,7 +99,7 @@ impl Fs for RealFs { Ok(()) } - async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> { + async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> { if !options.overwrite && smol::fs::metadata(target).await.is_ok() { if options.ignore_if_exists { return Ok(()); @@ -101,23 +108,7 @@ impl Fs for RealFs { } } - let metadata = smol::fs::metadata(source).await?; - let _ = smol::fs::remove_dir_all(target).await; - if metadata.is_dir() { - self.create_dir(target).await?; - let mut children = smol::fs::read_dir(source).await?; - while let Some(child) = children.next().await { - if let Ok(child) = child { - let child_source_path = child.path(); - let child_target_path = target.join(child.file_name()); - self.copy(&child_source_path, &child_target_path, options) - .await?; - } - } - } else { - smol::fs::copy(source, target).await?; - } - + smol::fs::copy(source, target).await?; Ok(()) } @@ -252,35 +243,115 @@ impl Fs for RealFs { } #[cfg(any(test, feature = "test-support"))] -#[derive(Clone, Debug)] -struct FakeFsEntry { - metadata: Metadata, - content: Option, +pub struct FakeFs { + // Use an unfair lock to ensure tests are deterministic. + state: Mutex, + executor: Weak, } #[cfg(any(test, feature = "test-support"))] struct FakeFsState { - entries: std::collections::BTreeMap, + root: Arc>, next_inode: u64, event_txs: Vec>>, } +#[cfg(any(test, feature = "test-support"))] +#[derive(Debug)] +enum FakeFsEntry { + File { + inode: u64, + mtime: SystemTime, + content: String, + }, + Dir { + inode: u64, + mtime: SystemTime, + entries: BTreeMap>>, + }, + Symlink { + target: PathBuf, + }, +} + #[cfg(any(test, feature = "test-support"))] impl FakeFsState { - fn validate_path(&self, path: &Path) -> Result<()> { - if path.is_absolute() - && path - .parent() - .and_then(|path| self.entries.get(path)) - .map_or(false, |e| e.metadata.is_dir) - { - Ok(()) - } else { - Err(anyhow!("invalid path {:?}", path)) - } + async fn read_path<'a>(&'a self, target: &Path) -> Result>> { + Ok(self + .try_read_path(target) + .await + .ok_or_else(|| anyhow!("path does not exist: {}", target.display()))? + .0) } - async fn emit_event(&mut self, paths: I) + async fn try_read_path<'a>( + &'a self, + target: &Path, + ) -> Option<(Arc>, PathBuf)> { + let mut path = target.to_path_buf(); + let mut real_path = PathBuf::new(); + let mut entry_stack = Vec::new(); + 'outer: loop { + let mut path_components = path.components().collect::>(); + while let Some(component) = path_components.pop_front() { + match component { + Component::Prefix(_) => panic!("prefix paths aren't supported"), + Component::RootDir => { + entry_stack.clear(); + entry_stack.push(self.root.clone()); + real_path.clear(); + real_path.push("/"); + } + Component::CurDir => {} + Component::ParentDir => { + entry_stack.pop()?; + real_path.pop(); + } + Component::Normal(name) => { + let current_entry = entry_stack.last().cloned()?; + let current_entry = current_entry.lock().await; + if let FakeFsEntry::Dir { entries, .. } = &*current_entry { + let entry = entries.get(name.to_str().unwrap()).cloned()?; + let _entry = entry.lock().await; + if let FakeFsEntry::Symlink { target, .. } = &*_entry { + let mut target = target.clone(); + target.extend(path_components); + path = target; + continue 'outer; + } else { + entry_stack.push(entry.clone()); + real_path.push(name); + } + } else { + return None; + } + } + } + } + break; + } + entry_stack.pop().map(|entry| (entry, real_path)) + } + + async fn write_path(&self, path: &Path, callback: Fn) -> Result + where + Fn: FnOnce(btree_map::Entry>>) -> Result, + { + let path = normalize_path(path); + let filename = path + .file_name() + .ok_or_else(|| anyhow!("cannot overwrite the root"))?; + let parent_path = path.parent().unwrap(); + + let parent = self.read_path(parent_path).await?; + let mut parent = parent.lock().await; + let new_entry = parent + .dir_entries(parent_path)? + .entry(filename.to_str().unwrap().into()); + callback(new_entry) + } + + fn emit_event(&mut self, paths: I) where I: IntoIterator, T: Into, @@ -301,33 +372,17 @@ impl FakeFsState { } } -#[cfg(any(test, feature = "test-support"))] -pub struct FakeFs { - // Use an unfair lock to ensure tests are deterministic. - state: futures::lock::Mutex, - executor: std::sync::Weak, -} - #[cfg(any(test, feature = "test-support"))] impl FakeFs { - pub fn new(executor: std::sync::Arc) -> std::sync::Arc { - let mut entries = std::collections::BTreeMap::new(); - entries.insert( - Path::new("/").to_path_buf(), - FakeFsEntry { - metadata: Metadata { + pub fn new(executor: Arc) -> Arc { + Arc::new(Self { + executor: Arc::downgrade(&executor), + state: Mutex::new(FakeFsState { + root: Arc::new(Mutex::new(FakeFsEntry::Dir { inode: 0, mtime: SystemTime::now(), - is_dir: true, - is_symlink: false, - }, - content: None, - }, - ); - std::sync::Arc::new(Self { - executor: std::sync::Arc::downgrade(&executor), - state: futures::lock::Mutex::new(FakeFsState { - entries, + entries: Default::default(), + })), next_inode: 1, event_txs: Default::default(), }), @@ -337,23 +392,48 @@ impl FakeFs { pub async fn insert_file(&self, path: impl AsRef, content: String) { let mut state = self.state.lock().await; let path = path.as_ref(); - state.validate_path(path).unwrap(); - let inode = state.next_inode; state.next_inode += 1; - state.entries.insert( - path.to_path_buf(), - FakeFsEntry { - metadata: Metadata { - inode, - mtime: SystemTime::now(), - is_dir: false, - is_symlink: false, - }, - content: Some(content), - }, - ); - state.emit_event(&[path]).await; + let file = Arc::new(Mutex::new(FakeFsEntry::File { + inode, + mtime: SystemTime::now(), + content, + })); + state + .write_path(path, move |entry| { + match entry { + btree_map::Entry::Vacant(e) => { + e.insert(file); + } + btree_map::Entry::Occupied(mut e) => { + *e.get_mut() = file; + } + } + Ok(()) + }) + .await + .unwrap(); + state.emit_event(&[path]); + } + + pub async fn insert_symlink(&self, path: impl AsRef, target: PathBuf) { + let mut state = self.state.lock().await; + let path = path.as_ref(); + let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target })); + state + .write_path(path.as_ref(), move |e| match e { + btree_map::Entry::Vacant(e) => { + e.insert(file); + Ok(()) + } + btree_map::Entry::Occupied(mut e) => { + *e.get_mut() = file; + Ok(()) + } + }) + .await + .unwrap(); + state.emit_event(&[path]); } #[must_use] @@ -392,13 +472,22 @@ impl FakeFs { } pub async fn files(&self) -> Vec { - self.state - .lock() - .await - .entries - .iter() - .filter_map(|(path, entry)| entry.content.as_ref().map(|_| path.clone())) - .collect() + let mut result = Vec::new(); + let mut queue = collections::VecDeque::new(); + queue.push_back((PathBuf::from("/"), self.state.lock().await.root.clone())); + while let Some((path, entry)) = queue.pop_front() { + let e = entry.lock().await; + match &*e { + FakeFsEntry::File { .. } => result.push(path), + FakeFsEntry::Dir { entries, .. } => { + for (name, entry) in entries { + queue.push_back((path.join(name), entry.clone())); + } + } + FakeFsEntry::Symlink { .. } => {} + } + } + result } async fn simulate_random_delay(&self) { @@ -410,182 +499,193 @@ impl FakeFs { } } +#[cfg(any(test, feature = "test-support"))] +impl FakeFsEntry { + fn is_file(&self) -> bool { + matches!(self, Self::File { .. }) + } + + fn file_content(&self, path: &Path) -> Result<&String> { + if let Self::File { content, .. } = self { + Ok(content) + } else { + Err(anyhow!("not a file: {}", path.display())) + } + } + + fn set_file_content(&mut self, path: &Path, new_content: String) -> Result<()> { + if let Self::File { content, mtime, .. } = self { + *mtime = SystemTime::now(); + *content = new_content; + Ok(()) + } else { + Err(anyhow!("not a file: {}", path.display())) + } + } + + fn dir_entries( + &mut self, + path: &Path, + ) -> Result<&mut BTreeMap>>> { + if let Self::Dir { entries, .. } = self { + Ok(entries) + } else { + Err(anyhow!("not a directory: {}", path.display())) + } + } +} + #[cfg(any(test, feature = "test-support"))] #[async_trait::async_trait] impl Fs for FakeFs { async fn create_dir(&self, path: &Path) -> Result<()> { self.simulate_random_delay().await; - let state = &mut *self.state.lock().await; - let path = normalize_path(path); - let mut ancestor_path = PathBuf::new(); - let mut created_dir_paths = Vec::new(); - for component in path.components() { - ancestor_path.push(component); - let entry = state - .entries - .entry(ancestor_path.clone()) - .or_insert_with(|| { - let inode = state.next_inode; - state.next_inode += 1; - created_dir_paths.push(ancestor_path.clone()); - FakeFsEntry { - metadata: Metadata { - inode, - mtime: SystemTime::now(), - is_dir: true, - is_symlink: false, - }, - content: None, - } - }); - if !entry.metadata.is_dir { - return Err(anyhow!( - "cannot create directory because {:?} is a file", - ancestor_path - )); - } - } - state.emit_event(&created_dir_paths).await; - + let mut state = self.state.lock().await; + let inode = state.next_inode; + state.next_inode += 1; + state + .write_path(path, |entry| { + entry.or_insert(Arc::new(Mutex::new(FakeFsEntry::Dir { + inode, + mtime: SystemTime::now(), + entries: Default::default(), + }))); + Ok(()) + }) + .await?; + state.emit_event(&[path]); Ok(()) } async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> { self.simulate_random_delay().await; let mut state = self.state.lock().await; + let inode = state.next_inode; + state.next_inode += 1; + let file = Arc::new(Mutex::new(FakeFsEntry::File { + inode, + mtime: SystemTime::now(), + content: String::new(), + })); + state + .write_path(path, |entry| { + match entry { + btree_map::Entry::Occupied(mut e) => { + if options.overwrite { + *e.get_mut() = file; + } else if !options.ignore_if_exists { + return Err(anyhow!("path already exists: {}", path.display())); + } + } + btree_map::Entry::Vacant(e) => { + e.insert(file); + } + } + Ok(()) + }) + .await?; + state.emit_event(&[path]); + Ok(()) + } + + async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> { + let old_path = normalize_path(old_path); + let new_path = normalize_path(new_path); + let mut state = self.state.lock().await; + let moved_entry = state + .write_path(&old_path, |e| { + if let btree_map::Entry::Occupied(e) = e { + Ok(e.remove()) + } else { + Err(anyhow!("path does not exist: {}", &old_path.display())) + } + }) + .await?; + state + .write_path(&new_path, |e| { + match e { + btree_map::Entry::Occupied(mut e) => { + if options.overwrite { + *e.get_mut() = moved_entry; + } else if !options.ignore_if_exists { + return Err(anyhow!("path already exists: {}", new_path.display())); + } + } + btree_map::Entry::Vacant(e) => { + e.insert(moved_entry); + } + } + Ok(()) + }) + .await?; + state.emit_event(&[old_path, new_path]); + Ok(()) + } + + async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> { + let source = normalize_path(source); + let target = normalize_path(target); + let mut state = self.state.lock().await; + let source_entry = state.read_path(&source).await?; + let content = source_entry.lock().await.file_content(&source)?.clone(); + let entry = state + .write_path(&target, |e| match e { + btree_map::Entry::Occupied(e) => { + if options.overwrite { + Ok(Some(e.get().clone())) + } else if !options.ignore_if_exists { + return Err(anyhow!("{target:?} already exists")); + } else { + Ok(None) + } + } + btree_map::Entry::Vacant(e) => Ok(Some( + e.insert(Arc::new(Mutex::new(FakeFsEntry::File { + inode: 0, + mtime: SystemTime::now(), + content: String::new(), + }))) + .clone(), + )), + }) + .await?; + if let Some(entry) = entry { + entry.lock().await.set_file_content(&target, content)?; + } + state.emit_event(&[target]); + Ok(()) + } + + async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> { let path = normalize_path(path); - state.validate_path(&path)?; - if let Some(entry) = state.entries.get_mut(&path) { - if entry.metadata.is_dir || entry.metadata.is_symlink { - return Err(anyhow!( - "cannot create file because {:?} is a dir or a symlink", - path - )); - } + let parent_path = path + .parent() + .ok_or_else(|| anyhow!("cannot remove the root"))?; + let base_name = path.file_name().unwrap(); - if options.overwrite { - entry.metadata.mtime = SystemTime::now(); - entry.content = Some(Default::default()); - } else if !options.ignore_if_exists { - return Err(anyhow!( - "cannot create file because {:?} already exists", - &path - )); - } - } else { - let inode = state.next_inode; - state.next_inode += 1; - let entry = FakeFsEntry { - metadata: Metadata { - inode, - mtime: SystemTime::now(), - is_dir: false, - is_symlink: false, - }, - content: Some(Default::default()), - }; - state.entries.insert(path.to_path_buf(), entry); - } - state.emit_event(&[path]).await; + let state = self.state.lock().await; + let parent_entry = state.read_path(parent_path).await?; + let mut parent_entry = parent_entry.lock().await; + let entry = parent_entry + .dir_entries(parent_path)? + .entry(base_name.to_str().unwrap().into()); - Ok(()) - } - - async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> { - let source = normalize_path(source); - let target = normalize_path(target); - - let mut state = self.state.lock().await; - state.validate_path(&source)?; - state.validate_path(&target)?; - - if !options.overwrite && state.entries.contains_key(&target) { - if options.ignore_if_exists { - return Ok(()); - } else { - return Err(anyhow!("{target:?} already exists")); - } - } - - let mut removed = Vec::new(); - state.entries.retain(|path, entry| { - if let Ok(relative_path) = path.strip_prefix(&source) { - removed.push((relative_path.to_path_buf(), entry.clone())); - false - } else { - true - } - }); - - for (relative_path, entry) in removed { - let new_path = normalize_path(&target.join(relative_path)); - state.entries.insert(new_path, entry); - } - - state.emit_event(&[source, target]).await; - Ok(()) - } - - async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> { - let source = normalize_path(source); - let target = normalize_path(target); - - let mut state = self.state.lock().await; - state.validate_path(&source)?; - state.validate_path(&target)?; - - if !options.overwrite && state.entries.contains_key(&target) { - if options.ignore_if_exists { - return Ok(()); - } else { - return Err(anyhow!("{target:?} already exists")); - } - } - - let mut new_entries = Vec::new(); - for (path, entry) in &state.entries { - if let Ok(relative_path) = path.strip_prefix(&source) { - new_entries.push((relative_path.to_path_buf(), entry.clone())); - } - } - - let mut events = Vec::new(); - for (relative_path, entry) in new_entries { - let new_path = normalize_path(&target.join(relative_path)); - events.push(new_path.clone()); - state.entries.insert(new_path, entry); - } - - state.emit_event(&events).await; - Ok(()) - } - - async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> { - let dir_path = normalize_path(dir_path); - let mut state = self.state.lock().await; - state.validate_path(&dir_path)?; - if let Some(entry) = state.entries.get(&dir_path) { - if !entry.metadata.is_dir { - return Err(anyhow!( - "cannot remove {dir_path:?} because it is not a dir" - )); - } - - if !options.recursive { - let descendants = state - .entries - .keys() - .filter(|path| path.starts_with(path)) - .count(); - if descendants > 1 { - return Err(anyhow!("{dir_path:?} is not empty")); + match entry { + btree_map::Entry::Vacant(_) => { + if !options.ignore_if_not_exists { + return Err(anyhow!("{path:?} does not exist")); } } - - state.entries.retain(|path, _| !path.starts_with(&dir_path)); - state.emit_event(&[dir_path]).await; - } else if !options.ignore_if_not_exists { - return Err(anyhow!("{dir_path:?} does not exist")); + btree_map::Entry::Occupied(e) => { + { + let mut entry = e.get().lock().await; + let children = entry.dir_entries(&path)?; + if !options.recursive && !children.is_empty() { + return Err(anyhow!("{path:?} is not empty")); + } + } + e.remove(); + } } Ok(()) @@ -593,18 +693,28 @@ impl Fs for FakeFs { async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> { let path = normalize_path(path); + let parent_path = path + .parent() + .ok_or_else(|| anyhow!("cannot remove the root"))?; + let base_name = path.file_name().unwrap(); let mut state = self.state.lock().await; - state.validate_path(&path)?; - if let Some(entry) = state.entries.get(&path) { - if entry.metadata.is_dir { - return Err(anyhow!("cannot remove {path:?} because it is not a file")); + let parent_entry = state.read_path(parent_path).await?; + let mut parent_entry = parent_entry.lock().await; + let entry = parent_entry + .dir_entries(parent_path)? + .entry(base_name.to_str().unwrap().into()); + match entry { + btree_map::Entry::Vacant(_) => { + if !options.ignore_if_not_exists { + return Err(anyhow!("{path:?} does not exist")); + } + } + btree_map::Entry::Occupied(e) => { + e.get().lock().await.file_content(&path)?; + e.remove(); } - - state.entries.remove(&path); - state.emit_event(&[path]).await; - } else if !options.ignore_if_not_exists { - return Err(anyhow!("{path:?} does not exist")); } + state.emit_event(&[path]); Ok(()) } @@ -617,86 +727,84 @@ impl Fs for FakeFs { let path = normalize_path(path); self.simulate_random_delay().await; let state = self.state.lock().await; - let text = state - .entries - .get(&path) - .and_then(|e| e.content.as_ref()) - .ok_or_else(|| anyhow!("file {:?} does not exist", path))?; - Ok(text.clone()) + let entry = state.read_path(&path).await?; + let entry = entry.lock().await; + entry.file_content(&path).cloned() } async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> { self.simulate_random_delay().await; - let mut state = self.state.lock().await; let path = normalize_path(path); - state.validate_path(&path)?; let content = chunks(text, line_ending).collect(); - if let Some(entry) = state.entries.get_mut(&path) { - if entry.metadata.is_dir { - Err(anyhow!("cannot overwrite a directory with a file")) - } else { - entry.content = Some(content); - entry.metadata.mtime = SystemTime::now(); - state.emit_event(&[path]).await; - Ok(()) - } - } else { - let inode = state.next_inode; - state.next_inode += 1; - let entry = FakeFsEntry { - metadata: Metadata { - inode, - mtime: SystemTime::now(), - is_dir: false, - is_symlink: false, - }, - content: Some(content), - }; - state.entries.insert(path.to_path_buf(), entry); - state.emit_event(&[path]).await; - Ok(()) - } + self.insert_file(path, content).await; + Ok(()) } async fn canonicalize(&self, path: &Path) -> Result { + let path = normalize_path(path); self.simulate_random_delay().await; - Ok(normalize_path(path)) + let state = self.state.lock().await; + if let Some((_, real_path)) = state.try_read_path(&path).await { + Ok(real_path) + } else { + Err(anyhow!("path does not exist: {}", path.display())) + } } async fn is_file(&self, path: &Path) -> bool { let path = normalize_path(path); self.simulate_random_delay().await; let state = self.state.lock().await; - state - .entries - .get(&path) - .map_or(false, |entry| !entry.metadata.is_dir) + if let Some((entry, _)) = state.try_read_path(&path).await { + entry.lock().await.is_file() + } else { + false + } } async fn metadata(&self, path: &Path) -> Result> { self.simulate_random_delay().await; - let state = self.state.lock().await; let path = normalize_path(path); - Ok(state.entries.get(&path).map(|entry| entry.metadata.clone())) + let state = self.state.lock().await; + if let Some((entry, real_path)) = state.try_read_path(&path).await { + let entry = entry.lock().await; + let is_symlink = real_path != path; + + Ok(Some(match &*entry { + FakeFsEntry::File { inode, mtime, .. } => Metadata { + inode: *inode, + mtime: *mtime, + is_dir: false, + is_symlink, + }, + FakeFsEntry::Dir { inode, mtime, .. } => Metadata { + inode: *inode, + mtime: *mtime, + is_dir: true, + is_symlink, + }, + FakeFsEntry::Symlink { .. } => unreachable!(), + })) + } else { + Ok(None) + } } async fn read_dir( &self, - abs_path: &Path, + path: &Path, ) -> Result>>>> { - use futures::{future, stream}; self.simulate_random_delay().await; + let path = normalize_path(path); let state = self.state.lock().await; - let abs_path = normalize_path(abs_path); - Ok(Box::pin(stream::iter(state.entries.clone()).filter_map( - move |(child_path, _)| { - future::ready(if child_path.parent() == Some(&abs_path) { - Some(Ok(child_path)) - } else { - None - }) - }, - ))) + let entry = state.read_path(&path).await?; + let mut entry = entry.lock().await; + let children = entry.dir_entries(&path)?; + let paths = children + .keys() + .map(|file_name| Ok(path.join(file_name))) + .collect::>(); + Ok(Box::pin(futures::stream::iter(paths))) } async fn watch( @@ -773,3 +881,112 @@ pub fn normalize_path(path: &Path) -> PathBuf { } ret } + +pub fn copy_recursive<'a>( + fs: &'a dyn Fs, + source: &'a Path, + target: &'a Path, + options: CopyOptions, +) -> BoxFuture<'a, Result<()>> { + use futures::future::FutureExt; + + async move { + let metadata = fs + .metadata(source) + .await? + .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?; + if metadata.is_dir { + if !options.overwrite && fs.metadata(target).await.is_ok() { + if options.ignore_if_exists { + return Ok(()); + } else { + return Err(anyhow!("{target:?} already exists")); + } + } + + let _ = fs + .remove_dir( + target, + RemoveOptions { + recursive: true, + ignore_if_not_exists: true, + }, + ) + .await; + fs.create_dir(target).await?; + let mut children = fs.read_dir(source).await?; + while let Some(child_path) = children.next().await { + if let Ok(child_path) = child_path { + if let Some(file_name) = child_path.file_name() { + let child_target_path = target.join(file_name); + copy_recursive(fs, &child_path, &child_target_path, options).await?; + } + } + } + + Ok(()) + } else { + fs.copy_file(source, target, options).await + } + } + .boxed() +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::TestAppContext; + use serde_json::json; + + #[gpui::test] + async fn test_fake_fs(cx: &mut TestAppContext) { + let fs = FakeFs::new(cx.background()); + + fs.insert_tree( + "/root", + json!({ + "dir1": { + "a": "A", + "b": "B" + }, + "dir2": { + "c": "C", + "dir3": { + "d": "D" + } + } + }), + ) + .await; + + assert_eq!( + fs.files().await, + vec![ + PathBuf::from("/root/dir1/a"), + PathBuf::from("/root/dir1/b"), + PathBuf::from("/root/dir2/c"), + PathBuf::from("/root/dir2/dir3/d"), + ] + ); + + fs.insert_symlink("/root/dir2/link-to-dir3", "./dir3".into()) + .await; + + assert_eq!( + fs.canonicalize("/root/dir2/link-to-dir3".as_ref()) + .await + .unwrap(), + PathBuf::from("/root/dir2/dir3"), + ); + assert_eq!( + fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref()) + .await + .unwrap(), + PathBuf::from("/root/dir2/dir3/d"), + ); + assert_eq!( + fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(), + "D", + ); + } +} diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cc972b9bcd..7823a99e82 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1,4 +1,4 @@ -use crate::{ProjectEntryId, RemoveOptions}; +use crate::{copy_recursive, ProjectEntryId, RemoveOptions}; use super::{ fs::{self, Fs}, @@ -731,8 +731,13 @@ impl LocalWorktree { let fs = self.fs.clone(); let abs_new_path = abs_new_path.clone(); async move { - fs.copy(&abs_old_path, &abs_new_path, Default::default()) - .await + copy_recursive( + fs.as_ref(), + &abs_old_path, + &abs_new_path, + Default::default(), + ) + .await } }); From f9a5ed3a85730fa8837ebb4ad4800632e0abb3e8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Jul 2022 18:08:20 -0700 Subject: [PATCH 58/71] Start work on a test for a worktree with symlink cycles --- crates/project/src/worktree.rs | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 7823a99e82..0d2df99595 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2812,6 +2812,61 @@ mod tests { }) } + #[gpui::test] + async fn test_circular_symlinks(cx: &mut TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + "lib": { + "a": { + "a.txt": "" + }, + "b": { + "b.txt": "" + } + } + }), + ) + .await; + fs.insert_symlink("/root/lib/a/lib", "..".into()).await; + fs.insert_symlink("/root/lib/b/lib", "..".into()).await; + + let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client); + let tree = Worktree::local( + client, + Arc::from(Path::new("/root")), + true, + fs, + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + tree.read_with(cx, |tree, _| { + assert_eq!( + tree.entries(false) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![ + Path::new(""), + Path::new("lib"), + Path::new("lib/a"), + Path::new("lib/a/a.txt"), + Path::new("lib/a/lib"), + Path::new("lib/b"), + Path::new("lib/b/b.txt"), + Path::new("lib/b/lib"), + ] + ); + }) + } + #[gpui::test] async fn test_rescan_with_gitignore(cx: &mut TestAppContext) { let parent_dir = temp_tree(json!({ From df33556693a78cc0977de80c12eddefc037ad964 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 13:40:30 +0200 Subject: [PATCH 59/71] Introduce a new `TreeSet` struct in `sum_tree` This is just a special case of `TreeMap` where `V = ()`. --- crates/sum_tree/src/sum_tree.rs | 2 +- crates/sum_tree/src/tree_map.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 193786112b..efd5c43480 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -5,7 +5,7 @@ use arrayvec::ArrayVec; pub use cursor::{Cursor, FilterCursor, Iter}; use std::marker::PhantomData; use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc}; -pub use tree_map::TreeMap; +pub use tree_map::{TreeMap, TreeSet}; #[cfg(test)] const TREE_BASE: usize = 2; diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 80143aad69..5218d2b4db 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -20,6 +20,11 @@ pub struct MapKey(K); #[derive(Clone, Debug, Default)] pub struct MapKeyRef<'a, K>(Option<&'a K>); +#[derive(Clone)] +pub struct TreeSet(TreeMap) +where + K: Clone + Debug + Default + Ord; + impl TreeMap { pub fn from_ordered_entries(entries: impl IntoIterator) -> Self { let tree = SumTree::from_iter( @@ -136,6 +141,32 @@ where } } +impl Default for TreeSet +where + K: Clone + Debug + Default + Ord, +{ + fn default() -> Self { + Self(Default::default()) + } +} + +impl TreeSet +where + K: Clone + Debug + Default + Ord, +{ + pub fn insert(&mut self, key: K) { + self.0.insert(key, ()); + } + + pub fn contains(&self, key: &K) -> bool { + self.0.get(key).is_some() + } + + pub fn iter<'a>(&'a self) -> impl 'a + Iterator { + self.0.iter().map(|(k, _)| k) + } +} + #[cfg(test)] mod tests { use super::*; From b48118830ffd328a2f27c557ab82c4932f3e0ec3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 13:41:09 +0200 Subject: [PATCH 60/71] Detect cycles when scanning a directory --- crates/project/src/worktree.rs | 61 ++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 0d2df99595..53c73e8f5f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -47,7 +47,7 @@ use std::{ task::Poll, time::{Duration, SystemTime}, }; -use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap}; +use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use util::{ResultExt, TryFutureExt}; lazy_static! { @@ -1491,6 +1491,16 @@ impl LocalSnapshot { } } + fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet { + let mut inodes = TreeSet::default(); + for ancestor in path.ancestors().skip(1) { + if let Some(entry) = self.entry_for_path(ancestor) { + inodes.insert(entry.inode); + } + } + inodes + } + fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc { let mut new_ignores = Vec::new(); for ancestor in abs_path.ancestors().skip(1) { @@ -2053,14 +2063,16 @@ impl BackgroundScanner { async fn scan_dirs(&mut self) -> Result<()> { let root_char_bag; let root_abs_path; - let next_entry_id; + let root_inode; let is_dir; + let next_entry_id; { let snapshot = self.snapshot.lock(); root_char_bag = snapshot.root_char_bag; root_abs_path = snapshot.abs_path.clone(); + root_inode = snapshot.root_entry().map(|e| e.inode); + is_dir = snapshot.root_entry().map_or(false, |e| e.is_dir()); next_entry_id = snapshot.next_entry_id.clone(); - is_dir = snapshot.root_entry().map_or(false, |e| e.is_dir()) }; // Populate ignores above the root. @@ -2088,12 +2100,18 @@ impl BackgroundScanner { if is_dir { let path: Arc = Arc::from(Path::new("")); + let mut ancestor_inodes = TreeSet::default(); + if let Some(root_inode) = root_inode { + ancestor_inodes.insert(root_inode); + } + let (tx, rx) = channel::unbounded(); self.executor .block(tx.send(ScanJob { abs_path: root_abs_path.to_path_buf(), path, ignore_stack, + ancestor_inodes, scan_queue: tx.clone(), })) .unwrap(); @@ -2195,24 +2213,30 @@ impl BackgroundScanner { root_char_bag, ); - if child_metadata.is_dir { + if child_entry.is_dir() { let is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true); child_entry.is_ignored = is_ignored; - new_entries.push(child_entry); - new_jobs.push(ScanJob { - abs_path: child_abs_path, - path: child_path, - ignore_stack: if is_ignored { - IgnoreStack::all() - } else { - ignore_stack.clone() - }, - scan_queue: job.scan_queue.clone(), - }); + + if !job.ancestor_inodes.contains(&child_entry.inode) { + let mut ancestor_inodes = job.ancestor_inodes.clone(); + ancestor_inodes.insert(child_entry.inode); + new_jobs.push(ScanJob { + abs_path: child_abs_path, + path: child_path, + ignore_stack: if is_ignored { + IgnoreStack::all() + } else { + ignore_stack.clone() + }, + ancestor_inodes, + scan_queue: job.scan_queue.clone(), + }); + } } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); - new_entries.push(child_entry); - }; + } + + new_entries.push(child_entry); } self.snapshot @@ -2292,11 +2316,13 @@ impl BackgroundScanner { fs_entry.is_ignored = ignore_stack.is_all(); snapshot.insert_entry(fs_entry, self.fs.as_ref()); if metadata.is_dir { + let ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); self.executor .block(scan_queue_tx.send(ScanJob { abs_path, path, ignore_stack, + ancestor_inodes, scan_queue: scan_queue_tx.clone(), })) .unwrap(); @@ -2458,6 +2484,7 @@ struct ScanJob { path: Arc, ignore_stack: Arc, scan_queue: Sender, + ancestor_inodes: TreeSet, } struct UpdateIgnoreStatusJob { From b3218641cdd3e879d8df5010f4f33a5fa4ca1a44 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 16:38:26 +0200 Subject: [PATCH 61/71] Create intermediate directories in `FakeFs::create_dir` --- crates/project/src/fs.rs | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index b6525f5497..e6d49863c1 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -541,19 +541,31 @@ impl Fs for FakeFs { async fn create_dir(&self, path: &Path) -> Result<()> { self.simulate_random_delay().await; let mut state = self.state.lock().await; - let inode = state.next_inode; - state.next_inode += 1; - state - .write_path(path, |entry| { - entry.or_insert(Arc::new(Mutex::new(FakeFsEntry::Dir { - inode, - mtime: SystemTime::now(), - entries: Default::default(), - }))); - Ok(()) - }) - .await?; - state.emit_event(&[path]); + + let mut created_dirs = Vec::new(); + let mut cur_path = PathBuf::new(); + for component in path.components() { + cur_path.push(component); + if cur_path == Path::new("/") { + continue; + } + + let inode = state.next_inode; + state.next_inode += 1; + state + .write_path(&cur_path, |entry| { + entry.or_insert(Arc::new(Mutex::new(FakeFsEntry::Dir { + inode, + mtime: SystemTime::now(), + entries: Default::default(), + }))); + created_dirs.push(cur_path.clone()); + Ok(()) + }) + .await?; + } + + state.emit_event(&created_dirs); Ok(()) } From f9df5fe5955d8645f57ce663d8f0d70ddc363cbd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 17:22:18 +0200 Subject: [PATCH 62/71] Detect cycles also when processing events --- crates/project/src/worktree.rs | 42 ++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 53c73e8f5f..203b512d9b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2315,8 +2315,10 @@ impl BackgroundScanner { ); fs_entry.is_ignored = ignore_stack.is_all(); snapshot.insert_entry(fs_entry, self.fs.as_ref()); - if metadata.is_dir { - let ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); + + let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); + if metadata.is_dir && !ancestor_inodes.contains(&metadata.inode) { + ancestor_inodes.insert(metadata.inode); self.executor .block(scan_queue_tx.send(ScanJob { abs_path, @@ -2771,7 +2773,7 @@ mod tests { use anyhow::Result; use client::test::FakeHttpClient; use fs::RealFs; - use gpui::TestAppContext; + use gpui::{executor::Deterministic, TestAppContext}; use rand::prelude::*; use serde_json::json; use std::{ @@ -2839,8 +2841,8 @@ mod tests { }) } - #[gpui::test] - async fn test_circular_symlinks(cx: &mut TestAppContext) { + #[gpui::test(iterations = 10)] + async fn test_circular_symlinks(executor: Arc, cx: &mut TestAppContext) { let fs = FakeFs::new(cx.background()); fs.insert_tree( "/root", @@ -2865,7 +2867,7 @@ mod tests { client, Arc::from(Path::new("/root")), true, - fs, + fs.clone(), Default::default(), &mut cx.to_async(), ) @@ -2891,7 +2893,33 @@ mod tests { Path::new("lib/b/lib"), ] ); - }) + }); + + fs.rename( + Path::new("/root/lib/a/lib"), + Path::new("/root/lib/a/lib-2"), + Default::default(), + ) + .await + .unwrap(); + executor.run_until_parked(); + tree.read_with(cx, |tree, _| { + assert_eq!( + tree.entries(false) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![ + Path::new(""), + Path::new("lib"), + Path::new("lib/a"), + Path::new("lib/a/a.txt"), + Path::new("lib/a/lib-2"), + Path::new("lib/b"), + Path::new("lib/b/b.txt"), + Path::new("lib/b/lib"), + ] + ); + }); } #[gpui::test] From 11ef6bfbae6ecf951546affb78ba383456798563 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 18 Jul 2022 17:36:40 +0200 Subject: [PATCH 63/71] Avoid unnecessarily emitting fake fs events for dirs that exist --- crates/project/src/fs.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index e6d49863c1..397d5ea63a 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -554,12 +554,14 @@ impl Fs for FakeFs { state.next_inode += 1; state .write_path(&cur_path, |entry| { - entry.or_insert(Arc::new(Mutex::new(FakeFsEntry::Dir { - inode, - mtime: SystemTime::now(), - entries: Default::default(), - }))); - created_dirs.push(cur_path.clone()); + entry.or_insert_with(|| { + created_dirs.push(cur_path.clone()); + Arc::new(Mutex::new(FakeFsEntry::Dir { + inode, + mtime: SystemTime::now(), + entries: Default::default(), + })) + }); Ok(()) }) .await?; From ec25fa9260076ea57bff60bbe661cd8dfc405152 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 18 Jul 2022 09:54:48 -0700 Subject: [PATCH 64/71] Extract handler set from MouseRegion --- crates/gpui/src/presenter.rs | 1 - crates/gpui/src/scene.rs | 257 +------------------- crates/gpui/src/scene/mouse_region.rs | 327 ++++++++++++++++++++++++++ 3 files changed, 332 insertions(+), 253 deletions(-) create mode 100644 crates/gpui/src/scene/mouse_region.rs diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 243863149d..a2b3230190 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -252,7 +252,6 @@ impl Presenter { } else if let Some(handler) = region .handlers .get(&(MouseRegionEvent::down_out_disc(), Some(*button))) - .cloned() { mouse_down_out_handlers.push(( handler, diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index f5577c5e5d..a1f12b76d3 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,7 +1,8 @@ -use collections::HashMap; +mod mouse_region; + use serde::Deserialize; use serde_json::json; -use std::{any::TypeId, borrow::Cow, mem::Discriminant, rc::Rc, sync::Arc}; +use std::{borrow::Cow, sync::Arc}; use crate::{ color::Color, @@ -9,8 +10,9 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, platform::CursorStyle, - EventContext, ImageData, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent, + ImageData, }; +pub use mouse_region::*; pub struct Scene { scale_factor: f32, @@ -45,255 +47,6 @@ pub struct CursorRegion { pub style: CursorStyle, } -#[derive(Debug)] -pub enum MouseRegionEvent { - Move(MouseMovedEvent), - Drag(Vector2F, MouseMovedEvent), - Hover(bool, MouseMovedEvent), - Down(MouseButtonEvent), - Up(MouseButtonEvent), - Click(MouseButtonEvent), - DownOut(MouseButtonEvent), - ScrollWheel(ScrollWheelEvent), -} - -impl MouseRegionEvent { - pub fn move_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Move(Default::default())) - } - pub fn drag_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Drag( - Default::default(), - Default::default(), - )) - } - pub fn hover_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Hover( - Default::default(), - Default::default(), - )) - } - pub fn down_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Down(Default::default())) - } - pub fn up_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Up(Default::default())) - } - pub fn click_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Click(Default::default())) - } - pub fn down_out_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::DownOut(Default::default())) - } - pub fn scroll_wheel_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) - } - - pub fn handler_key(&self) -> (Discriminant, Option) { - match self { - MouseRegionEvent::Move(_) => (Self::move_disc(), None), - MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => { - (Self::drag_disc(), *pressed_button) - } - MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None), - MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => { - (Self::down_disc(), Some(*button)) - } - MouseRegionEvent::Up(MouseButtonEvent { button, .. }) => { - (Self::up_disc(), Some(*button)) - } - MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => { - (Self::click_disc(), Some(*button)) - } - MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => { - (Self::down_out_disc(), Some(*button)) - } - MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), - } - } -} - -#[derive(Clone, Default)] -pub struct MouseRegion { - pub view_id: usize, - pub discriminant: Option<(TypeId, usize)>, - pub bounds: RectF, - pub handlers: HashMap< - (Discriminant, Option), - Rc, - >, -} - -impl MouseRegion { - pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self { - Self { - view_id, - discriminant, - bounds, - handlers: Default::default(), - } - } - - pub fn handle_all( - view_id: usize, - discriminant: Option<(TypeId, usize)>, - bounds: RectF, - ) -> Self { - let mut handlers: HashMap< - (Discriminant, Option), - Rc, - > = Default::default(); - handlers.insert((MouseRegionEvent::move_disc(), None), Rc::new(|_, _| {})); - handlers.insert((MouseRegionEvent::hover_disc(), None), Rc::new(|_, _| {})); - for button in MouseButton::all() { - handlers.insert( - (MouseRegionEvent::drag_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - handlers.insert( - (MouseRegionEvent::down_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - handlers.insert( - (MouseRegionEvent::up_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - handlers.insert( - (MouseRegionEvent::click_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - handlers.insert( - (MouseRegionEvent::down_out_disc(), Some(button)), - Rc::new(|_, _| {}), - ); - } - handlers.insert( - (MouseRegionEvent::scroll_wheel_disc(), None), - Rc::new(|_, _| {}), - ); - - Self { - view_id, - discriminant, - bounds, - handlers, - } - } - - pub fn on_down( - mut self, - button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers.insert((MouseRegionEvent::down_disc(), Some(button)), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Down(mouse_button_event) = region_event { - handler(mouse_button_event, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}", - region_event); - } - })); - self - } - - pub fn on_up( - mut self, - button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers.insert((MouseRegionEvent::up_disc(), Some(button)), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Up(mouse_button_event) = region_event { - handler(mouse_button_event, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}", - region_event); - } - })); - self - } - - pub fn on_click( - mut self, - button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers.insert((MouseRegionEvent::click_disc(), Some(button)), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Click(mouse_button_event) = region_event { - handler(mouse_button_event, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}", - region_event); - } - })); - self - } - - pub fn on_down_out( - mut self, - button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers.insert((MouseRegionEvent::down_out_disc(), Some(button)), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::DownOut(mouse_button_event) = region_event { - handler(mouse_button_event, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}", - region_event); - } - })); - self - } - - pub fn on_drag( - mut self, - button: MouseButton, - handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers.insert((MouseRegionEvent::drag_disc(), Some(button)), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event { - handler(prev_drag_position, mouse_moved_event, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}", - region_event); - } - })); - self - } - - pub fn on_hover( - mut self, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers.insert((MouseRegionEvent::hover_disc(), None), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Hover(hover, mouse_moved_event) = region_event { - handler(hover, mouse_moved_event, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}", - region_event); - } - })); - self - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct MouseRegionId { - pub view_id: usize, - pub discriminant: (TypeId, usize), -} - #[derive(Default, Debug)] pub struct Quad { pub bounds: RectF, diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs new file mode 100644 index 0000000000..3d680876c7 --- /dev/null +++ b/crates/gpui/src/scene/mouse_region.rs @@ -0,0 +1,327 @@ +use std::{any::TypeId, mem::Discriminant, rc::Rc}; + +use collections::HashMap; +use pathfinder_geometry::{rect::RectF, vector::Vector2F}; + +use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; + +#[derive(Clone, Default)] +pub struct MouseRegion { + pub view_id: usize, + pub discriminant: Option<(TypeId, usize)>, + pub bounds: RectF, + pub handlers: HandlerSet, +} + +impl MouseRegion { + pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self { + Self { + view_id, + discriminant, + bounds, + handlers: Default::default(), + } + } + + pub fn handle_all( + view_id: usize, + discriminant: Option<(TypeId, usize)>, + bounds: RectF, + ) -> Self { + Self { + view_id, + discriminant, + bounds, + handlers: HandlerSet::handle_all(), + } + } + + pub fn on_down( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_down(button, handler); + self + } + + pub fn on_up( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_up(button, handler); + self + } + + pub fn on_click( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_click(button, handler); + self + } + + pub fn on_down_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_down_out(button, handler); + self + } + + pub fn on_drag( + mut self, + button: MouseButton, + handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_drag(button, handler); + self + } + + pub fn on_hover( + mut self, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_hover(handler); + self + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct MouseRegionId { + pub view_id: usize, + pub discriminant: (TypeId, usize), +} + +#[derive(Clone, Default)] +pub struct HandlerSet { + pub set: HashMap< + (Discriminant, Option), + Rc, + >, +} + +impl HandlerSet { + pub fn handle_all() -> Self { + let mut set: HashMap< + (Discriminant, Option), + Rc, + > = Default::default(); + + set.insert((MouseRegionEvent::move_disc(), None), Rc::new(|_, _| {})); + set.insert((MouseRegionEvent::hover_disc(), None), Rc::new(|_, _| {})); + for button in MouseButton::all() { + set.insert( + (MouseRegionEvent::drag_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + set.insert( + (MouseRegionEvent::down_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + set.insert( + (MouseRegionEvent::up_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + set.insert( + (MouseRegionEvent::click_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + set.insert( + (MouseRegionEvent::down_out_disc(), Some(button)), + Rc::new(|_, _| {}), + ); + } + set.insert( + (MouseRegionEvent::scroll_wheel_disc(), None), + Rc::new(|_, _| {}), + ); + + HandlerSet { set } + } + + pub fn get( + &self, + key: &(Discriminant, Option), + ) -> Option> { + self.set.get(key).cloned() + } + + pub fn on_down( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::down_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Down(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}", + region_event); + } + })); + self + } + + pub fn on_up( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::up_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Up(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}", + region_event); + } + })); + self + } + + pub fn on_click( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::click_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Click(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}", + region_event); + } + })); + self + } + + pub fn on_down_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::DownOut(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}", + region_event); + } + })); + self + } + + pub fn on_drag( + mut self, + button: MouseButton, + handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::drag_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event { + handler(prev_drag_position, mouse_moved_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}", + region_event); + } + })); + self + } + + pub fn on_hover( + mut self, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::hover_disc(), None), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Hover(hover, mouse_moved_event) = region_event { + handler(hover, mouse_moved_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}", + region_event); + } + })); + self + } +} + +#[derive(Debug)] +pub enum MouseRegionEvent { + Move(MouseMovedEvent), + Drag(Vector2F, MouseMovedEvent), + Hover(bool, MouseMovedEvent), + Down(MouseButtonEvent), + Up(MouseButtonEvent), + Click(MouseButtonEvent), + DownOut(MouseButtonEvent), + ScrollWheel(ScrollWheelEvent), +} + +impl MouseRegionEvent { + pub fn move_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Move(Default::default())) + } + pub fn drag_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Drag( + Default::default(), + Default::default(), + )) + } + pub fn hover_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Hover( + Default::default(), + Default::default(), + )) + } + pub fn down_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Down(Default::default())) + } + pub fn up_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Up(Default::default())) + } + pub fn click_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::Click(Default::default())) + } + pub fn down_out_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::DownOut(Default::default())) + } + pub fn scroll_wheel_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) + } + + pub fn handler_key(&self) -> (Discriminant, Option) { + match self { + MouseRegionEvent::Move(_) => (Self::move_disc(), None), + MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => { + (Self::drag_disc(), *pressed_button) + } + MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None), + MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => { + (Self::down_disc(), Some(*button)) + } + MouseRegionEvent::Up(MouseButtonEvent { button, .. }) => { + (Self::up_disc(), Some(*button)) + } + MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => { + (Self::click_disc(), Some(*button)) + } + MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => { + (Self::down_out_disc(), Some(*button)) + } + MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), + } + } +} From deeefed7ebaf196e03cb2b7141a70acbdb1b1091 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 18 Jul 2022 10:08:14 -0700 Subject: [PATCH 65/71] Change mouse_event_handler to use HandlerSet --- .../gpui/src/elements/mouse_event_handler.rs | 27 +++++++++++-------- crates/gpui/src/scene/mouse_region.rs | 11 +++++++- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 3a8a5f6d63..40c35b80bc 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -7,7 +7,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, platform::CursorStyle, - scene::CursorRegion, + scene::{CursorRegion, HandlerSet}, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, @@ -16,8 +16,9 @@ use serde_json::json; pub struct MouseEventHandler { child: ElementBox, + discriminant: (TypeId, usize), cursor_style: Option, - region: MouseRegion, + handlers: HandlerSet, padding: Padding, } @@ -31,7 +32,8 @@ impl MouseEventHandler { Self { child: render_child(cx.mouse_state::(id), cx), cursor_style: None, - region: MouseRegion::new(0, Some((TypeId::of::(), id)), Default::default()), + discriminant: (TypeId::of::(), id), + handlers: Default::default(), padding: Default::default(), } } @@ -46,7 +48,7 @@ impl MouseEventHandler { button: MouseButton, handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, ) -> Self { - self.region = self.region.on_down(button, handler); + self.handlers = self.handlers.on_down(button, handler); self } @@ -55,7 +57,7 @@ impl MouseEventHandler { button: MouseButton, handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, ) -> Self { - self.region = self.region.on_click(button, handler); + self.handlers = self.handlers.on_click(button, handler); self } @@ -64,7 +66,7 @@ impl MouseEventHandler { button: MouseButton, handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, ) -> Self { - self.region = self.region.on_down_out(button, handler); + self.handlers = self.handlers.on_down_out(button, handler); self } @@ -73,7 +75,7 @@ impl MouseEventHandler { button: MouseButton, handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, ) -> Self { - self.region = self.region.on_drag(button, handler); + self.handlers = self.handlers.on_drag(button, handler); self } @@ -81,7 +83,7 @@ impl MouseEventHandler { mut self, handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, ) -> Self { - self.region = self.region.on_hover(handler); + self.handlers = self.handlers.on_hover(handler); self } @@ -126,9 +128,12 @@ impl Element for MouseEventHandler { }); } - self.region.view_id = cx.current_view_id(); - self.region.bounds = hit_bounds; - cx.scene.push_mouse_region(self.region.clone()); + cx.scene.push_mouse_region(MouseRegion::from_handlers( + cx.current_view_id(), + Some(self.discriminant.clone()), + hit_bounds, + self.handlers.clone(), + )); self.child.paint(bounds.origin(), visible_bounds, cx); } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 3d680876c7..d3d788c466 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -15,11 +15,20 @@ pub struct MouseRegion { impl MouseRegion { pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self { + Self::from_handlers(view_id, discriminant, bounds, Default::default()) + } + + pub fn from_handlers( + view_id: usize, + discriminant: Option<(TypeId, usize)>, + bounds: RectF, + handlers: HandlerSet, + ) -> Self { Self { view_id, discriminant, bounds, - handlers: Default::default(), + handlers, } } From 7998771d9f566c31303ce643a9254b393de05ed3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 18 Jul 2022 14:13:12 -0700 Subject: [PATCH 66/71] Avoid panic when unable to access a GPU This will remove noise from our panic logs. Co-authored-by: Nathan Sobo --- crates/gpui/src/platform/mac/window.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 43ce09ffff..1b3cde9538 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -11,8 +11,8 @@ use crate::{ use block::ConcreteBlock; use cocoa::{ appkit::{ - CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, - NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask, + CGPoint, NSApplication, NSBackingStoreBuffered, NSModalResponse, NSScreen, NSView, + NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask, }, base::{id, nil}, foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString}, @@ -228,8 +228,16 @@ impl Window { native_window.setFrame_display_(screen.visibleFrame(), YES); } - let device = - metal::Device::system_default().expect("could not find default metal device"); + let device = if let Some(device) = metal::Device::system_default() { + device + } else { + let alert: id = msg_send![class!(NSAlert), alloc]; + let _: () = msg_send![alert, init]; + let _: () = msg_send![alert, setAlertStyle: 2]; + let _: () = msg_send![alert, setMessageText: ns_string("Unable to access a compatible graphics device")]; + let _: NSModalResponse = msg_send![alert, runModal]; + std::process::exit(1); + }; let layer: id = msg_send![class!(CAMetalLayer), layer]; let _: () = msg_send![layer, setDevice: device.as_ptr()]; From 43613fe2acd52099dc433de153c3cc68d2500a96 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 18 Jul 2022 14:55:24 -0700 Subject: [PATCH 67/71] Ensure that fs watches are dropped when dropping the event stream --- crates/project/src/fs.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index 397d5ea63a..460b788afd 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -226,11 +226,13 @@ impl Fs for RealFs { ) -> Pin>>> { let (tx, rx) = smol::channel::unbounded(); let (stream, handle) = EventStream::new(&[path], latency); - std::mem::forget(handle); std::thread::spawn(move || { stream.run(move |events| smol::block_on(tx.send(events)).is_ok()); }); - Box::pin(rx) + Box::pin(rx.chain(futures::stream::once(async move { + drop(handle); + vec![] + }))) } fn is_fake(&self) -> bool { From c1dfb6294b152f9ebfa958dd0cac945c41111ee2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 18 Jul 2022 16:10:09 -0700 Subject: [PATCH 68/71] Dismiss project panel's rename editor when it loses focus --- crates/project_panel/src/project_panel.rs | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index eaff23a476..a2034dc679 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -184,6 +184,20 @@ impl ProjectPanel { ) }); + cx.observe_focus(&filename_editor, |this, _, is_focused, cx| { + if !is_focused { + if this + .edit_state + .as_ref() + .map_or(false, |state| state.processing_filename.is_none()) + { + this.edit_state = None; + this.update_visible_entries(None, cx); + } + } + }) + .detach(); + let mut this = Self { project: project.clone(), list: Default::default(), @@ -1544,6 +1558,41 @@ mod tests { " .dockerignore", ] ); + + panel.update(cx, |panel, cx| panel.rename(&Default::default(), cx)); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v root1", + " > .git", + " > a", + " v b", + " > [EDITOR: '3'] <== selected", + " > 4", + " > new-dir", + " a-different-filename", + " > C", + " .dockerignore", + ] + ); + + // Dismiss the rename editor when it loses focus. + workspace.update(cx, |_, cx| cx.focus_self()); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v root1", + " > .git", + " > a", + " v b", + " > 3 <== selected", + " > 4", + " > new-dir", + " a-different-filename", + " > C", + " .dockerignore", + ] + ); } fn toggle_expand_dir( From 142a019adae2d49fe4898c2d76fa41b760b5abb1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 18 Jul 2022 16:24:57 -0700 Subject: [PATCH 69/71] 0.48.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 ebf1b41829..74ce008db6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6992,7 +6992,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.47.1" +version = "0.48.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 37bfbdee8a..2ff870b436 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.1" +version = "0.48.0" [lib] name = "zed" From 815ce22fb79ae05badc0bed70a7c4487104fbd81 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 18 Jul 2022 19:00:52 -0700 Subject: [PATCH 70/71] Revert "Merge pull request #1359 from zed-industries/plugin-json-enable" This reverts commit 94b443a704319a3db7b2a1e460c21b7d8966e8e7, reversing changes made to 285832e810fb03946037a47082f35c290a6a960e. --- crates/zed/src/languages.rs | 9 +- crates/zed/src/languages/json.rs | 106 ++++++++++++++++++++ crates/zed/src/languages/language_plugin.rs | 1 + 3 files changed, 110 insertions(+), 6 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 2937d05516..8dc20bdbd1 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -3,11 +3,11 @@ 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; mod installation; +mod json; mod language_plugin; mod python; mod rust; @@ -33,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", @@ -53,10 +53,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 0000000000..7b6569d336 --- /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 8f87b03908..bd9a9d005f 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -9,6 +9,7 @@ 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 e5602b0ad487f3f3e317ad3b888ca6f5899040db Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 18 Jul 2022 19:11:12 -0700 Subject: [PATCH 71/71] 0.48.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 74ce008db6..97bfa15994 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6992,7 +6992,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.48.0" +version = "0.48.1" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 2ff870b436..0fe39b4096 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.48.0" +version = "0.48.1" [lib] name = "zed"