From 416696a68601e3354ffb46d14e3d09fea751bec6 Mon Sep 17 00:00:00 2001 From: Dragan Okanovic <1355455+abstractalgo@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:36:21 +0100 Subject: [PATCH 01/52] Fix typo in build instructions (#6444) Typo fix. Release Notes: - N/A --- docs/src/developing_zed__building_zed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index 4ff4d37fe7..6aecc3ad4c 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -78,4 +78,4 @@ Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` ### Cargo errors claiming that a dependency is using unstable features -Try `cargo clean` and `cargo build`, +Try `cargo clean` and `cargo build`. From 90c1d8f734615552297a65e12dd430bf0825cdb0 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 24 Jan 2024 17:46:40 -0500 Subject: [PATCH 02/52] Update `Cargo.lock` (#6445) This PR updates `Cargo.lock` to reflect the changes made in #6435. Release Notes: - N/A --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 04b3dee49c..2916c5e6a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9662,7 +9662,6 @@ dependencies = [ "client", "collab_ui", "collections", - "color", "command_palette", "copilot", "copilot_ui", From 6ed7cc78332b4b823aff9dafa24d9762fe91e5b4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 24 Jan 2024 17:36:50 -0800 Subject: [PATCH 03/52] Simplify language server startup (#6449) These are just some small refactorings of our language-server-starting code, motivated by another change that I decided to bail on: https://github.com/zed-industries/zed/pull/6448. --- crates/language/src/language.rs | 27 +++--- crates/project/src/project.rs | 109 ++++++++++--------------- crates/zed/src/languages/c.rs | 2 +- crates/zed/src/languages/css.rs | 4 +- crates/zed/src/languages/elixir.rs | 6 +- crates/zed/src/languages/go.rs | 2 +- crates/zed/src/languages/html.rs | 4 +- crates/zed/src/languages/json.rs | 6 +- crates/zed/src/languages/lua.rs | 2 +- crates/zed/src/languages/nu.rs | 2 +- crates/zed/src/languages/php.rs | 6 +- crates/zed/src/languages/python.rs | 2 +- crates/zed/src/languages/ruby.rs | 2 +- crates/zed/src/languages/rust.rs | 6 +- crates/zed/src/languages/svelte.rs | 4 +- crates/zed/src/languages/tailwind.rs | 6 +- crates/zed/src/languages/typescript.rs | 10 +-- crates/zed/src/languages/uiua.rs | 2 +- crates/zed/src/languages/vue.rs | 4 +- crates/zed/src/languages/yaml.rs | 2 +- 20 files changed, 91 insertions(+), 117 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1033198bab..1add9c7c25 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -139,12 +139,11 @@ pub struct CachedLspAdapter { impl CachedLspAdapter { pub async fn new(adapter: Arc) -> Arc { - let name = adapter.name().await; + let name = adapter.name(); let short_name = adapter.short_name(); - let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token().await; - let language_ids = adapter.language_ids().await; + let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources(); + let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token(); + let language_ids = adapter.language_ids(); Arc::new(CachedLspAdapter { name, @@ -261,7 +260,7 @@ pub trait LspAdapterDelegate: Send + Sync { #[async_trait] pub trait LspAdapter: 'static + Send + Sync { - async fn name(&self) -> LanguageServerName; + fn name(&self) -> LanguageServerName; fn short_name(&self) -> &'static str; @@ -337,7 +336,7 @@ pub trait LspAdapter: 'static + Send + Sync { } /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp_types::InitializeParams`] - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { None } @@ -356,15 +355,15 @@ pub trait LspAdapter: 'static + Send + Sync { ]) } - async fn disk_based_diagnostic_sources(&self) -> Vec { + fn disk_based_diagnostic_sources(&self) -> Vec { Default::default() } - async fn disk_based_diagnostics_progress_token(&self) -> Option { + fn disk_based_diagnostics_progress_token(&self) -> Option { None } - async fn language_ids(&self) -> HashMap { + fn language_ids(&self) -> HashMap { Default::default() } @@ -1881,7 +1880,7 @@ impl Default for FakeLspAdapter { #[cfg(any(test, feature = "test-support"))] #[async_trait] impl LspAdapter for Arc { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName(self.name.into()) } @@ -1919,15 +1918,15 @@ impl LspAdapter for Arc { fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - async fn disk_based_diagnostic_sources(&self) -> Vec { + fn disk_based_diagnostic_sources(&self) -> Vec { self.disk_based_diagnostics_sources.clone() } - async fn disk_based_diagnostics_progress_token(&self) -> Option { + fn disk_based_diagnostics_progress_token(&self) -> Option { self.disk_based_diagnostics_progress_token.clone() } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { self.initialization_options.clone() } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 638a64c056..840f9b20db 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -974,8 +974,7 @@ impl Project { // Start all the newly-enabled language servers. for (worktree, language) in language_servers_to_start { - let worktree_path = worktree.read(cx).abs_path(); - self.start_language_servers(&worktree, worktree_path, language, cx); + self.start_language_servers(&worktree, language, cx); } // Restart all language servers with changed initialization options. @@ -2774,8 +2773,8 @@ impl Project { }; if let Some(file) = buffer_file { let worktree = file.worktree.clone(); - if let Some(tree) = worktree.read(cx).as_local() { - self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); + if worktree.read(cx).is_local() { + self.start_language_servers(&worktree, new_language, cx); } } } @@ -2783,7 +2782,6 @@ impl Project { fn start_language_servers( &mut self, worktree: &Model, - worktree_path: Arc, language: Arc, cx: &mut ModelContext, ) { @@ -2793,22 +2791,14 @@ impl Project { return; } - let worktree_id = worktree.read(cx).id(); for adapter in language.lsp_adapters() { - self.start_language_server( - worktree_id, - worktree_path.clone(), - adapter.clone(), - language.clone(), - cx, - ); + self.start_language_server(worktree, adapter.clone(), language.clone(), cx); } } fn start_language_server( &mut self, - worktree_id: WorktreeId, - worktree_path: Arc, + worktree: &Model, adapter: Arc, language: Arc, cx: &mut ModelContext, @@ -2817,6 +2807,9 @@ impl Project { return; } + let worktree = worktree.read(cx); + let worktree_id = worktree.id(); + let worktree_path = worktree.abs_path(); let key = (worktree_id, adapter.name.clone()); if self.language_server_ids.contains_key(&key) { return; @@ -2949,20 +2942,14 @@ impl Project { this.update(&mut cx, |this, cx| { let worktrees = this.worktrees.clone(); for worktree in worktrees { - let worktree = match worktree.upgrade() { - Some(worktree) => worktree.read(cx), - None => continue, - }; - let worktree_id = worktree.id(); - let root_path = worktree.abs_path(); - - this.start_language_server( - worktree_id, - root_path, - adapter.clone(), - language.clone(), - cx, - ); + if let Some(worktree) = worktree.upgrade() { + this.start_language_server( + &worktree, + adapter.clone(), + language.clone(), + cx, + ); + } } }) .ok(); @@ -3176,7 +3163,7 @@ impl Project { } }) .detach(); - let mut initialization_options = adapter.adapter.initialization_options().await; + let mut initialization_options = adapter.adapter.initialization_options(); match (&mut initialization_options, override_options) { (Some(initialization_options), Some(override_options)) => { merge_json_value_into(override_options, initialization_options); @@ -3332,7 +3319,7 @@ impl Project { worktree_id: WorktreeId, adapter_name: LanguageServerName, cx: &mut ModelContext, - ) -> Task<(Option, Vec)> { + ) -> Task> { let key = (worktree_id, adapter_name); if let Some(server_id) = self.language_server_ids.remove(&key) { log::info!("stopping language server {}", key.1 .0); @@ -3370,8 +3357,6 @@ impl Project { let server_state = self.language_servers.remove(&server_id); cx.emit(Event::LanguageServerRemoved(server_id)); cx.spawn(move |this, mut cx| async move { - let mut root_path = None; - let server = match server_state { Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), @@ -3379,7 +3364,6 @@ impl Project { }; if let Some(server) = server { - root_path = Some(server.root_path().clone()); if let Some(shutdown) = server.shutdown() { shutdown.await; } @@ -3393,10 +3377,10 @@ impl Project { .ok(); } - (root_path, orphaned_worktrees) + orphaned_worktrees }) } else { - Task::ready((None, Vec::new())) + Task::ready(Vec::new()) } } @@ -3426,7 +3410,6 @@ impl Project { None } - // TODO This will break in the case where the adapter's root paths and worktrees are not equal fn restart_language_servers( &mut self, worktree: Model, @@ -3434,50 +3417,42 @@ impl Project { cx: &mut ModelContext, ) { let worktree_id = worktree.read(cx).id(); - let fallback_path = worktree.read(cx).abs_path(); - let mut stops = Vec::new(); - for adapter in language.lsp_adapters() { - stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx)); - } - - if stops.is_empty() { + let stop_tasks = language + .lsp_adapters() + .iter() + .map(|adapter| { + let stop_task = self.stop_language_server(worktree_id, adapter.name.clone(), cx); + (stop_task, adapter.name.clone()) + }) + .collect::>(); + if stop_tasks.is_empty() { return; } - let mut stops = stops.into_iter(); cx.spawn(move |this, mut cx| async move { - let (original_root_path, mut orphaned_worktrees) = stops.next().unwrap().await; - for stop in stops { - let (_, worktrees) = stop.await; - orphaned_worktrees.extend_from_slice(&worktrees); + // For each stopped language server, record all of the worktrees with which + // it was associated. + let mut affected_worktrees = Vec::new(); + for (stop_task, language_server_name) in stop_tasks { + for affected_worktree_id in stop_task.await { + affected_worktrees.push((affected_worktree_id, language_server_name.clone())); + } } - let this = match this.upgrade() { - Some(this) => this, - None => return, - }; - this.update(&mut cx, |this, cx| { - // Attempt to restart using original server path. Fallback to passed in - // path if we could not retrieve the root path - let root_path = original_root_path - .map(|path_buf| Arc::from(path_buf.as_path())) - .unwrap_or(fallback_path); - - this.start_language_servers(&worktree, root_path, language.clone(), cx); + // Restart the language server for the given worktree. + this.start_language_servers(&worktree, language.clone(), cx); // Lookup new server ids and set them for each of the orphaned worktrees - for adapter in language.lsp_adapters() { + for (affected_worktree_id, language_server_name) in affected_worktrees { if let Some(new_server_id) = this .language_server_ids - .get(&(worktree_id, adapter.name.clone())) + .get(&(worktree_id, language_server_name.clone())) .cloned() { - for &orphaned_worktree in &orphaned_worktrees { - this.language_server_ids - .insert((orphaned_worktree, adapter.name.clone()), new_server_id); - } + this.language_server_ids + .insert((affected_worktree_id, language_server_name), new_server_id); } } }) diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index a0b00d7797..c4cc44140d 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -15,7 +15,7 @@ pub struct CLspAdapter; #[async_trait] impl super::LspAdapter for CLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("clangd".into()) } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index fdbc179209..a3b10c751c 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -33,7 +33,7 @@ impl CssLspAdapter { #[async_trait] impl LspAdapter for CssLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("vscode-css-language-server".into()) } @@ -91,7 +91,7 @@ impl LspAdapter for CssLspAdapter { get_cached_server_binary(container_dir, &*self.node).await } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true })) diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 90352c78b4..6172be1683 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -67,7 +67,7 @@ pub struct ElixirLspAdapter; #[async_trait] impl LspAdapter for ElixirLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("elixir-ls".into()) } @@ -301,7 +301,7 @@ pub struct NextLspAdapter; #[async_trait] impl LspAdapter for NextLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("next-ls".into()) } @@ -452,7 +452,7 @@ pub struct LocalLspAdapter { #[async_trait] impl LspAdapter for LocalLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("local-ls".into()) } diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 0daf1527c3..fc129a6727 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -33,7 +33,7 @@ lazy_static! { #[async_trait] impl super::LspAdapter for GoLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("gopls".into()) } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index b8f1c70cce..ac0b2a000a 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -33,7 +33,7 @@ impl HtmlLspAdapter { #[async_trait] impl LspAdapter for HtmlLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("vscode-html-language-server".into()) } @@ -91,7 +91,7 @@ impl LspAdapter for HtmlLspAdapter { get_cached_server_binary(container_dir, &*self.node).await } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true })) diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 19af72f7c6..b90459cc25 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -38,7 +38,7 @@ impl JsonLspAdapter { #[async_trait] impl LspAdapter for JsonLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("json-language-server".into()) } @@ -96,7 +96,7 @@ impl LspAdapter for JsonLspAdapter { get_cached_server_binary(container_dir, &*self.node).await } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true })) @@ -140,7 +140,7 @@ impl LspAdapter for JsonLspAdapter { }) } - async fn language_ids(&self) -> HashMap { + fn language_ids(&self) -> HashMap { [("JSON".into(), "jsonc".into())].into_iter().collect() } } diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 5fffb37e81..c9753e0f6c 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -18,7 +18,7 @@ pub struct LuaLspAdapter; #[async_trait] impl super::LspAdapter for LuaLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("lua-language-server".into()) } diff --git a/crates/zed/src/languages/nu.rs b/crates/zed/src/languages/nu.rs index a3631b8471..8327cd16f4 100644 --- a/crates/zed/src/languages/nu.rs +++ b/crates/zed/src/languages/nu.rs @@ -8,7 +8,7 @@ pub struct NuLanguageServer; #[async_trait] impl LspAdapter for NuLanguageServer { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("nu".into()) } diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index e3d0f1c690..c071c807f8 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -36,7 +36,7 @@ impl IntelephenseLspAdapter { #[async_trait] impl LspAdapter for IntelephenseLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("intelephense".into()) } @@ -96,10 +96,10 @@ impl LspAdapter for IntelephenseLspAdapter { None } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { None } - async fn language_ids(&self) -> HashMap { + fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } } diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 8a30121d49..437e3a2d87 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -30,7 +30,7 @@ impl PythonLspAdapter { #[async_trait] impl LspAdapter for PythonLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("pyright".into()) } diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index 3890b90dbd..69294a5470 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -8,7 +8,7 @@ pub struct RubyLanguageServer; #[async_trait] impl LspAdapter for RubyLanguageServer { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("solargraph".into()) } diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 6f6ffa4188..dea88bfd3e 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -18,7 +18,7 @@ pub struct RustLspAdapter; #[async_trait] impl LspAdapter for RustLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("rust-analyzer".into()) } @@ -98,11 +98,11 @@ impl LspAdapter for RustLspAdapter { }) } - async fn disk_based_diagnostic_sources(&self) -> Vec { + fn disk_based_diagnostic_sources(&self) -> Vec { vec!["rustc".into()] } - async fn disk_based_diagnostics_progress_token(&self) -> Option { + fn disk_based_diagnostics_progress_token(&self) -> Option { Some("rust-analyzer/flycheck".into()) } diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 34dab81772..cd184e97b8 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -32,7 +32,7 @@ impl SvelteLspAdapter { #[async_trait] impl LspAdapter for SvelteLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("svelte-language-server".into()) } @@ -90,7 +90,7 @@ impl LspAdapter for SvelteLspAdapter { get_cached_server_binary(container_dir, &*self.node).await } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true })) diff --git a/crates/zed/src/languages/tailwind.rs b/crates/zed/src/languages/tailwind.rs index 5c39ee0efd..4fdb7e84d4 100644 --- a/crates/zed/src/languages/tailwind.rs +++ b/crates/zed/src/languages/tailwind.rs @@ -34,7 +34,7 @@ impl TailwindLspAdapter { #[async_trait] impl LspAdapter for TailwindLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("tailwindcss-language-server".into()) } @@ -92,7 +92,7 @@ impl LspAdapter for TailwindLspAdapter { get_cached_server_binary(container_dir, &*self.node).await } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true, "userLanguages": { @@ -112,7 +112,7 @@ impl LspAdapter for TailwindLspAdapter { }) } - async fn language_ids(&self) -> HashMap { + fn language_ids(&self) -> HashMap { HashMap::from_iter([ ("HTML".to_string(), "html".to_string()), ("CSS".to_string(), "css".to_string()), diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 133e9095c0..ff38e4a736 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -46,7 +46,7 @@ struct TypeScriptVersions { #[async_trait] impl LspAdapter for TypeScriptLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("typescript-language-server".into()) } @@ -150,7 +150,7 @@ impl LspAdapter for TypeScriptLspAdapter { }) } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { Some(json!({ "provideFormatter": true, "tsserver": { @@ -159,7 +159,7 @@ impl LspAdapter for TypeScriptLspAdapter { })) } - async fn language_ids(&self) -> HashMap { + fn language_ids(&self) -> HashMap { HashMap::from_iter([ ("TypeScript".into(), "typescript".into()), ("JavaScript".into(), "javascript".into()), @@ -227,7 +227,7 @@ impl LspAdapter for EsLintLspAdapter { }) } - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("eslint".into()) } @@ -315,7 +315,7 @@ impl LspAdapter for EsLintLspAdapter { None } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { None } } diff --git a/crates/zed/src/languages/uiua.rs b/crates/zed/src/languages/uiua.rs index 0efdfdd70d..50101b6651 100644 --- a/crates/zed/src/languages/uiua.rs +++ b/crates/zed/src/languages/uiua.rs @@ -8,7 +8,7 @@ pub struct UiuaLanguageServer; #[async_trait] impl LspAdapter for UiuaLanguageServer { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("uiua".into()) } diff --git a/crates/zed/src/languages/vue.rs b/crates/zed/src/languages/vue.rs index 16afd2e299..ae1d51eaa0 100644 --- a/crates/zed/src/languages/vue.rs +++ b/crates/zed/src/languages/vue.rs @@ -40,7 +40,7 @@ impl VueLspAdapter { } #[async_trait] impl super::LspAdapter for VueLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("vue-language-server".into()) } @@ -60,7 +60,7 @@ impl super::LspAdapter for VueLspAdapter { ts_version: self.node.npm_package_latest_version("typescript").await?, }) as Box<_>) } - async fn initialization_options(&self) -> Option { + fn initialization_options(&self) -> Option { let typescript_sdk_path = self.typescript_install_path.lock(); let typescript_sdk_path = typescript_sdk_path .as_ref() diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 6f29f62f8e..fcdfe1f911 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -35,7 +35,7 @@ impl YamlLspAdapter { #[async_trait] impl LspAdapter for YamlLspAdapter { - async fn name(&self) -> LanguageServerName { + fn name(&self) -> LanguageServerName { LanguageServerName("yaml-language-server".into()) } From f185aca25a0d370b8812d82ffeec903da1178c87 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Jan 2024 19:10:53 -0700 Subject: [PATCH 04/52] Use the correct snapshot when calculating mouse positions --- crates/editor/src/element.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 65d2e06d35..bcf2504d34 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -567,7 +567,7 @@ impl EditorElement { cx, ); hover_at(editor, Some(point), cx); - Self::update_visible_cursor(editor, point, cx); + Self::update_visible_cursor(editor, point, position_map, cx); } None => { update_inlay_link_and_hover_points( @@ -592,9 +592,10 @@ impl EditorElement { fn update_visible_cursor( editor: &mut Editor, point: DisplayPoint, + position_map: &PositionMap, cx: &mut ViewContext, ) { - let snapshot = editor.snapshot(cx); + let snapshot = &position_map.snapshot; let Some(hub) = editor.collaboration_hub() else { return; }; From 249a6da54a10228d2f77119486d4042631ab10c4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Jan 2024 19:40:01 -0700 Subject: [PATCH 05/52] Fix circular locking in prompts Sometimes Cocoa calls app delegate methods (notably the display link) while we're calling Cocoa methods. This causes a deadlock unless we are careful to run cocao methods while we're not holding our internal locks --- crates/gpui/src/platform/mac/platform.rs | 114 ++++++++++++----------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 58a759865e..7c6f3df266 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -534,67 +534,77 @@ impl Platform for MacPlatform { &self, options: PathPromptOptions, ) -> oneshot::Receiver>> { - unsafe { - let panel = NSOpenPanel::openPanel(nil); - panel.setCanChooseDirectories_(options.directories.to_objc()); - panel.setCanChooseFiles_(options.files.to_objc()); - panel.setAllowsMultipleSelection_(options.multiple.to_objc()); - panel.setResolvesAliases_(false.to_objc()); - let (done_tx, done_rx) = oneshot::channel(); - let done_tx = Cell::new(Some(done_tx)); - let block = ConcreteBlock::new(move |response: NSModalResponse| { - let result = if response == NSModalResponse::NSModalResponseOk { - let mut result = Vec::new(); - let urls = panel.URLs(); - for i in 0..urls.count() { - let url = urls.objectAtIndex(i); - if url.isFileURL() == YES { - if let Ok(path) = ns_url_to_path(url) { - result.push(path) + let (done_tx, done_rx) = oneshot::channel(); + self.foreground_executor() + .spawn(async move { + unsafe { + let panel = NSOpenPanel::openPanel(nil); + panel.setCanChooseDirectories_(options.directories.to_objc()); + panel.setCanChooseFiles_(options.files.to_objc()); + panel.setAllowsMultipleSelection_(options.multiple.to_objc()); + panel.setResolvesAliases_(false.to_objc()); + let done_tx = Cell::new(Some(done_tx)); + let block = ConcreteBlock::new(move |response: NSModalResponse| { + let result = if response == NSModalResponse::NSModalResponseOk { + let mut result = Vec::new(); + let urls = panel.URLs(); + for i in 0..urls.count() { + let url = urls.objectAtIndex(i); + if url.isFileURL() == YES { + if let Ok(path) = ns_url_to_path(url) { + result.push(path) + } + } } - } - } - Some(result) - } else { - None - }; + Some(result) + } else { + None + }; - if let Some(done_tx) = done_tx.take() { - let _ = done_tx.send(result); + if let Some(done_tx) = done_tx.take() { + let _ = done_tx.send(result); + } + }); + let block = block.copy(); + let _: () = msg_send![panel, beginWithCompletionHandler: block]; } - }); - let block = block.copy(); - let _: () = msg_send![panel, beginWithCompletionHandler: block]; - done_rx - } + }) + .detach(); + done_rx } fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { - unsafe { - let panel = NSSavePanel::savePanel(nil); - let path = ns_string(directory.to_string_lossy().as_ref()); - let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc()); - panel.setDirectoryURL(url); + let directory = directory.to_owned(); + let (done_tx, done_rx) = oneshot::channel(); + self.foreground_executor() + .spawn(async move { + unsafe { + let panel = NSSavePanel::savePanel(nil); + let path = ns_string(directory.to_string_lossy().as_ref()); + let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc()); + panel.setDirectoryURL(url); - let (done_tx, done_rx) = oneshot::channel(); - let done_tx = Cell::new(Some(done_tx)); - let block = ConcreteBlock::new(move |response: NSModalResponse| { - let mut result = None; - if response == NSModalResponse::NSModalResponseOk { - let url = panel.URL(); - if url.isFileURL() == YES { - result = ns_url_to_path(panel.URL()).ok() - } - } + let done_tx = Cell::new(Some(done_tx)); + let block = ConcreteBlock::new(move |response: NSModalResponse| { + let mut result = None; + if response == NSModalResponse::NSModalResponseOk { + let url = panel.URL(); + if url.isFileURL() == YES { + result = ns_url_to_path(panel.URL()).ok() + } + } - if let Some(done_tx) = done_tx.take() { - let _ = done_tx.send(result); + if let Some(done_tx) = done_tx.take() { + let _ = done_tx.send(result); + } + }); + let block = block.copy(); + let _: () = msg_send![panel, beginWithCompletionHandler: block]; } - }); - let block = block.copy(); - let _: () = msg_send![panel, beginWithCompletionHandler: block]; - done_rx - } + }) + .detach(); + + done_rx } fn reveal_path(&self, path: &Path) { From 4bcd3494b70974e06df8a0d8d743dc1e194fb2c9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 16 Jan 2024 23:16:46 -0700 Subject: [PATCH 06/52] Try to send typed errors back and forth TEMP TEMP First pass of structured errors Improved error handling for channel joining failures --- crates/client/src/client.rs | 7 +- crates/collab/src/db/queries/channels.rs | 10 +- crates/collab/src/rpc.rs | 15 +- crates/rpc/proto/zed.proto | 12 ++ crates/rpc/src/error.rs | 223 +++++++++++++++++++++++ crates/rpc/src/peer.rs | 41 +++-- crates/rpc/src/rpc.rs | 2 + crates/workspace/src/workspace.rs | 26 ++- 8 files changed, 292 insertions(+), 44 deletions(-) create mode 100644 crates/rpc/src/error.rs diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 370a2ba6ff..3c2a831ce2 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -689,12 +689,7 @@ impl Client { Ok(()) } Err(error) => { - client.respond_with_error( - receipt, - proto::Error { - message: format!("{:?}", error), - }, - )?; + client.respond_with_error(receipt, error.to_proto())?; Err(error) } } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index c2428150fc..ac18907894 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1,5 +1,5 @@ use super::*; -use rpc::proto::channel_member::Kind; +use rpc::{proto::channel_member::Kind, ErrorCode, ErrorCodeExt}; use sea_orm::TryGetableMany; impl Database { @@ -166,7 +166,7 @@ impl Database { } if role.is_none() || role == Some(ChannelRole::Banned) { - Err(anyhow!("not allowed"))? + Err(ErrorCode::Forbidden.anyhow())? } let role = role.unwrap(); @@ -1201,7 +1201,7 @@ impl Database { Ok(channel::Entity::find_by_id(channel_id) .one(&*tx) .await? - .ok_or_else(|| anyhow!("no such channel"))?) + .ok_or_else(|| proto::ErrorCode::NoSuchChannel.anyhow())?) } pub(crate) async fn get_or_create_channel_room( @@ -1219,7 +1219,9 @@ impl Database { let room_id = if let Some(room) = room { if let Some(env) = room.environment { if &env != environment { - Err(anyhow!("must join using the {} release", env))?; + Err(ErrorCode::WrongReleaseChannel + .with_tag("required", &env) + .anyhow())?; } } room.id diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 2a715fa4a4..7170f7f1c5 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -10,7 +10,7 @@ use crate::{ User, UserId, }, executor::Executor, - AppState, Result, + AppState, Error, Result, }; use anyhow::anyhow; use async_tungstenite::tungstenite::{ @@ -44,7 +44,7 @@ use rpc::{ self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo, RequestMessage, ShareProject, UpdateChannelBufferCollaborators, }, - Connection, ConnectionId, Peer, Receipt, TypedEnvelope, + Connection, ConnectionId, ErrorCode, ErrorCodeExt, ErrorExt, Peer, Receipt, TypedEnvelope, }; use serde::{Serialize, Serializer}; use std::{ @@ -543,12 +543,11 @@ impl Server { } } Err(error) => { - peer.respond_with_error( - receipt, - proto::Error { - message: error.to_string(), - }, - )?; + let proto_err = match &error { + Error::Internal(err) => err.to_proto(), + _ => ErrorCode::Internal.message(format!("{}", error)).to_proto(), + }; + peer.respond_with_error(receipt, proto_err)?; Err(error) } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 941053be53..ecfab714f4 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -197,6 +197,18 @@ message Ack {} message Error { string message = 1; + ErrorCode code = 2; + repeated string tags = 3; +} + +enum ErrorCode { + Internal = 0; + NoSuchChannel = 1; + Disconnected = 2; + SignedOut = 3; + UpgradeRequired = 4; + Forbidden = 5; + WrongReleaseChannel = 6; } message Test { diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs new file mode 100644 index 0000000000..64c75a8d2f --- /dev/null +++ b/crates/rpc/src/error.rs @@ -0,0 +1,223 @@ +/// Some helpers for structured error handling. +/// +/// The helpers defined here allow you to pass type-safe error codes from +/// the collab server to the client; and provide a mechanism for additional +/// structured data alongside the message. +/// +/// When returning an error, it can be as simple as: +/// +/// `return Err(Error::Forbidden.into())` +/// +/// If you'd like to log more context, you can set a message. These messages +/// show up in our logs, but are not shown visibly to users. +/// +/// `return Err(Error::Forbidden.message("not an admin").into())` +/// +/// If you'd like to provide enough context that the UI can render a good error +/// message (or would be helpful to see in a structured format in the logs), you +/// can use .with_tag(): +/// +/// `return Err(Error::WrongReleaseChannel.with_tag("required", "stable").into())` +/// +/// When handling an error you can use .error_code() to match which error it was +/// and .error_tag() to read any tags. +/// +/// ``` +/// match err.error_code() { +/// ErrorCode::Forbidden => alert("I'm sorry I can't do that.") +/// ErrorCode::WrongReleaseChannel => +/// alert(format!("You need to be on the {} release channel.", err.error_tag("required").unwrap())) +/// ErrorCode::Internal => alert("Sorry, something went wrong") +/// } +/// ``` +/// +use crate::proto; +pub use proto::ErrorCode; + +/// ErrorCodeExt provides some helpers for structured error handling. +/// +/// The primary implementation is on the proto::ErrorCode to easily convert +/// that into an anyhow::Error, which we use pervasively. +/// +/// The RPCError struct provides support for further metadata if needed. +pub trait ErrorCodeExt { + /// Return an anyhow::Error containing this. + /// (useful in places where .into() doesn't have enough type information) + fn anyhow(self) -> anyhow::Error; + + /// Add a message to the error (by default the error code is used) + fn message(self, msg: String) -> RPCError; + + /// Add a tag to the error. Tags are key value pairs that can be used + /// to send semi-structured data along with the error. + fn with_tag(self, k: &str, v: &str) -> RPCError; +} + +impl ErrorCodeExt for proto::ErrorCode { + fn anyhow(self) -> anyhow::Error { + self.into() + } + + fn message(self, msg: String) -> RPCError { + let err: RPCError = self.into(); + err.message(msg) + } + + fn with_tag(self, k: &str, v: &str) -> RPCError { + let err: RPCError = self.into(); + err.with_tag(k, v) + } +} + +/// ErrorExt provides helpers for structured error handling. +/// +/// The primary implementation is on the anyhow::Error, which is +/// what we use throughout our codebase. Though under the hood this +pub trait ErrorExt { + /// error_code() returns the ErrorCode (or ErrorCode::Internal if there is none) + fn error_code(&self) -> proto::ErrorCode; + /// error_tag() returns the value of the tag with the given key, if any. + fn error_tag(&self, k: &str) -> Option<&str>; + /// to_proto() convers the error into a proto::Error + fn to_proto(&self) -> proto::Error; +} + +impl ErrorExt for anyhow::Error { + fn error_code(&self) -> proto::ErrorCode { + if let Some(rpc_error) = self.downcast_ref::() { + rpc_error.code + } else { + proto::ErrorCode::Internal + } + } + + fn error_tag(&self, k: &str) -> Option<&str> { + if let Some(rpc_error) = self.downcast_ref::() { + rpc_error.error_tag(k) + } else { + None + } + } + + fn to_proto(&self) -> proto::Error { + if let Some(rpc_error) = self.downcast_ref::() { + rpc_error.to_proto() + } else { + ErrorCode::Internal.message(format!("{}", self)).to_proto() + } + } +} + +impl From for anyhow::Error { + fn from(value: proto::ErrorCode) -> Self { + RPCError { + request: None, + code: value, + msg: format!("{:?}", value).to_string(), + tags: Default::default(), + } + .into() + } +} + +#[derive(Clone, Debug)] +pub struct RPCError { + request: Option, + msg: String, + code: proto::ErrorCode, + tags: Vec, +} + +/// RPCError is a structured error type that is returned by the collab server. +/// In addition to a message, it lets you set a specific ErrorCode, and attach +/// small amounts of metadata to help the client handle the error appropriately. +/// +/// This struct is not typically used directly, as we pass anyhow::Error around +/// in the app; however it is useful for chaining .message() and .with_tag() on +/// ErrorCode. +impl RPCError { + /// from_proto converts a proto::Error into an anyhow::Error containing + /// an RPCError. + pub fn from_proto(error: &proto::Error, request: &str) -> anyhow::Error { + RPCError { + request: Some(request.to_string()), + code: error.code(), + msg: error.message.clone(), + tags: error.tags.clone(), + } + .into() + } +} + +impl ErrorCodeExt for RPCError { + fn message(mut self, msg: String) -> RPCError { + self.msg = msg; + self + } + + fn with_tag(mut self, k: &str, v: &str) -> RPCError { + self.tags.push(format!("{}={}", k, v)); + self + } + + fn anyhow(self) -> anyhow::Error { + self.into() + } +} + +impl ErrorExt for RPCError { + fn error_tag(&self, k: &str) -> Option<&str> { + for tag in &self.tags { + let mut parts = tag.split('='); + if let Some(key) = parts.next() { + if key == k { + return parts.next(); + } + } + } + None + } + + fn error_code(&self) -> proto::ErrorCode { + self.code + } + + fn to_proto(&self) -> proto::Error { + proto::Error { + code: self.code as i32, + message: self.msg.clone(), + tags: self.tags.clone(), + } + } +} + +impl std::error::Error for RPCError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl std::fmt::Display for RPCError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(request) = &self.request { + write!(f, "RPC request {} failed: {}", request, self.msg)? + } else { + write!(f, "{}", self.msg)? + } + for tag in &self.tags { + write!(f, " {}", tag)? + } + Ok(()) + } +} + +impl From for RPCError { + fn from(code: proto::ErrorCode) -> Self { + RPCError { + request: None, + code, + msg: format!("{:?}", code).to_string(), + tags: Default::default(), + } + } +} diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 20a36efdfe..cfb5223930 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -1,3 +1,5 @@ +use crate::{ErrorCode, ErrorCodeExt, ErrorExt, RPCError}; + use super::{ proto::{self, AnyTypedEnvelope, EnvelopedMessage, MessageStream, PeerId, RequestMessage}, Connection, @@ -423,11 +425,7 @@ impl Peer { let (response, _barrier) = rx.await.map_err(|_| anyhow!("connection was closed"))?; if let Some(proto::envelope::Payload::Error(error)) = &response.payload { - Err(anyhow!( - "RPC request {} failed - {}", - T::NAME, - error.message - )) + Err(RPCError::from_proto(&error, T::NAME)) } else { Ok(TypedEnvelope { message_id: response.id, @@ -516,9 +514,12 @@ impl Peer { envelope: Box, ) -> Result<()> { let connection = self.connection_state(envelope.sender_id())?; - let response = proto::Error { - message: format!("message {} was not handled", envelope.payload_type_name()), - }; + let response = ErrorCode::Internal + .message(format!( + "message {} was not handled", + envelope.payload_type_name() + )) + .to_proto(); let message_id = connection .next_message_id .fetch_add(1, atomic::Ordering::SeqCst); @@ -692,17 +693,17 @@ mod tests { server .send( server_to_client_conn_id, - proto::Error { - message: "message 1".to_string(), - }, + ErrorCode::Internal + .message("message 1".to_string()) + .to_proto(), ) .unwrap(); server .send( server_to_client_conn_id, - proto::Error { - message: "message 2".to_string(), - }, + ErrorCode::Internal + .message("message 2".to_string()) + .to_proto(), ) .unwrap(); server.respond(request.receipt(), proto::Ack {}).unwrap(); @@ -797,17 +798,17 @@ mod tests { server .send( server_to_client_conn_id, - proto::Error { - message: "message 1".to_string(), - }, + ErrorCode::Internal + .message("message 1".to_string()) + .to_proto(), ) .unwrap(); server .send( server_to_client_conn_id, - proto::Error { - message: "message 2".to_string(), - }, + ErrorCode::Internal + .message("message 2".to_string()) + .to_proto(), ) .unwrap(); server.respond(request1.receipt(), proto::Ack {}).unwrap(); diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index da0880377f..c22ecb740d 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -1,10 +1,12 @@ pub mod auth; mod conn; +mod error; mod notification; mod peer; pub mod proto; pub use conn::Connection; +pub use error::*; pub use notification::*; pub use peer::*; mod macros; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f645b1e7fa..e7f5e2e61e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -14,8 +14,8 @@ mod workspace_settings; use anyhow::{anyhow, Context as _, Result}; use call::ActiveCall; use client::{ - proto::{self, PeerId}, - Client, Status, TypedEnvelope, UserStore, + proto::{self, ErrorCode, PeerId}, + Client, ErrorExt, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; @@ -3919,10 +3919,10 @@ async fn join_channel_internal( | Status::Reconnecting | Status::Reauthenticating => continue, Status::Connected { .. } => break 'outer, - Status::SignedOut => return Err(anyhow!("not signed in")), - Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + Status::SignedOut => return Err(ErrorCode::SignedOut.into()), + Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()), Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { - return Err(anyhow!("zed is offline")) + return Err(ErrorCode::Disconnected.into()) } } } @@ -3995,9 +3995,23 @@ pub fn join_channel( if let Some(active_window) = active_window { active_window .update(&mut cx, |_, cx| { + let message:SharedString = match err.error_code() { + ErrorCode::SignedOut => { + "Failed to join channel\n\nPlease sign in to continue.".into() + }, + ErrorCode::UpgradeRequired => { + "Failed to join channel\n\nPlease update to the latest version of Zed to continue.".into() + }, + ErrorCode::NoSuchChannel => { + "Failed to find channel\n\nPlease check the link and try again.".into() + }, + ErrorCode::Disconnected => "Failed to join channel\n\nPlease check your internet connection and try again.".into(), + ErrorCode::WrongReleaseChannel => format!("Failed to join channel\n\nOther people in the channel are using the {} release of Zed, please switch to that release instead.", err.error_tag("required").unwrap_or("other")).into(), + _ => format!("Failed to join channel\n\n{}\n\nPlease try again.", err).into(), + }; cx.prompt( PromptLevel::Critical, - &format!("Failed to join channel: {}", err), + &message, &["Ok"], ) })? From df420c3767b146b50131affb35588543e00f3a0a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Jan 2024 12:52:01 -0700 Subject: [PATCH 07/52] Better naming --- crates/rpc/src/error.rs | 50 ++++++++++++++++++++--------------------- crates/rpc/src/peer.rs | 4 ++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 64c75a8d2f..97f93e7465 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -39,18 +39,18 @@ pub use proto::ErrorCode; /// The primary implementation is on the proto::ErrorCode to easily convert /// that into an anyhow::Error, which we use pervasively. /// -/// The RPCError struct provides support for further metadata if needed. +/// The RpcError struct provides support for further metadata if needed. pub trait ErrorCodeExt { /// Return an anyhow::Error containing this. /// (useful in places where .into() doesn't have enough type information) fn anyhow(self) -> anyhow::Error; /// Add a message to the error (by default the error code is used) - fn message(self, msg: String) -> RPCError; + fn message(self, msg: String) -> RpcError; /// Add a tag to the error. Tags are key value pairs that can be used /// to send semi-structured data along with the error. - fn with_tag(self, k: &str, v: &str) -> RPCError; + fn with_tag(self, k: &str, v: &str) -> RpcError; } impl ErrorCodeExt for proto::ErrorCode { @@ -58,13 +58,13 @@ impl ErrorCodeExt for proto::ErrorCode { self.into() } - fn message(self, msg: String) -> RPCError { - let err: RPCError = self.into(); + fn message(self, msg: String) -> RpcError { + let err: RpcError = self.into(); err.message(msg) } - fn with_tag(self, k: &str, v: &str) -> RPCError { - let err: RPCError = self.into(); + fn with_tag(self, k: &str, v: &str) -> RpcError { + let err: RpcError = self.into(); err.with_tag(k, v) } } @@ -78,13 +78,13 @@ pub trait ErrorExt { fn error_code(&self) -> proto::ErrorCode; /// error_tag() returns the value of the tag with the given key, if any. fn error_tag(&self, k: &str) -> Option<&str>; - /// to_proto() convers the error into a proto::Error + /// to_proto() converts the error into a proto::Error fn to_proto(&self) -> proto::Error; } impl ErrorExt for anyhow::Error { fn error_code(&self) -> proto::ErrorCode { - if let Some(rpc_error) = self.downcast_ref::() { + if let Some(rpc_error) = self.downcast_ref::() { rpc_error.code } else { proto::ErrorCode::Internal @@ -92,7 +92,7 @@ impl ErrorExt for anyhow::Error { } fn error_tag(&self, k: &str) -> Option<&str> { - if let Some(rpc_error) = self.downcast_ref::() { + if let Some(rpc_error) = self.downcast_ref::() { rpc_error.error_tag(k) } else { None @@ -100,7 +100,7 @@ impl ErrorExt for anyhow::Error { } fn to_proto(&self) -> proto::Error { - if let Some(rpc_error) = self.downcast_ref::() { + if let Some(rpc_error) = self.downcast_ref::() { rpc_error.to_proto() } else { ErrorCode::Internal.message(format!("{}", self)).to_proto() @@ -110,7 +110,7 @@ impl ErrorExt for anyhow::Error { impl From for anyhow::Error { fn from(value: proto::ErrorCode) -> Self { - RPCError { + RpcError { request: None, code: value, msg: format!("{:?}", value).to_string(), @@ -121,25 +121,25 @@ impl From for anyhow::Error { } #[derive(Clone, Debug)] -pub struct RPCError { +pub struct RpcError { request: Option, msg: String, code: proto::ErrorCode, tags: Vec, } -/// RPCError is a structured error type that is returned by the collab server. +/// RpcError is a structured error type that is returned by the collab server. /// In addition to a message, it lets you set a specific ErrorCode, and attach /// small amounts of metadata to help the client handle the error appropriately. /// /// This struct is not typically used directly, as we pass anyhow::Error around /// in the app; however it is useful for chaining .message() and .with_tag() on /// ErrorCode. -impl RPCError { +impl RpcError { /// from_proto converts a proto::Error into an anyhow::Error containing - /// an RPCError. + /// an RpcError. pub fn from_proto(error: &proto::Error, request: &str) -> anyhow::Error { - RPCError { + RpcError { request: Some(request.to_string()), code: error.code(), msg: error.message.clone(), @@ -149,13 +149,13 @@ impl RPCError { } } -impl ErrorCodeExt for RPCError { - fn message(mut self, msg: String) -> RPCError { +impl ErrorCodeExt for RpcError { + fn message(mut self, msg: String) -> RpcError { self.msg = msg; self } - fn with_tag(mut self, k: &str, v: &str) -> RPCError { + fn with_tag(mut self, k: &str, v: &str) -> RpcError { self.tags.push(format!("{}={}", k, v)); self } @@ -165,7 +165,7 @@ impl ErrorCodeExt for RPCError { } } -impl ErrorExt for RPCError { +impl ErrorExt for RpcError { fn error_tag(&self, k: &str) -> Option<&str> { for tag in &self.tags { let mut parts = tag.split('='); @@ -191,13 +191,13 @@ impl ErrorExt for RPCError { } } -impl std::error::Error for RPCError { +impl std::error::Error for RpcError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } -impl std::fmt::Display for RPCError { +impl std::fmt::Display for RpcError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(request) = &self.request { write!(f, "RPC request {} failed: {}", request, self.msg)? @@ -211,9 +211,9 @@ impl std::fmt::Display for RPCError { } } -impl From for RPCError { +impl From for RpcError { fn from(code: proto::ErrorCode) -> Self { - RPCError { + RpcError { request: None, code, msg: format!("{:?}", code).to_string(), diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index cfb5223930..9d789bd3d0 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -1,4 +1,4 @@ -use crate::{ErrorCode, ErrorCodeExt, ErrorExt, RPCError}; +use crate::{ErrorCode, ErrorCodeExt, ErrorExt, RpcError}; use super::{ proto::{self, AnyTypedEnvelope, EnvelopedMessage, MessageStream, PeerId, RequestMessage}, @@ -425,7 +425,7 @@ impl Peer { let (response, _barrier) = rx.await.map_err(|_| anyhow!("connection was closed"))?; if let Some(proto::envelope::Payload::Error(error)) = &response.payload { - Err(RPCError::from_proto(&error, T::NAME)) + Err(RpcError::from_proto(&error, T::NAME)) } else { Ok(TypedEnvelope { message_id: response.id, From 320088f1fa1cc09117a6d7622b781d0a4aa31659 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Jan 2024 20:56:39 -0700 Subject: [PATCH 08/52] Fix panic from unwrapping a relativize() --- crates/project/src/worktree.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4bd5e62994..c94385f14f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -970,13 +970,14 @@ impl LocalWorktree { let mut index_task = None; let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?; if let Some(repo) = snapshot.repository_for_path(&path) { - let repo_path = repo.work_directory.relativize(&snapshot, &path).unwrap(); - if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) { - let repo = repo.repo_ptr.clone(); - index_task = Some( - cx.background_executor() - .spawn(async move { repo.lock().load_index_text(&repo_path) }), - ); + if let Some(repo_path) = repo.work_directory.relativize(&snapshot, &path) { + if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) { + let repo = repo.repo_ptr.clone(); + index_task = Some( + cx.background_executor() + .spawn(async move { repo.lock().load_index_text(&repo_path) }), + ); + } } } From ba97661e1cdcc85a612171f0801525fc7ea19b49 Mon Sep 17 00:00:00 2001 From: Jeff Li Date: Thu, 25 Jan 2024 12:53:41 +0800 Subject: [PATCH 09/52] Fix backend dependencies link in local collaboration docs (#6461) Release Notes: - N/A --- docs/src/developing_zed__local_collaboration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/developing_zed__local_collaboration.md b/docs/src/developing_zed__local_collaboration.md index 0fc08ef767..15fe396911 100644 --- a/docs/src/developing_zed__local_collaboration.md +++ b/docs/src/developing_zed__local_collaboration.md @@ -1,6 +1,6 @@ # Local Collaboration -First, make sure you've installed Zed's [backend dependencies](/developing_zed__building_zed.html#backend-dependencies). +First, make sure you've installed Zed's [backend dependencies](./developing_zed__building_zed.md#backend-dependencies). ## Database setup From bf7489269e87c7270e4e77bd9479302ebffbcf09 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 25 Jan 2024 00:05:15 -0500 Subject: [PATCH 10/52] Clean up docs (#6464) This PR cleans up some of the Zed meta docs. Namely: - Removed references to the `zed-industries/community` repository - Removed old docs directory (it'll be in Git history if we need it) Release Notes: - N/A --- docs/old/backend-development.md | 52 ---------- docs/old/building-zed.md | 104 ------------------- docs/old/company-and-vision.md | 34 ------ docs/old/design-tools.md | 74 ------------- docs/old/index.md | 14 --- docs/old/local-collaboration.md | 22 ---- docs/old/release-process.md | 100 ------------------ docs/old/tools.md | 82 --------------- docs/old/zed/syntax-highlighting.md | 79 -------------- docs/src/configuring_zed__configuring_vim.md | 6 +- docs/src/feedback.md | 6 +- 11 files changed, 6 insertions(+), 567 deletions(-) delete mode 100644 docs/old/backend-development.md delete mode 100644 docs/old/building-zed.md delete mode 100644 docs/old/company-and-vision.md delete mode 100644 docs/old/design-tools.md delete mode 100644 docs/old/index.md delete mode 100644 docs/old/local-collaboration.md delete mode 100644 docs/old/release-process.md delete mode 100644 docs/old/tools.md delete mode 100644 docs/old/zed/syntax-highlighting.md diff --git a/docs/old/backend-development.md b/docs/old/backend-development.md deleted file mode 100644 index 59100d3628..0000000000 --- a/docs/old/backend-development.md +++ /dev/null @@ -1,52 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Developing Zed's Backend - -Zed's backend consists of the following components: - -- The Zed.dev web site - - implemented in the [`zed.dev`](https://github.com/zed-industries/zed.dev) repository - - hosted on [Vercel](https://vercel.com/zed-industries/zed-dev). -- The Zed Collaboration server - - implemented in the [`crates/collab`](https://github.com/zed-industries/zed/tree/main/crates/collab) directory of the main `zed` repository - - hosted on [DigitalOcean](https://cloud.digitalocean.com/projects/6c680a82-9d3b-4f1a-91e5-63a6ca4a8611), using Kubernetes -- The Zed Postgres database - - defined via migrations in the [`crates/collab/migrations`](https://github.com/zed-industries/zed/tree/main/crates/collab/migrations) directory - - hosted on DigitalOcean - ---- - -## Local Development - -Here's some things you need to develop backend code locally. - -### Dependencies - -- **Postgres** - download [Postgres.app](https://postgresapp.com). - -### Setup - -1. Check out the `zed` and `zed.dev` repositories into a common parent directory -2. Set the `GITHUB_TOKEN` environment variable to one of your GitHub personal access tokens (PATs). - - - You can create a PAT [here](https://github.com/settings/tokens). - - You may want to add something like this to your `~/.zshrc`: - - ``` - export GITHUB_TOKEN= - ``` - -3. In the `zed.dev` directory, run `npm install` to install dependencies. -4. In the `zed directory`, run `script/bootstrap` to set up the database -5. In the `zed directory`, run `foreman start` to start both servers - ---- - -## Production Debugging - -### Datadog - -Zed uses Datadog to collect metrics and logs from backend services. The Zed organization lives within Datadog's _US5_ [site](https://docs.datadoghq.com/getting_started/site/), so it can be accessed at [us5.datadoghq.com](https://us5.datadoghq.com). Useful things to look at in Datadog: - -- The [Logs](https://us5.datadoghq.com/logs) page shows logs from Zed.dev and the Collab server, and the internals of Zed's Kubernetes cluster. -- The [collab metrics dashboard](https://us5.datadoghq.com/dashboard/y2d-gxz-h4h/collab?from_ts=1660517946462&to_ts=1660604346462&live=true) shows metrics about the running collab server diff --git a/docs/old/building-zed.md b/docs/old/building-zed.md deleted file mode 100644 index 79db4a36c9..0000000000 --- a/docs/old/building-zed.md +++ /dev/null @@ -1,104 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Building Zed - -How to build Zed from source for the first time. - -## Prerequisites - -- Be added to the GitHub organization -- Be added to the Vercel team - -## Process - -Expect this to take 30min to an hour! Some of these steps will take quite a while based on your connection speed, and how long your first build will be. - -1. Install the [GitHub CLI](https://cli.github.com/): - - `brew install gh` -1. Clone the `zed` repo - - `gh repo clone zed-industries/zed` -1. Install Xcode from the macOS App Store -1. Install Xcode command line tools - - `xcode-select --install` - - If xcode-select --print-path prints /Library/Developer/CommandLineTools… run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` -1. Install [Postgres](https://postgresapp.com) -1. Install rust/rustup - - `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` -1. Install the wasm toolchain - - `rustup target add wasm32-wasi` -1. Install Livekit & Foreman - - `brew install livekit` - - `brew install foreman` -1. Generate an GitHub API Key - - Go to https://github.com/settings/tokens and Generate new token - - GitHub currently provides two kinds of tokens: - - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected - Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories - - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - - Keep the token in the browser tab/editor for the next two steps -1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` -1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: - ``` - cd .. - git clone https://github.com/zed-industries/zed.dev - cd zed.dev && npm install - npm install -g vercel - ``` -1. Link your zed.dev project to Vercel - - `vercel link` - - Select the `zed-industries` team. If you don't have this get someone on the team to add you to it. - - Select the `zed.dev` project -1. Run `vercel pull` to pull down the environment variables and project info from Vercel -1. Open Postgres.app -1. From `./path/to/zed/`: - - Run: - - `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap` - - Replace `{yourGithubAPIToken}` with the API token you generated above. - - You don't need to include the GITHUB_TOKEN if you exported it above. - - Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault). - - If you get: - - ```bash - Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! - Please create a new installation in /opt/homebrew using one of the - "Alternative Installs" from: - https://docs.brew.sh/Installation - ``` - - In that case try: - - `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` - - If Homebrew is not in your PATH: - - Replace `{username}` with your home folder name (usually your login name) - - `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/{username}/.zprofile` - - `eval "$(/opt/homebrew/bin/brew shellenv)"` -1. To run the Zed app: - - If you are working on zed: - - `cargo run` - - If you are just using the latest version, but not working on zed: - - `cargo run --release` - - If you need to run the collaboration server locally: - - `script/zed-local` - -## Troubleshooting - -### `error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)` - -- Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` - -### `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` - -### Seeding errors during `script/bootstrap` runs - -``` -seeding database... -thread 'main' panicked at 'failed to deserialize github user from 'https://api.github.com/orgs/zed-industries/teams/staff/members': reqwest::Error { kind: Decode, source: Error("invalid type: map, expected a sequence", line: 1, column: 0) }', crates/collab/src/bin/seed.rs:111:10 -``` - -Wrong permissions for `GITHUB_TOKEN` token used, the token needs to be able to read from private repos. -For Classic GitHub Tokens, that required OAuth scope `repo` (seacrh the scope name above for more details) - -Same command - -`sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer` - -### If you experience errors that mention some dependency is using unstable features - -Try `cargo clean` and `cargo build` diff --git a/docs/old/company-and-vision.md b/docs/old/company-and-vision.md deleted file mode 100644 index 389d0774a7..0000000000 --- a/docs/old/company-and-vision.md +++ /dev/null @@ -1,34 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Company & Vision - -## Vision - -Our goal is to make Zed the primary tool software teams use to collaborate. - -To do this, Zed will... - -* Make collaboration a first-class feature of the code authoring environment. -* Enable text-based conversations about any piece of text, independent of whether/when it was committed to version control. -* Make it smooth to edit and discuss code with teammates in real time. -* Make it easy to recall past conversations any area of the code. - -We believe the best way to make collaboration amazing is to build it into a new editor rather than retrofitting an existing editor. This means that in order for a team to adopt Zed for collaboration, each team member will need to adopt it as their editor as well. - -For this reason, we need to deliver a clearly superior experience as a single-user code editor in addition to being an excellent collaboration tool. This will take time, but we believe the dominance of VS Code demonstrates that it's possible for a single tool to capture substantial market share. We can proceed incrementally, capturing one team at a time and gradually transitioning conversations away from GitHub. - -## Zed Values - -Everyone wants to work quickly and have a lot of users. What are we unwilling to sacrifice in pursuit of those goals? - -- **Performance.** Speed is core to our brand and value proposition. It's important that we consistently deliver a response in less than 8ms on modern hardware for fine-grained actions. Coarse-grained actions should render feedback within 50ms. We consider the performance goals of the product at all times, and take the time to ensure our code meets them with reasonable usage. Once we have met our goals, we assess the impact vs effort of further performance investment and know when to say when. We measure our performance in the field and make an effort to maintain or improve real-world performance and promptly address regressions. - -- **Craftsmanship.** Zed is a premium product, and we put care into design and user experience. We can always cut scope, but what we do ship should be quality. Incomplete is okay, so long as we're executing on a coherent subset well. Half-baked, unintuitive, or broken is not okay. - -- **Shipping.** Knowledge matters only in as much as it drives results. We're here to build a real product in the real world. We care a lot about the experience of developing Zed, but we care about the user's experience more. - -- **Code quality.** This enables craftsmanship. Nobody is creative in a trash heap, and we're willing to dedicate time to keep our codebase clean. If we're spending no time refactoring, we are likely underinvesting. When we realize a design flaw, we assess its centrality to the rest of the system and consider budgeting time to address it. If we're spending all of our time refactoring, we are likely either overinvesting or paying off debt from past underinvestment. It's up to each engineer to allocate a reasonable refactoring budget. We shouldn't be navel gazing, but we also shouldn't be afraid to invest. - -- **Pairing.** Zed depends on regular pair programming to promote cohesion on our remote team. We believe pairing is a powerful substitute for beuracratic management, excessive documentation, and tedious code review. Nobody has to pair all day, every day, but everyone is responsible for pairing at least 2 hours a week with a variety of other engineers. If anyone wants to pair all day every day, that is explicitly endorsed and credited. If pairing temporarily reduces our throughput due to working on one thing instead of two, we trust that it will pay for itself in the long term by increasing our velocity and allowing us to more effectively grow our team. - -- **Long-term thinking.** The Zed vision began several years ago, and we expect Zed to be around many years from today. We must always be mindful to avoid overengineering for the future, but we should also keep the long-term in mind. Are we building a system our future selves would want to work on in 5 years? diff --git a/docs/old/design-tools.md b/docs/old/design-tools.md deleted file mode 100644 index 70e5459811..0000000000 --- a/docs/old/design-tools.md +++ /dev/null @@ -1,74 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Design Tools & Links - -Generally useful tools and resources for design. - -## General - -[Names of Signs & Symbols](https://www.prepressure.com/fonts/basics/character-names#curlybrackets) - -[The Noun Project](https://thenounproject.com/) - Icons for everything, attempts to describe all of human language visually. - -[SVG Repo](https://www.svgrepo.com/) - Open-licensed SVG Vector and Icons - -[Font Awsesome](https://fontawesome.com/) - High quality icons, has been around for many years. - ---- - -## Color - -[Opacity/Transparency Hex Values](https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4) - -[Color Ramp Generator](https://lyft-colorbox.herokuapp.com) - -[Designing a Comprehensive Color System -](https://www.rethinkhq.com/videos/designing-a-comprehensive-color-system-for-lyft) - [Linda Dong](https://twitter.com/lindadong) - ---- - -## Figma & Plugins - -[Figma Plugins for Designers](https://www.uiprep.com/blog/21-best-figma-plugins-for-designers-in-2021) - -[Icon Resizer](https://www.figma.com/community/plugin/739117729229117975/Icon-Resizer) - -[Code Syntax Highlighter](https://www.figma.com/community/plugin/938793197191698232/Code-Syntax-Highlighter) - -[Proportional Scale](https://www.figma.com/community/plugin/756895186298946525/Proportional-Scale) - -[LilGrid](https://www.figma.com/community/plugin/795397421598343178/LilGrid) - -Organize your selection into a grid. - -[Automator](https://www.figma.com/community/plugin/1005114571859948695/Automator) - -Build photoshop-style batch actions to automate things. - -[Figma Tokens](https://www.figma.com/community/plugin/843461159747178978/Figma-Tokens) - -Use tokens in Figma and generate JSON from them. - ---- - -## Design Systems - -### Naming - -[Naming Design Tokens](https://uxdesign.cc/naming-design-tokens-9454818ed7cb) - -### Storybook - -[Collaboration with design tokens and storybook](https://zure.com/blog/collaboration-with-design-tokens-and-storybook/) - -### Example DS Documentation - -[Tailwind CSS Documentation](https://tailwindcss.com/docs/container) - -[Material Design Docs](https://material.io/design/color/the-color-system.html#color-usage-and-palettes) - -[Carbon Design System Docs](https://www.carbondesignsystem.com) - -[Adobe Spectrum](https://spectrum.adobe.com/) - - Great documentation, like [Color System](https://spectrum.adobe.com/page/color-system/) and [Design Tokens](https://spectrum.adobe.com/page/design-tokens/). - - A good place to start if thinking about building a design system. diff --git a/docs/old/index.md b/docs/old/index.md deleted file mode 100644 index 0fc5bfbc89..0000000000 --- a/docs/old/index.md +++ /dev/null @@ -1,14 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Welcome to Zed - -Welcome! These internal docs are a work in progress. You can contribute to them by submitting a PR directly! - -## Contents - -- [The Company](./company-and-vision.md) -- [Tools We Use](./tools.md) -- [Building Zed](./building-zed.md) -- [Release Process](./release-process.md) -- [Backend Development](./backend-development.md) -- [Design Tools & Links](./design-tools.md) diff --git a/docs/old/local-collaboration.md b/docs/old/local-collaboration.md deleted file mode 100644 index 7bbbda3645..0000000000 --- a/docs/old/local-collaboration.md +++ /dev/null @@ -1,22 +0,0 @@ -# Local Collaboration - -## Setting up the local collaboration server - -### Setting up for the first time? - -1. Make sure you have livekit installed (`brew install livekit`) -1. Install [Postgres](https://postgresapp.com) and run it. -1. Then, from the root of the repo, run `script/bootstrap`. - -### Have a db that is out of date? / Need to migrate? - -1. Make sure you have livekit installed (`brew install livekit`) -1. Try `cd crates/collab && cargo run -- migrate` from the root of the repo. -1. Run `script/seed-db` - -## Testing collab locally - -1. Run `foreman start` from the root of the repo. -1. In another terminal run `script/zed-local`. -1. Two copies of Zed will open. Add yourself as a contact in the one that is not you. -1. Start a collaboration session as normal with any open project. diff --git a/docs/old/release-process.md b/docs/old/release-process.md deleted file mode 100644 index fc237d9590..0000000000 --- a/docs/old/release-process.md +++ /dev/null @@ -1,100 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Zed's Release Process - -The process to create and ship a Zed release - -## Overview - -### Release Channels - -Users of Zed can choose between two _release channels_ - 'Stable' and 'Preview'. Most people use Stable, but Preview exists so that the Zed team and other early-adopters can test new features before they are released to our general user-base. - -### Weekly (Minor) Releases - -We normally publish new releases of Zed on Wednesdays, for both the Stable and Preview channels. For each of these releases, we bump Zed's _minor_ version number. - -For the Preview channel, we build the new release based on what's on the `main` branch. For the Stable channel, we build the new release based on the last Preview release. - -### Hotfix (Patch) Releases - -When we find a _regression_ in Zed (a bug that wasn't present in an earlier version), or find a significant bug in a newly-released feature, we typically publish a hotfix release. For these releases, we bump Zed's _patch_ version number. - -### Server Deployments - -Often, changes in the Zed app require corresponding changes in the `collab` server. At the currente stage of our copmany, we don't attempt to keep our server backwards-compatible with older versions of the app. Instead, when making a change, we simply bump Zed's _protocol version_ number (in the `rpc` crate), which causes the server to recognize that it isn't compatible with earlier versions of the Zed app. - -This means that when releasing a new version of Zed that has changes to the RPC protocol, we need to deploy a new version of the `collab` server at the same time. - -## Instructions - -### Publishing a Minor Release - -1. Announce your intent to publish a new version in Discord. This gives other people a chance to raise concerns or postpone the release if they want to get something merged before publishing a new version. -1. Open your terminal and `cd` into your local copy of Zed. Checkout `main` and perform a `git pull` to ensure you have the latest version. -1. Run the following command, which will update two git branches and two git tags (one for each release channel): - - ``` - script/bump-zed-minor-versions - ``` - -1. The script will make local changes only, and print out a shell command that you can use to push all of these branches and tags. -1. Pushing the two new tags will trigger two CI builds that, when finished, will create two draft releases (Stable and Preview) containing `Zed.dmg` files. -1. Now you need to write the release notes for the Stable and Preview releases. For the Stable release, you can just copy the release notes from the last week's Preview release, plus any hotfixes that were published on the Preview channel since then. Some of the hotfixes may not be relevant for the Stable release notes, if they were fixing bugs that were only present in Preview. -1. For the Preview release, you can retrieve the list of changes by running this command (make sure you have at least `Node 18` installed): - - ``` - GITHUB_ACCESS_TOKEN=your_access_token script/get-preview-channel-changes - ``` - -1. The script will list all the merged pull requests and you can use it as a reference to write the release notes. If there were protocol changes, it will also emit a warning. -1. Once CI creates the draft releases, add each release's notes and save the drafts. -1. If there have been server-side changes since the last release, you'll need to re-deploy the `collab` server. See below. -1. Before publishing, download the Zed.dmg and smoke test it to ensure everything looks good. - -### Publishing a Patch Release - -1. Announce your intent to publish a new patch version in Discord. -1. Open your terminal and `cd` into your local copy of Zed. Check out the branch corresponding to the release channel where the fix is needed. For example, if the fix is for a bug in Stable, and the current stable version is `0.63.0`, then checkout the branch `v0.63.x`. Run `git pull` to ensure your branch is up-to-date. -1. Find the merge commit where your bug-fix landed on `main`. You can browse the merged pull requests on main by running `git log main --grep Merge`. -1. Cherry-pick those commits onto the current release branch: - - ``` - git cherry-pick -m1 - ``` - -1. Run the following command, which will bump the version of Zed and create a new tag: - - ``` - script/bump-zed-patch-version - ``` - -1. The script will make local changes only, and print out a shell command that you can use to push all the branch and tag. -1. Pushing the new tag will trigger a CI build that, when finished, will create a draft release containing a `Zed.dmg` file. -1. Once the draft release is created, fill in the release notes based on the bugfixes that you cherry-picked. -1. If any of the bug-fixes require server-side changes, you'll need to re-deploy the `collab` server. See below. -1. Before publishing, download the Zed.dmg and smoke test it to ensure everything looks good. -1. Clicking publish on the release will cause old Zed instances to auto-update and the Zed.dev releases page to re-build and display the new release. - -### Deploying the Server - -1. Deploying the server is a two-step process that begins with pushing a tag. 1. Check out the commit you'd like to deploy. Often it will be the head of `main`, but could be on any branch. -1. Run the following script, which will bump the version of the `collab` crate and create a new tag. The script takes an argument `minor` or `patch`, to indicate how to increment the version. If you're releasing new features, use `minor`. If it's just a bugfix, use `patch` - - ``` - script/bump-collab-version patch - ``` - -1. This script will make local changes only, and print out a shell command that you can use to push the branch and tag. -1. Pushing the new tag will trigger a CI build that, when finished will upload a new versioned docker image to the DigitalOcean docker registry. -1. If needing a migration: - - First check that the migration is valid. The database serves both preview and stable simultaneously, so new columns need to have defaults and old tables or columns can't be dropped. - - Then use `script/deploy-migration` (production, staging, preview, nightly). ex: `script/deploy-migration preview 0.19.0` - - If there is an 'Error: container is waiting to start', you can review logs manually with: `kubectl --namespace logs ` to make sure the mgiration ran successfully. -1. Once that CI job completes, you will be able to run the following command to deploy that docker image. The script takes two arguments: an environment (`production`, `preview`, or `staging`), and a version number (e.g. `0.10.1`): - -``` -script/deploy preview 0.10.1 -``` - -1. This command should complete quickly, updating the given environment to use the given version number of the `collab` server. \ No newline at end of file diff --git a/docs/old/tools.md b/docs/old/tools.md deleted file mode 100644 index 56c3c0c963..0000000000 --- a/docs/old/tools.md +++ /dev/null @@ -1,82 +0,0 @@ -[⬅ Back to Index](./index.md) - -# Tools - -Tools to get started at Zed. Work in progress, submit a PR to add any missing tools here! - ---- - -## Everyday Tools - -### Calendar - -To gain access to company calendar, visit [this link](https://calendar.google.com/calendar/u/0/r?cid=Y18xOGdzcGE1aG5wdHJocGRoNWtlb2tlbWxzc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t). - -If you would like the company calendar to be synced with a calendar application (Apple Calendar, etc.): - -- Add your company account (i.e `joseph@zed.dev`) to your calendar application -- Visit [this link](https://calendar.google.com/calendar/u/0/syncselect), check `Zed Industries (Read Only)` under `Shared Calendars`, and save it. - -### 1Password - -We have a shared company 1Password with all of our credentials. To gain access: - -1. Go to [zed-industries.1password.com](https://zed-industries.1password.com). -1. Sign in with your `@zed.dev` email address. -1. Make your account and let an admin know you've signed up. -1. Once they approve your sign up, you'll have access to all of the company credentials. - -### Slack - -Have a team member add you to the [Zed Industries](https://zed-industries.slack.com/) slack. - -### Discord - -We have a Discord community. You can use [this link](https://discord.gg/SSD9eJrn6s) to join. **!Don't share this link, this is specifically for team members!** - -Once you have joined the community, let a team member know and we can add your correct role. - ---- - -## Engineering - -### Github - -For now, all the Zed source code lives on [Github](https://github.com/zed-industries). A founder will need to add you to the team and set up the appropriate permissions. - -Useful repos: -- [zed-industries/zed](https://github.com/zed-industries/zed) - Zed source -- [zed-industries/zed.dev](https://github.com/zed-industries/zed.dev) - Zed.dev site and collab API -- [zed-industries/docs](https://github.com/zed-industries/docs) - Zed public docs -- [zed-industries/community](https://github.com/zed-industries/community) - Zed community feedback & discussion - -### Vercel - -We use Vercel for all of our web deployments and some backend things. If you sign up with your `@zed.dev` email you should be invited to join the team automatically. If not, ask a founder to invite you to the Vercel team. - -### Environment Variables - -You can get access to many of our shared environment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. - -For Vercel, go to `settings` -> `Environment Variables` (either on the entire org, or on a specific project depending on where it is shared.) For a given Vercel project if you have their CLI installed you can use `vercel pull` or `vercel env` to pull values down directly. More on those in their [CLI docs](https://vercel.com/docs/cli/env). - ---- - -## Design - -### Figma - -We use Figma for all of our design work. To gain access: - -1. Use [this link](https://www.figma.com/team_invite/redeem/Xg4RcNXHhwP5netIvVBmKQ) to join the Figma team. -1. You should now have access to all of the company files. -1. You should go to the team page and "favorite" (star) any relevant projects so they show up in your sidebar. -1. Download the [Figma app](https://www.figma.com/downloads/) for easier access on desktop. - -### Campsite - -We use Campsite to review and discuss designs. To gain access: - -1. Download the [Campsite app](https://campsite.design/desktop/download). -1. Open it and sign in with your `@zed.dev` email address. -1. You can access our company campsite directly: [app.campsite.design/zed](https://app.campsite.design/zed) diff --git a/docs/old/zed/syntax-highlighting.md b/docs/old/zed/syntax-highlighting.md deleted file mode 100644 index 846bf96876..0000000000 --- a/docs/old/zed/syntax-highlighting.md +++ /dev/null @@ -1,79 +0,0 @@ -# Syntax Highlighting in Zed - -This doc is a work in progress! - -## Defining syntax highlighting rules - -We use tree-sitter queries to match certain properties to highlight. - -### Simple Example: - -```scheme -(property_identifier) @property -``` - -```ts -const font: FontFamily = { - weight: "normal", - underline: false, - italic: false, -} -``` - -Match a property identifier and highlight it using the identifier `@property`. In the above example, `weight`, `underline`, and `italic` would be highlighted. - -### Complex example: - -```scheme -(_ - return_type: (type_annotation - [ - (type_identifier) @type.return - (generic_type - name: (type_identifier) @type.return) - ])) -``` - -```ts -function buildDefaultSyntax(colorScheme: Theme): Partial { - // ... -} -``` - -Match a function return type, and highlight the type using the identifier `@type.return`. In the above example, `Partial` would be highlighted. - -### Example - Typescript - -Here is an example portion of our `highlights.scm` for TypeScript: - -```scheme -; crates/zed/src/languages/typescript/highlights.scm - -; Variables - -(identifier) @variable - -; Properties - -(property_identifier) @property - -; Function and method calls - -(call_expression - function: (identifier) @function) - -(call_expression - function: (member_expression - property: (property_identifier) @function.method)) - -; Function and method definitions - -(function - name: (identifier) @function) -(function_declaration - name: (identifier) @function) -(method_definition - name: (property_identifier) @function.method) - -; ... -``` diff --git a/docs/src/configuring_zed__configuring_vim.md b/docs/src/configuring_zed__configuring_vim.md index ddef07e3c2..4058fa16ff 100644 --- a/docs/src/configuring_zed__configuring_vim.md +++ b/docs/src/configuring_zed__configuring_vim.md @@ -5,7 +5,7 @@ Zed includes a vim emulation layer known as “vim mode”. This document aims t ### Philosophy Vim mode in Zed is supposed to primarily "do what you expect": it mostly tries to copy vim exactly, but will use Zed-specific functionality when available to make things smoother. -This means Zed will never be 100% vim compatible, but should be 100% vim familiar! We expect that our vim mode already copes with 90% of your workflow, and we'd like to keep improving it. If you find things that you can’t yet do in vim mode, but which you rely on in your current workflow, please leave feedback in the editor itself (`:feedback`), or [file an issue](https://github.com/zed-industries/community). +This means Zed will never be 100% vim compatible, but should be 100% vim familiar! We expect that our vim mode already copes with 90% of your workflow, and we'd like to keep improving it. If you find things that you can’t yet do in vim mode, but which you rely on in your current workflow, please leave feedback in the editor itself (`:feedback`), or [file an issue](https://github.com/zed-industries/zed). ### Zed-specific features Zed is built on a modern foundation that (among other things) uses tree-sitter to understand the content of the file you're editing, and supports multiple cursors out of the box. @@ -82,7 +82,7 @@ For vim-specific shortcuts, you may find the following template a good place to You can see the bindings that are enabled by default in vim mode [here](https://zed.dev/ref/vim.json). -The details of the context are a little out of scope for this doc, but suffice to say that `menu` is true when a menu is open (e.g. the completions menu), `VimWaiting` is true after you type `f` or `t` when we’re waiting for a new key (and you probably don’t want bindings to happen). Please reach out on [Github](https://github.com/zed-industries/community) if you want help making a key bindings work. +The details of the context are a little out of scope for this doc, but suffice to say that `menu` is true when a menu is open (e.g. the completions menu), `VimWaiting` is true after you type `f` or `t` when we’re waiting for a new key (and you probably don’t want bindings to happen). Please reach out on [GitHub](https://github.com/zed-industries/zed) if you want help making a key bindings work. ### Command palette @@ -90,7 +90,7 @@ Vim mode allows you to enable Zed’s command palette with `:`. This means that Additionally vim mode contains a number of aliases for popular vim commands to ensure that muscle memory works. For example `:w` will save the file. -We do not (yet) emulate the full power of vim’s command line, in particular we special case specific patterns instead of using vim's range selection syntax, and we do not support arguments to commands yet. Please reach out on [Github](https://github.com/zed-industries/community) as you find things that are missing from the command palette. +We do not (yet) emulate the full power of vim’s command line, in particular we special case specific patterns instead of using vim's range selection syntax, and we do not support arguments to commands yet. Please reach out on [GitHub](https://github.com/zed-industries/zed) as you find things that are missing from the command palette. As mentioned above, one thing to be aware of is that the regex engine is slightly different from vim's in `:%s/a/b`. diff --git a/docs/src/feedback.md b/docs/src/feedback.md index 466b9dc05e..b7424817d2 100644 --- a/docs/src/feedback.md +++ b/docs/src/feedback.md @@ -1,8 +1,8 @@ # Giving feedback -### Community repository +### Zed repository -We track our issues at [`zed-industries/community`](https://github.com/zed-industries/zed/issues). +We track our issues at [`zed-industries/zed`](https://github.com/zed-industries/zed/issues). #### Feature requests @@ -22,7 +22,7 @@ Anonymous feedback can be submitted from within Zed via the feedback editor (com ### Zed forum -Use the [community forum](https://github.com/zed-industries/zed/discussions) to ask questions and learn from one another. We will be present in the forum and answering questions as well. +Use the [Zed forum](https://github.com/zed-industries/zed/discussions) to ask questions and learn from one another. We will be present in the forum and answering questions as well. ### Email From f9170cb2394e78b94aa8c3721e4f8d9b9368ee3e Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 25 Jan 2024 00:30:26 -0500 Subject: [PATCH 11/52] Remove references to community repo --- crates/feedback/src/feedback.rs | 4 ++-- crates/feedback/src/feedback_modal.rs | 9 ++++----- docs/src/telemetry.md | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 03fd395bb4..d9d0338cd2 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -15,7 +15,7 @@ actions!( CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature, - OpenZedCommunityRepo + OpenZedRepo ] ); @@ -52,7 +52,7 @@ pub fn init(cx: &mut AppContext) { ); cx.open_url(&url); }) - .register_action(move |_, _: &OpenZedCommunityRepo, cx| { + .register_action(move |_, _: &OpenZedRepo, cx| { let url = "https://github.com/zed-industries/zed"; cx.open_url(&url); }); diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 99c96fe880..4bb97ca3a2 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -19,7 +19,7 @@ use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip}; use util::ResultExt; use workspace::{ModalView, Toast, Workspace}; -use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo}; +use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo}; // For UI testing purposes const SEND_SUCCESS_IN_DEV_MODE: bool = true; @@ -417,8 +417,7 @@ impl Render for FeedbackModal { "Submit" }; - let open_community_repo = - cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo))); + let open_zed_repo = cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedRepo))); v_flex() .elevation_3(cx) @@ -485,12 +484,12 @@ impl Render for FeedbackModal { .justify_between() .gap_1() .child( - Button::new("community_repository", "Community Repository") + Button::new("zed_repository", "Zed Repository") .style(ButtonStyle::Transparent) .icon(IconName::ExternalLink) .icon_position(IconPosition::End) .icon_size(IconSize::Small) - .on_click(open_community_repo), + .on_click(open_zed_repo), ) .child( h_flex() diff --git a/docs/src/telemetry.md b/docs/src/telemetry.md index ab10d380aa..966424dd48 100644 --- a/docs/src/telemetry.md +++ b/docs/src/telemetry.md @@ -144,4 +144,4 @@ The telemetry settings can also be configured via the `welcome` screen, which ca ### Concerns and Questions -If you have concerns about telemetry, please feel free to open issues in our [community repository](https://github.com/zed-industries/zed/issues/new/choose). +If you have concerns about telemetry, please feel free to open issues in our [Zed repository](https://github.com/zed-industries/zed/issues/new/choose). From 5fcc75be1fe614309fde0dbb8c112eff82415b62 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Thu, 25 Jan 2024 01:03:53 -0500 Subject: [PATCH 12/52] gpui: Add hello world example (#6465) Added hello world example to the gpui crate copied from https://gpui.rs Release Notes: - N/A --- crates/gpui/examples/hello_world.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 crates/gpui/examples/hello_world.rs diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs new file mode 100644 index 0000000000..736fd14450 --- /dev/null +++ b/crates/gpui/examples/hello_world.rs @@ -0,0 +1,29 @@ +use gpui::*; + +struct HelloWorld { + text: SharedString, +} + +impl Render for HelloWorld { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div() + .flex() + .bg(rgb(0x2e7d32)) + .size_full() + .justify_center() + .items_center() + .text_xl() + .text_color(rgb(0xffffff)) + .child(format!("Hello, {}!", &self.text)) + } +} + +fn main() { + App::new().run(|cx: &mut AppContext| { + cx.open_window(WindowOptions::default(), |cx| { + cx.new_view(|_cx| HelloWorld { + text: "World".into(), + }) + }); + }); +} From 01424a62ea724938b7db4487215e8f9d7815c71f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Jan 2024 22:47:27 -0700 Subject: [PATCH 13/52] Allow prompts to have detail, and use for good Make channel panel errors louder --- crates/assistant/src/assistant_panel.rs | 1 + crates/auto_update/src/auto_update.rs | 3 +- crates/collab_ui/src/collab_panel.rs | 64 +++++++++++++------ .../src/collab_panel/channel_modal.rs | 8 +-- crates/feedback/src/feedback.rs | 3 +- crates/feedback/src/feedback_modal.rs | 4 +- crates/gpui/src/platform.rs | 8 ++- crates/gpui/src/platform/mac/window.rs | 11 +++- crates/gpui/src/platform/test/window.rs | 1 + crates/gpui/src/window.rs | 5 +- crates/project_panel/src/project_panel.rs | 1 + crates/rpc/proto/zed.proto | 1 + crates/search/src/project_search.rs | 1 + crates/workspace/src/notifications.rs | 42 +++++++++++- crates/workspace/src/pane.rs | 17 +++-- crates/workspace/src/workspace.rs | 46 +++++++------ crates/zed/src/zed.rs | 11 ++-- 17 files changed, 162 insertions(+), 65 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b2c539fcc2..3fcbb9a3c9 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2959,6 +2959,7 @@ impl InlineAssistant { cx.prompt( PromptLevel::Info, prompt_text.as_str(), + None, &["Continue", "Cancel"], ) })?; diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index eb78ff3a4c..08e9e38dd5 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -130,7 +130,8 @@ pub fn check(_: &Check, cx: &mut WindowContext) { } else { drop(cx.prompt( gpui::PromptLevel::Info, - "Auto-updates disabled for non-bundled app.", + "Could not check for updates", + Some("Auto-updates disabled for non-bundled app."), &["Ok"], )); } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 656000783b..e0244b5e32 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -22,7 +22,10 @@ use gpui::{ }; use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev}; use project::{Fs, Project}; -use rpc::proto::{self, PeerId}; +use rpc::{ + proto::{self, PeerId}, + ErrorCode, ErrorExt, +}; use serde_derive::{Deserialize, Serialize}; use settings::Settings; use smallvec::SmallVec; @@ -35,7 +38,7 @@ use ui::{ use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - notifications::{NotifyResultExt, NotifyTaskExt}, + notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt}, Workspace, }; @@ -879,7 +882,7 @@ impl CollabPanel { .update(cx, |workspace, cx| { let app_state = workspace.app_state().clone(); workspace::join_remote_project(project_id, host_user_id, app_state, cx) - .detach_and_log_err(cx); + .detach_and_prompt_err("Failed to join project", cx, |_, _| None); }) .ok(); })) @@ -1017,7 +1020,12 @@ impl CollabPanel { ) }) }) - .detach_and_notify_err(cx) + .detach_and_prompt_err("Failed to grant write access", cx, |e, _| { + match e.error_code() { + ErrorCode::NeedsCla => Some("This user has not yet signed the CLA at https://zed.dev/cla.".into()), + _ => None, + } + }) }), ) } else if role == proto::ChannelRole::Member { @@ -1038,7 +1046,7 @@ impl CollabPanel { ) }) }) - .detach_and_notify_err(cx) + .detach_and_prompt_err("Failed to revoke write access", cx, |_, _| None) }), ) } else { @@ -1258,7 +1266,11 @@ impl CollabPanel { app_state, cx, ) - .detach_and_log_err(cx); + .detach_and_prompt_err( + "Failed to join project", + cx, + |_, _| None, + ); } } ListEntry::ParticipantScreen { peer_id, .. } => { @@ -1432,7 +1444,7 @@ impl CollabPanel { fn leave_call(cx: &mut WindowContext) { ActiveCall::global(cx) .update(cx, |call, cx| call.hang_up(cx)) - .detach_and_log_err(cx); + .detach_and_prompt_err("Failed to hang up", cx, |_, _| None); } fn toggle_contact_finder(&mut self, cx: &mut ViewContext) { @@ -1534,11 +1546,11 @@ impl CollabPanel { cx: &mut ViewContext, ) { if let Some(clipboard) = self.channel_clipboard.take() { - self.channel_store.update(cx, |channel_store, cx| { - channel_store - .move_channel(clipboard.channel_id, Some(to_channel_id), cx) - .detach_and_log_err(cx) - }) + self.channel_store + .update(cx, |channel_store, cx| { + channel_store.move_channel(clipboard.channel_id, Some(to_channel_id), cx) + }) + .detach_and_prompt_err("Failed to move channel", cx, |_, _| None) } } @@ -1610,7 +1622,12 @@ impl CollabPanel { "Are you sure you want to remove the channel \"{}\"?", channel.name ); - let answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]); + let answer = cx.prompt( + PromptLevel::Warning, + &prompt_message, + None, + &["Remove", "Cancel"], + ); cx.spawn(|this, mut cx| async move { if answer.await? == 0 { channel_store @@ -1631,7 +1648,12 @@ impl CollabPanel { "Are you sure you want to remove \"{}\" from your contacts?", github_login ); - let answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]); + let answer = cx.prompt( + PromptLevel::Warning, + &prompt_message, + None, + &["Remove", "Cancel"], + ); cx.spawn(|_, mut cx| async move { if answer.await? == 0 { user_store @@ -1641,7 +1663,7 @@ impl CollabPanel { } anyhow::Ok(()) }) - .detach_and_log_err(cx); + .detach_and_prompt_err("Failed to remove contact", cx, |_, _| None); } fn respond_to_contact_request( @@ -1654,7 +1676,7 @@ impl CollabPanel { .update(cx, |store, cx| { store.respond_to_contact_request(user_id, accept, cx) }) - .detach_and_log_err(cx); + .detach_and_prompt_err("Failed to respond to contact request", cx, |_, _| None); } fn respond_to_channel_invite( @@ -1675,7 +1697,7 @@ impl CollabPanel { .update(cx, |call, cx| { call.invite(recipient_user_id, Some(self.project.clone()), cx) }) - .detach_and_log_err(cx); + .detach_and_prompt_err("Call failed", cx, |_, _| None); } fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { @@ -1691,7 +1713,7 @@ impl CollabPanel { Some(handle), cx, ) - .detach_and_log_err(cx) + .detach_and_prompt_err("Failed to join channel", cx, |_, _| None) } fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { @@ -1704,7 +1726,7 @@ impl CollabPanel { panel.update(cx, |panel, cx| { panel .select_channel(channel_id, None, cx) - .detach_and_log_err(cx); + .detach_and_notify_err(cx); }); } }); @@ -1981,7 +2003,7 @@ impl CollabPanel { .update(cx, |channel_store, cx| { channel_store.move_channel(dragged_channel.id, None, cx) }) - .detach_and_log_err(cx) + .detach_and_prompt_err("Failed to move channel", cx, |_, _| None) })) }) } @@ -2257,7 +2279,7 @@ impl CollabPanel { .update(cx, |channel_store, cx| { channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) }) - .detach_and_log_err(cx) + .detach_and_prompt_err("Failed to move channel", cx, |_, _| None) })) .child( ListItem::new(channel_id as usize) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 3d7facf2e8..891c609316 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -14,7 +14,7 @@ use rpc::proto::channel_member; use std::sync::Arc; use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing}; use util::TryFutureExt; -use workspace::{notifications::NotifyTaskExt, ModalView}; +use workspace::{notifications::DetachAndPromptErr, ModalView}; actions!( channel_modal, @@ -498,7 +498,7 @@ impl ChannelModalDelegate { cx.notify(); }) }) - .detach_and_notify_err(cx); + .detach_and_prompt_err("Failed to update role", cx, |_, _| None); Some(()) } @@ -530,7 +530,7 @@ impl ChannelModalDelegate { cx.notify(); }) }) - .detach_and_notify_err(cx); + .detach_and_prompt_err("Failed to remove member", cx, |_, _| None); Some(()) } @@ -556,7 +556,7 @@ impl ChannelModalDelegate { cx.notify(); }) }) - .detach_and_notify_err(cx); + .detach_and_prompt_err("Failed to invite member", cx, |_, _| None); } fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext>) { diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 03fd395bb4..4a92a307de 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -31,7 +31,8 @@ pub fn init(cx: &mut AppContext) { let prompt = cx.prompt( PromptLevel::Info, - &format!("Copied into clipboard:\n\n{specs}"), + "Copied into clipboard", + Some(&specs), &["OK"], ); cx.spawn(|_, _cx| async move { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 99c96fe880..446ebe6021 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -97,7 +97,7 @@ impl ModalView for FeedbackModal { return true; } - let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]); + let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", None, &["Yes", "No"]); cx.spawn(move |this, mut cx| async move { if answer.await.ok() == Some(0) { @@ -222,6 +222,7 @@ impl FeedbackModal { let answer = cx.prompt( PromptLevel::Info, "Ready to submit your feedback?", + None, &["Yes, Submit!", "No"], ); let client = cx.global::>().clone(); @@ -255,6 +256,7 @@ impl FeedbackModal { let prompt = cx.prompt( PromptLevel::Critical, FEEDBACK_SUBMISSION_ERROR_TEXT, + None, &["OK"], ); cx.spawn(|_, _cx| async move { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 3d2679dd7e..a7b71c7885 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -150,7 +150,13 @@ pub(crate) trait PlatformWindow { fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: PlatformInputHandler); fn take_input_handler(&mut self) -> Option; - fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; + fn prompt( + &self, + level: PromptLevel, + msg: &str, + detail: Option<&str>, + answers: &[&str], + ) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); fn set_edited(&mut self, edited: bool); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 8954612a4d..a244286c2d 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -772,7 +772,13 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().input_handler.take() } - fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver { + fn prompt( + &self, + level: PromptLevel, + msg: &str, + detail: Option<&str>, + answers: &[&str], + ) -> oneshot::Receiver { // macOs applies overrides to modal window buttons after they are added. // Two most important for this logic are: // * Buttons with "Cancel" title will be displayed as the last buttons in the modal @@ -808,6 +814,9 @@ impl PlatformWindow for MacWindow { }; let _: () = msg_send![alert, setAlertStyle: alert_style]; let _: () = msg_send![alert, setMessageText: ns_string(msg)]; + if let Some(detail) = detail { + let _: () = msg_send![alert, setInformativeText: ns_string(detail)]; + } for (ix, answer) in answers .iter() diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index c03384aadf..af58707c6d 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -185,6 +185,7 @@ impl PlatformWindow for TestWindow { &self, _level: crate::PromptLevel, _msg: &str, + _detail: Option<&str>, _answers: &[&str], ) -> futures::channel::oneshot::Receiver { self.0 diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index d8522837b7..618c7eb4e4 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1478,9 +1478,12 @@ impl<'a> WindowContext<'a> { &self, level: PromptLevel, message: &str, + detail: Option<&str>, answers: &[&str], ) -> oneshot::Receiver { - self.window.platform_window.prompt(level, message, answers) + self.window + .platform_window + .prompt(level, message, detail, answers) } /// Returns all available actions for the focused element. diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2def70a811..b20866fc65 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -778,6 +778,7 @@ impl ProjectPanel { let answer = cx.prompt( PromptLevel::Info, &format!("Delete {file_name:?}?"), + None, &["Delete", "Cancel"], ); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ecfab714f4..8bea2f24e0 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -209,6 +209,7 @@ enum ErrorCode { UpgradeRequired = 4; Forbidden = 5; WrongReleaseChannel = 6; + NeedsCla = 7; } message Test { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c1ebcfe0a6..0b169949b9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -746,6 +746,7 @@ impl ProjectSearchView { cx.prompt( PromptLevel::Info, prompt_text.as_str(), + None, &["Continue", "Cancel"], ) })?; diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 1b41b7040c..30d8ec9e82 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -1,8 +1,8 @@ use crate::{Toast, Workspace}; use collections::HashMap; use gpui::{ - AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render, - Task, View, ViewContext, VisualContext, WindowContext, + AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, + PromptLevel, Render, Task, View, ViewContext, VisualContext, WindowContext, }; use std::{any::TypeId, ops::DerefMut}; @@ -299,7 +299,7 @@ pub trait NotifyTaskExt { impl NotifyTaskExt for Task> where - E: std::fmt::Debug + 'static, + E: std::fmt::Debug + Sized + 'static, R: 'static, { fn detach_and_notify_err(self, cx: &mut WindowContext) { @@ -307,3 +307,39 @@ where .detach(); } } + +pub trait DetachAndPromptErr { + fn detach_and_prompt_err( + self, + msg: &str, + cx: &mut WindowContext, + f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option + 'static, + ); +} + +impl DetachAndPromptErr for Task> +where + R: 'static, +{ + fn detach_and_prompt_err( + self, + msg: &str, + cx: &mut WindowContext, + f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option + 'static, + ) { + let msg = msg.to_owned(); + cx.spawn(|mut cx| async move { + if let Err(err) = self.await { + log::error!("{err:?}"); + if let Ok(prompt) = cx.update(|cx| { + let detail = f(&err, cx) + .unwrap_or_else(|| format!("{err:?}. Please try again.", err = err)); + cx.prompt(PromptLevel::Critical, &msg, Some(&detail), &["Ok"]) + }) { + prompt.await.ok(); + } + } + }) + .detach(); + } +} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 7f164c6e69..2336230c86 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -870,7 +870,7 @@ impl Pane { items: &mut dyn Iterator>, all_dirty_items: usize, cx: &AppContext, - ) -> String { + ) -> (String, String) { /// Quantity of item paths displayed in prompt prior to cutoff.. const FILE_NAMES_CUTOFF_POINT: usize = 10; let mut file_names: Vec<_> = items @@ -894,10 +894,12 @@ impl Pane { file_names.push(format!(".. {} files not shown", not_shown_files).into()); } } - let file_names = file_names.join("\n"); - format!( - "Do you want to save changes to the following {} files?\n{file_names}", - all_dirty_items + ( + format!( + "Do you want to save changes to the following {} files?", + all_dirty_items + ), + file_names.join("\n"), ) } @@ -929,11 +931,12 @@ impl Pane { cx.spawn(|pane, mut cx| async move { if save_intent == SaveIntent::Close && dirty_items.len() > 1 { let answer = pane.update(&mut cx, |_, cx| { - let prompt = + let (prompt, detail) = Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); cx.prompt( PromptLevel::Warning, &prompt, + Some(&detail), &["Save all", "Discard all", "Cancel"], ) })?; @@ -1131,6 +1134,7 @@ impl Pane { cx.prompt( PromptLevel::Warning, CONFLICT_MESSAGE, + None, &["Overwrite", "Discard", "Cancel"], ) })?; @@ -1154,6 +1158,7 @@ impl Pane { cx.prompt( PromptLevel::Warning, &prompt, + None, &["Save", "Don't Save", "Cancel"], ) })?; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e7f5e2e61e..35072c4030 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -30,8 +30,8 @@ use gpui::{ DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, - Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, - WindowBounds, WindowContext, WindowHandle, WindowOptions, + Render, SharedString, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, + WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -1159,6 +1159,7 @@ impl Workspace { cx.prompt( PromptLevel::Warning, "Do you want to leave the current call?", + None, &["Close window and hang up", "Cancel"], ) })?; @@ -1214,7 +1215,7 @@ impl Workspace { // Override save mode and display "Save all files" prompt if save_intent == SaveIntent::Close && dirty_items.len() > 1 { let answer = workspace.update(&mut cx, |_, cx| { - let prompt = Pane::file_names_for_prompt( + let (prompt, detail) = Pane::file_names_for_prompt( &mut dirty_items.iter().map(|(_, handle)| handle), dirty_items.len(), cx, @@ -1222,6 +1223,7 @@ impl Workspace { cx.prompt( PromptLevel::Warning, &prompt, + Some(&detail), &["Save all", "Discard all", "Cancel"], ) })?; @@ -3887,13 +3889,16 @@ async fn join_channel_internal( if should_prompt { if let Some(workspace) = requesting_window { - let answer = workspace.update(cx, |_, cx| { - cx.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - ) - })?.await; + let answer = workspace + .update(cx, |_, cx| { + cx.prompt( + PromptLevel::Warning, + "Do you want to switch channels?", + Some("Leaving this call will unshare your current project."), + &["Yes, Join Channel", "Cancel"], + ) + })? + .await; if answer == Ok(1) { return Ok(false); @@ -3995,23 +4000,27 @@ pub fn join_channel( if let Some(active_window) = active_window { active_window .update(&mut cx, |_, cx| { - let message:SharedString = match err.error_code() { + let detail: SharedString = match err.error_code() { ErrorCode::SignedOut => { - "Failed to join channel\n\nPlease sign in to continue.".into() + "Please sign in to continue.".into() }, ErrorCode::UpgradeRequired => { - "Failed to join channel\n\nPlease update to the latest version of Zed to continue.".into() + "Your are running an unsupported version of Zed. Please update to continue.".into() }, ErrorCode::NoSuchChannel => { - "Failed to find channel\n\nPlease check the link and try again.".into() + "No matching channel was found. Please check the link and try again.".into() }, - ErrorCode::Disconnected => "Failed to join channel\n\nPlease check your internet connection and try again.".into(), - ErrorCode::WrongReleaseChannel => format!("Failed to join channel\n\nOther people in the channel are using the {} release of Zed, please switch to that release instead.", err.error_tag("required").unwrap_or("other")).into(), - _ => format!("Failed to join channel\n\n{}\n\nPlease try again.", err).into(), + ErrorCode::Forbidden => { + "This channel is private, and you do not have access. Please ask someone to add you and try again.".into() + }, + ErrorCode::Disconnected => "Please check your internet connection and try again.".into(), + ErrorCode::WrongReleaseChannel => format!("Others in the channel are using the {} release of Zed. Please switch to join this call.", err.error_tag("required").unwrap_or("other")).into(), + _ => format!("{}\n\nPlease try again.", err).into(), }; cx.prompt( PromptLevel::Critical, - &message, + "Failed to join channel", + Some(&detail), &["Ok"], ) })? @@ -4238,6 +4247,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) { cx.prompt( PromptLevel::Info, "Are you sure you want to restart?", + None, &["Restart", "Cancel"], ) }) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index cbb0fff6fa..f6f513a4b3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -370,16 +370,12 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View, cx: &mut ViewCo } fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { - use std::fmt::Write as _; - let app_name = cx.global::().display_name(); let version = env!("CARGO_PKG_VERSION"); - let mut message = format!("{app_name} {version}"); - if let Some(sha) = cx.try_global::() { - write!(&mut message, "\n\n{}", sha.0).unwrap(); - } + let message = format!("{app_name} {version}"); + let detail = cx.try_global::().map(|sha| sha.0.as_ref()); - let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]); + let prompt = cx.prompt(PromptLevel::Info, &message, detail, &["OK"]); cx.foreground_executor() .spawn(async { prompt.await.ok(); @@ -410,6 +406,7 @@ fn quit(_: &Quit, cx: &mut AppContext) { cx.prompt( PromptLevel::Info, "Are you sure you want to quit?", + None, &["Quit", "Cancel"], ) }) From 5c1de4ce26deb5b39671428aad5623e96784aabb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 24 Jan 2024 23:33:54 -0700 Subject: [PATCH 14/52] Try to flesh out our panic stacks more Although we now usually get the .ips file alongside a panic, when we don't it's much harder to track down where panics happen because we're not showing inlined frames. Try iterating over all symbols in a frame to see if that improves panic reporting. --- crates/zed/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5abb046165..fc8c282b42 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -543,7 +543,12 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin let mut backtrace = backtrace .frames() .iter() - .filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?))) + .flat_map(|frame| { + frame + .symbols() + .iter() + .filter_map(|frame| Some(format!("{:#}", frame.name()?))) + }) .collect::>(); // Strip out leading stack frames for rust panic-handling. From dd07d2f8a26208164267122d18119ad327689bd8 Mon Sep 17 00:00:00 2001 From: gmorenz Date: Thu, 25 Jan 2024 03:04:29 -0500 Subject: [PATCH 15/52] Update cocoa to crates.io version (#6452) https://github.com/servo/core-foundation-rs/pull/457 was released in cocoa 0.25, so this patch dependency on github can be removed. Release Notes: - N/A --- Cargo.lock | 110 +++++++++++++++++++++--------- Cargo.toml | 7 -- crates/gpui/Cargo.toml | 2 +- crates/live_kit_client/Cargo.toml | 2 +- 4 files changed, 79 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2916c5e6a4..1d7c55b720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1423,29 +1423,30 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" -source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", - "core-graphics", - "foreign-types", + "core-graphics 0.23.1", + "foreign-types 0.5.0", "libc", "objc", ] [[package]] name = "cocoa-foundation" -version = "0.1.1" -source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", - "foreign-types", "libc", "objc", ] @@ -1750,10 +1751,11 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" -source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "core-foundation-sys 0.8.3", + "core-foundation-sys 0.8.6", "libc", "uuid 0.5.1", ] @@ -1766,29 +1768,44 @@ checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" [[package]] name = "core-foundation-sys" -version = "0.8.3" -source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" version = "0.22.3" -source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", "libc", ] [[package]] name = "core-graphics-types" -version = "0.1.1" -source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] @@ -1808,8 +1825,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ "core-foundation", - "core-graphics", - "foreign-types", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "libc", ] @@ -1840,7 +1857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c" dependencies = [ "alsa", - "core-foundation-sys 0.8.3", + "core-foundation-sys 0.8.6", "coreaudio-rs", "dasp_sample", "jni 0.19.0", @@ -2639,7 +2656,7 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "core-foundation", - "core-graphics", + "core-graphics 0.22.3", "core-text", "dirs-next", "dwrote", @@ -2672,7 +2689,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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", ] [[package]] @@ -2681,6 +2719,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.2.0" @@ -3063,7 +3107,7 @@ dependencies = [ "cocoa", "collections", "core-foundation", - "core-graphics", + "core-graphics 0.22.3", "core-text", "ctor", "derive_more", @@ -3071,7 +3115,7 @@ dependencies = [ "env_logger", "etagere", "font-kit", - "foreign-types", + "foreign-types 0.3.2", "futures 0.3.28", "gpui_macros", "image", @@ -3397,7 +3441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", - "core-foundation-sys 0.8.3", + "core-foundation-sys 0.8.6", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", @@ -3981,8 +4025,8 @@ dependencies = [ "cocoa", "collections", "core-foundation", - "core-graphics", - "foreign-types", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "futures 0.3.28", "gpui", "hmac 0.12.1", @@ -4142,7 +4186,7 @@ dependencies = [ "block", "bytes 1.5.0", "core-foundation", - "foreign-types", + "foreign-types 0.3.2", "metal", "objc", ] @@ -4197,7 +4241,7 @@ dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", - "foreign-types", + "foreign-types 0.3.2", "log", "objc", ] @@ -4865,7 +4909,7 @@ checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ "bitflags 2.4.1", "cfg-if 1.0.0", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -6694,7 +6738,7 @@ checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", - "core-foundation-sys 0.8.3", + "core-foundation-sys 0.8.6", "libc", "security-framework-sys", ] @@ -6705,7 +6749,7 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ - "core-foundation-sys 0.8.3", + "core-foundation-sys 0.8.6", "libc", ] @@ -7712,7 +7756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" dependencies = [ "cfg-if 1.0.0", - "core-foundation-sys 0.8.3", + "core-foundation-sys 0.8.6", "libc", "ntapi 0.4.1", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 7acc460af8..121d42223b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,13 +165,6 @@ tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" } # wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" } -# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 -cocoa = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } -cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } -core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } -core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } -core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } - [profile.dev] split-debuginfo = "unpacked" debug = "limited" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 4cd4f5c133..e5a40cccd5 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -76,7 +76,7 @@ cbindgen = "0.26.0" media = { path = "../media" } anyhow.workspace = true block = "0.1" -cocoa = "0.24" +cocoa = "0.25" core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" diff --git a/crates/live_kit_client/Cargo.toml b/crates/live_kit_client/Cargo.toml index 65b6e222ac..65fa9352ac 100644 --- a/crates/live_kit_client/Cargo.toml +++ b/crates/live_kit_client/Cargo.toml @@ -53,7 +53,7 @@ async-trait.workspace = true block = "0.1" bytes = "1.2" byteorder = "1.4" -cocoa = "0.24" +cocoa = "0.25" core-foundation = "0.9.3" core-graphics = "0.22.3" foreign-types = "0.3" From 0c4679f8923a4fa766f7e4d2edd73601d33de27f Mon Sep 17 00:00:00 2001 From: Sharun <715417+sharunkumar@users.noreply.github.com> Date: Thu, 25 Jan 2024 03:16:42 -0500 Subject: [PATCH 16/52] bugfix: fix LineEnding for windows --- crates/text/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index fd23d3dca4..acc170a508 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2634,7 +2634,7 @@ impl Default for LineEnding { return Self::Unix; #[cfg(not(unix))] - return Self::CRLF; + return Self::Windows; } } From dcf05812c223d2a5b976d9f9e6929ff2f2c97c62 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 25 Jan 2024 09:56:14 +0100 Subject: [PATCH 17/52] Add Conrad and Thorsten to .mailmap file --- .mailmap | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.mailmap b/.mailmap index 1064df3b50..95c6553042 100644 --- a/.mailmap +++ b/.mailmap @@ -37,3 +37,8 @@ Nathan Sobo Nathan Sobo Piotr Osiewicz Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> +Conrad Irwin +Conrad Irwin +Thorsten Ball +Thorsten Ball +Thorsten Ball From d17d37ff616818184e14120c4d7727483adc12ea Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 25 Jan 2024 11:04:43 +0100 Subject: [PATCH 18/52] Log error if worktree fails to relativize git repo path We saw a panic that was caused by the previous `Option.unwrap()`, so this changes the method to return a `Result` and logs the error if possible. Co-authored-by: Antonio --- crates/project/src/worktree.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index c94385f14f..3ece157ba5 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -194,12 +194,14 @@ impl AsRef for RepositoryWorkDirectory { pub struct WorkDirectoryEntry(ProjectEntryId); impl WorkDirectoryEntry { - pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Option { - worktree.entry_for_id(self.0).and_then(|entry| { - path.strip_prefix(&entry.path) - .ok() - .map(move |path| path.into()) - }) + pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Result { + let entry = worktree + .entry_for_id(self.0) + .ok_or_else(|| anyhow!("entry not found"))?; + let path = path + .strip_prefix(&entry.path) + .map_err(|_| anyhow!("could not relativize {:?} against {:?}", path, entry.path))?; + Ok(path.into()) } } @@ -970,12 +972,13 @@ impl LocalWorktree { let mut index_task = None; let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?; if let Some(repo) = snapshot.repository_for_path(&path) { - if let Some(repo_path) = repo.work_directory.relativize(&snapshot, &path) { - if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) { - let repo = repo.repo_ptr.clone(); + if let Some(repo_path) = repo.work_directory.relativize(&snapshot, &path).log_err() + { + if let Some(git_repo) = snapshot.git_repositories.get(&*repo.work_directory) { + let git_repo = git_repo.repo_ptr.clone(); index_task = Some( cx.background_executor() - .spawn(async move { repo.lock().load_index_text(&repo_path) }), + .spawn(async move { git_repo.lock().load_index_text(&repo_path) }), ); } } From 062288dea55f82f9efaf39ddd398f8e8b44969a2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:28:21 +0100 Subject: [PATCH 19/52] Search woes (#6698) Fixes #6441 Release Notes: - Fixed "SelectNextMatch", "SelectPrevMatch" and "SelectAllMatches" actions not working when search bar is not visible. - Fixed "SelectPrevMatch" not being bound in project search. --------- Co-authored-by: Kirill --- crates/search/src/buffer_search.rs | 226 +++---------------- crates/search/src/buffer_search/registrar.rs | 172 ++++++++++++++ crates/search/src/project_search.rs | 8 +- 3 files changed, 209 insertions(+), 197 deletions(-) create mode 100644 crates/search/src/buffer_search/registrar.rs diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 5f7275495a..ad08636852 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,3 +1,5 @@ +mod registrar; + use crate::{ history::SearchHistory, mode::{next_mode, SearchMode}, @@ -29,6 +31,9 @@ use workspace::{ ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; +pub use registrar::DivRegistrar; +use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; + #[derive(PartialEq, Clone, Deserialize)] pub struct Deploy { pub focus: bool, @@ -422,230 +427,59 @@ impl ToolbarItemView for BufferSearchBar { } } -/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. -pub trait SearchActionsRegistrar { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ); - - fn register_handler_for_dismissed_search( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ); -} - -type GetSearchBar = - for<'a, 'b> fn(&'a T, &'a mut ViewContext<'b, T>) -> Option>; - -/// Registers search actions on a div that can be taken out. -pub struct DivRegistrar<'a, 'b, T: 'static> { - div: Option
, - cx: &'a mut ViewContext<'b, T>, - search_getter: GetSearchBar, -} - -impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> { - pub fn new(search_getter: GetSearchBar, cx: &'a mut ViewContext<'b, T>) -> Self { - Self { - div: Some(div()), - cx, - search_getter, - } - } - pub fn into_div(self) -> Div { - // This option is always Some; it's an option in the first place because we want to call methods - // on div that require ownership. - self.div.unwrap() - } -} - -impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - let getter = self.search_getter; - self.div = self.div.take().map(|div| { - div.on_action(self.cx.listener(move |this, action, cx| { - let should_notify = (getter)(this, cx) - .clone() - .map(|search_bar| { - search_bar.update(cx, |search_bar, cx| { - if search_bar.is_dismissed() - || search_bar.active_searchable_item.is_none() - { - false - } else { - callback(search_bar, action, cx); - true - } - }) - }) - .unwrap_or(false); - if should_notify { - cx.notify(); - } else { - cx.propagate(); - } - })) - }); - } - - fn register_handler_for_dismissed_search( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - let getter = self.search_getter; - self.div = self.div.take().map(|div| { - div.on_action(self.cx.listener(move |this, action, cx| { - let should_notify = (getter)(this, cx) - .clone() - .map(|search_bar| { - search_bar.update(cx, |search_bar, cx| { - if search_bar.is_dismissed() { - callback(search_bar, action, cx); - true - } else { - false - } - }) - }) - .unwrap_or(false); - if should_notify { - cx.notify(); - } else { - cx.propagate(); - } - })) - }); - } -} - -/// Register actions for an active pane. -impl SearchActionsRegistrar for Workspace { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.register_action(move |workspace, action: &A, cx| { - if workspace.has_active_modal(cx) { - cx.propagate(); - return; - } - - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - let should_notify = search_bar.update(cx, move |search_bar, cx| { - if search_bar.is_dismissed() - || search_bar.active_searchable_item.is_none() - { - false - } else { - callback(search_bar, action, cx); - true - } - }); - if should_notify { - cx.notify(); - } else { - cx.propagate(); - } - } - }) - }); - }); - } - - fn register_handler_for_dismissed_search( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.register_action(move |workspace, action: &A, cx| { - if workspace.has_active_modal(cx) { - cx.propagate(); - return; - } - - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - let should_notify = search_bar.update(cx, move |search_bar, cx| { - if search_bar.is_dismissed() { - callback(search_bar, action, cx); - true - } else { - false - } - }); - if should_notify { - cx.notify(); - } else { - cx.propagate(); - } - } - }) - }); - }); - } -} - impl BufferSearchBar { pub fn register(registrar: &mut impl SearchActionsRegistrar) { - registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { + registrar.register_handler(ForDeployed(|this, action: &ToggleCaseSensitive, cx| { if this.supported_options().case { this.toggle_case_sensitive(action, cx); } - }); - registrar.register_handler(|this, action: &ToggleWholeWord, cx| { + })); + registrar.register_handler(ForDeployed(|this, action: &ToggleWholeWord, cx| { if this.supported_options().word { this.toggle_whole_word(action, cx); } - }); - registrar.register_handler(|this, action: &ToggleReplace, cx| { + })); + registrar.register_handler(ForDeployed(|this, action: &ToggleReplace, cx| { if this.supported_options().replacement { this.toggle_replace(action, cx); } - }); - registrar.register_handler(|this, _: &ActivateRegexMode, cx| { + })); + registrar.register_handler(ForDeployed(|this, _: &ActivateRegexMode, cx| { if this.supported_options().regex { this.activate_search_mode(SearchMode::Regex, cx); } - }); - registrar.register_handler(|this, _: &ActivateTextMode, cx| { + })); + registrar.register_handler(ForDeployed(|this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx); - }); - registrar.register_handler(|this, action: &CycleMode, cx| { + })); + registrar.register_handler(ForDeployed(|this, action: &CycleMode, cx| { if this.supported_options().regex { // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting // cycling. this.cycle_mode(action, cx) } - }); - registrar.register_handler(|this, action: &SelectNextMatch, cx| { + })); + registrar.register_handler(WithResults(|this, action: &SelectNextMatch, cx| { this.select_next_match(action, cx); - }); - registrar.register_handler(|this, action: &SelectPrevMatch, cx| { + })); + registrar.register_handler(WithResults(|this, action: &SelectPrevMatch, cx| { this.select_prev_match(action, cx); - }); - registrar.register_handler(|this, action: &SelectAllMatches, cx| { + })); + registrar.register_handler(WithResults(|this, action: &SelectAllMatches, cx| { this.select_all_matches(action, cx); - }); - registrar.register_handler(|this, _: &editor::actions::Cancel, cx| { + })); + registrar.register_handler(ForDeployed(|this, _: &editor::actions::Cancel, cx| { this.dismiss(&Dismiss, cx); - }); + })); // register deploy buffer search for both search bar states, since we want to focus into the search bar // when the deploy action is triggered in the buffer. - registrar.register_handler(|this, deploy, cx| { + registrar.register_handler(ForDeployed(|this, deploy, cx| { this.deploy(deploy, cx); - }); - registrar.register_handler_for_dismissed_search(|this, deploy, cx| { + })); + registrar.register_handler(ForDismissed(|this, deploy, cx| { this.deploy(deploy, cx); - }) + })) } pub fn new(cx: &mut ViewContext) -> Self { @@ -930,7 +764,7 @@ impl BufferSearchBar { event: &editor::EditorEvent, cx: &mut ViewContext, ) { - if let editor::EditorEvent::Edited { .. } = event { + if let editor::EditorEvent::Edited = event { self.query_contains_error = false; self.clear_matches(cx); let search = self.update_matches(cx); diff --git a/crates/search/src/buffer_search/registrar.rs b/crates/search/src/buffer_search/registrar.rs new file mode 100644 index 0000000000..39e2af95cf --- /dev/null +++ b/crates/search/src/buffer_search/registrar.rs @@ -0,0 +1,172 @@ +use gpui::{div, Action, Div, InteractiveElement, View, ViewContext}; +use workspace::Workspace; + +use crate::BufferSearchBar; + +/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. +pub trait SearchActionsRegistrar { + fn register_handler(&mut self, callback: impl ActionExecutor); +} + +type SearchBarActionCallback = fn(&mut BufferSearchBar, &A, &mut ViewContext); + +type GetSearchBar = + for<'a, 'b> fn(&'a T, &'a mut ViewContext<'b, T>) -> Option>; + +/// Registers search actions on a div that can be taken out. +pub struct DivRegistrar<'a, 'b, T: 'static> { + div: Option
, + cx: &'a mut ViewContext<'b, T>, + search_getter: GetSearchBar, +} + +impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> { + pub fn new(search_getter: GetSearchBar, cx: &'a mut ViewContext<'b, T>) -> Self { + Self { + div: Some(div()), + cx, + search_getter, + } + } + pub fn into_div(self) -> Div { + // This option is always Some; it's an option in the first place because we want to call methods + // on div that require ownership. + self.div.unwrap() + } +} + +impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { + fn register_handler(&mut self, callback: impl ActionExecutor) { + let getter = self.search_getter; + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + let should_notify = (getter)(this, cx) + .clone() + .map(|search_bar| { + search_bar.update(cx, |search_bar, cx| { + callback.execute(search_bar, action, cx) + }) + }) + .unwrap_or(false); + if should_notify { + cx.notify(); + } else { + cx.propagate(); + } + })) + }); + } +} + +/// Register actions for an active pane. +impl SearchActionsRegistrar for Workspace { + fn register_handler(&mut self, callback: impl ActionExecutor) { + self.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + + let pane = workspace.active_pane(); + let callback = callback.clone(); + pane.update(cx, |this, cx| { + this.toolbar().update(cx, move |this, cx| { + if let Some(search_bar) = this.item_of_type::() { + let should_notify = search_bar.update(cx, move |search_bar, cx| { + callback.execute(search_bar, action, cx) + }); + if should_notify { + cx.notify(); + } else { + cx.propagate(); + } + } + }) + }); + }); + } +} + +type DidHandleAction = bool; +/// Potentially executes the underlying action if some preconditions are met (e.g. buffer search bar is visible) +pub trait ActionExecutor: 'static + Clone { + fn execute( + &self, + search_bar: &mut BufferSearchBar, + action: &A, + cx: &mut ViewContext, + ) -> DidHandleAction; +} + +/// Run an action when the search bar has been dismissed from the panel. +pub struct ForDismissed(pub(super) SearchBarActionCallback); +impl Clone for ForDismissed { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl ActionExecutor for ForDismissed { + fn execute( + &self, + search_bar: &mut BufferSearchBar, + action: &A, + cx: &mut ViewContext, + ) -> DidHandleAction { + if search_bar.is_dismissed() { + self.0(search_bar, action, cx); + true + } else { + false + } + } +} + +/// Run an action when the search bar is deployed. +pub struct ForDeployed(pub(super) SearchBarActionCallback); +impl Clone for ForDeployed { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl ActionExecutor for ForDeployed { + fn execute( + &self, + search_bar: &mut BufferSearchBar, + action: &A, + cx: &mut ViewContext, + ) -> DidHandleAction { + if search_bar.is_dismissed() || search_bar.active_searchable_item.is_none() { + false + } else { + self.0(search_bar, action, cx); + true + } + } +} + +/// Run an action when the search bar has any matches, regardless of whether it +/// is visible or not. +pub struct WithResults(pub(super) SearchBarActionCallback); +impl Clone for WithResults { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl ActionExecutor for WithResults { + fn execute( + &self, + search_bar: &mut BufferSearchBar, + action: &A, + cx: &mut ViewContext, + ) -> DidHandleAction { + if search_bar.active_match_index.is_some() { + self.0(search_bar, action, cx); + true + } else { + false + } + } +} diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 0b169949b9..0aedb3fd41 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -88,6 +88,12 @@ pub fn init(cx: &mut AppContext) { register_workspace_action(workspace, move |search_bar, action: &CycleMode, cx| { search_bar.cycle_mode(action, cx) }); + register_workspace_action( + workspace, + move |search_bar, action: &SelectPrevMatch, cx| { + search_bar.select_prev_match(action, cx) + }, + ); register_workspace_action( workspace, move |search_bar, action: &SelectNextMatch, cx| { @@ -1550,7 +1556,7 @@ impl ProjectSearchBar { } } - pub fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext) { + fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext) { if let Some(search) = self.active_project_search.as_ref() { search.update(cx, |this, cx| { this.select_match(Direction::Next, cx); From 0a78c676475d8e3ca3d569f22e8bce0d3291cff5 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 25 Jan 2024 12:56:12 +0100 Subject: [PATCH 20/52] Improve performance of select-all-matches This fixes #6440. The previous approach was calling select-next-match in a loop, which leaves optimizations on the table when you already know that you want to select all of the matches. So what we did here is to optimize the code for the "give me all matches" case: 1. Find all results in the current buffer 2. Build up *all* selections 3. Sort selections & throw away overlapping ones (keep oldest) 4. Unfold if necessary 5. Render selections On my M3 Max searching for ` --- crates/editor/src/actions.rs | 10 +---- crates/editor/src/editor.rs | 70 +++++++++++++++++++++++++++---- crates/editor/src/editor_tests.rs | 12 ++++++ 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 4edc1d12ea..9cfaeaaf4a 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -13,12 +13,6 @@ pub struct SelectPrevious { pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct SelectAllMatches { - #[serde(default)] - pub replace_newest: bool, -} - #[derive(PartialEq, Clone, Deserialize, Default)] pub struct SelectToBeginningOfLine { #[serde(default)] @@ -81,7 +75,6 @@ impl_actions!( [ SelectNext, SelectPrevious, - SelectAllMatches, SelectToBeginningOfLine, MovePageUp, MovePageDown, @@ -128,6 +121,7 @@ gpui::actions!( DeleteToNextWordEnd, DeleteToPreviousSubwordStart, DeleteToPreviousWordStart, + DisplayCursorNames, DuplicateLine, ExpandMacroRecursively, FindAllReferences, @@ -185,6 +179,7 @@ gpui::actions!( ScrollCursorCenter, ScrollCursorTop, SelectAll, + SelectAllMatches, SelectDown, SelectLargerSyntaxNode, SelectLeft, @@ -214,6 +209,5 @@ gpui::actions!( Undo, UndoSelection, UnfoldLines, - DisplayCursorNames ] ); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9dd41fa25d..d15c2591b3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6113,6 +6113,7 @@ impl Editor { || (!movement::is_inside_word(&display_map, display_range.start) && !movement::is_inside_word(&display_map, display_range.end)) { + // TODO: This is n^2, because we might check all the selections if selections .iter() .find(|selection| selection.range().overlaps(&offset_range)) @@ -6222,25 +6223,76 @@ impl Editor { pub fn select_all_matches( &mut self, - action: &SelectAllMatches, + _action: &SelectAllMatches, cx: &mut ViewContext, ) -> Result<()> { self.push_to_selection_history(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - loop { - self.select_next_match_internal(&display_map, action.replace_newest, None, cx)?; + self.select_next_match_internal(&display_map, false, None, cx)?; + let Some(select_next_state) = self.select_next_state.as_mut() else { + return Ok(()); + }; + if select_next_state.done { + return Ok(()); + } - if self - .select_next_state - .as_ref() - .map(|selection_state| selection_state.done) - .unwrap_or(true) + let mut new_selections = self.selections.all::(cx); + + let buffer = &display_map.buffer_snapshot; + let query_matches = select_next_state + .query + .stream_find_iter(buffer.bytes_in_range(0..buffer.len())); + + for query_match in query_matches { + let query_match = query_match.unwrap(); // can only fail due to I/O + let offset_range = query_match.start()..query_match.end(); + let display_range = offset_range.start.to_display_point(&display_map) + ..offset_range.end.to_display_point(&display_map); + + if !select_next_state.wordwise + || (!movement::is_inside_word(&display_map, display_range.start) + && !movement::is_inside_word(&display_map, display_range.end)) { - break; + self.selections.change_with(cx, |selections| { + new_selections.push(Selection { + id: selections.new_selection_id(), + start: offset_range.start, + end: offset_range.end, + reversed: false, + goal: SelectionGoal::None, + }); + }); } } + new_selections.sort_by_key(|selection| selection.start); + let mut ix = 0; + while ix + 1 < new_selections.len() { + let current_selection = &new_selections[ix]; + let next_selection = &new_selections[ix + 1]; + if current_selection.range().overlaps(&next_selection.range()) { + if current_selection.id < next_selection.id { + new_selections.remove(ix + 1); + } else { + new_selections.remove(ix); + } + } else { + ix += 1; + } + } + + select_next_state.done = true; + self.unfold_ranges( + new_selections.iter().map(|selection| selection.range()), + false, + false, + cx, + ); + self.change_selections(Some(Autoscroll::fit()), cx, |selections| { + selections.select(new_selections) + }); + Ok(()) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a6e3d19995..03a601db99 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3820,6 +3820,18 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); } +#[gpui::test] +async fn test_select_all_matches(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + cx.set_state("abc\nˇabc abc\ndefabc\nabc"); + + cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); +} + #[gpui::test] async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); From e13fb31287ac698551f0ead745889adc00b2cd3e Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:36:52 +0100 Subject: [PATCH 21/52] small refactoring in color crate (#6695) Make the code more readable --- crates/color/src/color.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs index 8529f3bc5f..43bbc1c032 100644 --- a/crates/color/src/color.rs +++ b/crates/color/src/color.rs @@ -59,10 +59,7 @@ pub fn hex_to_hsla(s: &str) -> Result { // Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA let hex = match hex.len() { - 3 => hex - .chars() - .map(|c| c.to_string().repeat(2)) - .collect::(), + 3 => hex.chars().map(|c| c.to_string().repeat(2)).collect(), 4 => { let (rgb, alpha) = hex.split_at(3); let rgb = rgb @@ -80,14 +77,12 @@ pub fn hex_to_hsla(s: &str) -> Result { let hex_val = u32::from_str_radix(&hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?; - let r = ((hex_val >> 24) & 0xFF) as f32 / 255.0; - let g = ((hex_val >> 16) & 0xFF) as f32 / 255.0; - let b = ((hex_val >> 8) & 0xFF) as f32 / 255.0; - let a = (hex_val & 0xFF) as f32 / 255.0; - - let color = RGBAColor { r, g, b, a }; - - Ok(color) + Ok(RGBAColor { + r: ((hex_val >> 24) & 0xFF) as f32 / 255.0, + g: ((hex_val >> 16) & 0xFF) as f32 / 255.0, + b: ((hex_val >> 8) & 0xFF) as f32 / 255.0, + a: (hex_val & 0xFF) as f32 / 255.0, + }) } // These derives implement to and from palette's color types. @@ -128,8 +123,7 @@ where Rgb: FromColorUnclamped, { fn from_color_unclamped(color: RGBAColor) -> Self { - let srgb = Srgb::new(color.r, color.g, color.b); - Self::from_color_unclamped(srgb) + Self::from_color_unclamped(Srgb::new(color.r, color.g, color.b)) } } From 2b844f5cb537bb1fb08e26054906f98db974faa5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 25 Jan 2024 15:25:33 +0200 Subject: [PATCH 22/52] Sort file finder matched entries better (#6704) * applicable history items were sorted by latest opened order, now sorted by match score as the search matches * adjust the match sorting to show paths in the alphanumerical order (in case of a tie on other params) Release Notes: - Improved file finder entries ordering --------- Co-authored-by: Piotr --- crates/file_finder/src/file_finder.rs | 228 ++++++++++++++++---- crates/file_finder/src/file_finder_tests.rs | 143 +++++++++--- 2 files changed, 301 insertions(+), 70 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 672ed6272e..63a1966fbe 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -11,6 +11,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; use std::{ + cmp, path::{Path, PathBuf}, sync::{ atomic::{self, AtomicBool}, @@ -143,16 +144,51 @@ pub struct FileFinderDelegate { history_items: Vec, } +/// Use a custom ordering for file finder: the regular one +/// defines max element with the highest score and the latest alphanumerical path (in case of a tie on other params), e.g: +/// `[{score: 0.5, path = "c/d" }, { score: 0.5, path = "/a/b" }]` +/// +/// In the file finder, we would prefer to have the max element with the highest score and the earliest alphanumerical path, e.g: +/// `[{ score: 0.5, path = "/a/b" }, {score: 0.5, path = "c/d" }]` +/// as the files are shown in the project panel lists. +#[derive(Debug, Clone, PartialEq, Eq)] +struct ProjectPanelOrdMatch(PathMatch); + +impl Ord for ProjectPanelOrdMatch { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl PartialOrd for ProjectPanelOrdMatch { + fn partial_cmp(&self, other: &Self) -> Option { + Some( + self.0 + .score + .partial_cmp(&other.0.score) + .unwrap_or(cmp::Ordering::Equal) + .then_with(|| self.0.worktree_id.cmp(&other.0.worktree_id)) + .then_with(|| { + other + .0 + .distance_to_relative_ancestor + .cmp(&self.0.distance_to_relative_ancestor) + }) + .then_with(|| self.0.path.cmp(&other.0.path).reverse()), + ) + } +} + #[derive(Debug, Default)] struct Matches { - history: Vec<(FoundPath, Option)>, - search: Vec, + history: Vec<(FoundPath, Option)>, + search: Vec, } #[derive(Debug)] enum Match<'a> { - History(&'a FoundPath, Option<&'a PathMatch>), - Search(&'a PathMatch), + History(&'a FoundPath, Option<&'a ProjectPanelOrdMatch>), + Search(&'a ProjectPanelOrdMatch), } impl Matches { @@ -176,45 +212,44 @@ impl Matches { &mut self, history_items: &Vec, query: &PathLikeWithPosition, - mut new_search_matches: Vec, + new_search_matches: impl Iterator, extend_old_matches: bool, ) { let matching_history_paths = matching_history_item_paths(history_items, query); - new_search_matches - .retain(|path_match| !matching_history_paths.contains_key(&path_match.path)); - let history_items_to_show = history_items - .iter() - .filter_map(|history_item| { - Some(( - history_item.clone(), - Some( - matching_history_paths - .get(&history_item.project.path)? - .clone(), - ), - )) - }) - .collect::>(); - self.history = history_items_to_show; + let new_search_matches = new_search_matches + .filter(|path_match| !matching_history_paths.contains_key(&path_match.0.path)); + let history_items_to_show = history_items.iter().filter_map(|history_item| { + Some(( + history_item.clone(), + Some( + matching_history_paths + .get(&history_item.project.path)? + .clone(), + ), + )) + }); + self.history.clear(); + util::extend_sorted( + &mut self.history, + history_items_to_show, + 100, + |(_, a), (_, b)| b.cmp(a), + ); + if extend_old_matches { self.search - .retain(|path_match| !matching_history_paths.contains_key(&path_match.path)); - util::extend_sorted( - &mut self.search, - new_search_matches.into_iter(), - 100, - |a, b| b.cmp(a), - ) + .retain(|path_match| !matching_history_paths.contains_key(&path_match.0.path)); } else { - self.search = new_search_matches; + self.search.clear(); } + util::extend_sorted(&mut self.search, new_search_matches, 100, |a, b| b.cmp(a)); } } fn matching_history_item_paths( history_items: &Vec, query: &PathLikeWithPosition, -) -> HashMap, PathMatch> { +) -> HashMap, ProjectPanelOrdMatch> { let history_items_by_worktrees = history_items .iter() .filter_map(|found_path| { @@ -257,7 +292,12 @@ fn matching_history_item_paths( max_results, ) .into_iter() - .map(|path_match| (Arc::clone(&path_match.path), path_match)), + .map(|path_match| { + ( + Arc::clone(&path_match.path), + ProjectPanelOrdMatch(path_match), + ) + }), ); } matching_history_paths @@ -383,7 +423,9 @@ impl FileFinderDelegate { &cancel_flag, cx.background_executor().clone(), ) - .await; + .await + .into_iter() + .map(ProjectPanelOrdMatch); let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); picker .update(&mut cx, |picker, cx| { @@ -401,7 +443,7 @@ impl FileFinderDelegate { search_id: usize, did_cancel: bool, query: PathLikeWithPosition, - matches: Vec, + matches: impl IntoIterator, cx: &mut ViewContext>, ) { if search_id >= self.latest_search_id { @@ -412,8 +454,12 @@ impl FileFinderDelegate { .latest_search_query .as_ref() .map(|query| query.path_like.path_query()); - self.matches - .push_new_matches(&self.history_items, &query, matches, extend_old_matches); + self.matches.push_new_matches( + &self.history_items, + &query, + matches.into_iter(), + extend_old_matches, + ); self.latest_search_query = Some(query); self.latest_search_did_cancel = did_cancel; cx.notify(); @@ -471,12 +517,12 @@ impl FileFinderDelegate { if let Some(found_path_match) = found_path_match { path_match .positions - .extend(found_path_match.positions.iter()) + .extend(found_path_match.0.positions.iter()) } self.labels_for_path_match(&path_match) } - Match::Search(path_match) => self.labels_for_path_match(path_match), + Match::Search(path_match) => self.labels_for_path_match(&path_match.0), }; if file_name_positions.is_empty() { @@ -556,14 +602,14 @@ impl FileFinderDelegate { if let Some((worktree, relative_path)) = project.find_local_worktree(query_path, cx) { - path_matches.push(PathMatch { - score: 0.0, + path_matches.push(ProjectPanelOrdMatch(PathMatch { + score: 1.0, positions: Vec::new(), worktree_id: worktree.read(cx).id().to_usize(), path: Arc::from(relative_path), path_prefix: "".into(), distance_to_relative_ancestor: usize::MAX, - }); + })); } }) .log_err(); @@ -724,8 +770,8 @@ impl PickerDelegate for FileFinderDelegate { Match::Search(m) => split_or_open( workspace, ProjectPath { - worktree_id: WorktreeId::from_usize(m.worktree_id), - path: m.path.clone(), + worktree_id: WorktreeId::from_usize(m.0.worktree_id), + path: m.0.path.clone(), }, cx, ), @@ -805,3 +851,101 @@ impl PickerDelegate for FileFinderDelegate { ) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_custom_project_search_ordering_in_file_finder() { + let mut file_finder_sorted_output = vec![ + ProjectPanelOrdMatch(PathMatch { + score: 0.5, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("b0.5")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 1.0, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("c1.0")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 1.0, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("a1.0")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 0.5, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("a0.5")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 1.0, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("b1.0")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ]; + file_finder_sorted_output.sort_by(|a, b| b.cmp(a)); + + assert_eq!( + file_finder_sorted_output, + vec![ + ProjectPanelOrdMatch(PathMatch { + score: 1.0, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("a1.0")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 1.0, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("b1.0")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 1.0, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("c1.0")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 0.5, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("a0.5")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ProjectPanelOrdMatch(PathMatch { + score: 0.5, + positions: Vec::new(), + worktree_id: 0, + path: Arc::from(Path::new("b0.5")), + path_prefix: Arc::from(""), + distance_to_relative_ancestor: 0, + }), + ] + ); + } +} diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index 107fe891be..ca07cbf083 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -114,7 +114,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) { .await; picker.update(cx, |picker, _| { assert_eq!( - collect_search_results(picker), + collect_search_matches(picker).search_only(), vec![PathBuf::from("a/b/file2.txt")], "Matching abs path should be the only match" ) @@ -136,7 +136,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) { .await; picker.update(cx, |picker, _| { assert_eq!( - collect_search_results(picker), + collect_search_matches(picker).search_only(), Vec::::new(), "Mismatching abs path should produce no matches" ) @@ -169,7 +169,7 @@ async fn test_complex_path(cx: &mut TestAppContext) { picker.update(cx, |picker, _| { assert_eq!(picker.delegate.matches.len(), 1); assert_eq!( - collect_search_results(picker), + collect_search_matches(picker).search_only(), vec![PathBuf::from("其他/S数据表格/task.xlsx")], ) }); @@ -486,7 +486,7 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) { assert_eq!(matches.len(), 1); let (file_name, file_name_positions, full_path, full_path_positions) = - delegate.labels_for_path_match(&matches[0]); + delegate.labels_for_path_match(&matches[0].0); assert_eq!(file_name, "the-file"); assert_eq!(file_name_positions, &[0, 1, 4]); assert_eq!(full_path, "the-file"); @@ -556,9 +556,9 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) { delegate.matches.history.is_empty(), "Search matches expected" ); - let matches = delegate.matches.search.clone(); - assert_eq!(matches[0].path.as_ref(), Path::new("dir2/a.txt")); - assert_eq!(matches[1].path.as_ref(), Path::new("dir1/a.txt")); + let matches = &delegate.matches.search; + assert_eq!(matches[0].0.path.as_ref(), Path::new("dir2/a.txt")); + assert_eq!(matches[1].0.path.as_ref(), Path::new("dir1/a.txt")); }); } @@ -957,7 +957,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) { Some(PathBuf::from("/src/test/first.rs")) )); assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present"); - assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs")); + assert_eq!(delegate.matches.search.first().unwrap().0.path.as_ref(), Path::new("test/fourth.rs")); }); let second_query = "fsdasdsa"; @@ -1002,10 +1002,65 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) { Some(PathBuf::from("/src/test/first.rs")) )); assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query"); - assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs")); + assert_eq!(delegate.matches.search.first().unwrap().0.path.as_ref(), Path::new("test/fourth.rs")); }); } +#[gpui::test] +async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "test": { + "1_qw": "// First file that matches the query", + "2_second": "// Second file", + "3_third": "// Third file", + "4_fourth": "// Fourth file", + "5_qwqwqw": "// A file with 3 more matches than the first one", + "6_qwqwqw": "// Same query matches as above, but closer to the end of the list due to the name", + "7_qwqwqw": "// One more, same amount of query matches as above", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + // generate some history to select from + open_close_queried_buffer("1", 1, "1_qw", &workspace, cx).await; + open_close_queried_buffer("2", 1, "2_second", &workspace, cx).await; + open_close_queried_buffer("3", 1, "3_third", &workspace, cx).await; + open_close_queried_buffer("2", 1, "2_second", &workspace, cx).await; + open_close_queried_buffer("6", 1, "6_qwqwqw", &workspace, cx).await; + + let finder = open_file_picker(&workspace, cx); + let query = "qw"; + finder + .update(cx, |finder, cx| { + finder.delegate.update_matches(query.to_string(), cx) + }) + .await; + finder.update(cx, |finder, _| { + let search_matches = collect_search_matches(finder); + assert_eq!( + search_matches.history, + vec![PathBuf::from("test/1_qw"), PathBuf::from("test/6_qwqwqw"),], + ); + assert_eq!( + search_matches.search, + vec![ + PathBuf::from("test/5_qwqwqw"), + PathBuf::from("test/7_qwqwqw"), + ], + ); + }); +} + #[gpui::test] async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); @@ -1048,14 +1103,14 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo .matches .search .iter() - .map(|path_match| path_match.path.to_path_buf()) + .map(|path_match| path_match.0.path.to_path_buf()) .collect::>(); assert_eq!( search_entries, vec![ PathBuf::from("collab_ui/collab_ui.rs"), - PathBuf::from("collab_ui/third.rs"), PathBuf::from("collab_ui/first.rs"), + PathBuf::from("collab_ui/third.rs"), PathBuf::from("collab_ui/second.rs"), ], "Despite all search results having the same directory name, the most matching one should be on top" @@ -1097,7 +1152,7 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext) .matches .history .iter() - .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf()) + .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").0.path.to_path_buf()) .collect::>(); assert_eq!( history_entries, @@ -1124,7 +1179,8 @@ async fn open_close_queried_buffer( assert_eq!( finder.delegate.matches.len(), expected_matches, - "Unexpected number of matches found for query {input}" + "Unexpected number of matches found for query `{input}`, matches: {:?}", + finder.delegate.matches ); finder.delegate.history_items.clone() }); @@ -1137,7 +1193,7 @@ async fn open_close_queried_buffer( let active_editor_title = active_editor.read(cx).title(cx); assert_eq!( expected_editor_title, active_editor_title, - "Unexpected editor title for query {input}" + "Unexpected editor title for query `{input}`" ); }); @@ -1210,18 +1266,49 @@ fn active_file_picker( }) } -fn collect_search_results(picker: &Picker) -> Vec { - let matches = &picker.delegate.matches; - assert!( - matches.history.is_empty(), - "Should have no history matches, but got: {:?}", - matches.history - ); - let mut results = matches - .search - .iter() - .map(|path_match| Path::new(path_match.path_prefix.as_ref()).join(&path_match.path)) - .collect::>(); - results.sort(); - results +#[derive(Debug)] +struct SearchEntries { + history: Vec, + search: Vec, +} + +impl SearchEntries { + #[track_caller] + fn search_only(self) -> Vec { + assert!( + self.history.is_empty(), + "Should have no history matches, but got: {:?}", + self.history + ); + self.search + } +} + +fn collect_search_matches(picker: &Picker) -> SearchEntries { + let matches = &picker.delegate.matches; + SearchEntries { + history: matches + .history + .iter() + .map(|(history_path, path_match)| { + path_match + .as_ref() + .map(|path_match| { + Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path) + }) + .unwrap_or_else(|| { + history_path + .absolute + .as_deref() + .unwrap_or_else(|| &history_path.project.path) + .to_path_buf() + }) + }) + .collect(), + search: matches + .search + .iter() + .map(|path_match| Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path)) + .collect(), + } } From 7068161bd7405553fd3fecaf44057449d8137878 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 25 Jan 2024 09:51:47 -0500 Subject: [PATCH 23/52] Sort `.mailmap` alphabetically (#6711) This PR sorts the entries in the `.mailmap` file to keep them in alphabetical order. Release Notes: - N/A --- .mailmap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.mailmap b/.mailmap index 95c6553042..ec1f3c1f16 100644 --- a/.mailmap +++ b/.mailmap @@ -11,6 +11,8 @@ Antonio Scandurra Antonio Scandurra +Conrad Irwin +Conrad Irwin Joseph T. Lyons Joseph T. Lyons Julia @@ -37,8 +39,6 @@ Nathan Sobo Nathan Sobo Piotr Osiewicz Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> -Conrad Irwin -Conrad Irwin Thorsten Ball -Thorsten Ball Thorsten Ball +Thorsten Ball From ca27ac21c2071a5682fbdff2ee9dffc0abc8543b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 25 Jan 2024 09:59:57 -0700 Subject: [PATCH 24/52] Fail faster on serialization failure Co-Authored-By: Thorsten --- crates/collab/src/db.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index f3eeb68afc..06bdb0f729 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -303,13 +303,14 @@ impl Database { } } - async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: u32) -> bool { + async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: usize) -> bool { // If the error is due to a failure to serialize concurrent transactions, then retry // this transaction after a delay. With each subsequent retry, double the delay duration. // Also vary the delay randomly in order to ensure different database connections retry // at different times. - if is_serialization_error(error) { - let base_delay = 4_u64 << prev_attempt_count.min(16); + const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.]; + if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() { + let base_delay = SLEEPS[prev_attempt_count]; let randomized_delay = base_delay as f32 * self.rng.lock().await.gen_range(0.5..=2.0); log::info!( "retrying transaction after serialization error. delay: {} ms.", From cff2e8bbe0828524147eefa6bb4af0fa2dd3a8a5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 25 Jan 2024 18:32:32 +0100 Subject: [PATCH 25/52] Fix typos discovered by codespell --- crates/client/src/user.rs | 2 +- .../20231009181554_add_release_channel_to_rooms.sql | 2 +- crates/collab/src/db/tests/message_tests.rs | 2 +- crates/editor/src/element.rs | 2 +- crates/editor/src/inlay_hint_cache.rs | 12 ++++++------ crates/gpui/src/app.rs | 2 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/language/src/language.rs | 2 +- crates/terminal_view/src/terminal_view.rs | 4 ++-- crates/theme/src/styles/colors.rs | 2 +- crates/theme_importer/README.md | 2 +- crates/zed/src/zed.rs | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 122046827a..e571d2dc15 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -287,7 +287,7 @@ impl UserStore { load_users.await?; // Users are fetched in parallel above and cached in call to get_users - // No need to paralellize here + // No need to parallelize here let mut updated_contacts = Vec::new(); let this = this .upgrade() diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql index 8f3a704add..3f32ee35c5 100644 --- a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -1 +1 @@ -ALTER TABLE rooms ADD COLUMN enviroment TEXT; +ALTER TABLE rooms ADD COLUMN environment TEXT; diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 10d9778612..190d2275d6 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -100,7 +100,7 @@ async fn test_channel_message_nonces(db: &Arc) { .await .unwrap(); - // As user A, create messages that re-use the same nonces. The requests + // As user A, create messages that reuse the same nonces. The requests // succeed, but return the same ids. let id1 = db .create_channel_message( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index bcf2504d34..8a944c2cb4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3582,7 +3582,7 @@ mod tests { ); // multi-buffer support - // in DisplayPoint co-ordinates, this is what we're dealing with: + // in DisplayPoint coordinates, this is what we're dealing with: // 0: [[file // 1: header]] // 2: aaaaaa diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 3fba722492..c4c18aefbf 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1971,7 +1971,7 @@ pub mod tests { assert_eq!( lsp_request_count.load(Ordering::Relaxed), 3, - "Should query for new hints when they got reenabled" + "Should query for new hints when they got re-enabled" ); assert_eq!( vec![ @@ -1980,7 +1980,7 @@ pub mod tests { "type hint".to_string(), ], cached_hint_labels(editor), - "Should get its cached hints fully repopulated after the hints got reenabled" + "Should get its cached hints fully repopulated after the hints got re-enabled" ); assert_eq!( vec!["parameter hint".to_string()], @@ -1990,11 +1990,11 @@ pub mod tests { let inlay_cache = editor.inlay_hint_cache(); assert_eq!( inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds, - "Cache should update editor settings when hints got reenabled" + "Cache should update editor settings when hints got re-enabled" ); assert_eq!( inlay_cache.version, edits_made, - "Cache should update its version after hints got reenabled" + "Cache should update its version after hints got re-enabled" ); }); @@ -2736,7 +2736,7 @@ pub mod tests { assert_eq!(expected_hints, cached_hint_labels(editor), "After multibuffer was scrolled to the end, further scrolls up should not bring more hints"); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer"); + assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scrolled buffer"); }); editor_edited.store(true, Ordering::Release); @@ -2762,7 +2762,7 @@ pub mod tests { assert_eq!( expected_hints, cached_hint_labels(editor), - "After multibuffer edit, editor gets scolled back to the last selection; \ + "After multibuffer edit, editor gets scrolled back to the last selection; \ all hints should be invalidated and required for all of its visible excerpts" ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6f75eafaf2..57f2254ef2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -574,7 +574,7 @@ impl AppContext { } /// Displays a platform modal for selecting a new path where a file can be saved. - /// The provided directory will be used to set the iniital location. + /// The provided directory will be used to set the initial location. /// When a path is selected, it is relayed asynchronously via the returned oneshot channel. /// If cancelled, a `None` will be relayed instead. pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 9db75b75ba..ed23205ae7 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -40,7 +40,7 @@ impl Overlay { self } - /// Sets the position in window co-ordinates + /// Sets the position in window coordinates /// (otherwise the location the overlay is rendered is used) pub fn position(mut self, anchor: Point) -> Self { self.anchor_position = Some(anchor); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1add9c7c25..9e2e7a0b66 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -584,7 +584,7 @@ impl<'de> Deserialize<'de> for BracketPairConfig { } /// Describes a single bracket pair and how an editor should react to e.g. inserting -/// an opening bracket or to a newline character insertion inbetween `start` and `end` characters. +/// an opening bracket or to a newline character insertion in between `start` and `end` characters. #[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct BracketPair { /// Starting substring for a bracket. diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 180667b113..c0074cf53a 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -911,7 +911,7 @@ impl SearchableItem for TerminalView { } } -///Get's the working directory for the given workspace, respecting the user's settings. +///Gets the working directory for the given workspace, respecting the user's settings. pub fn get_working_directory( workspace: &Workspace, cx: &AppContext, @@ -932,7 +932,7 @@ pub fn get_working_directory( res.or_else(home_dir) } -///Get's the first project's home directory, or the home directory +///Gets the first project's home directory, or the home directory fn first_project_directory(workspace: &Workspace, cx: &AppContext) -> Option { workspace .worktrees(cx) diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index b830d19fee..17910ef973 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -236,7 +236,7 @@ pub struct ThemeColors { #[derive(Refineable, Clone)] pub struct ThemeStyles { pub system: SystemColors, - /// An array of colors used for theme elements that iterrate through a series of colors. + /// An array of colors used for theme elements that iterate through a series of colors. /// /// Example: Player colors, rainbow brackets and indent guides, etc. pub accents: Vec, diff --git a/crates/theme_importer/README.md b/crates/theme_importer/README.md index a610d694d7..9373b54ae1 100644 --- a/crates/theme_importer/README.md +++ b/crates/theme_importer/README.md @@ -66,7 +66,7 @@ Copy that json file into the theme family directory and tidy up the filenames as A LICENSE file is required to import a theme family. Failing to provide a complete text license will cause it to be skipped when the import is run. -If the theme only provices a license code (e.g. MIT, Apache 2.0, etc.) then put that code into the LICENSE file. +If the theme only provides a license code (e.g. MIT, Apache 2.0, etc.) then put that code into the LICENSE file. If no license is provided, either contact the theme creator or don't add the theme. diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f6f513a4b3..bf001dac72 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -143,7 +143,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { cx.on_window_should_close(move |cx| { handle .update(cx, |workspace, cx| { - // We'll handle closing asynchoronously + // We'll handle closing asynchronously workspace.close_window(&Default::default(), cx); false }) From adb6f3e9f722e46f33391ef50dadcd8f7b00080e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 25 Jan 2024 11:03:13 -0700 Subject: [PATCH 26/52] Move expensive participant update out of transaction Co-Authored-By: Marshall --- crates/collab/src/db.rs | 53 ++++++++++- crates/collab/src/db/queries/channels.rs | 64 ++++--------- crates/collab/src/db/tests/message_tests.rs | 11 +-- crates/collab/src/rpc.rs | 100 +++++++++++--------- 4 files changed, 131 insertions(+), 97 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 06bdb0f729..d1a717e66e 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -169,6 +169,30 @@ impl Database { self.run(body).await } + pub async fn weak_transaction(&self, f: F) -> Result + where + F: Send + Fn(TransactionHandle) -> Fut, + Fut: Send + Future>, + { + let body = async { + let (tx, result) = self.with_weak_transaction(&f).await?; + match result { + Ok(result) => match tx.commit().await.map_err(Into::into) { + Ok(()) => return Ok(result), + Err(error) => { + return Err(error); + } + }, + Err(error) => { + tx.rollback().await?; + return Err(error); + } + } + }; + + self.run(body).await + } + /// The same as room_transaction, but if you need to only optionally return a Room. async fn optional_room_transaction(&self, f: F) -> Result>> where @@ -284,6 +308,30 @@ impl Database { Ok((tx, result)) } + async fn with_weak_transaction( + &self, + f: &F, + ) -> Result<(DatabaseTransaction, Result)> + where + F: Send + Fn(TransactionHandle) -> Fut, + Fut: Send + Future>, + { + let tx = self + .pool + .begin_with_config(Some(IsolationLevel::ReadCommitted), None) + .await?; + + let mut tx = Arc::new(Some(tx)); + let result = f(TransactionHandle(tx.clone())).await; + let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else { + return Err(anyhow!( + "couldn't complete transaction because it's still in use" + ))?; + }; + + Ok((tx, result)) + } + async fn run(&self, future: F) -> Result where F: Future>, @@ -457,9 +505,8 @@ pub struct NewUserResult { /// The result of moving a channel. #[derive(Debug)] pub struct MoveChannelResult { - pub participants_to_update: HashMap, - pub participants_to_remove: HashSet, - pub moved_channels: HashSet, + pub previous_participants: Vec, + pub descendent_ids: Vec, } /// The result of renaming a channel. diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index ac18907894..95c9716a91 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -22,7 +22,6 @@ impl Database { Ok(self .create_channel(name, None, creator_id) .await? - .channel .id) } @@ -36,7 +35,6 @@ impl Database { Ok(self .create_channel(name, Some(parent), creator_id) .await? - .channel .id) } @@ -46,7 +44,7 @@ impl Database { name: &str, parent_channel_id: Option, admin_id: UserId, - ) -> Result { + ) -> Result { let name = Self::sanitize_channel_name(name)?; self.transaction(move |tx| async move { let mut parent = None; @@ -72,14 +70,7 @@ impl Database { .insert(&*tx) .await?; - let participants_to_update; - if let Some(parent) = &parent { - participants_to_update = self - .participants_to_notify_for_channel_change(parent, &*tx) - .await?; - } else { - participants_to_update = vec![]; - + if parent.is_none() { channel_member::ActiveModel { id: ActiveValue::NotSet, channel_id: ActiveValue::Set(channel.id), @@ -89,12 +80,9 @@ impl Database { } .insert(&*tx) .await?; - }; + } - Ok(CreateChannelResult { - channel: Channel::from_model(channel, ChannelRole::Admin), - participants_to_update, - }) + Ok(Channel::from_model(channel, ChannelRole::Admin)) }) .await } @@ -718,6 +706,19 @@ impl Database { }) } + pub async fn new_participants_to_notify( + &self, + parent_channel_id: ChannelId, + ) -> Result> { + self.weak_transaction(|tx| async move { + let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?; + self.participants_to_notify_for_channel_change(&parent_channel, &*tx) + .await + }) + .await + } + + // TODO: this is very expensive, and we should rethink async fn participants_to_notify_for_channel_change( &self, new_parent: &channel::Model, @@ -1287,7 +1288,7 @@ impl Database { let mut model = channel.into_active_model(); model.parent_path = ActiveValue::Set(new_parent_path); - let channel = model.update(&*tx).await?; + model.update(&*tx).await?; if new_parent_channel.is_none() { channel_member::ActiveModel { @@ -1314,34 +1315,9 @@ impl Database { .all(&*tx) .await?; - let participants_to_update: HashMap<_, _> = self - .participants_to_notify_for_channel_change( - new_parent_channel.as_ref().unwrap_or(&channel), - &*tx, - ) - .await? - .into_iter() - .collect(); - - let mut moved_channels: HashSet = HashSet::default(); - for id in descendent_ids { - moved_channels.insert(id); - } - moved_channels.insert(channel_id); - - let mut participants_to_remove: HashSet = HashSet::default(); - for participant in previous_participants { - if participant.kind == proto::channel_member::Kind::AncestorMember { - if !participants_to_update.contains_key(&participant.user_id) { - participants_to_remove.insert(participant.user_id); - } - } - } - Ok(Some(MoveChannelResult { - participants_to_remove, - participants_to_update, - moved_channels, + previous_participants, + descendent_ids, })) }) .await diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 10d9778612..22319ecc96 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -15,11 +15,11 @@ test_both_dbs!( async fn test_channel_message_retrieval(db: &Arc) { let user = new_test_user(db, "user@example.com").await; - let result = db.create_channel("channel", None, user).await.unwrap(); + let channel = db.create_channel("channel", None, user).await.unwrap(); let owner_id = db.create_server("test").await.unwrap().0 as u32; db.join_channel_chat( - result.channel.id, + channel.id, rpc::ConnectionId { owner_id, id: 0 }, user, ) @@ -30,7 +30,7 @@ async fn test_channel_message_retrieval(db: &Arc) { for i in 0..10 { all_messages.push( db.create_channel_message( - result.channel.id, + channel.id, user, &i.to_string(), &[], @@ -45,7 +45,7 @@ async fn test_channel_message_retrieval(db: &Arc) { } let messages = db - .get_channel_messages(result.channel.id, user, 3, None) + .get_channel_messages(channel.id, user, 3, None) .await .unwrap() .into_iter() @@ -55,7 +55,7 @@ async fn test_channel_message_retrieval(db: &Arc) { let messages = db .get_channel_messages( - result.channel.id, + channel.id, user, 4, Some(MessageId::from_proto(all_messages[6])), @@ -370,7 +370,6 @@ async fn test_channel_message_mentions(db: &Arc) { .create_channel("channel", None, user_a) .await .unwrap() - .channel .id; db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member) .await diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 7170f7f1c5..415119bcd1 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3,9 +3,9 @@ mod connection_pool; use crate::{ auth::{self, Impersonator}, db::{ - self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreateChannelResult, + self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId, - MoveChannelResult, NotificationId, ProjectId, RemoveChannelMemberResult, + NotificationId, ProjectId, RemoveChannelMemberResult, RenameChannelResult, RespondToChannelInvite, RoomId, ServerId, SetChannelVisibilityResult, User, UserId, }, @@ -2301,10 +2301,7 @@ async fn create_channel( let db = session.db().await; let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id)); - let CreateChannelResult { - channel, - participants_to_update, - } = db + let channel = db .create_channel(&request.name, parent_id, session.user_id) .await?; @@ -2313,6 +2310,13 @@ async fn create_channel( parent_id: request.parent_id, })?; + let participants_to_update; + if let Some(parent) = parent_id { + participants_to_update = db.new_participants_to_notify(parent).await?; + } else { + participants_to_update = vec![]; + } + let connection_pool = session.connection_pool().await; for (user_id, channels) in participants_to_update { let update = build_channels_update(channels, vec![]); @@ -2566,50 +2570,58 @@ async fn move_channel( let channel_id = ChannelId::from_proto(request.channel_id); let to = request.to.map(ChannelId::from_proto); - let result = session - .db() - .await - .move_channel(channel_id, to, session.user_id) - .await?; + let result = session.db().await.move_channel(channel_id, to, session.user_id).await?; - notify_channel_moved(result, session).await?; + if let Some(result) = result { + let participants_to_update: HashMap<_, _> = session.db().await + .new_participants_to_notify( + to.unwrap_or(channel_id) + ) + .await? + .into_iter() + .collect(); + + let mut moved_channels: HashSet = HashSet::default(); + for id in result.descendent_ids { + moved_channels.insert(id); + } + moved_channels.insert(channel_id); + + let mut participants_to_remove: HashSet = HashSet::default(); + for participant in result.previous_participants { + if participant.kind == proto::channel_member::Kind::AncestorMember { + if !participants_to_update.contains_key(&participant.user_id) { + participants_to_remove.insert(participant.user_id); + } + } + } + + let moved_channels: Vec = moved_channels.iter().map(|id| id.to_proto()).collect(); + + let connection_pool = session.connection_pool().await; + for (user_id, channels) in participants_to_update { + let mut update = build_channels_update(channels, vec![]); + update.delete_channels = moved_channels.clone(); + for connection_id in connection_pool.user_connection_ids(user_id) { + session.peer.send(connection_id, update.clone())?; + } + } + + for user_id in participants_to_remove { + let update = proto::UpdateChannels { + delete_channels: moved_channels.clone(), + ..Default::default() + }; + for connection_id in connection_pool.user_connection_ids(user_id) { + session.peer.send(connection_id, update.clone())?; + } + } + } response.send(Ack {})?; Ok(()) } -async fn notify_channel_moved(result: Option, session: Session) -> Result<()> { - let Some(MoveChannelResult { - participants_to_remove, - participants_to_update, - moved_channels, - }) = result - else { - return Ok(()); - }; - let moved_channels: Vec = moved_channels.iter().map(|id| id.to_proto()).collect(); - - let connection_pool = session.connection_pool().await; - for (user_id, channels) in participants_to_update { - let mut update = build_channels_update(channels, vec![]); - update.delete_channels = moved_channels.clone(); - for connection_id in connection_pool.user_connection_ids(user_id) { - session.peer.send(connection_id, update.clone())?; - } - } - - for user_id in participants_to_remove { - let update = proto::UpdateChannels { - delete_channels: moved_channels.clone(), - ..Default::default() - }; - for connection_id in connection_pool.user_connection_ids(user_id) { - session.peer.send(connection_id, update.clone())?; - } - } - Ok(()) -} - /// Get the list of channel members async fn get_channel_members( request: proto::GetChannelMembers, From fbdca993ffc843a46d610dcc499569ca3b501edb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 25 Jan 2024 11:12:02 -0700 Subject: [PATCH 27/52] Format --- crates/collab/src/db/queries/channels.rs | 5 +- crates/collab/src/db/tests/message_tests.rs | 16 +--- crates/collab/src/rpc.rs | 99 +++++++++++---------- 3 files changed, 56 insertions(+), 64 deletions(-) diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 95c9716a91..10b73a7094 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -19,10 +19,7 @@ impl Database { #[cfg(test)] pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result { - Ok(self - .create_channel(name, None, creator_id) - .await? - .id) + Ok(self.create_channel(name, None, creator_id).await?.id) } #[cfg(test)] diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 22319ecc96..23c4da3a81 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -18,13 +18,9 @@ async fn test_channel_message_retrieval(db: &Arc) { let channel = db.create_channel("channel", None, user).await.unwrap(); let owner_id = db.create_server("test").await.unwrap().0 as u32; - db.join_channel_chat( - channel.id, - rpc::ConnectionId { owner_id, id: 0 }, - user, - ) - .await - .unwrap(); + db.join_channel_chat(channel.id, rpc::ConnectionId { owner_id, id: 0 }, user) + .await + .unwrap(); let mut all_messages = Vec::new(); for i in 0..10 { @@ -366,11 +362,7 @@ async fn test_channel_message_mentions(db: &Arc) { let user_b = new_test_user(db, "user_b@example.com").await; let user_c = new_test_user(db, "user_c@example.com").await; - let channel = db - .create_channel("channel", None, user_a) - .await - .unwrap() - .id; + let channel = db.create_channel("channel", None, user_a).await.unwrap().id; db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member) .await .unwrap(); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 415119bcd1..bb173784d3 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3,11 +3,10 @@ mod connection_pool; use crate::{ auth::{self, Impersonator}, db::{ - self, BufferId, ChannelId, ChannelRole, ChannelsForUser, - CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId, - NotificationId, ProjectId, RemoveChannelMemberResult, - RenameChannelResult, RespondToChannelInvite, RoomId, ServerId, SetChannelVisibilityResult, - User, UserId, + self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database, + InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId, + RemoveChannelMemberResult, RenameChannelResult, RespondToChannelInvite, RoomId, ServerId, + SetChannelVisibilityResult, User, UserId, }, executor::Executor, AppState, Error, Result, @@ -2570,53 +2569,57 @@ async fn move_channel( let channel_id = ChannelId::from_proto(request.channel_id); let to = request.to.map(ChannelId::from_proto); - let result = session.db().await.move_channel(channel_id, to, session.user_id).await?; + let result = session + .db() + .await + .move_channel(channel_id, to, session.user_id) + .await?; if let Some(result) = result { - let participants_to_update: HashMap<_, _> = session.db().await - .new_participants_to_notify( - to.unwrap_or(channel_id) - ) - .await? - .into_iter() - .collect(); + let participants_to_update: HashMap<_, _> = session + .db() + .await + .new_participants_to_notify(to.unwrap_or(channel_id)) + .await? + .into_iter() + .collect(); - let mut moved_channels: HashSet = HashSet::default(); - for id in result.descendent_ids { - moved_channels.insert(id); - } - moved_channels.insert(channel_id); - - let mut participants_to_remove: HashSet = HashSet::default(); - for participant in result.previous_participants { - if participant.kind == proto::channel_member::Kind::AncestorMember { - if !participants_to_update.contains_key(&participant.user_id) { - participants_to_remove.insert(participant.user_id); - } - } - } - - let moved_channels: Vec = moved_channels.iter().map(|id| id.to_proto()).collect(); - - let connection_pool = session.connection_pool().await; - for (user_id, channels) in participants_to_update { - let mut update = build_channels_update(channels, vec![]); - update.delete_channels = moved_channels.clone(); - for connection_id in connection_pool.user_connection_ids(user_id) { - session.peer.send(connection_id, update.clone())?; - } - } - - for user_id in participants_to_remove { - let update = proto::UpdateChannels { - delete_channels: moved_channels.clone(), - ..Default::default() - }; - for connection_id in connection_pool.user_connection_ids(user_id) { - session.peer.send(connection_id, update.clone())?; - } - } + let mut moved_channels: HashSet = HashSet::default(); + for id in result.descendent_ids { + moved_channels.insert(id); } + moved_channels.insert(channel_id); + + let mut participants_to_remove: HashSet = HashSet::default(); + for participant in result.previous_participants { + if participant.kind == proto::channel_member::Kind::AncestorMember { + if !participants_to_update.contains_key(&participant.user_id) { + participants_to_remove.insert(participant.user_id); + } + } + } + + let moved_channels: Vec = moved_channels.iter().map(|id| id.to_proto()).collect(); + + let connection_pool = session.connection_pool().await; + for (user_id, channels) in participants_to_update { + let mut update = build_channels_update(channels, vec![]); + update.delete_channels = moved_channels.clone(); + for connection_id in connection_pool.user_connection_ids(user_id) { + session.peer.send(connection_id, update.clone())?; + } + } + + for user_id in participants_to_remove { + let update = proto::UpdateChannels { + delete_channels: moved_channels.clone(), + ..Default::default() + }; + for connection_id in connection_pool.user_connection_ids(user_id) { + session.peer.send(connection_id, update.clone())?; + } + } + } response.send(Ack {})?; Ok(()) From dd25902aeba09d1a122291d63b08fd7ea97ca934 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 25 Jan 2024 11:17:09 -0700 Subject: [PATCH 28/52] collab 0.41.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d7c55b720..41987a70b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1453,7 +1453,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.40.1" +version = "0.41.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 2a82323c38..8607f5a99a 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.40.1" +version = "0.41.0" publish = false license = "AGPL-3.0-only" From e9edad1d51c6c575348662be8405e79935900802 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:25:21 +0100 Subject: [PATCH 29/52] language: Accept multiple values in line_comment language knob. (#6713) This opens up a possibility of supporting multiple comment continuation flavours in editor, e.g. doc comments for Rust (which we seize as well in this commit). Only the first `line_comment` value is used for Editor::ToggleComments Fixes: https://github.com/zed-industries/zed/issues/6692 Release Notes: - Added support for doc-comment continuations in Rust language. --- crates/editor/src/editor.rs | 62 +++++++++++-------- crates/editor/src/editor_tests.rs | 8 +-- crates/language/src/buffer_tests.rs | 34 ++++++---- crates/language/src/language.rs | 14 +++-- crates/zed/src/languages/bash/config.toml | 2 +- crates/zed/src/languages/c/config.toml | 2 +- crates/zed/src/languages/cpp/config.toml | 2 +- crates/zed/src/languages/elixir/config.toml | 2 +- crates/zed/src/languages/elm/config.toml | 2 +- crates/zed/src/languages/glsl/config.toml | 2 +- crates/zed/src/languages/go/config.toml | 2 +- .../zed/src/languages/javascript/config.toml | 4 +- crates/zed/src/languages/json/config.toml | 2 +- crates/zed/src/languages/lua/config.toml | 2 +- crates/zed/src/languages/nix/config.toml | 2 +- crates/zed/src/languages/nu/config.toml | 2 +- crates/zed/src/languages/php/config.toml | 2 +- crates/zed/src/languages/python/config.toml | 2 +- crates/zed/src/languages/racket/config.toml | 2 +- crates/zed/src/languages/ruby/config.toml | 2 +- crates/zed/src/languages/rust/config.toml | 2 +- crates/zed/src/languages/scheme/config.toml | 2 +- crates/zed/src/languages/toml/config.toml | 2 +- crates/zed/src/languages/tsx/config.toml | 4 +- .../zed/src/languages/typescript/config.toml | 2 +- crates/zed/src/languages/uiua/config.toml | 2 +- crates/zed/src/languages/yaml/config.toml | 2 +- 27 files changed, 95 insertions(+), 73 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d15c2591b3..a572729e76 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2502,34 +2502,43 @@ impl Editor { ) }); // Comment extension on newline is allowed only for cursor selections - let comment_delimiter = language.line_comment_prefix().filter(|_| { + let comment_delimiter = language.line_comment_prefixes().filter(|_| { let is_comment_extension_enabled = multi_buffer.settings_at(0, cx).extend_comment_on_newline; is_cursor && is_comment_extension_enabled }); - let comment_delimiter = if let Some(delimiter) = comment_delimiter { - buffer - .buffer_line_for_row(start_point.row) - .is_some_and(|(snapshot, range)| { - let mut index_of_first_non_whitespace = 0; - let line_starts_with_comment = snapshot - .chars_for_range(range) - .skip_while(|c| { - let should_skip = c.is_whitespace(); - if should_skip { - index_of_first_non_whitespace += 1; - } - should_skip - }) - .take(delimiter.len()) - .eq(delimiter.chars()); - let cursor_is_placed_after_comment_marker = - index_of_first_non_whitespace + delimiter.len() - <= start_point.column as usize; - line_starts_with_comment - && cursor_is_placed_after_comment_marker + let get_comment_delimiter = |delimiters: &[Arc]| { + let max_len_of_delimiter = + delimiters.iter().map(|delimiter| delimiter.len()).max()?; + let (snapshot, range) = + buffer.buffer_line_for_row(start_point.row)?; + + let mut index_of_first_non_whitespace = 0; + let comment_candidate = snapshot + .chars_for_range(range) + .skip_while(|c| { + let should_skip = c.is_whitespace(); + if should_skip { + index_of_first_non_whitespace += 1; + } + should_skip }) - .then(|| delimiter.clone()) + .take(max_len_of_delimiter) + .collect::(); + let comment_prefix = delimiters.iter().find(|comment_prefix| { + comment_candidate.starts_with(comment_prefix.as_ref()) + })?; + let cursor_is_placed_after_comment_marker = + index_of_first_non_whitespace + comment_prefix.len() + <= start_point.column as usize; + if cursor_is_placed_after_comment_marker { + Some(comment_prefix.clone()) + } else { + None + } + }; + let comment_delimiter = if let Some(delimiters) = comment_delimiter { + get_comment_delimiter(delimiters) } else { None }; @@ -6561,7 +6570,10 @@ impl Editor { } // If the language has line comments, toggle those. - if let Some(full_comment_prefix) = language.line_comment_prefix() { + if let Some(full_comment_prefix) = language + .line_comment_prefixes() + .and_then(|prefixes| prefixes.first()) + { // Split the comment prefix's trailing whitespace into a separate string, // as that portion won't be used for detecting if a line is a comment. let comment_prefix = full_comment_prefix.trim_end_matches(' '); @@ -6569,7 +6581,7 @@ impl Editor { let mut all_selection_lines_are_comments = true; for row in start_row..=end_row { - if snapshot.is_line_blank(row) && start_row < end_row { + if start_row < end_row && snapshot.is_line_blank(row) { continue; } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 03a601db99..8c31a2bcdf 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1942,7 +1942,7 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) { let language = Arc::new(Language::new( LanguageConfig { - line_comment: Some("//".into()), + line_comments: vec!["//".into()], ..LanguageConfig::default() }, None, @@ -5736,7 +5736,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx).await; let language = Arc::new(Language::new( LanguageConfig { - line_comment: Some("// ".into()), + line_comments: vec!["// ".into()], ..Default::default() }, Some(tree_sitter_rust::language()), @@ -5838,7 +5838,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) let language = Arc::new(Language::new( LanguageConfig { - line_comment: Some("// ".into()), + line_comments: vec!["// ".into()], ..Default::default() }, Some(tree_sitter_rust::language()), @@ -5993,7 +5993,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { let javascript_language = Arc::new(Language::new( LanguageConfig { name: "JavaScript".into(), - line_comment: Some("// ".into()), + line_comments: vec!["// ".into()], ..Default::default() }, Some(tree_sitter_typescript::language_tsx()), diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 6ad345d4e3..6079f1de93 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1657,7 +1657,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { let language = Language::new( LanguageConfig { name: "JavaScript".into(), - line_comment: Some("// ".into()), + line_comments: vec!["// ".into()], brackets: BracketPairConfig { pairs: vec![ BracketPair { @@ -1681,7 +1681,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { overrides: [( "element".into(), LanguageConfigOverride { - line_comment: Override::Remove { remove: true }, + line_comments: Override::Remove { remove: true }, block_comment: Override::Set(("{/*".into(), "*/}".into())), ..Default::default() }, @@ -1718,7 +1718,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { let snapshot = buffer.snapshot(); let config = snapshot.language_scope_at(0).unwrap(); - assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!(config.line_comment_prefixes().unwrap(), &[Arc::from("// ")]); // Both bracket pairs are enabled assert_eq!( config.brackets().map(|e| e.1).collect::>(), @@ -1728,7 +1728,10 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { let string_config = snapshot .language_scope_at(text.find("b\"").unwrap()) .unwrap(); - assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!( + string_config.line_comment_prefixes().unwrap(), + &[Arc::from("// ")] + ); // Second bracket pair is disabled assert_eq!( string_config.brackets().map(|e| e.1).collect::>(), @@ -1739,7 +1742,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { let element_config = snapshot .language_scope_at(text.find("").unwrap()) .unwrap(); - assert_eq!(element_config.line_comment_prefix(), None); + assert_eq!(element_config.line_comment_prefixes(), None); assert_eq!( element_config.block_comment_delimiters(), Some((&"{/*".into(), &"*/}".into())) @@ -1753,7 +1756,10 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { let tag_config = snapshot .language_scope_at(text.find(" d=").unwrap() + 1) .unwrap(); - assert_eq!(tag_config.line_comment_prefix().unwrap().as_ref(), "// "); + assert_eq!( + tag_config.line_comment_prefixes().unwrap(), + &[Arc::from("// ")] + ); assert_eq!( tag_config.brackets().map(|e| e.1).collect::>(), &[true, true] @@ -1765,10 +1771,9 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { .unwrap(); assert_eq!( expression_in_element_config - .line_comment_prefix() - .unwrap() - .as_ref(), - "// " + .line_comment_prefixes() + .unwrap(), + &[Arc::from("// ")] ); assert_eq!( expression_in_element_config @@ -1884,14 +1889,17 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { let snapshot = buffer.snapshot(); let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap(); - assert_eq!(html_config.line_comment_prefix(), None); + assert_eq!(html_config.line_comment_prefixes(), Some(&vec![])); assert_eq!( html_config.block_comment_delimiters(), Some((&"".into())) ); let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap(); - assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# "); + assert_eq!( + ruby_config.line_comment_prefixes().unwrap(), + &[Arc::from("# ")] + ); assert_eq!(ruby_config.block_comment_delimiters(), None); buffer @@ -2293,7 +2301,7 @@ fn ruby_lang() -> Language { LanguageConfig { name: "Ruby".into(), path_suffixes: vec!["rb".to_string()], - line_comment: Some("# ".into()), + line_comments: vec!["# ".into()], ..Default::default() }, Some(tree_sitter_ruby::language()), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1add9c7c25..125c8c67f5 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -416,8 +416,10 @@ pub struct LanguageConfig { #[serde(default)] pub collapsed_placeholder: String, /// A line comment string that is inserted in e.g. `toggle comments` action. + /// A language can have multiple flavours of line comments. All of the provided line comments are + /// used for comment continuations on the next line, but only the first one is used for Editor::ToggleComments. #[serde(default)] - pub line_comment: Option>, + pub line_comments: Vec>, /// Starting and closing characters of a block comment. #[serde(default)] pub block_comment: Option<(Arc, Arc)>, @@ -460,7 +462,7 @@ pub struct LanguageScope { #[derive(Clone, Deserialize, Default, Debug)] pub struct LanguageConfigOverride { #[serde(default)] - pub line_comment: Override>, + pub line_comments: Override>>, #[serde(default)] pub block_comment: Override<(Arc, Arc)>, #[serde(skip_deserializing)] @@ -506,7 +508,7 @@ impl Default for LanguageConfig { increase_indent_pattern: Default::default(), decrease_indent_pattern: Default::default(), autoclose_before: Default::default(), - line_comment: Default::default(), + line_comments: Default::default(), block_comment: Default::default(), scope_opt_in_language_servers: Default::default(), overrides: Default::default(), @@ -1710,10 +1712,10 @@ impl LanguageScope { /// Returns line prefix that is inserted in e.g. line continuations or /// in `toggle comments` action. - pub fn line_comment_prefix(&self) -> Option<&Arc> { + pub fn line_comment_prefixes(&self) -> Option<&Vec>> { Override::as_option( - self.config_override().map(|o| &o.line_comment), - self.language.config.line_comment.as_ref(), + self.config_override().map(|o| &o.line_comments), + Some(&self.language.config.line_comments), ) } diff --git a/crates/zed/src/languages/bash/config.toml b/crates/zed/src/languages/bash/config.toml index 8c4513b250..cbf186fd3d 100644 --- a/crates/zed/src/languages/bash/config.toml +++ b/crates/zed/src/languages/bash/config.toml @@ -1,6 +1,6 @@ name = "Shell Script" path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile"] -line_comment = "# " +line_comments = ["# "] first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b" brackets = [ { start = "[", end = "]", close = true, newline = false }, diff --git a/crates/zed/src/languages/c/config.toml b/crates/zed/src/languages/c/config.toml index f986f4b834..f99c0416cd 100644 --- a/crates/zed/src/languages/c/config.toml +++ b/crates/zed/src/languages/c/config.toml @@ -1,6 +1,6 @@ name = "C" path_suffixes = ["c"] -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/cpp/config.toml b/crates/zed/src/languages/cpp/config.toml index d9b38bca06..7630f1dd79 100644 --- a/crates/zed/src/languages/cpp/config.toml +++ b/crates/zed/src/languages/cpp/config.toml @@ -1,6 +1,6 @@ name = "C++" path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"] -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/elixir/config.toml b/crates/zed/src/languages/elixir/config.toml index 8983c0e49b..a3aab0ee8a 100644 --- a/crates/zed/src/languages/elixir/config.toml +++ b/crates/zed/src/languages/elixir/config.toml @@ -1,6 +1,6 @@ name = "Elixir" path_suffixes = ["ex", "exs"] -line_comment = "# " +line_comments = ["# "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/elm/config.toml b/crates/zed/src/languages/elm/config.toml index 5051427a93..b95c85d444 100644 --- a/crates/zed/src/languages/elm/config.toml +++ b/crates/zed/src/languages/elm/config.toml @@ -1,6 +1,6 @@ name = "Elm" path_suffixes = ["elm"] -line_comment = "-- " +line_comments = ["-- "] block_comment = ["{- ", " -}"] brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/glsl/config.toml b/crates/zed/src/languages/glsl/config.toml index 4081a6381f..9b60179662 100644 --- a/crates/zed/src/languages/glsl/config.toml +++ b/crates/zed/src/languages/glsl/config.toml @@ -1,6 +1,6 @@ name = "GLSL" path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"] -line_comment = "// " +line_comments = ["// "] block_comment = ["/* ", " */"] brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/go/config.toml b/crates/zed/src/languages/go/config.toml index 1951e193f0..d07c46731a 100644 --- a/crates/zed/src/languages/go/config.toml +++ b/crates/zed/src/languages/go/config.toml @@ -1,6 +1,6 @@ name = "Go" path_suffixes = ["go"] -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/javascript/config.toml b/crates/zed/src/languages/javascript/config.toml index 3b8862e358..a19d2588ed 100644 --- a/crates/zed/src/languages/javascript/config.toml +++ b/crates/zed/src/languages/javascript/config.toml @@ -1,7 +1,7 @@ name = "JavaScript" path_suffixes = ["js", "jsx", "mjs", "cjs"] first_line_pattern = '^#!.*\bnode\b' -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, @@ -18,7 +18,7 @@ scope_opt_in_language_servers = ["tailwindcss-language-server"] prettier_parser_name = "babel" [overrides.element] -line_comment = { remove = true } +line_comments = { remove = true } block_comment = ["{/* ", " */}"] [overrides.string] diff --git a/crates/zed/src/languages/json/config.toml b/crates/zed/src/languages/json/config.toml index 37a6d3a54c..eed86826d5 100644 --- a/crates/zed/src/languages/json/config.toml +++ b/crates/zed/src/languages/json/config.toml @@ -1,6 +1,6 @@ name = "JSON" path_suffixes = ["json"] -line_comment = "// " +line_comments = ["// "] autoclose_before = ",]}" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/lua/config.toml b/crates/zed/src/languages/lua/config.toml index d3e44edfe9..31946f46e9 100644 --- a/crates/zed/src/languages/lua/config.toml +++ b/crates/zed/src/languages/lua/config.toml @@ -1,6 +1,6 @@ name = "Lua" path_suffixes = ["lua"] -line_comment = "-- " +line_comments = ["-- "] autoclose_before = ",]}" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/nix/config.toml b/crates/zed/src/languages/nix/config.toml index 778f0a6f05..20b3921271 100644 --- a/crates/zed/src/languages/nix/config.toml +++ b/crates/zed/src/languages/nix/config.toml @@ -1,6 +1,6 @@ name = "Nix" path_suffixes = ["nix"] -line_comment = "# " +line_comments = ["# "] block_comment = ["/* ", " */"] autoclose_before = ";:.,=}])>` \n\t\"" brackets = [ diff --git a/crates/zed/src/languages/nu/config.toml b/crates/zed/src/languages/nu/config.toml index d382b0705a..63f5a61591 100644 --- a/crates/zed/src/languages/nu/config.toml +++ b/crates/zed/src/languages/nu/config.toml @@ -1,6 +1,6 @@ name = "Nu" path_suffixes = ["nu"] -line_comment = "# " +line_comments = ["# "] autoclose_before = ";:.,=}])>` \n\t\"" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/php/config.toml b/crates/zed/src/languages/php/config.toml index f5ad67c12d..8a143da372 100644 --- a/crates/zed/src/languages/php/config.toml +++ b/crates/zed/src/languages/php/config.toml @@ -1,7 +1,7 @@ name = "PHP" path_suffixes = ["php"] first_line_pattern = '^#!.*php' -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/python/config.toml b/crates/zed/src/languages/python/config.toml index 6777f6e60d..3496f845dc 100644 --- a/crates/zed/src/languages/python/config.toml +++ b/crates/zed/src/languages/python/config.toml @@ -1,7 +1,7 @@ name = "Python" path_suffixes = ["py", "pyi", "mpy"] first_line_pattern = '^#!.*\bpython[0-9.]*\b' -line_comment = "# " +line_comments = ["# "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/racket/config.toml b/crates/zed/src/languages/racket/config.toml index 0177e6ef6d..aea5ec5fce 100644 --- a/crates/zed/src/languages/racket/config.toml +++ b/crates/zed/src/languages/racket/config.toml @@ -1,6 +1,6 @@ name = "Racket" path_suffixes = ["rkt"] -line_comment = "; " +line_comments = ["; "] autoclose_before = "])" brackets = [ { start = "[", end = "]", close = true, newline = false }, diff --git a/crates/zed/src/languages/ruby/config.toml b/crates/zed/src/languages/ruby/config.toml index 6c8c615015..32e43c7cb1 100644 --- a/crates/zed/src/languages/ruby/config.toml +++ b/crates/zed/src/languages/ruby/config.toml @@ -1,7 +1,7 @@ name = "Ruby" path_suffixes = ["rb", "Gemfile"] first_line_pattern = '^#!.*\bruby\b' -line_comment = "# " +line_comments = ["# "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/rust/config.toml b/crates/zed/src/languages/rust/config.toml index 8216ba0a74..9382c9d78b 100644 --- a/crates/zed/src/languages/rust/config.toml +++ b/crates/zed/src/languages/rust/config.toml @@ -1,6 +1,6 @@ name = "Rust" path_suffixes = ["rs"] -line_comment = "// " +line_comments = ["// ", "/// ", "//! "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/scheme/config.toml b/crates/zed/src/languages/scheme/config.toml index 7b47698833..c080f6d2ee 100644 --- a/crates/zed/src/languages/scheme/config.toml +++ b/crates/zed/src/languages/scheme/config.toml @@ -1,6 +1,6 @@ name = "Scheme" path_suffixes = ["scm", "ss"] -line_comment = "; " +line_comments = ["; "] autoclose_before = "])" brackets = [ { start = "[", end = "]", close = true, newline = false }, diff --git a/crates/zed/src/languages/toml/config.toml b/crates/zed/src/languages/toml/config.toml index 188239a8e0..701dbbd65a 100644 --- a/crates/zed/src/languages/toml/config.toml +++ b/crates/zed/src/languages/toml/config.toml @@ -1,6 +1,6 @@ name = "TOML" path_suffixes = ["Cargo.lock", "toml"] -line_comment = "# " +line_comments = ["# "] autoclose_before = ",]}" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/tsx/config.toml b/crates/zed/src/languages/tsx/config.toml index 0dae25d779..6806924d4a 100644 --- a/crates/zed/src/languages/tsx/config.toml +++ b/crates/zed/src/languages/tsx/config.toml @@ -1,6 +1,6 @@ name = "TSX" path_suffixes = ["tsx"] -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, @@ -17,7 +17,7 @@ scope_opt_in_language_servers = ["tailwindcss-language-server"] prettier_parser_name = "typescript" [overrides.element] -line_comment = { remove = true } +line_comments = { remove = true } block_comment = ["{/* ", " */}"] [overrides.string] diff --git a/crates/zed/src/languages/typescript/config.toml b/crates/zed/src/languages/typescript/config.toml index d1ebffc559..4d51a74098 100644 --- a/crates/zed/src/languages/typescript/config.toml +++ b/crates/zed/src/languages/typescript/config.toml @@ -1,6 +1,6 @@ name = "TypeScript" path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"] -line_comment = "// " +line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, diff --git a/crates/zed/src/languages/uiua/config.toml b/crates/zed/src/languages/uiua/config.toml index 72fdc91040..88cd8c7ad0 100644 --- a/crates/zed/src/languages/uiua/config.toml +++ b/crates/zed/src/languages/uiua/config.toml @@ -1,6 +1,6 @@ name = "Uiua" path_suffixes = ["ua"] -line_comment = "# " +line_comments = ["# "] autoclose_before = ")]}\"" brackets = [ { start = "{", end = "}", close = true, newline = false}, diff --git a/crates/zed/src/languages/yaml/config.toml b/crates/zed/src/languages/yaml/config.toml index 4e91dd348b..dce8d68d66 100644 --- a/crates/zed/src/languages/yaml/config.toml +++ b/crates/zed/src/languages/yaml/config.toml @@ -1,6 +1,6 @@ name = "YAML" path_suffixes = ["yml", "yaml"] -line_comment = "# " +line_comments = ["# "] autoclose_before = ",]}" brackets = [ { start = "{", end = "}", close = true, newline = true }, From 3d898c562eed75ddb1cb9d6970d149b91a77a4dd Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 25 Jan 2024 10:50:59 -0800 Subject: [PATCH 30/52] Update crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql Co-authored-by: Christian Clauss --- .../migrations/20231009181554_add_release_channel_to_rooms.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql index 3f32ee35c5..8f3a704add 100644 --- a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -1 +1 @@ -ALTER TABLE rooms ADD COLUMN environment TEXT; +ALTER TABLE rooms ADD COLUMN enviroment TEXT; From 6103f678752d30c7c27520f3a8ab13c152aa9c34 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 25 Jan 2024 13:53:50 -0500 Subject: [PATCH 31/52] Make follower avatars smaller (#6724) This PR makes the avatars of followers in a facepile smaller than the leader's avatar. Screenshot 2024-01-25 at 1 42 14 PM Release Notes: - Adjusted the size of follower avatars to be smaller than the leader. Co-authored-by: Conrad --- crates/collab_ui/src/chat_panel.rs | 8 +++++--- crates/collab_ui/src/collab_titlebar_item.rs | 4 +++- crates/ui/src/components/avatar/avatar.rs | 10 +++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index ebba8ffc26..6c829bd9ae 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -343,9 +343,11 @@ impl ChatPanel { this.pt_3().child( h_flex() .text_ui_sm() - .child(div().absolute().child( - Avatar::new(message.sender.avatar_uri.clone()).size(cx.rem_size()), - )) + .child( + div().absolute().child( + Avatar::new(message.sender.avatar_uri.clone()).size(rems(1.)), + ), + ) .child( div() .pl(cx.rem_size() + px(6.0)) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 43a749ec95..29175983d3 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -542,7 +542,9 @@ impl CollabTitlebarItem { })? .clone(); - Some(Avatar::new(follower.avatar_uri.clone())) + Some(div().mt(-px(4.)).child( + Avatar::new(follower.avatar_uri.clone()).size(rems(0.75)), + )) }, )) .children(if extra_count > 0 { diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs index 54f5557e35..d93b280e4b 100644 --- a/crates/ui/src/components/avatar/avatar.rs +++ b/crates/ui/src/components/avatar/avatar.rs @@ -27,7 +27,7 @@ pub enum AvatarShape { #[derive(IntoElement)] pub struct Avatar { image: Img, - size: Option, + size: Option, border_color: Option, indicator: Option, } @@ -82,8 +82,8 @@ impl Avatar { } /// Size overrides the avatar size. By default they are 1rem. - pub fn size(mut self, size: impl Into>) -> Self { - self.size = size.into(); + pub fn size>(mut self, size: impl Into>) -> Self { + self.size = size.into().map(Into::into); self } @@ -105,8 +105,8 @@ impl RenderOnce for Avatar { px(0.) }; - let image_size = self.size.unwrap_or_else(|| cx.rem_size()); - let container_size = image_size + border_width * 2.; + let image_size = self.size.unwrap_or_else(|| rems(1.).into()); + let container_size = image_size.to_pixels(cx.rem_size()) + border_width * 2.; div() .size(container_size) From 006e003698253435967a0117592dc122b570f29f Mon Sep 17 00:00:00 2001 From: SweetPPro Date: Thu, 25 Jan 2024 20:05:17 +0100 Subject: [PATCH 32/52] Add syntax highlighting for `.htm` and `.shtml` files (#6705) enabled syntax highlighting for **.htm** and **.shtml** files Release Notes: - Added support for syntax highlighting in HTML files with `.htm` and `.shtml` extensions ([#4510](https://github.com/zed-industries/zed/issues/4510)). --- crates/zed/src/languages/html/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/languages/html/config.toml b/crates/zed/src/languages/html/config.toml index 0105f0d60d..3c24490fc4 100644 --- a/crates/zed/src/languages/html/config.toml +++ b/crates/zed/src/languages/html/config.toml @@ -1,5 +1,5 @@ name = "HTML" -path_suffixes = ["html"] +path_suffixes = ["html", "htm", "shtml"] autoclose_before = ">})" block_comment = [""] brackets = [ From 0457ad3f6e1ddac7be6fb814615aa6c7e4baf102 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 25 Jan 2024 11:11:28 -0800 Subject: [PATCH 33/52] Update contributing --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ba2616237..6fc2f79dc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,7 @@ We plan to set aside time each week to pair program with contributors on promisi - Add test coverage and documentation - Choose tasks that align with our roadmap - Pair with us and watch us code to learn the codebase +- Low effort PRs, such as those that just re-arrange syntax, won't be merged without a compelling justification ## Bird-eye's view of Zed From a3d431d86f5b613d4ad66a515d3f88a292bb19b8 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 25 Jan 2024 14:19:22 -0500 Subject: [PATCH 34/52] Remove placeholder description from PR template (#6726) This PR removes the placeholder description from the PR template, opting to just leave empty space instead. I've seen lots of instances where authors will not delete the placeholder, and it ends up in Git history, which is not desirable. Release Notes: - N/A --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ad7563616d..443e1b58da 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -[[PR Description]] + Release Notes: From b5fc91e4555653d9331e22ec19b3eda324e5129b Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Thu, 25 Jan 2024 11:22:33 -0800 Subject: [PATCH 35/52] Github => GitHub --- crates/copilot_ui/src/sign_in.rs | 8 ++++---- crates/util/src/github.rs | 2 +- docs/src/configuring_zed__configuring_vim.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index 2bea2e016c..53c6d75182 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -88,15 +88,15 @@ impl CopilotCodeVerification { let connect_button_label = if connect_clicked { "Waiting for connection..." } else { - "Connect to Github" + "Connect to GitHub" }; v_flex() .flex_1() .gap_2() .items_center() - .child(Headline::new("Use Github Copilot in Zed.").size(HeadlineSize::Large)) + .child(Headline::new("Use GitHub Copilot in Zed.").size(HeadlineSize::Large)) .child( - Label::new("Using Copilot requires an active subscription on Github.") + Label::new("Using Copilot requires an active subscription on GitHub.") .color(Color::Muted), ) .child(Self::render_device_code(data, cx)) @@ -139,7 +139,7 @@ impl CopilotCodeVerification { "You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.", ).color(Color::Warning)) .child( - Button::new("copilot-subscribe-button", "Subscribe on Github") + Button::new("copilot-subscribe-button", "Subscribe on GitHub") .full_width() .on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)), ) diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index e4b316e12d..720eec845d 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -59,7 +59,7 @@ pub async fn latest_github_release( Err(_) => { log::error!( - "Error deserializing Github API response text: {:?}", + "Error deserializing GitHub API response text: {:?}", String::from_utf8_lossy(body.as_slice()) ); return Err(anyhow!("error deserializing latest release")); diff --git a/docs/src/configuring_zed__configuring_vim.md b/docs/src/configuring_zed__configuring_vim.md index 4058fa16ff..22b84b274f 100644 --- a/docs/src/configuring_zed__configuring_vim.md +++ b/docs/src/configuring_zed__configuring_vim.md @@ -33,7 +33,7 @@ g h Show inline error (hover) # Insert mode ctrl-x ctrl-o Open the completion menu -ctrl-x ctrl-c Request Github Copilot suggestion (if configured) +ctrl-x ctrl-c Request GitHub Copilot suggestion (if configured) ctrl-x ctrl-a Open the inline AI assistant (if configured) ctrl-x ctrl-l Open the LSP code actions ctrl-x ctrl-z Hides all suggestions From 039ef1ad5a932d57297a8242c55ff1d69c628271 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 25 Jan 2024 12:46:51 -0800 Subject: [PATCH 36/52] Put back Project's completion-documention handler (#6451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This restores the ability for project guests to see documentation on autocomplete suggestions. @ForLoveOfCats This code might have gotten lost during the GPUI upgrade. I'm not sure what happened. I tested this locally, and it seems to fix completion docs for guests. Could you 👀 and see if there are any tests or any other code that got lost along with this during the upgrade? --- crates/project/src/project.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 840f9b20db..9476463e33 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -590,6 +590,7 @@ impl Project { client.add_model_request_handler(Self::handle_delete_project_entry); client.add_model_request_handler(Self::handle_expand_project_entry); client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion); + client.add_model_request_handler(Self::handle_resolve_completion_documentation); client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_on_type_formatting); client.add_model_request_handler(Self::handle_inlay_hints); @@ -7726,6 +7727,40 @@ impl Project { }) } + async fn handle_resolve_completion_documentation( + this: Model, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let lsp_completion = serde_json::from_slice(&envelope.payload.lsp_completion)?; + + let completion = this + .read_with(&mut cx, |this, _| { + let id = LanguageServerId(envelope.payload.language_server_id as usize); + let Some(server) = this.language_server_for_id(id) else { + return Err(anyhow!("No language server {id}")); + }; + + Ok(server.request::(lsp_completion)) + })?? + .await?; + + let mut is_markdown = false; + let text = match completion.documentation { + Some(lsp::Documentation::String(text)) => text, + + Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value })) => { + is_markdown = kind == lsp::MarkupKind::Markdown; + value + } + + _ => String::new(), + }; + + Ok(proto::ResolveCompletionDocumentationResponse { text, is_markdown }) + } + async fn handle_apply_code_action( this: Model, envelope: TypedEnvelope, From 20c90f07e11d6f58bd1664478efaa3e8114f0044 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 25 Jan 2024 16:04:58 -0500 Subject: [PATCH 37/52] Clean up references in doc comments in `language` crate (#6729) This PR cleans up a handful of references in doc comments in the `language` crate so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/language/src/buffer.rs | 16 ++++++---------- crates/language/src/diagnostic_set.rs | 10 +++++----- crates/language/src/language.rs | 12 +++++++----- crates/language/src/proto.rs | 2 +- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 67d66341e2..64d15e6856 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -197,7 +197,7 @@ pub struct Diagnostic { /// Whether this diagnostic is considered to originate from an analysis of /// files on disk, as opposed to any unsaved buffer contents. This is a /// property of a given diagnostic source, and is configured for a given - /// language server via the [LspAdapter::disk_based_diagnostic_sources] method + /// language server via the [`LspAdapter::disk_based_diagnostic_sources`](crate::LspAdapter::disk_based_diagnostic_sources) method /// for the language server. pub is_disk_based: bool, /// Whether this diagnostic marks unnecessary code. @@ -236,7 +236,7 @@ pub async fn prepare_completion_documentation( } } -/// Documentation associated with a [Completion]. +/// Documentation associated with a [`Completion`]. #[derive(Clone, Debug)] pub enum Documentation { /// There is no documentation for this completion. @@ -301,7 +301,7 @@ pub enum Operation { lamport_timestamp: clock::Lamport, /// Whether the selections are in 'line mode'. line_mode: bool, - /// The [CursorShape] associated with these selections. + /// The [`CursorShape`] associated with these selections. cursor_shape: CursorShape, }, @@ -347,7 +347,7 @@ pub enum Event { /// The file associated with a buffer. pub trait File: Send + Sync { - /// Returns the [LocalFile] associated with this file, if the + /// Returns the [`LocalFile`] associated with this file, if the /// file is local. fn as_local(&self) -> Option<&dyn LocalFile>; @@ -378,7 +378,7 @@ pub trait File: Send + Sync { /// Returns whether the file has been deleted. fn is_deleted(&self) -> bool; - /// Converts this file into an [Any] trait object. + /// Converts this file into an [`Any`] trait object. fn as_any(&self) -> &dyn Any; /// Converts this file into a protobuf message. @@ -1538,8 +1538,6 @@ impl Buffer { /// Starts a transaction, providing the current time. Subsequent transactions /// that occur within a short period of time will be grouped together. This /// is controlled by the buffer's undo grouping duration. - /// - /// See [`Buffer::set_group_interval`]. pub fn start_transaction_at(&mut self, now: Instant) -> Option { self.transaction_depth += 1; if self.was_dirty_before_starting_transaction.is_none() { @@ -1556,8 +1554,6 @@ impl Buffer { /// Terminates the current transaction, providing the current time. Subsequent transactions /// that occur within a short period of time will be grouped together. This /// is controlled by the buffer's undo grouping duration. - /// - /// See [`Buffer::set_group_interval`]. pub fn end_transaction_at( &mut self, now: Instant, @@ -2420,7 +2416,7 @@ impl BufferSnapshot { } /// Iterates over chunks of text in the given range of the buffer. Text is chunked - /// in an arbitrary way due to being stored in a [`rope::Rope`]. The text is also + /// in an arbitrary way due to being stored in a [`Rope`](text::Rope). The text is also /// returned in chunks where each chunk has a single syntax highlighting style and /// diagnostic status. pub fn chunks(&self, range: Range, language_aware: bool) -> BufferChunks { diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index e04406f28e..5d85fbc290 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -12,7 +12,7 @@ use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; /// A set of diagnostics associated with a given buffer, provided /// by a single language server. /// -/// The diagnostics are stored in a [SumTree], which allows this struct +/// The diagnostics are stored in a [`SumTree`], which allows this struct /// to be cheaply copied, and allows for efficient retrieval of the /// diagnostics that intersect a given range of the buffer. #[derive(Clone, Debug, Default)] @@ -21,9 +21,9 @@ pub struct DiagnosticSet { } /// A single diagnostic in a set. Generic over its range type, because -/// the diagnostics are stored internally as [Anchor]s, but can be -/// resolved to different coordinates types like [usize] byte offsets or -/// [Point]s. +/// the diagnostics are stored internally as [`Anchor`]s, but can be +/// resolved to different coordinates types like [`usize`] byte offsets or +/// [`Point`](gpui::Point)s. #[derive(Clone, Debug, PartialEq, Eq)] pub struct DiagnosticEntry { /// The range of the buffer where the diagnostic applies. @@ -52,7 +52,7 @@ pub struct Summary { } impl DiagnosticEntry { - /// Returns a raw LSP diagnostic ssed to provide diagnostic context to lsp + /// Returns a raw LSP diagnostic ssed to provide diagnostic context to LSP /// codeAction request pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic { let code = self diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 62dde0ba63..ca4a559617 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -298,10 +298,12 @@ pub trait LspAdapter: 'static + Send + Sync { delegate: &dyn LspAdapterDelegate, ) -> Option; - /// Returns true if a language server can be reinstalled. - /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is false. + /// Returns `true` if a language server can be reinstalled. + /// + /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is `false`. + /// /// Implementations that rely on software already installed on user's system - /// should have [`can_be_reinstalled`] return false. + /// should have [`can_be_reinstalled`](Self::can_be_reinstalled) return `false`. fn can_be_reinstalled(&self) -> bool { true } @@ -313,7 +315,7 @@ pub trait LspAdapter: 'static + Send + Sync { fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} - /// A callback called for each [`lsp_types::CompletionItem`] obtained from LSP server. + /// A callback called for each [`lsp::CompletionItem`] obtained from LSP server. /// Some LspAdapter implementations might want to modify the obtained item to /// change how it's displayed. async fn process_completion(&self, _: &mut lsp::CompletionItem) {} @@ -335,7 +337,7 @@ pub trait LspAdapter: 'static + Send + Sync { None } - /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp_types::InitializeParams`] + /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp::InitializeParams`] fn initialization_options(&self) -> Option { None } diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index d4b553de47..45136ba1d7 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -103,7 +103,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation { } } -/// Serializes an [`operation::EditOperation`] to be sent over RPC. +/// Serializes an [`EditOperation`] to be sent over RPC. pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit { proto::operation::Edit { replica_id: operation.timestamp.replica_id as u32, From 50b9e5d8d291dadbf8a30355b5f10145247b83c1 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 25 Jan 2024 18:44:35 -0500 Subject: [PATCH 38/52] Add Gleam support (#6733) This PR adds support for [Gleam](https://gleam.run/). Screenshot 2024-01-25 at 6 39 18 PM Screenshot 2024-01-25 at 6 39 37 PM Screenshot 2024-01-25 at 6 39 55 PM There are still some areas of improvement, like extending what constructs we support in the outline view, but this is a good start. Release Notes: - Added Gleam support ([#5162](https://github.com/zed-industries/zed/issues/5162)). --- Cargo.lock | 10 ++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 6 + crates/zed/src/languages/gleam.rs | 118 ++++++++++++++++ crates/zed/src/languages/gleam/config.toml | 10 ++ crates/zed/src/languages/gleam/highlights.scm | 130 ++++++++++++++++++ crates/zed/src/languages/gleam/outline.scm | 4 + 8 files changed, 280 insertions(+) create mode 100644 crates/zed/src/languages/gleam.rs create mode 100644 crates/zed/src/languages/gleam/config.toml create mode 100644 crates/zed/src/languages/gleam/highlights.scm create mode 100644 crates/zed/src/languages/gleam/outline.scm diff --git a/Cargo.lock b/Cargo.lock index 41987a70b8..fc1217161a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8503,6 +8503,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-gleam" +version = "0.34.0" +source = "git+https://github.com/gleam-lang/tree-sitter-gleam?rev=58b7cac8fc14c92b0677c542610d8738c373fa81#58b7cac8fc14c92b0677c542610d8738c373fa81" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-glsl" version = "0.1.4" @@ -9781,6 +9790,7 @@ dependencies = [ "tree-sitter-elixir", "tree-sitter-elm", "tree-sitter-embedded-template", + "tree-sitter-gleam", "tree-sitter-glsl", "tree-sitter-go", "tree-sitter-heex", diff --git a/Cargo.toml b/Cargo.toml index 121d42223b..4c3658b1a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,6 +140,7 @@ tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir" tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"} tree-sitter-embedded-template = "0.20.0" tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" } +tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" } tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 683a7c6fed..d1a83e0b3f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -121,6 +121,7 @@ tree-sitter-elixir.workspace = true tree-sitter-elm.workspace = true tree-sitter-embedded-template.workspace = true tree-sitter-glsl.workspace = true +tree-sitter-gleam.workspace = true tree-sitter-go.workspace = true tree-sitter-heex.workspace = true tree-sitter-json.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 3fdcad46fe..0ad95b9fde 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -12,6 +12,7 @@ use self::elixir::ElixirSettings; mod c; mod css; mod elixir; +mod gleam; mod go; mod html; mod json; @@ -99,6 +100,11 @@ pub fn init( ), } + language( + "gleam", + tree_sitter_gleam::language(), + vec![Arc::new(gleam::GleamLspAdapter)], + ); language( "go", tree_sitter_go::language(), diff --git a/crates/zed/src/languages/gleam.rs b/crates/zed/src/languages/gleam.rs new file mode 100644 index 0000000000..90b10a3657 --- /dev/null +++ b/crates/zed/src/languages/gleam.rs @@ -0,0 +1,118 @@ +use std::any::Any; +use std::ffi::OsString; +use std::path::PathBuf; + +use anyhow::{anyhow, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; +use async_trait::async_trait; +use futures::io::BufReader; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use smol::fs; +use util::github::{latest_github_release, GitHubLspBinaryVersion}; +use util::{async_maybe, ResultExt}; + +fn server_binary_arguments() -> Vec { + vec!["lsp".into()] +} + +pub struct GleamLspAdapter; + +#[async_trait] +impl LspAdapter for GleamLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("gleam".into()) + } + + fn short_name(&self) -> &'static str { + "gleam" + } + + async fn fetch_latest_server_version( + &self, + delegate: &dyn LspAdapterDelegate, + ) -> Result> { + let release = + latest_github_release("gleam-lang/gleam", false, delegate.http_client()).await?; + + let asset_name = format!( + "gleam-{version}-{arch}-apple-darwin.tar.gz", + version = release.name, + arch = std::env::consts::ARCH + ); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + Ok(Box::new(GitHubLspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + })) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let binary_path = container_dir.join("gleam"); + + if fs::metadata(&binary_path).await.is_err() { + let mut response = delegate + .http_client() + .get(&version.url, Default::default(), true) + .await + .map_err(|err| anyhow!("error downloading release: {}", err))?; + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(container_dir).await?; + } + + Ok(LanguageServerBinary { + path: binary_path, + arguments: server_binary_arguments(), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--version".into()]; + binary + }) + } +} + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + async_maybe!({ + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + + anyhow::Ok(LanguageServerBinary { + path: last.ok_or_else(|| anyhow!("no cached binary"))?, + arguments: server_binary_arguments(), + }) + }) + .await + .log_err() +} diff --git a/crates/zed/src/languages/gleam/config.toml b/crates/zed/src/languages/gleam/config.toml new file mode 100644 index 0000000000..a841862709 --- /dev/null +++ b/crates/zed/src/languages/gleam/config.toml @@ -0,0 +1,10 @@ +name = "Gleam" +path_suffixes = ["gleam"] +line_comments = ["// ", "/// "] +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] }, +] diff --git a/crates/zed/src/languages/gleam/highlights.scm b/crates/zed/src/languages/gleam/highlights.scm new file mode 100644 index 0000000000..a95f6cb031 --- /dev/null +++ b/crates/zed/src/languages/gleam/highlights.scm @@ -0,0 +1,130 @@ +; Comments +(module_comment) @comment +(statement_comment) @comment +(comment) @comment + +; Constants +(constant + name: (identifier) @constant) + +; Modules +(module) @module +(import alias: (identifier) @module) +(remote_type_identifier + module: (identifier) @module) +(remote_constructor_name + module: (identifier) @module) +((field_access + record: (identifier) @module + field: (label) @function) + (#is-not? local)) + +; Functions +(unqualified_import (identifier) @function) +(unqualified_import "type" (type_identifier) @type) +(unqualified_import (type_identifier) @constructor) +(function + name: (identifier) @function) +(external_function + name: (identifier) @function) +(function_parameter + name: (identifier) @variable.parameter) +((function_call + function: (identifier) @function) + (#is-not? local)) +((binary_expression + operator: "|>" + right: (identifier) @function) + (#is-not? local)) + +; "Properties" +; Assumed to be intended to refer to a name for a field; something that comes +; before ":" or after "." +; e.g. record field names, tuple indices, names for named arguments, etc +(label) @property +(tuple_access + index: (integer) @property) + +; Attributes +(attribute + "@" @attribute + name: (identifier) @attribute) + +(attribute_value (identifier) @constant) + +; Type names +(remote_type_identifier) @type +(type_identifier) @type + +; Data constructors +(constructor_name) @constructor + +; Literals +(string) @string +((escape_sequence) @warning + ; Deprecated in v0.33.0-rc2: + (#eq? @warning "\\e")) +(escape_sequence) @string.escape +(bit_string_segment_option) @function.builtin +(integer) @number +(float) @number + +; Reserved identifiers +; TODO: when tree-sitter supports `#any-of?` in the Rust bindings, +; refactor this to use `#any-of?` rather than `#match?` +((identifier) @warning + (#match? @warning "^(auto|delegate|derive|else|implement|macro|test|echo)$")) + +; Variables +(identifier) @variable +(discard) @comment.unused + +; Keywords +[ + (visibility_modifier) ; "pub" + (opacity_modifier) ; "opaque" + "as" + "assert" + "case" + "const" + ; DEPRECATED: 'external' was removed in v0.30. + "external" + "fn" + "if" + "import" + "let" + "panic" + "todo" + "type" + "use" +] @keyword + +; Operators +(binary_expression + operator: _ @operator) +(boolean_negation "!" @operator) +(integer_negation "-" @operator) + +; Punctuation +[ + "(" + ")" + "[" + "]" + "{" + "}" + "<<" + ">>" +] @punctuation.bracket +[ + "." + "," + ;; Controversial -- maybe some are operators? + ":" + "#" + "=" + "->" + ".." + "-" + "<-" +] @punctuation.delimiter diff --git a/crates/zed/src/languages/gleam/outline.scm b/crates/zed/src/languages/gleam/outline.scm new file mode 100644 index 0000000000..7d983f5947 --- /dev/null +++ b/crates/zed/src/languages/gleam/outline.scm @@ -0,0 +1,4 @@ +(function + (visibility_modifier)? @context + "fn" @context + name: (_) @name) @item From 81aac492bdd07efa426117f8ef704823cb10f402 Mon Sep 17 00:00:00 2001 From: Lino Le Van <11367844+lino-levan@users.noreply.github.com> Date: Fri, 26 Jan 2024 02:44:51 +0100 Subject: [PATCH 39/52] Add Deno LSP support (#5816) This PR adds support for the deno LSP. Should be reviewable now. Release Notes: - Added support for the Deno LSP ([#5361](https://github.com/zed-industries/zed/issues/5361)). --- assets/settings/default.json | 4 + crates/zed/src/languages.rs | 83 ++++++++---- crates/zed/src/languages/deno.rs | 223 +++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 27 deletions(-) create mode 100644 crates/zed/src/languages/deno.rs diff --git a/assets/settings/default.json b/assets/settings/default.json index a920a9c68a..01165c9e31 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -447,6 +447,10 @@ // "lsp": "elixir_ls" }, + // Settings specific to our deno integration + "deno": { + "enable": false + }, // Different settings for specific languages. "languages": { "Plain Text": { diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0ad95b9fde..bba9137708 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -7,10 +7,11 @@ use settings::Settings; use std::{borrow::Cow, str, sync::Arc}; use util::{asset_str, paths::PLUGINS_DIR}; -use self::elixir::ElixirSettings; +use self::{deno::DenoSettings, elixir::ElixirSettings}; mod c; mod css; +mod deno; mod elixir; mod gleam; mod go; @@ -51,6 +52,7 @@ pub fn init( cx: &mut AppContext, ) { ElixirSettings::register(cx); + DenoSettings::register(cx); let language = |name, grammar, adapters| { languages.register(name, load_config(name), grammar, adapters, load_queries) @@ -140,32 +142,59 @@ pub fn init( vec![Arc::new(rust::RustLspAdapter)], ); language("toml", tree_sitter_toml::language(), vec![]); - language( - "tsx", - tree_sitter_typescript::language_tsx(), - vec![ - Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), - Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], - ); - language( - "typescript", - tree_sitter_typescript::language_typescript(), - vec![ - Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), - ], - ); - language( - "javascript", - tree_sitter_typescript::language_tsx(), - vec![ - Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), - Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], - ); + match &DenoSettings::get(None, cx).enable { + true => { + language( + "tsx", + tree_sitter_typescript::language_tsx(), + vec![ + Arc::new(deno::DenoLspAdapter::new()), + Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), + ], + ); + language( + "typescript", + tree_sitter_typescript::language_typescript(), + vec![Arc::new(deno::DenoLspAdapter::new())], + ); + language( + "javascript", + tree_sitter_typescript::language_tsx(), + vec![ + Arc::new(deno::DenoLspAdapter::new()), + Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), + ], + ); + } + false => { + language( + "tsx", + tree_sitter_typescript::language_tsx(), + vec![ + Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), + ], + ); + language( + "typescript", + tree_sitter_typescript::language_typescript(), + vec![ + Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + ], + ); + language( + "javascript", + tree_sitter_typescript::language_tsx(), + vec![ + Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), + ], + ); + } + } language( "html", tree_sitter_html::language(), diff --git a/crates/zed/src/languages/deno.rs b/crates/zed/src/languages/deno.rs new file mode 100644 index 0000000000..671248007a --- /dev/null +++ b/crates/zed/src/languages/deno.rs @@ -0,0 +1,223 @@ +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use collections::HashMap; +use futures::StreamExt; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::{CodeActionKind, LanguageServerBinary}; +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use serde_json::json; +use settings::Settings; +use smol::{fs, fs::File}; +use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; +use util::{fs::remove_matching, github::latest_github_release}; +use util::{github::GitHubLspBinaryVersion, ResultExt}; + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct DenoSettings { + pub enable: bool, +} + +#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)] +pub struct DenoSettingsContent { + enable: Option, +} + +impl Settings for DenoSettings { + const KEY: Option<&'static str> = Some("deno"); + + type FileContent = DenoSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &mut gpui::AppContext, + ) -> Result + where + Self: Sized, + { + Self::load_via_json_merge(default_value, user_values) + } +} + +fn deno_server_binary_arguments() -> Vec { + vec!["lsp".into()] +} + +pub struct DenoLspAdapter {} + +impl DenoLspAdapter { + pub fn new() -> Self { + DenoLspAdapter {} + } +} + +#[async_trait] +impl LspAdapter for DenoLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("deno-language-server".into()) + } + + fn short_name(&self) -> &'static str { + "deno-ts" + } + + async fn fetch_latest_server_version( + &self, + delegate: &dyn LspAdapterDelegate, + ) -> Result> { + let release = latest_github_release("denoland/deno", false, delegate.http_client()).await?; + let asset_name = format!("deno-{}-apple-darwin.zip", consts::ARCH); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + }; + Ok(Box::new(version) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let zip_path = container_dir.join(format!("deno_{}.zip", version.name)); + let version_dir = container_dir.join(format!("deno_{}", version.name)); + let binary_path = version_dir.join("deno"); + + if fs::metadata(&binary_path).await.is_err() { + let mut response = delegate + .http_client() + .get(&version.url, Default::default(), true) + .await + .context("error downloading release")?; + let mut file = File::create(&zip_path).await?; + if !response.status().is_success() { + Err(anyhow!( + "download failed with status {}", + response.status().to_string() + ))?; + } + futures::io::copy(response.body_mut(), &mut file).await?; + + let unzip_status = smol::process::Command::new("unzip") + .current_dir(&container_dir) + .arg(&zip_path) + .arg("-d") + .arg(&version_dir) + .output() + .await? + .status; + if !unzip_status.success() { + Err(anyhow!("failed to unzip deno archive"))?; + } + + remove_matching(&container_dir, |entry| entry != version_dir).await; + } + + Ok(LanguageServerBinary { + path: binary_path, + arguments: deno_server_binary_arguments(), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir).await + } + + fn code_action_kinds(&self) -> Option> { + Some(vec![ + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR, + CodeActionKind::REFACTOR_EXTRACT, + CodeActionKind::SOURCE, + ]) + } + + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + use lsp::CompletionItemKind as Kind; + let len = item.label.len(); + let grammar = language.grammar()?; + let highlight_id = match item.kind? { + Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"), + Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"), + Kind::CONSTANT => grammar.highlight_id_for_name("constant"), + Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"), + Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("property"), + _ => None, + }?; + + let text = match &item.detail { + Some(detail) => format!("{} {}", item.label, detail), + None => item.label.clone(), + }; + + Some(language::CodeLabel { + text, + runs: vec![(0..len, highlight_id)], + filter_range: 0..len, + }) + } + + fn initialization_options(&self) -> Option { + Some(json!({ + "provideFormatter": true, + })) + } + + fn language_ids(&self) -> HashMap { + HashMap::from_iter([ + ("TypeScript".into(), "typescript".into()), + ("JavaScript".into(), "javascript".into()), + ("TSX".into(), "typescriptreact".into()), + ]) + } +} + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + + match last { + Some(path) if path.is_dir() => { + let binary = path.join("deno"); + if fs::metadata(&binary).await.is_ok() { + return Ok(LanguageServerBinary { + path: binary, + arguments: deno_server_binary_arguments(), + }); + } + } + _ => {} + } + + Err(anyhow!("no cached binary")) + })() + .await + .log_err() +} From 37647a67a79414549ae6dd5f30f039f8cf93ca00 Mon Sep 17 00:00:00 2001 From: 0x29a Date: Fri, 26 Jan 2024 10:03:23 +0800 Subject: [PATCH 40/52] ci: Bump `actions/checkout` to v4 (#6736) change: - bump actions/checkout version Release Notes: - N/A --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish_collab_image.yml | 2 +- .github/workflows/randomized_tests.yml | 2 +- .github/workflows/release_nightly.yml | 6 +++--- .github/workflows/update_top_ranking_issues.yml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4340ac1cb4..bbaa12ecdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - test steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" @@ -55,7 +55,7 @@ jobs: - test steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" @@ -88,7 +88,7 @@ jobs: node-version: "18" - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" diff --git a/.github/workflows/publish_collab_image.yml b/.github/workflows/publish_collab_image.yml index b012e65841..18289faf81 100644 --- a/.github/workflows/publish_collab_image.yml +++ b/.github/workflows/publish_collab_image.yml @@ -26,7 +26,7 @@ jobs: run: docker system prune - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: 'recursive' diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index a5e2d983ed..b629b1a806 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -28,7 +28,7 @@ jobs: node-version: "18" - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 33ccb4cba9..25a4c64a77 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -21,7 +21,7 @@ jobs: - test steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" @@ -38,7 +38,7 @@ jobs: needs: style steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" @@ -66,7 +66,7 @@ jobs: node-version: "18" - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: clean: false submodules: "recursive" diff --git a/.github/workflows/update_top_ranking_issues.yml b/.github/workflows/update_top_ranking_issues.yml index 245f60114d..8b93bee7e2 100644 --- a/.github/workflows/update_top_ranking_issues.yml +++ b/.github/workflows/update_top_ranking_issues.yml @@ -7,7 +7,7 @@ jobs: update_top_ranking_issues: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.10.5" From e5b71cc6ac81494de9114286f97696f1272c5a38 Mon Sep 17 00:00:00 2001 From: Allan Calix Date: Wed, 24 Jan 2024 20:07:18 -0800 Subject: [PATCH 41/52] Add zig support --- Cargo.lock | 10 + Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 6 + crates/zed/src/languages/zig.rs | 125 +++++++++++ crates/zed/src/languages/zig/config.toml | 10 + crates/zed/src/languages/zig/folds.scm | 16 ++ crates/zed/src/languages/zig/highlights.scm | 234 ++++++++++++++++++++ crates/zed/src/languages/zig/indents.scm | 22 ++ crates/zed/src/languages/zig/injections.scm | 5 + 10 files changed, 430 insertions(+) create mode 100644 crates/zed/src/languages/zig.rs create mode 100644 crates/zed/src/languages/zig/config.toml create mode 100644 crates/zed/src/languages/zig/folds.scm create mode 100644 crates/zed/src/languages/zig/highlights.scm create mode 100644 crates/zed/src/languages/zig/indents.scm create mode 100644 crates/zed/src/languages/zig/injections.scm diff --git a/Cargo.lock b/Cargo.lock index fc1217161a..24b3633753 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8716,6 +8716,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-zig" +version = "0.0.1" +source = "git+https://github.com/maxxnino/tree-sitter-zig?rev=0d08703e4c3f426ec61695d7617415fff97029bd#0d08703e4c3f426ec61695d7617415fff97029bd" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -9812,6 +9821,7 @@ dependencies = [ "tree-sitter-uiua", "tree-sitter-vue", "tree-sitter-yaml", + "tree-sitter-zig", "unindent", "url", "urlencoding", diff --git a/Cargo.toml b/Cargo.toml index 4c3658b1a3..bad9e149bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,7 @@ tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", re tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa"} tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42"} tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"} +tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" } [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d1a83e0b3f..a4084a7bd9 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -142,6 +142,7 @@ tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true tree-sitter-vue.workspace = true tree-sitter-uiua.workspace = true +tree-sitter-zig.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index bba9137708..cb557ced8d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -31,6 +31,7 @@ mod typescript; mod uiua; mod vue; mod yaml; +mod zig; // 1. Add tree-sitter-{language} parser to zed crate // 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below @@ -112,6 +113,11 @@ pub fn init( tree_sitter_go::language(), vec![Arc::new(go::GoLspAdapter)], ); + language( + "zig", + tree_sitter_zig::language(), + vec![Arc::new(zig::ZlsAdapter)], + ); language( "heex", tree_sitter_heex::language(), diff --git a/crates/zed/src/languages/zig.rs b/crates/zed/src/languages/zig.rs new file mode 100644 index 0000000000..734d21e2ea --- /dev/null +++ b/crates/zed/src/languages/zig.rs @@ -0,0 +1,125 @@ +use anyhow::{anyhow, Context, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; +use async_trait::async_trait; +use futures::{io::BufReader, StreamExt}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; +use smol::fs; +use std::env::consts::ARCH; +use std::{any::Any, path::PathBuf}; +use util::async_maybe; +use util::github::latest_github_release; +use util::{github::GitHubLspBinaryVersion, ResultExt}; + +pub struct ZlsAdapter; + +#[async_trait] +impl LspAdapter for ZlsAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("zls".into()) + } + + fn short_name(&self) -> &'static str { + "zls" + } + + async fn fetch_latest_server_version( + &self, + delegate: &dyn LspAdapterDelegate, + ) -> Result> { + let release = latest_github_release("zigtools/zls", false, delegate.http_client()).await?; + let asset_name = format!("zls-{}-macos.tar.gz", ARCH); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { + name: release.name, + url: asset.browser_download_url.clone(), + }; + + Ok(Box::new(version) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let binary_path = container_dir.join("bin/zls"); + + if fs::metadata(&binary_path).await.is_err() { + let mut response = delegate + .http_client() + .get(&version.url, Default::default(), true) + .await + .context("error downloading release")?; + let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); + let archive = Archive::new(decompressed_bytes); + archive.unpack(container_dir).await?; + } + + fs::set_permissions( + &binary_path, + ::from_mode(0o755), + ) + .await?; + Ok(LanguageServerBinary { + path: binary_path, + arguments: vec![], + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) + } +} + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + async_maybe!({ + let mut last_binary_path = 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_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name == "zls") + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: Vec::new(), + }) + } else { + Err(anyhow!("no cached binary")) + } + }) + .await + .log_err() +} diff --git a/crates/zed/src/languages/zig/config.toml b/crates/zed/src/languages/zig/config.toml new file mode 100644 index 0000000000..2ac71fd5c0 --- /dev/null +++ b/crates/zed/src/languages/zig/config.toml @@ -0,0 +1,10 @@ +name = "Zig" +path_suffixes = ["zig"] +line_comment = "// " +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/zig/folds.scm b/crates/zed/src/languages/zig/folds.scm new file mode 100644 index 0000000000..9659874395 --- /dev/null +++ b/crates/zed/src/languages/zig/folds.scm @@ -0,0 +1,16 @@ +[ + (Block) + (ContainerDecl) + (SwitchExpr) + (InitList) + (AsmExpr) + (ErrorSetDecl) + (LINESTRING) + ( + [ + (IfPrefix) + (WhilePrefix) + (ForPrefix) + ] + ) +] @fold diff --git a/crates/zed/src/languages/zig/highlights.scm b/crates/zed/src/languages/zig/highlights.scm new file mode 100644 index 0000000000..189d6aa468 --- /dev/null +++ b/crates/zed/src/languages/zig/highlights.scm @@ -0,0 +1,234 @@ +[ + (container_doc_comment) + (doc_comment) + (line_comment) +] @comment + +[ + variable: (IDENTIFIER) + variable_type_function: (IDENTIFIER) +] @variable + +parameter: (IDENTIFIER) @parameter + +[ + field_member: (IDENTIFIER) + field_access: (IDENTIFIER) +] @field + +;; assume TitleCase is a type +( + [ + variable_type_function: (IDENTIFIER) + field_access: (IDENTIFIER) + parameter: (IDENTIFIER) + ] @type + (#match? @type "^[A-Z]([a-z]+[A-Za-z0-9]*)*$") +) +;; assume camelCase is a function +( + [ + variable_type_function: (IDENTIFIER) + field_access: (IDENTIFIER) + parameter: (IDENTIFIER) + ] @function + (#match? @function "^[a-z]+([A-Z][a-z0-9]*)+$") +) + +;; assume all CAPS_1 is a constant +( + [ + variable_type_function: (IDENTIFIER) + field_access: (IDENTIFIER) + ] @constant + (#match? @constant "^[A-Z][A-Z_0-9]+$") +) + +[ + function_call: (IDENTIFIER) + function: (IDENTIFIER) +] @function + +exception: "!" @exception + +( + (IDENTIFIER) @variable.builtin + (#eq? @variable.builtin "_") +) + +(PtrTypeStart "c" @variable.builtin) + +( + (ContainerDeclType + [ + (ErrorUnionExpr) + "enum" + ] + ) + (ContainerField (IDENTIFIER) @constant) +) + +field_constant: (IDENTIFIER) @constant + +(BUILTINIDENTIFIER) @keyword + +; No idea why this doesnt work +; ((BUILTINIDENTIFIER) @include +; (#any-of? @include "@import" "@cImport")) + +(INTEGER) @number + +(FLOAT) @float + +[ + "true" + "false" +] @boolean + +[ + (LINESTRING) + (STRINGLITERALSINGLE) +] @string + +(CHAR_LITERAL) @character +(EscapeSequence) @string.escape +(FormatSequence) @string.special + +(BreakLabel (IDENTIFIER) @label) +(BlockLabel (IDENTIFIER) @label) + +[ + "asm" + "defer" + "errdefer" + "test" + "struct" + "union" + "enum" + "opaque" + "error" +] @keyword + +[ + "async" + "await" + "suspend" + "nosuspend" + "resume" +] @keyword.coroutine + +[ + "fn" +] @keyword.function + +[ + "and" + "or" + "orelse" +] @keyword.operator + +[ + "return" +] @keyword.return + +[ + "if" + "else" + "switch" +] @conditional + +[ + "for" + "while" + "break" + "continue" +] @keyword + +[ + "usingnamespace" +] @include + +[ + "try" + "catch" +] @keyword + +[ + "anytype" + (BuildinTypeExpr) +] @type.builtin + +[ + "const" + "var" + "volatile" + "allowzero" + "noalias" +] @type.qualifier + +[ + "addrspace" + "align" + "callconv" + "linksection" +] @storageclass + +[ + "comptime" + "export" + "extern" + "inline" + "noinline" + "packed" + "pub" + "threadlocal" +] @attribute + +[ + "null" + "unreachable" + "undefined" +] @constant.builtin + +[ + (CompareOp) + (BitwiseOp) + (BitShiftOp) + (AdditionOp) + (AssignOp) + (MultiplyOp) + (PrefixOp) + "*" + "**" + "->" + ".?" + ".*" + "?" +] @operator + +[ + ";" + "." + "," + ":" +] @punctuation.delimiter + +[ + ".." + "..." +] @punctuation.special + +[ + "[" + "]" + "(" + ")" + "{" + "}" + (Payload "|") + (PtrPayload "|") + (PtrIndexPayload "|") +] @punctuation.bracket + +; Error +(ERROR) @error diff --git a/crates/zed/src/languages/zig/indents.scm b/crates/zed/src/languages/zig/indents.scm new file mode 100644 index 0000000000..a2af44ee40 --- /dev/null +++ b/crates/zed/src/languages/zig/indents.scm @@ -0,0 +1,22 @@ +[ + (Block) + (ContainerDecl) + (SwitchExpr) + (InitList) +] @indent + +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @branch + +[ + (line_comment) + (container_doc_comment) + (doc_comment) + (LINESTRING) +] @ignore diff --git a/crates/zed/src/languages/zig/injections.scm b/crates/zed/src/languages/zig/injections.scm new file mode 100644 index 0000000000..e3ff406d36 --- /dev/null +++ b/crates/zed/src/languages/zig/injections.scm @@ -0,0 +1,5 @@ +[ + (container_doc_comment) + (doc_comment) + (line_comment) +] @comment From d519fc6b0284d0d549c3730c6a1c8f131feb9d70 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 25 Jan 2024 19:15:31 -0700 Subject: [PATCH 42/52] Vim :% and :0 Fixes #4303 --- crates/vim/src/command.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index f1b4853feb..1886ddd0ab 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -5,7 +5,7 @@ use serde_derive::Deserialize; use workspace::{SaveIntent, Workspace}; use crate::{ - motion::{EndOfDocument, Motion}, + motion::{EndOfDocument, Motion, StartOfDocument}, normal::{ move_cursor, search::{FindCommand, ReplaceCommand}, @@ -235,6 +235,8 @@ pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option ) "$" => ("$", EndOfDocument.boxed_clone()), + "%" => ("%", EndOfDocument.boxed_clone()), + "0" => ("0", StartOfDocument.boxed_clone()), _ => { if query.starts_with("/") || query.starts_with("?") { From ebbfff5ce8329ba83af88c1bbf0e29f820fb3797 Mon Sep 17 00:00:00 2001 From: Allan Calix Date: Thu, 25 Jan 2024 19:17:18 -0800 Subject: [PATCH 43/52] Updates zigs highlight to emit right captures --- crates/zed/src/languages/zig/highlights.scm | 18 +++++++++--------- crates/zed/src/languages/zig/indents.scm | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/zed/src/languages/zig/highlights.scm b/crates/zed/src/languages/zig/highlights.scm index 189d6aa468..62bb548401 100644 --- a/crates/zed/src/languages/zig/highlights.scm +++ b/crates/zed/src/languages/zig/highlights.scm @@ -25,6 +25,7 @@ parameter: (IDENTIFIER) @parameter ] @type (#match? @type "^[A-Z]([a-z]+[A-Za-z0-9]*)*$") ) + ;; assume camelCase is a function ( [ @@ -32,7 +33,7 @@ parameter: (IDENTIFIER) @parameter field_access: (IDENTIFIER) parameter: (IDENTIFIER) ] @function - (#match? @function "^[a-z]+([A-Z][a-z0-9]*)+$") + (#match? @function "^[a-z]+([A-Z][a-z0-9]+)$") ) ;; assume all CAPS_1 is a constant @@ -49,7 +50,7 @@ parameter: (IDENTIFIER) @parameter function: (IDENTIFIER) ] @function -exception: "!" @exception +exception: "!" @keyword.exception ( (IDENTIFIER) @variable.builtin @@ -72,13 +73,12 @@ field_constant: (IDENTIFIER) @constant (BUILTINIDENTIFIER) @keyword -; No idea why this doesnt work -; ((BUILTINIDENTIFIER) @include -; (#any-of? @include "@import" "@cImport")) +((BUILTINIDENTIFIER) @keyword.import + (#any-of? @keyword.import "@import" "@cImport")) (INTEGER) @number -(FLOAT) @float +(FLOAT) @number.float [ "true" @@ -135,7 +135,7 @@ field_constant: (IDENTIFIER) @constant "if" "else" "switch" -] @conditional +] @keyword [ "for" @@ -146,7 +146,7 @@ field_constant: (IDENTIFIER) @constant [ "usingnamespace" -] @include +] @keyword.import [ "try" @@ -171,7 +171,7 @@ field_constant: (IDENTIFIER) @constant "align" "callconv" "linksection" -] @storageclass +] @keyword.storage [ "comptime" diff --git a/crates/zed/src/languages/zig/indents.scm b/crates/zed/src/languages/zig/indents.scm index a2af44ee40..9c27ddcba3 100644 --- a/crates/zed/src/languages/zig/indents.scm +++ b/crates/zed/src/languages/zig/indents.scm @@ -3,7 +3,7 @@ (ContainerDecl) (SwitchExpr) (InitList) -] @indent +] @indent.begin [ "(" @@ -12,11 +12,11 @@ "]" "{" "}" -] @branch +] @indent.branch [ (line_comment) (container_doc_comment) (doc_comment) (LINESTRING) -] @ignore +] @indent.ignore From 742329ee8f25c932f1b61adbcd5709e8bf7c41da Mon Sep 17 00:00:00 2001 From: Allan Calix Date: Thu, 25 Jan 2024 20:30:07 -0800 Subject: [PATCH 44/52] Highlight a broader range of zig functions --- crates/zed/src/languages/zig/highlights.scm | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/zed/src/languages/zig/highlights.scm b/crates/zed/src/languages/zig/highlights.scm index 62bb548401..53b08aef85 100644 --- a/crates/zed/src/languages/zig/highlights.scm +++ b/crates/zed/src/languages/zig/highlights.scm @@ -26,14 +26,10 @@ parameter: (IDENTIFIER) @parameter (#match? @type "^[A-Z]([a-z]+[A-Za-z0-9]*)*$") ) -;; assume camelCase is a function ( - [ - variable_type_function: (IDENTIFIER) - field_access: (IDENTIFIER) - parameter: (IDENTIFIER) - ] @function - (#match? @function "^[a-z]+([A-Z][a-z0-9]+)$") + (_ + variable_type_function: (IDENTIFIER) @function + (FnCallArguments)) ) ;; assume all CAPS_1 is a constant From f7a2118b159ac98a4fda8f15974090781522b407 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Fri, 26 Jan 2024 08:59:03 +0100 Subject: [PATCH 45/52] chore: remove unused deps in `color` crate (#6745) --- Cargo.lock | 13 ------------- crates/color/Cargo.toml | 15 --------------- 2 files changed, 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24b3633753..c34310604e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1587,22 +1587,9 @@ dependencies = [ name = "color" version = "0.1.0" dependencies = [ - "anyhow", - "fs", - "indexmap 1.9.3", "itertools 0.11.0", "palette", - "parking_lot 0.11.2", - "refineable", - "schemars", - "serde", - "serde_derive", - "serde_json", - "settings", "story", - "toml 0.5.11", - "util", - "uuid 1.4.1", ] [[package]] diff --git a/crates/color/Cargo.toml b/crates/color/Cargo.toml index 5a086e7088..11d1d9f7cc 100644 --- a/crates/color/Cargo.toml +++ b/crates/color/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" publish = false license = "GPL-3.0-only" - [features] default = [] stories = ["dep:itertools", "dep:story"] @@ -15,20 +14,6 @@ path = "src/color.rs" doctest = true [dependencies] -# TODO: Clean up dependencies -anyhow.workspace = true -fs = { path = "../fs" } -indexmap = "1.6.2" -parking_lot.workspace = true -refineable.workspace = true -schemars.workspace = true -serde.workspace = true -serde_derive.workspace = true -serde_json.workspace = true -settings = { path = "../settings" } story = { path = "../story", optional = true } -toml.workspace = true -uuid.workspace = true -util = { path = "../util" } itertools = { version = "0.11.0", optional = true } palette = "0.7.3" From c4cf5f2b2c5ebcace1c6647956f6f4fd4459c3cf Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 25 Jan 2024 16:58:47 +0100 Subject: [PATCH 46/52] Upgrade alacritty_terminal in hopes to avoid PTY poll failing We saw stack traces in our #panic channel pop up that failed on this line: https://github.com/zed-industries/alacritty/blob/33306142195b354ef3485ca2b1d8a85dfc6605ca/alacritty_terminal/src/event_loop.rs#L323-L324 With this message: thread 'PTY reader' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 9, kind: Uncategorized, message: "Bad file descriptor" }' /Users/administrator/.cargo/git/checkouts/alacritty-afea874b09a502a5/3330614/alacritty_terminal/src/event_loop.rs:324 We don't know how to reproduce the error. It doesn't seem related to the number of open PTY handles, because `openpty` itself didn't fail. We can only assume that something went wrong between `openpty` and the setup of the polling. Since Alacritty itself changed its polling mechanism significantly by switching from `mio` to `polling` (https://github.com/alacritty/alacritty/pull/6846) we upgraded with the hope that this will fix the bug. Co-authored-by: Antonio Co-authored-by: Federico Co-authored-by: David Co-authored-by: Bennet --- Cargo.lock | 239 +++++++------------ crates/terminal/Cargo.toml | 2 +- crates/terminal/src/mappings/colors.rs | 5 +- crates/terminal/src/terminal.rs | 74 +++--- crates/terminal_view/src/terminal_element.rs | 13 +- 5 files changed, 128 insertions(+), 205 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c34310604e..870fd9a320 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,50 +101,25 @@ dependencies = [ "util", ] -[[package]] -name = "alacritty_config" -version = "0.1.2-dev" -source = "git+https://github.com/zed-industries/alacritty?rev=33306142195b354ef3485ca2b1d8a85dfc6605ca#33306142195b354ef3485ca2b1d8a85dfc6605ca" -dependencies = [ - "log", - "serde", - "toml 0.7.8", -] - -[[package]] -name = "alacritty_config_derive" -version = "0.2.2-dev" -source = "git+https://github.com/zed-industries/alacritty?rev=33306142195b354ef3485ca2b1d8a85dfc6605ca#33306142195b354ef3485ca2b1d8a85dfc6605ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", -] - [[package]] name = "alacritty_terminal" -version = "0.20.0-dev" -source = "git+https://github.com/zed-industries/alacritty?rev=33306142195b354ef3485ca2b1d8a85dfc6605ca#33306142195b354ef3485ca2b1d8a85dfc6605ca" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35229555d7cc7e83392dfc27c96bec560b1076d756184893296cd60125f4a264" dependencies = [ - "alacritty_config", - "alacritty_config_derive", - "base64 0.13.1", + "base64 0.21.4", "bitflags 2.4.1", "home", "libc", "log", - "mio 0.6.23", - "mio-anonymous-pipes", - "mio-extras", - "miow 0.3.7", - "nix 0.26.4", + "miow 0.6.0", "parking_lot 0.12.1", - "regex-automata 0.1.10", + "piper", + "polling 3.3.2", + "regex-automata 0.4.5", + "rustix-openpty", "serde", - "serde_yaml", "signal-hook", - "signal-hook-mio", - "toml 0.7.8", "unicode-width", "vte", "windows-sys 0.48.0", @@ -444,7 +419,7 @@ dependencies = [ "futures-lite", "log", "parking", - "polling", + "polling 2.8.0", "rustix 0.37.23", "slab", "socket2 0.4.9", @@ -1155,7 +1130,7 @@ dependencies = [ "serde_json", "syn 1.0.109", "tempfile", - "toml 0.5.11", + "toml", ] [[package]] @@ -1515,7 +1490,7 @@ dependencies = [ "time", "tokio", "tokio-tungstenite", - "toml 0.5.11", + "toml", "tonic", "tower", "tracing", @@ -2018,6 +1993,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "dashmap" version = "5.5.3" @@ -3611,7 +3592,7 @@ dependencies = [ "log", "mime", "once_cell", - "polling", + "polling 2.8.0", "slab", "sluice", "tracing", @@ -3951,12 +3932,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linkme" version = "0.3.17" @@ -4313,19 +4288,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mio-anonymous-pipes" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bc513025fe5005a3aa561b50fdb2cda5a150b84800ae02acd8aa9ed62ca1a6b" -dependencies = [ - "mio 0.6.23", - "miow 0.3.7", - "parking_lot 0.11.2", - "spsc-buffer", - "winapi 0.3.9", -] - [[package]] name = "mio-extras" version = "2.0.6" @@ -4338,17 +4300,6 @@ dependencies = [ "slab", ] -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio 0.6.23", -] - [[package]] name = "miow" version = "0.2.2" @@ -4363,11 +4314,11 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.7" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +checksum = "359f76430b20a79f9e20e115b3428614e654f04fab314482fc0fda0ebd3c6044" dependencies = [ - "winapi 0.3.9", + "windows-sys 0.48.0", ] [[package]] @@ -4527,17 +4478,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if 1.0.0", - "libc", -] - [[package]] name = "nix" version = "0.27.1" @@ -5308,6 +5248,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.0", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -5399,6 +5350,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41" +dependencies = [ + "cfg-if 1.0.0", + "concurrent-queue", + "pin-project-lite 0.2.13", + "rustix 0.38.30", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "pollster" version = "0.2.5" @@ -5475,7 +5440,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] @@ -5582,7 +5547,7 @@ dependencies = [ "terminal", "text", "thiserror", - "toml 0.5.11", + "toml", "unindent", "util", ] @@ -6025,6 +5990,17 @@ dependencies = [ "regex-syntax 0.7.5", ] +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -6037,6 +6013,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rend" version = "0.4.0" @@ -6405,11 +6387,23 @@ checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.1", "errno", + "itoa", "libc", "linux-raw-sys 0.4.12", "windows-sys 0.52.0", ] +[[package]] +name = "rustix-openpty" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" +dependencies = [ + "errno", + "libc", + "rustix 0.38.30", +] + [[package]] name = "rustls" version = "0.19.1" @@ -6878,15 +6872,6 @@ dependencies = [ "syn 2.0.37", ] -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6899,18 +6884,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" -dependencies = [ - "indexmap 1.9.3", - "ryu", - "serde", - "yaml-rust", -] - [[package]] name = "settings" version = "0.1.0" @@ -6932,7 +6905,7 @@ dependencies = [ "serde_json", "serde_json_lenient", "smallvec", - "toml 0.5.11", + "toml", "tree-sitter", "tree-sitter-json 0.19.0", "unindent", @@ -7038,18 +7011,6 @@ dependencies = [ "signal-hook-registry", ] -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio 0.6.23", - "mio-uds", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -7240,12 +7201,6 @@ dependencies = [ "der", ] -[[package]] -name = "spsc-buffer" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b" - [[package]] name = "sqlez" version = "0.1.0" @@ -7909,7 +7864,7 @@ dependencies = [ "serde_json", "settings", "story", - "toml 0.5.11", + "toml", "util", "uuid 1.4.1", ] @@ -8214,26 +8169,11 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -8242,8 +8182,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] @@ -9083,10 +9021,12 @@ dependencies = [ [[package]] name = "vte" -version = "0.11.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b" dependencies = [ + "bitflags 2.4.1", + "cursor-icon", "log", "serde", "utf8parse", @@ -9660,15 +9600,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "0.5.1" @@ -9777,7 +9708,7 @@ dependencies = [ "theme_selector", "thiserror", "tiny_http", - "toml 0.5.11", + "toml", "tree-sitter", "tree-sitter-bash", "tree-sitter-c", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 3511367055..dd2da4a626 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -18,7 +18,7 @@ db = { path = "../db" } theme = { path = "../theme" } util = { path = "../util" } -alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "33306142195b354ef3485ca2b1d8a85dfc6605ca" } +alacritty_terminal = "0.21" procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } smallvec.workspace = true smol.workspace = true diff --git a/crates/terminal/src/mappings/colors.rs b/crates/terminal/src/mappings/colors.rs index adcc0d8080..876e366606 100644 --- a/crates/terminal/src/mappings/colors.rs +++ b/crates/terminal/src/mappings/colors.rs @@ -1,5 +1,4 @@ -use alacritty_terminal::term::color::Rgb as AlacRgb; - +use alacritty_terminal::vte::ansi::Rgb as AlacRgb; use gpui::Rgba; //Convenience method to convert from a GPUI color to an alacritty Rgb @@ -8,5 +7,5 @@ pub fn to_alac_rgb(color: impl Into) -> AlacRgb { let r = ((color.r * color.a) * 255.) as u8; let g = ((color.g * color.a) * 255.) as u8; let b = ((color.b * color.a) * 255.) as u8; - AlacRgb::new(r, g, b) + AlacRgb { r, g, b } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ee45bc8f7c..997bd599b6 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -3,8 +3,6 @@ pub use alacritty_terminal; pub mod terminal_settings; use alacritty_terminal::{ - ansi::{ClearMode, Handler}, - config::{Config, Program, PtyConfig, Scrolling}, event::{Event as AlacTermEvent, EventListener, Notify, WindowSize}, event_loop::{EventLoop, Msg, Notifier}, grid::{Dimensions, Scroll as AlacScroll}, @@ -13,11 +11,11 @@ use alacritty_terminal::{ sync::FairMutex, term::{ cell::Cell, - color::Rgb, search::{Match, RegexIter, RegexSearch}, - RenderableCursor, TermMode, + Config, RenderableCursor, TermMode, }, - tty::{self, setup_env}, + tty::{self, setup_env, ToWinsize}, + vte::ansi::{ClearMode, Handler, NamedPrivateMode, PrivateMode, Rgb}, Term, }; use anyhow::{bail, Result}; @@ -58,7 +56,6 @@ use gpui::{ }; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; -use lazy_static::lazy_static; actions!( terminal, @@ -75,15 +72,6 @@ const DEBUG_TERMINAL_HEIGHT: Pixels = px(30.); const DEBUG_CELL_WIDTH: Pixels = px(5.); const DEBUG_LINE_HEIGHT: Pixels = px(5.); -lazy_static! { - // Regex Copied from alacritty's ui_config.rs and modified its declaration slightly: - // * avoid Rust-specific escaping. - // * use more strict regex for `file://` protocol matching: original regex has `file:` inside, but we want to avoid matching `some::file::module` strings. - static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap(); - - static ref WORD_REGEX: RegexSearch = RegexSearch::new(r#"[\w.\[\]:/@\-~]+"#).unwrap(); -} - ///Upward flowing events, for changing the title and such #[derive(Clone, Debug)] pub enum Event { @@ -294,14 +282,18 @@ impl TerminalBuilder { alternate_scroll: AlternateScroll, window: AnyWindowHandle, ) -> Result { - let pty_config = { + let pty_options = { let alac_shell = match shell.clone() { Shell::System => None, - Shell::Program(program) => Some(Program::Just(program)), - Shell::WithArguments { program, args } => Some(Program::WithArgs { program, args }), + Shell::Program(program) => { + Some(alacritty_terminal::tty::Shell::new(program, Vec::new())) + } + Shell::WithArguments { program, args } => { + Some(alacritty_terminal::tty::Shell::new(program, args)) + } }; - PtyConfig { + alacritty_terminal::tty::Options { shell: alac_shell, working_directory: working_directory.clone(), hold: false, @@ -312,43 +304,38 @@ impl TerminalBuilder { env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); env.insert("ZED_TERM".to_string(), true.to_string()); - let alac_scrolling = Scrolling::default(); - // alac_scrolling.set_history((BACK_BUFFER_SIZE * 2) as u32); - let config = Config { - pty_config: pty_config.clone(), - env, - scrolling: alac_scrolling, + scrolling_history: 10000, ..Default::default() }; - setup_env(&config); + setup_env(); //Spawn a task so the Alacritty EventLoop can communicate with us in a view context //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... let mut term = Term::new( - &config, + config, &TerminalSize::default(), ZedListener(events_tx.clone()), ); //Start off blinking if we need to if let Some(TerminalBlink::On) = blink_settings { - term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) + term.set_private_mode(PrivateMode::Named(NamedPrivateMode::BlinkingCursor)); } //Alacritty defaults to alternate scrolling being on, so we just need to turn it off. if let AlternateScroll::Off = alternate_scroll { - term.unset_mode(alacritty_terminal::ansi::Mode::AlternateScroll) + term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll)); } let term = Arc::new(FairMutex::new(term)); //Setup the pty... let pty = match tty::new( - &pty_config, + &pty_options, TerminalSize::default().into(), window.window_id().as_u64(), ) { @@ -370,13 +357,16 @@ impl TerminalBuilder { term.clone(), ZedListener(events_tx.clone()), pty, - pty_config.hold, + pty_options.hold, false, ); //Kick things off let pty_tx = event_loop.channel(); - let _io_thread = event_loop.spawn(); + let _io_thread = event_loop.spawn(); // DANGER + + let url_regex = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap(); + let word_regex = RegexSearch::new(r#"[\w.\[\]:/@\-~]+"#).unwrap(); let terminal = Terminal { pty_tx: Notifier(pty_tx), @@ -396,6 +386,8 @@ impl TerminalBuilder { selection_phase: SelectionPhase::Ended, cmd_pressed: false, hovered_word: false, + url_regex, + word_regex, }; Ok(TerminalBuilder { @@ -514,7 +506,7 @@ impl Default for TerminalContent { selection_text: Default::default(), selection: Default::default(), cursor: RenderableCursor { - shape: alacritty_terminal::ansi::CursorShape::Block, + shape: alacritty_terminal::vte::ansi::CursorShape::Block, point: AlacPoint::new(Line(0), Column(0)), }, cursor_char: Default::default(), @@ -550,6 +542,8 @@ pub struct Terminal { selection_phase: SelectionPhase, cmd_pressed: bool, hovered_word: bool, + url_regex: RegexSearch, + word_regex: RegexSearch, } impl Terminal { @@ -760,7 +754,7 @@ impl Terminal { let url_match = min_index..=max_index; Some((url, true, url_match)) - } else if let Some(word_match) = regex_match_at(term, point, &WORD_REGEX) { + } else if let Some(word_match) = regex_match_at(term, point, &mut self.word_regex) { let maybe_url_or_path = term.bounds_to_string(*word_match.start(), *word_match.end()); let original_match = word_match.clone(); @@ -777,7 +771,7 @@ impl Terminal { (word_match, maybe_url_or_path) }; - let is_url = match regex_match_at(term, point, &URL_REGEX) { + let is_url = match regex_match_at(term, point, &mut self.url_regex) { Some(url_match) => { // `]` is a valid symbol in the `file://` URL, so the regex match will include it // consider that when ensuring that the URL match is the same as the original word @@ -1275,14 +1269,14 @@ impl Terminal { pub fn find_matches( &mut self, - searcher: RegexSearch, + mut searcher: RegexSearch, cx: &mut ModelContext, ) -> Task>> { let term = self.term.clone(); cx.background_executor().spawn(async move { let term = term.lock(); - all_search_matches(&term, &searcher).collect() + all_search_matches(&term, &mut searcher).collect() }) } @@ -1332,7 +1326,7 @@ impl EventEmitter for Terminal {} /// Based on alacritty/src/display/hint.rs > regex_match_at /// Retrieve the match, if the specified point is inside the content matching the regex. -fn regex_match_at(term: &Term, point: AlacPoint, regex: &RegexSearch) -> Option { +fn regex_match_at(term: &Term, point: AlacPoint, regex: &mut RegexSearch) -> Option { visible_regex_match_iter(term, regex).find(|rm| rm.contains(&point)) } @@ -1340,7 +1334,7 @@ fn regex_match_at(term: &Term, point: AlacPoint, regex: &RegexSearch) -> O /// Iterate over all visible regex matches. pub fn visible_regex_match_iter<'a, T>( term: &'a Term, - regex: &'a RegexSearch, + regex: &'a mut RegexSearch, ) -> impl Iterator + 'a { let viewport_start = Line(-(term.grid().display_offset() as i32)); let viewport_end = viewport_start + term.bottommost_line(); @@ -1362,7 +1356,7 @@ fn make_selection(range: &RangeInclusive) -> Selection { fn all_search_matches<'a, T>( term: &'a Term, - regex: &'a RegexSearch, + regex: &'a mut RegexSearch, ) -> impl Iterator + 'a { let start = AlacPoint::new(term.grid().topmost_line(), Column(0)); let end = AlacPoint::new(term.grid().bottommost_line(), term.grid().last_column()); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index fc9e4bb21e..aebfdd28c2 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -11,12 +11,11 @@ use itertools::Itertools; use language::CursorShape; use settings::Settings; use terminal::{ - alacritty_terminal::ansi::NamedColor, alacritty_terminal::{ - ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape}, grid::Dimensions, index::Point as AlacPoint, term::{cell::Flags, TermMode}, + vte::ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, }, terminal_settings::TerminalSettings, IndexedCell, Terminal, TerminalContent, TerminalSize, @@ -308,7 +307,7 @@ impl TerminalElement { /// Converts the Alacritty cell styles to GPUI text styles and background color. fn cell_style( indexed: &IndexedCell, - fg: terminal::alacritty_terminal::ansi::Color, + fg: terminal::alacritty_terminal::vte::ansi::Color, // bg: terminal::alacritty_terminal::ansi::Color, colors: &Theme, text_style: &TextStyle, @@ -998,11 +997,11 @@ fn to_highlighted_range_lines( } /// Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent. -fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, theme: &Theme) -> Hsla { +fn convert_color(fg: &terminal::alacritty_terminal::vte::ansi::Color, theme: &Theme) -> Hsla { let colors = theme.colors(); match fg { // Named and theme defined colors - terminal::alacritty_terminal::ansi::Color::Named(n) => match n { + terminal::alacritty_terminal::vte::ansi::Color::Named(n) => match n { NamedColor::Black => colors.terminal_ansi_black, NamedColor::Red => colors.terminal_ansi_red, NamedColor::Green => colors.terminal_ansi_green, @@ -1034,11 +1033,11 @@ fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, theme: &Theme) NamedColor::DimForeground => colors.terminal_dim_foreground, }, // 'True' colors - terminal::alacritty_terminal::ansi::Color::Spec(rgb) => { + terminal::alacritty_terminal::vte::ansi::Color::Spec(rgb) => { terminal::rgba_color(rgb.r, rgb.g, rgb.b) } // 8 bit, indexed colors - terminal::alacritty_terminal::ansi::Color::Indexed(i) => { + terminal::alacritty_terminal::vte::ansi::Color::Indexed(i) => { terminal::get_color_at_index(*i as usize, theme) } } From 0aa1b1f070e385af04e385fb218d97cb3ce9a5f2 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 25 Jan 2024 18:07:55 +0100 Subject: [PATCH 47/52] Remove unused import --- 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 997bd599b6..fe9c789a09 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -14,7 +14,7 @@ use alacritty_terminal::{ search::{Match, RegexIter, RegexSearch}, Config, RenderableCursor, TermMode, }, - tty::{self, setup_env, ToWinsize}, + tty::{self, setup_env}, vte::ansi::{ClearMode, Handler, NamedPrivateMode, PrivateMode, Rgb}, Term, }; From 698108ac8b23b124784b1fa3a8b7ce25f1624c93 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 26 Jan 2024 10:42:07 +0100 Subject: [PATCH 48/52] Setup env correctly after upgrading alacritty_terminal Co-Authored-By: Antonio --- crates/terminal/src/terminal.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fe9c789a09..be8dc9ed01 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -277,7 +277,7 @@ impl TerminalBuilder { pub fn new( working_directory: Option, shell: Shell, - mut env: HashMap, + env: HashMap, blink_settings: Option, alternate_scroll: AlternateScroll, window: AnyWindowHandle, @@ -300,17 +300,22 @@ impl TerminalBuilder { } }; + // First, setup Alacritty's env + setup_env(); + + // Then setup configured environment variables + for (key, value) in env { + std::env::set_var(key, value); + } //TODO: Properly set the current locale, - env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); - env.insert("ZED_TERM".to_string(), true.to_string()); + std::env::set_var("LC_ALL", "en_US.UTF-8"); + std::env::set_var("ZED_TERM", "true"); let config = Config { scrolling_history: 10000, ..Default::default() }; - setup_env(); - //Spawn a task so the Alacritty EventLoop can communicate with us in a view context //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); From 8d2a401f0940e416bac0e0f0ba07365537026b99 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 26 Jan 2024 10:57:14 +0100 Subject: [PATCH 49/52] Fix panic when typing umlauts in command palette using Vim mode Co-Authored-By: Antonio --- crates/vim/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 1886ddd0ab..d12cf6152f 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -282,7 +282,7 @@ fn generate_positions(string: &str, query: &str) -> Vec { return positions; }; - for (i, c) in string.chars().enumerate() { + for (i, c) in string.char_indices() { if c == current { positions.push(i); if let Some(c) = chars.next() { From 14322c83656d5b5ce6cc7f03606a5c00460d6242 Mon Sep 17 00:00:00 2001 From: George Munyoro <32019551+georgemunyoro@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:53:03 +0200 Subject: [PATCH 50/52] Fix grammatical error in contributing guidelines (#6772) Release Notes: - N/A --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6fc2f79dc1..fb98e37768 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ We plan to set aside time each week to pair program with contributors on promisi - Pair with us and watch us code to learn the codebase - Low effort PRs, such as those that just re-arrange syntax, won't be merged without a compelling justification -## Bird-eye's view of Zed +## Bird's-eye view of Zed Zed is made up of several smaller crates - let's go over those you're most likely to interact with: From 47860a5dc81e031faa2e7e07b0fe71417a6a460f Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:06:44 +0100 Subject: [PATCH 51/52] Combine `observe_new_views` into one in feedback crate (#6755) Combine two `observe_new_views` calls into a single one in `init` function of feedback crate. Release Notes: - N/A --- crates/feedback/src/feedback.rs | 36 +++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index b4d2050d1d..8749fddaab 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -20,28 +20,24 @@ actions!( ); pub fn init(cx: &mut AppContext) { - // TODO: a way to combine these two into one? - cx.observe_new_views(feedback_modal::FeedbackModal::register) - .detach(); - - cx.observe_new_views(|workspace: &mut Workspace, _| { + cx.observe_new_views(|workspace: &mut Workspace, cx| { + feedback_modal::FeedbackModal::register(workspace, cx); workspace .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| { - let specs = SystemSpecs::new(&cx).to_string(); + let specs = SystemSpecs::new(&cx).to_string(); - let prompt = cx.prompt( - PromptLevel::Info, - "Copied into clipboard", - Some(&specs), - &["OK"], - ); - cx.spawn(|_, _cx| async move { - prompt.await.ok(); - }) - .detach(); - let item = ClipboardItem::new(specs.clone()); - cx.write_to_clipboard(item); + let prompt = cx.prompt( + PromptLevel::Info, + "Copied into clipboard", + Some(&specs), + &["OK"], + ); + cx.spawn(|_, _cx| async move { + prompt.await.ok(); }) + .detach(); + cx.write_to_clipboard(ClipboardItem::new(specs.clone())); + }) .register_action(|_, _: &RequestFeature, cx| { let url = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; cx.open_url(url); @@ -56,7 +52,7 @@ pub fn init(cx: &mut AppContext) { .register_action(move |_, _: &OpenZedRepo, cx| { let url = "https://github.com/zed-industries/zed"; cx.open_url(&url); - }); - }) + }); + }) .detach(); } From 8da5e57d6d8f11ee47f1d95503648888fa803f1a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 26 Jan 2024 09:15:13 -0500 Subject: [PATCH 52/52] Factor out URLs in `feedback` crate (#6776) This PR factors out the inlined URLs in the `feedback` crate so that they don't mess with `rustfmt`. Release Notes: - N/A --- crates/feedback/src/feedback.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 8749fddaab..40a8701931 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -19,6 +19,21 @@ actions!( ] ); +const fn zed_repo_url() -> &'static str { + "https://github.com/zed-industries/zed" +} + +const fn request_feature_url() -> &'static str { + "https://github.com/zed-industries/zed/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml" +} + +fn file_bug_report_url(specs: &SystemSpecs) -> String { + format!( + "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", + urlencoding::encode(&specs.to_string()) + ) +} + pub fn init(cx: &mut AppContext) { cx.observe_new_views(|workspace: &mut Workspace, cx| { feedback_modal::FeedbackModal::register(workspace, cx); @@ -39,20 +54,14 @@ pub fn init(cx: &mut AppContext) { cx.write_to_clipboard(ClipboardItem::new(specs.clone())); }) .register_action(|_, _: &RequestFeature, cx| { - let url = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml"; - cx.open_url(url); + cx.open_url(request_feature_url()); }) .register_action(move |_, _: &FileBugReport, cx| { - let url = format!( - "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}", - urlencoding::encode(&SystemSpecs::new(&cx).to_string()) - ); - cx.open_url(&url); + cx.open_url(&file_bug_report_url(&SystemSpecs::new(&cx))); }) .register_action(move |_, _: &OpenZedRepo, cx| { - let url = "https://github.com/zed-industries/zed"; - cx.open_url(&url); + cx.open_url(zed_repo_url()); }); - }) + }) .detach(); }