From 5ed3b44686de6f3ed8cc8882ab576faafc269d2b Mon Sep 17 00:00:00 2001 From: Ocean Date: Fri, 2 Feb 2024 16:55:54 +0900 Subject: [PATCH 001/101] Add rename to JetBrains keymaps (#7263) Add rename actions to JetBrains keymaps. Closes #7261. Release Notes: - Added rename keybindings to JetBrains keymap ([#7261](https://github.com/zed-industries/zed/issues/7261)). --- assets/keymaps/jetbrains.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 6be7289d8aeefc4dd837a90adfb576d3006efb16..d2dcbddbc498ed857b3d409051000db2bdd89bf6 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -42,6 +42,7 @@ "shift-alt-up": "editor::MoveLineUp", "shift-alt-down": "editor::MoveLineDown", "cmd-alt-l": "editor::Format", + "shift-f6": "editor::Rename", "cmd-[": "pane::GoBack", "cmd-]": "pane::GoForward", "alt-f7": "editor::FindAllReferences", @@ -83,7 +84,8 @@ { "context": "ProjectPanel", "bindings": { - "enter": "project_panel::Open" + "enter": "project_panel::Open", + "shift-f6": "project_panel::Rename" } } ] From 8fed9aaec29607e2719ee0a863eb75c9f44c39b7 Mon Sep 17 00:00:00 2001 From: Alfred Kristal Ern <83819096+KristalAlfred@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:17:46 +0100 Subject: [PATCH 002/101] Fix project panel selection related issues (#7245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #7003 and #7005. Selecting as a reaction to actually opening a new item doesn’t seem robust in all cases, the PR improves that. Release Notes: - Fixed missing project panel file selection in certain cases --- crates/project_panel/src/project_panel.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index f64099340ce5c892831f1503074469afc981d9a1..b43b5d84131e5d1f110224d50ed62b8df4db6969 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -262,11 +262,14 @@ impl ProjectPanel { if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { let file_path = entry.path.clone(); + let worktree_id = worktree.read(cx).id(); + let entry_id = entry.id; + workspace .open_path( ProjectPath { - worktree_id: worktree.read(cx).id(), - path: entry.path.clone(), + worktree_id, + path: file_path.clone(), }, None, focus_opened_item, @@ -281,8 +284,16 @@ impl ProjectPanel { _ => None, } }); - if !focus_opened_item { - if let Some(project_panel) = project_panel.upgrade() { + + if let Some(project_panel) = project_panel.upgrade() { + // Always select the entry, regardless of whether it is opened or not. + project_panel.update(cx, |project_panel, _| { + project_panel.selection = Some(Selection { + worktree_id, + entry_id + }); + }); + if !focus_opened_item { let focus_handle = project_panel.read(cx).focus_handle.clone(); cx.focus(&focus_handle); } From 2940a0ebd8a15b27d854215b38ca10d6b64567af Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 10:33:08 +0100 Subject: [PATCH 003/101] Revert "Avoid excessive blocking of main thread when rendering in direct mode (#7253)" (#7272) This reverts commit 020c38a8916c063cba36c2c88a69b5e287269d5a because it leads to glitches when selecting text. https://github.com/zed-industries/zed/assets/1185253/78c2c184-bc15-4b04-8c80-a23ca5c96afa Release Notes: - N/A --- crates/gpui/src/platform/mac/metal_renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index b3512ad2e9c82dcafbfcec82d8a160057eae86be..68027ceff6834842d3e4450c2ab1fe85996fdea6 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -314,7 +314,7 @@ impl MetalRenderer { command_buffer.commit(); self.sprite_atlas.clear_textures(AtlasTextureKind::Path); - command_buffer.wait_until_scheduled(); + command_buffer.wait_until_completed(); drawable.present(); } From ce4c15dca6f3885ddc7f2df64ca259b282b5a769 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:10:42 +0100 Subject: [PATCH 004/101] Show diagnostics in scrollbar (#7175) This PR implements support for displaying diagnostics in the scrollbar, similar to what is already done for search results, symbols, git diff, ... For example, changing a field name (`text`) without changing the references looks like this in `buffer.rs` (note the red lines in the scrollbar): ![image](https://github.com/zed-industries/zed/assets/53836821/c46f0d55-32e3-4334-8ad7-66d1578d5725) As you can see, the errors, warnings, ... are displayed in the scroll bar, which helps to identify possible problems with the current file. Relevant issues: #4866, #6819 Release Notes: - Added diagnostic indicators to the scrollbar --- assets/settings/default.json | 4 +- crates/editor/src/editor_settings.rs | 5 ++ crates/editor/src/element.rs | 62 +++++++++++++++++++++++++ crates/language/src/buffer.rs | 5 ++ crates/multi_buffer/src/multi_buffer.rs | 6 +++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index f78a4637e57739a8620e07d2f6bc271a26e7d64e..74dfa6de2c61f74eb40891adf2e4227001f12e35 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -127,7 +127,9 @@ // Whether to show selections in the scrollbar. "selections": true, // Whether to show symbols selections in the scrollbar. - "symbols_selections": true + "symbols_selections": true, + // Whether to show diagnostic indicators in the scrollbar. + "diagnostics": true }, "relative_line_numbers": false, // When to populate a new search's query based on the text under the cursor. diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 0cfc677e937e2513922451b1fc46eedacdf93a33..e9ecbe13d404e4c5ca3bbb3f3e831a795ddd2824 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -34,6 +34,7 @@ pub struct Scrollbar { pub git_diff: bool, pub selections: bool, pub symbols_selections: bool, + pub diagnostics: bool, } /// When to show the scrollbar in the editor. @@ -122,6 +123,10 @@ pub struct ScrollbarContent { /// /// Default: true pub symbols_selections: Option, + /// Whether to show diagnostic indicators in the scrollbar. + /// + /// Default: true + pub diagnostics: Option, } impl Settings for EditorSettings { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 57389fd8f9cbc887dcf77c115ddb15d179ecc797..b96ec9e068f29eb71af278209523ac6c2f0e5568 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -35,6 +35,7 @@ use gpui::{ }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; +use lsp::DiagnosticSeverity; use multi_buffer::Anchor; use project::{ project_settings::{GitGutterSetting, ProjectSettings}, @@ -1477,6 +1478,64 @@ impl EditorElement { } } + if layout.is_singleton && scrollbar_settings.diagnostics { + let max_point = layout + .position_map + .snapshot + .display_snapshot + .buffer_snapshot + .max_point(); + + let diagnostics = layout + .position_map + .snapshot + .buffer_snapshot + .diagnostics_in_range::<_, Point>(Point::zero()..max_point, false) + // We want to sort by severity, in order to paint the most severe diagnostics last. + .sorted_by_key(|diagnostic| std::cmp::Reverse(diagnostic.diagnostic.severity)); + + for diagnostic in diagnostics { + let start_display = diagnostic + .range + .start + .to_display_point(&layout.position_map.snapshot.display_snapshot); + let end_display = diagnostic + .range + .end + .to_display_point(&layout.position_map.snapshot.display_snapshot); + let start_y = y_for_row(start_display.row() as f32); + let mut end_y = if diagnostic.range.start == diagnostic.range.end { + y_for_row((end_display.row() + 1) as f32) + } else { + y_for_row((end_display.row()) as f32) + }; + + if end_y - start_y < px(1.) { + end_y = start_y + px(1.); + } + let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y)); + + let color = match diagnostic.diagnostic.severity { + DiagnosticSeverity::ERROR => cx.theme().status().error, + DiagnosticSeverity::WARNING => cx.theme().status().warning, + DiagnosticSeverity::INFORMATION => cx.theme().status().info, + _ => cx.theme().status().hint, + }; + cx.paint_quad(quad( + bounds, + Corners::default(), + color, + Edges { + top: Pixels::ZERO, + right: px(1.), + bottom: Pixels::ZERO, + left: px(1.), + }, + cx.theme().colors().scrollbar_thumb_border, + )); + } + } + cx.paint_quad(quad( thumb_bounds, Corners::default(), @@ -2106,6 +2165,9 @@ impl EditorElement { // Symbols Selections (is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::() || editor.has_background_highlights::())) || + // Diagnostics + (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics()) + || // Scrollmanager editor.scroll_manager.scrollbars_visible() } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index cd8b239f5cc150d835e1463ea2c74ba761209d8f..b42ea1f12d608cb236223b7618336881d7b082b5 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2993,6 +2993,11 @@ impl BufferSnapshot { self.git_diff.hunks_intersecting_range_rev(range, self) } + /// Returns if the buffer contains any diagnostics. + pub fn has_diagnostics(&self) -> bool { + !self.diagnostics.is_empty() + } + /// Returns all the diagnostics intersecting the given range. pub fn diagnostics_in_range<'a, T, O>( &'a self, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index e16fc3a5041223c77873564d5dd4cfbbfac3829e..6d182a11ead70dc59bb0f46459425531ed660379 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3052,6 +3052,12 @@ impl MultiBufferSnapshot { self.has_conflict } + pub fn has_diagnostics(&self) -> bool { + self.excerpts + .iter() + .any(|excerpt| excerpt.buffer.has_diagnostics()) + } + pub fn diagnostic_group<'a, O>( &'a self, group_id: usize, From 10cd978e932bac789910353b27cad3e440ada5c2 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 11:24:04 +0100 Subject: [PATCH 005/101] go: improve outline queries to get rid of whitespace (#7273) This changes the Go `outline.scm` queries a bit so that we don't have these stray whitespaces floating around. I'm sure there's more improvements possible, but for now I think this makes it look less wrong. Release Notes: - Improved outline and breadcrumbs for Go code. ## Before ![before](https://github.com/zed-industries/zed/assets/1185253/d2b93c41-639b-4819-b87e-d8456960c5a7) ## After ![after](https://github.com/zed-industries/zed/assets/1185253/1ff6efb1-75a4-487b-8947-f070073887d4) --- crates/zed/src/languages/go/outline.scm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/languages/go/outline.scm b/crates/zed/src/languages/go/outline.scm index 2ff7ef25a0a9c9623f329369bfeccd2e8c1f2fd5..8823b7beb80129c7f7f5094b297bb378992a1c8b 100644 --- a/crates/zed/src/languages/go/outline.scm +++ b/crates/zed/src/languages/go/outline.scm @@ -7,20 +7,21 @@ "func" @context name: (identifier) @name parameters: (parameter_list - "(" @context - ")" @context)) @item + "(" + ")")) @item (method_declaration "func" @context receiver: (parameter_list "(" @context (parameter_declaration + name: (_) @name type: (_) @context) ")" @context) name: (field_identifier) @name parameters: (parameter_list - "(" @context - ")" @context)) @item + "(" + ")")) @item (const_declaration "const" @context @@ -40,4 +41,4 @@ ")" @context)) @item (field_declaration - name: (_) @name) @item \ No newline at end of file + name: (_) @name) @item From d576cda7892d5b0407aad4147f0787ca51bf62ce Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 2 Feb 2024 01:04:27 +0100 Subject: [PATCH 006/101] project: Do not inline LSP related methods The way Rust generics works, having a generic argument puts the burden of codegen on the crate that instantiates a generic function, which in our case is an editor. --- crates/project/src/project.rs | 166 +++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 35 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0788594222d4662904264473ddaedf4b9db227f3..eedfa16e70069262d49dcd713dc6e995e31c9a3b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4414,13 +4414,13 @@ impl Project { } } - pub fn definition( + #[inline(never)] + fn definition_impl( &self, buffer: &Model, - position: T, + position: PointUtf16, cx: &mut ModelContext, ) -> Task>> { - let position = position.to_point_utf16(buffer.read(cx)); self.request_lsp( buffer.clone(), LanguageServerToQuery::Primary, @@ -4428,14 +4428,22 @@ impl Project { cx, ) } - - pub fn type_definition( + pub fn definition( &self, buffer: &Model, position: T, cx: &mut ModelContext, ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); + self.definition_impl(buffer, position, cx) + } + + fn type_definition_impl( + &self, + buffer: &Model, + position: PointUtf16, + cx: &mut ModelContext, + ) -> Task>> { self.request_lsp( buffer.clone(), LanguageServerToQuery::Primary, @@ -4443,14 +4451,23 @@ impl Project { cx, ) } - - pub fn references( + pub fn type_definition( &self, buffer: &Model, position: T, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); + + self.type_definition_impl(buffer, position, cx) + } + + fn references_impl( + &self, + buffer: &Model, + position: PointUtf16, + cx: &mut ModelContext, + ) -> Task>> { self.request_lsp( buffer.clone(), LanguageServerToQuery::Primary, @@ -4458,14 +4475,22 @@ impl Project { cx, ) } - - pub fn document_highlights( + pub fn references( &self, buffer: &Model, position: T, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); + self.references_impl(buffer, position, cx) + } + + fn document_highlights_impl( + &self, + buffer: &Model, + position: PointUtf16, + cx: &mut ModelContext, + ) -> Task>> { self.request_lsp( buffer.clone(), LanguageServerToQuery::Primary, @@ -4474,6 +4499,16 @@ impl Project { ) } + pub fn document_highlights( + &self, + buffer: &Model, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + let position = position.to_point_utf16(buffer.read(cx)); + self.document_highlights_impl(buffer, position, cx) + } + pub fn symbols(&self, query: &str, cx: &mut ModelContext) -> Task>> { if self.is_local() { let mut requests = Vec::new(); @@ -4694,13 +4729,12 @@ impl Project { } } - pub fn hover( + fn hover_impl( &self, buffer: &Model, - position: T, + position: PointUtf16, cx: &mut ModelContext, ) -> Task>> { - let position = position.to_point_utf16(buffer.read(cx)); self.request_lsp( buffer.clone(), LanguageServerToQuery::Primary, @@ -4708,14 +4742,23 @@ impl Project { cx, ) } - - pub fn completions( + pub fn hover( &self, buffer: &Model, position: T, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); + self.hover_impl(buffer, position, cx) + } + + #[inline(never)] + fn completions_impl( + &self, + buffer: &Model, + position: PointUtf16, + cx: &mut ModelContext, + ) -> Task>> { if self.is_local() { let snapshot = buffer.read(cx).snapshot(); let offset = position.to_offset(&snapshot); @@ -4762,6 +4805,15 @@ impl Project { Task::ready(Ok(Default::default())) } } + pub fn completions( + &self, + buffer: &Model, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + let position = position.to_point_utf16(buffer.read(cx)); + self.completions_impl(buffer, position, cx) + } pub fn resolve_completions( &self, @@ -5038,14 +5090,12 @@ impl Project { } } - pub fn code_actions( + fn code_actions_impl( &self, buffer_handle: &Model, - range: Range, + range: Range, cx: &mut ModelContext, ) -> Task>> { - let buffer = buffer_handle.read(cx); - let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); self.request_lsp( buffer_handle.clone(), LanguageServerToQuery::Primary, @@ -5054,6 +5104,17 @@ impl Project { ) } + pub fn code_actions( + &self, + buffer_handle: &Model, + range: Range, + cx: &mut ModelContext, + ) -> Task>> { + let buffer = buffer_handle.read(cx); + let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); + self.code_actions_impl(buffer_handle, range, cx) + } + pub fn apply_code_action( &self, buffer_handle: Model, @@ -5420,13 +5481,12 @@ impl Project { Ok(project_transaction) } - pub fn prepare_rename( + fn prepare_rename_impl( &self, buffer: Model, - position: T, + position: PointUtf16, cx: &mut ModelContext, ) -> Task>>> { - let position = position.to_point_utf16(buffer.read(cx)); self.request_lsp( buffer, LanguageServerToQuery::Primary, @@ -5434,11 +5494,20 @@ impl Project { cx, ) } - - pub fn perform_rename( + pub fn prepare_rename( &self, buffer: Model, position: T, + cx: &mut ModelContext, + ) -> Task>>> { + let position = position.to_point_utf16(buffer.read(cx)); + self.prepare_rename_impl(buffer, position, cx) + } + + fn perform_rename_impl( + &self, + buffer: Model, + position: PointUtf16, new_name: String, push_to_history: bool, cx: &mut ModelContext, @@ -5455,22 +5524,28 @@ impl Project { cx, ) } - - pub fn on_type_format( + pub fn perform_rename( &self, buffer: Model, position: T, + new_name: String, + push_to_history: bool, + cx: &mut ModelContext, + ) -> Task> { + let position = position.to_point_utf16(buffer.read(cx)); + self.perform_rename_impl(buffer, position, new_name, push_to_history, cx) + } + + pub fn on_type_format_impl( + &self, + buffer: Model, + position: PointUtf16, trigger: String, push_to_history: bool, cx: &mut ModelContext, ) -> Task>> { - let (position, tab_size) = buffer.update(cx, |buffer, cx| { - let position = position.to_point_utf16(buffer); - ( - position, - language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx) - .tab_size, - ) + let tab_size = buffer.update(cx, |buffer, cx| { + language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx).tab_size }); self.request_lsp( buffer.clone(), @@ -5485,6 +5560,18 @@ impl Project { ) } + pub fn on_type_format( + &self, + buffer: Model, + position: T, + trigger: String, + push_to_history: bool, + cx: &mut ModelContext, + ) -> Task>> { + let position = position.to_point_utf16(buffer.read(cx)); + self.on_type_format_impl(buffer, position, trigger, push_to_history, cx) + } + pub fn inlay_hints( &self, buffer_handle: Model, @@ -5493,6 +5580,15 @@ impl Project { ) -> Task>> { let buffer = buffer_handle.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); + self.inlay_hints_impl(buffer_handle, range, cx) + } + fn inlay_hints_impl( + &self, + buffer_handle: Model, + range: Range, + cx: &mut ModelContext, + ) -> Task>> { + let buffer = buffer_handle.read(cx); let range_start = range.start; let range_end = range.end; let buffer_id = buffer.remote_id().into(); From 79c1003b344ee513cf97ee8313c38c7c3f02c916 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 11:44:28 +0100 Subject: [PATCH 007/101] go: fix highlighting of brackets, variables, fields (#7276) This changes the highlighting of Go code to make it more similar to how we highlight Rust * normal variables have the normal color, vs. being highlighted. This really stuck out. * brackets are properly highlighted It also brings it closer to Neovim's tree-sitter highlighting by changing how struct fields are highlighted. Release Notes: - Improved highlighting of Go code by tuning highlighting of variables, brackets, and struct fields. ## Before & After ![screenshot-2024-02-02-11 38 08@2x](https://github.com/zed-industries/zed/assets/1185253/a754f166-89c1-40e8-a8da-b63155180896) --- crates/zed/src/languages/go/highlights.scm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages/go/highlights.scm b/crates/zed/src/languages/go/highlights.scm index 6a9be8aae07162ff76d931492c777e394453e4ad..8cbf976fb8dc3838626828f1c1ce7b393ccbd106 100644 --- a/crates/zed/src/languages/go/highlights.scm +++ b/crates/zed/src/languages/go/highlights.scm @@ -1,6 +1,10 @@ -(identifier) @variable (type_identifier) @type -(field_identifier) @property +(field_identifier) @variable.member + +(keyed_element + . + (literal_element + (identifier) @variable.member)) (call_expression function: (identifier) @function) @@ -15,6 +19,15 @@ (method_declaration name: (field_identifier) @function.method) +[ + "(" + ")" + "{" + "}" + "[" + "]" +] @punctuation.bracket + [ "--" "-" From 980d4f1003e8bee5d9117440334aaf4eb97fc4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Pf=C3=A4ffle?= <67913738+rpfaeffle@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:51:05 +0100 Subject: [PATCH 008/101] Add inline code blocks in markdown preview (#7277) Fixes #7236. The reason why the code was not displayed correctly is due to missing background color for the inline code block. Previously to this PR: SCR-20240202-mclv After this PR: SCR-20240202-mccs Release Notes: - Fixed inline code block not shown in markdown preview ([#7236](https://github.com/zed-industries/zed/issues/7236)). --- crates/rich_text/src/rich_text.rs | 34 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index f5ab38c4cef0f4eec2ed3ed8ec2f577f62bbf1bb..1063d89db3d65627a22aefbca8f5eb21cde41896 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -13,6 +13,7 @@ use util::RangeExt; pub enum Highlight { Code, Id(HighlightId), + InlineCode(bool), Highlight(HighlightStyle), Mention, SelfMention, @@ -67,6 +68,23 @@ impl RichText { background_color: Some(code_background), ..id.style(theme.syntax()).unwrap_or_default() }, + Highlight::InlineCode(link) => { + if !*link { + HighlightStyle { + background_color: Some(code_background), + ..Default::default() + } + } else { + HighlightStyle { + background_color: Some(code_background), + underline: Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + } + } + } Highlight::Highlight(highlight) => *highlight, Highlight::Mention => HighlightStyle { font_weight: Some(FontWeight::BOLD), @@ -184,22 +202,14 @@ pub fn render_markdown_mut( } Event::Code(t) => { text.push_str(t.as_ref()); - if link_url.is_some() { - highlights.push(( - prev_len..text.len(), - Highlight::Highlight(HighlightStyle { - underline: Some(UnderlineStyle { - thickness: 1.0.into(), - ..Default::default() - }), - ..Default::default() - }), - )); - } + let is_link = link_url.is_some(); + if let Some(link_url) = link_url.clone() { link_ranges.push(prev_len..text.len()); link_urls.push(link_url); } + + highlights.push((prev_len..text.len(), Highlight::InlineCode(is_link))) } Event::Start(tag) => match tag { Tag::Paragraph => new_paragraph(text, &mut list_stack), From 998f6cf80d3de4c289869edfa20e847605465776 Mon Sep 17 00:00:00 2001 From: Rashid Almheiri <69181766+huwaireb@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:58:07 +0400 Subject: [PATCH 009/101] Add OCaml support (#6929) This pull request implements support for the [OCaml Language](https://ocaml.org/). ### Additions - [x] [tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml) grammar - [x] Highlight, Indents, Outline queries - [x] A new file icon for .ml(i) files. Based on [ocaml/ocaml-logo](https://github.com/ocaml/ocaml-logo/blob/master/Colour/SVG/colour-transparent-icon.svg) - [x] LSP Integration with [ocaml-language-server](https://github.com/ocaml/ocaml-lsp) - [x] Completion Labels - [x] Symbol Labels ### Bug Fixes - [x] Improper parsing of LSP headers. ### Missing [will file a separate issue] - Documentation on completionItem, requires: `completionItem/resolve` with support for `documentation` as a provider. ### Screenshots
Zed Screenshot 2024-02-01 at 03 33 20
Release Notes: - Added OCaml Support ([#5316](https://github.com/zed-industries/zed/issues/5316)). > [!NOTE] > Partially completes #5316 > To complete #5316: > 1. addition of a reason tree-sitter grammar. > 2. opam/esy integration, however it may be better as it's own plugin. --- Cargo.lock | 10 + Cargo.toml | 17 +- assets/icons/file_icons/file_types.json | 5 + assets/icons/file_icons/ocaml.svg | 5 + crates/lsp/src/lsp.rs | 33 +- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 11 + .../languages/ocaml-interface/brackets.scm | 6 + .../src/languages/ocaml-interface/config.toml | 13 + .../languages/ocaml-interface/highlights.scm | 1 + .../src/languages/ocaml-interface/indents.scm | 21 ++ .../src/languages/ocaml-interface/outline.scm | 48 +++ crates/zed/src/languages/ocaml.rs | 317 ++++++++++++++++++ crates/zed/src/languages/ocaml/brackets.scm | 12 + crates/zed/src/languages/ocaml/config.toml | 18 + crates/zed/src/languages/ocaml/highlights.scm | 142 ++++++++ crates/zed/src/languages/ocaml/indents.scm | 43 +++ crates/zed/src/languages/ocaml/outline.scm | 59 ++++ docs/src/languages/ocaml.md | 31 ++ 19 files changed, 784 insertions(+), 9 deletions(-) create mode 100644 assets/icons/file_icons/ocaml.svg create mode 100644 crates/zed/src/languages/ocaml-interface/brackets.scm create mode 100644 crates/zed/src/languages/ocaml-interface/config.toml create mode 120000 crates/zed/src/languages/ocaml-interface/highlights.scm create mode 100644 crates/zed/src/languages/ocaml-interface/indents.scm create mode 100644 crates/zed/src/languages/ocaml-interface/outline.scm create mode 100644 crates/zed/src/languages/ocaml.rs create mode 100644 crates/zed/src/languages/ocaml/brackets.scm create mode 100644 crates/zed/src/languages/ocaml/config.toml create mode 100644 crates/zed/src/languages/ocaml/highlights.scm create mode 100644 crates/zed/src/languages/ocaml/indents.scm create mode 100644 crates/zed/src/languages/ocaml/outline.scm create mode 100644 docs/src/languages/ocaml.md diff --git a/Cargo.lock b/Cargo.lock index 4ae19e764cd6cdf4a9bd261d3615d767e7991db8..5d735cac63be106491a2619f3d9485336f06a159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8960,6 +8960,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-ocaml" +version = "0.20.4" +source = "git+https://github.com/tree-sitter/tree-sitter-ocaml?rev=4abfdc1c7af2c6c77a370aee974627be1c285b3b#4abfdc1c7af2c6c77a370aee974627be1c285b3b" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-php" version = "0.21.1" @@ -10418,6 +10427,7 @@ dependencies = [ "tree-sitter-markdown", "tree-sitter-nix", "tree-sitter-nu", + "tree-sitter-ocaml", "tree-sitter-php", "tree-sitter-proto", "tree-sitter-purescript", diff --git a/Cargo.toml b/Cargo.toml index f46d32a53c84b80e61a57d36ee60b90530728515..5a276ef8b3b54b22e7ddeefb1a664f765b991400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,11 +100,14 @@ ctor = "0.2.6" derive_more = "0.99.17" env_logger = "0.9" futures = "0.3" -git2 = { version = "0.15", default-features = false} +git2 = { version = "0.15", default-features = false } globset = "0.4" indoc = "1" # We explicitly disable a http2 support in isahc. -isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] } +isahc = { version = "1.7.2", default-features = false, features = [ + "static-curl", + "text-decoding", +] } lazy_static = "1.4.0" log = { version = "0.4.16", features = ["kv_unstable_serde"] } ordered-float = "2.1.1" @@ -122,7 +125,10 @@ schemars = "0.8" serde = { version = "1.0", features = ["derive", "rc"] } serde_derive = { version = "1.0", features = ["deserialize_in_place"] } serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } -serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] } +serde_json_lenient = { version = "0.1", features = [ + "preserve_order", + "raw_value", +] } serde_repr = "0.1" smallvec = { version = "1.6", features = ["union"] } smol = "1.2" @@ -137,7 +143,7 @@ tree-sitter = { version = "0.20", features = ["wasm"] } tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" } tree-sitter-c = "0.20.1" tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" } -tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" } +tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" } tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" } tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" } @@ -157,8 +163,9 @@ tree-sitter-lua = "0.0.14" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa" } +tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "4abfdc1c7af2c6c77a370aee974627be1c285b3b" } tree-sitter-php = "0.21.1" -tree-sitter-proto = {git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" } +tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" } tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" } tree-sitter-python = "0.20.2" tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" } diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 8cf7b314c905eae6adadbb9a05aaf34b81748fcc..29967deb8d48b0da6e7dcaeed3116ac9883fc28d 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -69,6 +69,8 @@ "mdb": "storage", "mdf": "storage", "mdx": "document", + "ml": "ocaml", + "mli": "ocaml", "mp3": "audio", "mp4": "video", "myd": "storage", @@ -179,6 +181,9 @@ "log": { "icon": "icons/file_icons/info.svg" }, + "ocaml": { + "icon": "icons/file_icons/ocaml.svg" + }, "phoenix": { "icon": "icons/file_icons/phoenix.svg" }, diff --git a/assets/icons/file_icons/ocaml.svg b/assets/icons/file_icons/ocaml.svg new file mode 100644 index 0000000000000000000000000000000000000000..457844f49a916759072bc85cd86879729d6d32fb --- /dev/null +++ b/assets/icons/file_icons/ocaml.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index f9cd138217cfed5d0eb3d409a7bbe8c4fe78e0a3..ee5fb3a1d2b7ba3b4303eff5b465d385419a190f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -332,11 +332,36 @@ impl LanguageServer { }; let header = std::str::from_utf8(&buffer)?; - let message_len: usize = header + let mut segments = header.lines(); + + let message_len: usize = segments + .next() + .context("unable to find the first line of the LSP message header")? .strip_prefix(CONTENT_LEN_HEADER) - .ok_or_else(|| anyhow!("invalid LSP message header {header:?}"))? - .trim_end() - .parse()?; + .context("invalid LSP message header")? + .parse() + .with_context(|| { + format!( + "failed to parse Content-Length of LSP message header: {}", + header + ) + })?; + + if let Some(second_segment) = segments.next() { + match second_segment { + "" => (), // Header end + header_field if header_field.starts_with("Content-Type:") => { + stdout.read_until(b'\n', &mut buffer).await?; + } + _ => { + anyhow::bail!( + "expected a Content-Type header field or a header ending CRLF, got {second_segment:?}" + ); + } + } + } else { + anyhow::bail!("unable to find the second line of the LSP message header"); + } buffer.resize(message_len, 0); stdout.read_exact(&mut buffer).await?; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 5b202aef436a5c80e77df04b9922e727481a3aca..61df53a9fb27c0fb4f48119eb2fe7ea0e2a7c45b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -130,6 +130,7 @@ tree-sitter-lua.workspace = true tree-sitter-markdown.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true +tree-sitter-ocaml.workspace = true tree-sitter-php.workspace = true tree-sitter-proto.workspace = true tree-sitter-purescript.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index add9f9c1924adc0aed11ffb01d75a65a845af8a1..15bd180a2ebeac074d9c0bf00ea134b8b5c92fc3 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -25,6 +25,7 @@ mod json; mod language_plugin; mod lua; mod nu; +mod ocaml; mod php; mod purescript; mod python; @@ -299,6 +300,16 @@ pub fn init( tree_sitter_nu::language(), vec![Arc::new(nu::NuLanguageServer {})], ); + language( + "ocaml", + tree_sitter_ocaml::language_ocaml(), + vec![Arc::new(ocaml::OCamlLspAdapter)], + ); + language( + "ocaml-interface", + tree_sitter_ocaml::language_ocaml_interface(), + vec![Arc::new(ocaml::OCamlLspAdapter)], + ); language( "vue", tree_sitter_vue::language(), diff --git a/crates/zed/src/languages/ocaml-interface/brackets.scm b/crates/zed/src/languages/ocaml-interface/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..0929a696fd8cf1f6ce272ab5f8dae95c07bdaffa --- /dev/null +++ b/crates/zed/src/languages/ocaml-interface/brackets.scm @@ -0,0 +1,6 @@ +("(" @open ")" @close) +("{" @open "}" @close) +("<" @open ">" @close) + +("sig" @open "end" @close) +("object" @open "end" @close) diff --git a/crates/zed/src/languages/ocaml-interface/config.toml b/crates/zed/src/languages/ocaml-interface/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..f7401f774ce6cd196658f299f338903dc946a77d --- /dev/null +++ b/crates/zed/src/languages/ocaml-interface/config.toml @@ -0,0 +1,13 @@ +name = "OCaml Interface" +path_suffixes = ["mli"] +block_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 }, + { start = "sig", end = " end", close = true, newline = true }, + # HACK: For some reason `object` alone does not work + { start = "object ", end = "end", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/ocaml-interface/highlights.scm b/crates/zed/src/languages/ocaml-interface/highlights.scm new file mode 120000 index 0000000000000000000000000000000000000000..e6f0d00d1d0faae4727ccd1bbbfb6e0edbd41f66 --- /dev/null +++ b/crates/zed/src/languages/ocaml-interface/highlights.scm @@ -0,0 +1 @@ +../ocaml/highlights.scm \ No newline at end of file diff --git a/crates/zed/src/languages/ocaml-interface/indents.scm b/crates/zed/src/languages/ocaml-interface/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..0de50a48bbad978867b4c17d8530c96dd399696d --- /dev/null +++ b/crates/zed/src/languages/ocaml-interface/indents.scm @@ -0,0 +1,21 @@ +[ + (type_binding) + + (value_specification) + (method_specification) + + (external) + (field_declaration) +] @indent + +(_ "<" ">" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent + +(_ "object" @start "end" @end) @indent + +(signature + "sig" @start + "end" @end) @indent + +";;" @outdent diff --git a/crates/zed/src/languages/ocaml-interface/outline.scm b/crates/zed/src/languages/ocaml-interface/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..b8539d4cd06feb15c70323a0aa4b8864a6d1ea35 --- /dev/null +++ b/crates/zed/src/languages/ocaml-interface/outline.scm @@ -0,0 +1,48 @@ +(module_type_definition + "module" @context + "type" @context + name: (_) @name) @item + +(module_definition + "module" @context + (module_binding name: (_) @name)) @item + +(type_definition + "type" @context + (type_binding name: (_) @name)) @item + +(class_definition + "class" @context + (class_binding + "virtual"? @context + name: (_) @name)) @item + +(class_type_definition + "class" @context + "type" @context + (class_type_binding + "virtual"? @context + name: (_) @name)) @item + +(instance_variable_definition + "val" @context + "method"? @context + name: (_) @name) @item + +(method_specification + "method" @context + "virtual"? @context + (method_name) @name) @item + +(value_specification + "val" @context + (value_name) @name) @item + +(external + "external" @context + (value_name) @name) @item + +(exception_definition + "exception" @context + (constructor_declaration + (constructor_name) @name)) @item diff --git a/crates/zed/src/languages/ocaml.rs b/crates/zed/src/languages/ocaml.rs new file mode 100644 index 0000000000000000000000000000000000000000..9878b89e333538c07403b7647111b5229f335a6e --- /dev/null +++ b/crates/zed/src/languages/ocaml.rs @@ -0,0 +1,317 @@ +use std::{any::Any, ops::Range, path::PathBuf, sync::Arc}; + +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use language::{CodeLabel, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; +use rope::Rope; + +const OPERATOR_CHAR: [char; 17] = [ + '~', '!', '?', '%', '<', ':', '.', '$', '&', '*', '+', '-', '/', '=', '>', '@', '^', +]; + +pub struct OCamlLspAdapter; + +#[async_trait] +impl LspAdapter for OCamlLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("ocamllsp".into()) + } + + fn short_name(&self) -> &'static str { + "ocaml" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(())) + } + + async fn fetch_server_binary( + &self, + _: Box, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + Err(anyhow!( + "ocamllsp (ocaml-language-server) must be installed manually." + )) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + Some(LanguageServerBinary { + path: "ocamllsp".into(), + arguments: vec![], + }) + } + + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + + async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + let name = &completion.label; + let detail = completion.detail.as_ref().map(|s| s.replace("\n", " ")); + + match completion.kind.zip(detail) { + // Error of 'b : ('a, 'b) result + // Stack_overflow : exn + Some((CompletionItemKind::CONSTRUCTOR | CompletionItemKind::ENUM_MEMBER, detail)) => { + let (argument, return_t) = detail + .split_once("->") + .map_or((None, detail.as_str()), |(arg, typ)| { + (Some(arg.trim()), typ.trim()) + }); + + let constr_decl = argument.map_or(name.to_string(), |argument| { + format!("{} of {}", name, argument) + }); + + let constr_host = if return_t.ends_with("exn") { + "exception " + } else { + "type t = " + }; + + let source_host = Rope::from([constr_host, &constr_decl].join(" ")); + let mut source_highlight = { + let constr_host_len = constr_host.len() + 1; + + language.highlight_text( + &source_host, + Range { + start: constr_host_len, + end: constr_host_len + constr_decl.len(), + }, + ) + }; + + let signature_host: Rope = Rope::from(format!("let _ : {} = ()", return_t)); + + // We include the ': ' in the range as we use it later + let mut signature_highlight = + language.highlight_text(&signature_host, 6..8 + return_t.len()); + + if let Some(last) = source_highlight.last() { + let offset = last.0.end + 1; + + signature_highlight.iter_mut().for_each(|(r, _)| { + r.start += offset; + r.end += offset; + }); + }; + + Some(CodeLabel { + text: format!("{} : {}", constr_decl, return_t), + runs: { + source_highlight.append(&mut signature_highlight); + source_highlight + }, + filter_range: 0..name.len(), + }) + } + // version : string + // NOTE: (~|?) are omitted as we don't use them in the fuzzy filtering + Some((CompletionItemKind::FIELD, detail)) + if name.starts_with("~") || name.starts_with("?") => + { + let label = name.trim_start_matches(&['~', '?']); + let text = format!("{} : {}", label, detail); + + let signature_host = Rope::from(format!("let _ : {} = ()", detail)); + let signature_highlight = + &mut language.highlight_text(&signature_host, 6..8 + detail.len()); + + let offset = label.len() + 1; + for (r, _) in signature_highlight.iter_mut() { + r.start += offset; + r.end += offset; + } + + let mut label_highlight = vec![( + 0..0 + label.len(), + language.grammar()?.highlight_id_for_name("property")?, + )]; + + Some(CodeLabel { + text, + runs: { + label_highlight.append(signature_highlight); + label_highlight + }, + filter_range: 0..label.len(), + }) + } + // version: string; + Some((CompletionItemKind::FIELD, detail)) => { + let (_record_t, field_t) = detail.split_once("->")?; + + let text = format!("{}: {};", name, field_t); + let source_host: Rope = Rope::from(format!("type t = {{ {} }}", text)); + + let runs: Vec<(Range, language::HighlightId)> = + language.highlight_text(&source_host, 11..11 + text.len()); + + Some(CodeLabel { + text, + runs, + filter_range: 0..name.len(), + }) + } + // let* : 'a t -> ('a -> 'b t) -> 'b t + Some((CompletionItemKind::VALUE, detail)) + if name.contains(OPERATOR_CHAR) + || (name.starts_with("let") && name.contains(OPERATOR_CHAR)) => + { + let text = format!("{} : {}", name, detail); + + let source_host = Rope::from(format!("let ({}) : {} = ()", name, detail)); + let mut runs = language.highlight_text(&source_host, 5..6 + text.len()); + + if runs.len() > 1 { + // ')' + runs.remove(1); + + for run in &mut runs[1..] { + run.0.start -= 1; + run.0.end -= 1; + } + } + + Some(CodeLabel { + text, + runs, + filter_range: 0..name.len(), + }) + } + // version : Version.t list -> Version.t option Lwt.t + Some((CompletionItemKind::VALUE, detail)) => { + let text = format!("{} : {}", name, detail); + + let source_host = Rope::from(format!("let {} = ()", text)); + let runs = language.highlight_text(&source_host, 4..4 + text.len()); + + Some(CodeLabel { + text, + runs, + filter_range: 0..name.len(), + }) + } + // status : string + Some((CompletionItemKind::METHOD, detail)) => { + let text = format!("{} : {}", name, detail); + + let method_host = Rope::from(format!("class c : object method {} end", text)); + let runs = language.highlight_text(&method_host, 24..24 + text.len()); + + Some(CodeLabel { + text, + runs, + filter_range: 0..name.len(), + }) + } + Some((kind, _)) => { + let highlight_name = match kind { + CompletionItemKind::MODULE | CompletionItemKind::INTERFACE => "title", + CompletionItemKind::KEYWORD => "keyword", + CompletionItemKind::TYPE_PARAMETER => "type", + _ => return None, + }; + + Some(CodeLabel { + text: name.clone(), + runs: vec![( + 0..name.len(), + language.grammar()?.highlight_id_for_name(highlight_name)?, + )], + filter_range: 0..name.len(), + }) + } + _ => None, + } + } + + async fn label_for_symbol( + &self, + name: &str, + kind: SymbolKind, + language: &Arc, + ) -> Option { + let (text, filter_range, display_range) = match kind { + SymbolKind::PROPERTY => { + let text = format!("type t = {{ {}: (); }}", name); + let filter_range: Range = 0..name.len(); + let display_range = 11..11 + name.len(); + (text, filter_range, display_range) + } + SymbolKind::FUNCTION + if name.contains(OPERATOR_CHAR) + || (name.starts_with("let") && name.contains(OPERATOR_CHAR)) => + { + let text = format!("let ({}) () = ()", name); + + let filter_range = 5..5 + name.len(); + let display_range = 0..filter_range.end + 1; + (text, filter_range, display_range) + } + SymbolKind::FUNCTION => { + let text = format!("let {} () = ()", name); + + let filter_range = 4..4 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + SymbolKind::CONSTRUCTOR => { + let text = format!("type t = {}", name); + let filter_range = 0..name.len(); + let display_range = 9..9 + name.len(); + (text, filter_range, display_range) + } + SymbolKind::MODULE => { + let text = format!("module {} = struct end", name); + let filter_range = 7..7 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + SymbolKind::CLASS => { + let text = format!("class {} = object end", name); + let filter_range = 6..6 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + SymbolKind::METHOD => { + let text = format!("class c = object method {} = () end", name); + let filter_range = 0..name.len(); + let display_range = 17..24 + name.len(); + (text, filter_range, display_range) + } + SymbolKind::STRING => { + let text = format!("type {} = T", name); + let filter_range = 5..5 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + _ => return None, + }; + + Some(CodeLabel { + runs: language.highlight_text(&text.as_str().into(), display_range.clone()), + text: text[display_range].to_string(), + filter_range, + }) + } +} diff --git a/crates/zed/src/languages/ocaml/brackets.scm b/crates/zed/src/languages/ocaml/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..8aa7be2eafa1bad26a52cd0e611acfbc5dd16344 --- /dev/null +++ b/crates/zed/src/languages/ocaml/brackets.scm @@ -0,0 +1,12 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("[|" @open "|]" @close) +("{" @open "}" @close) +("<" @open ">" @close) +("\"" @open "\"" @close) + +("begin" @open "end" @close) +("struct" @open "end" @close) +("sig" @open "end" @close) +("object" @open "end" @close) +("do" @open "done" @close) diff --git a/crates/zed/src/languages/ocaml/config.toml b/crates/zed/src/languages/ocaml/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..522db6dae1f1f86e4edde7b5400dc16f9b69d2b5 --- /dev/null +++ b/crates/zed/src/languages/ocaml/config.toml @@ -0,0 +1,18 @@ +name = "OCaml" +path_suffixes = ["ml"] +block_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, not_in = ["string"] }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "begin", end = " end", close = true, newline = true }, + { start = "struct", end = " end", close = true, newline = true }, + { start = "sig", end = " end", close = true, newline = true }, + # HACK: For some reason `object` alone does not work + { start = "object ", end = "end", close = true, newline = true }, + { start = "do", end = " done", close = true, newline = true } +] diff --git a/crates/zed/src/languages/ocaml/highlights.scm b/crates/zed/src/languages/ocaml/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..e5125b912e0d7974a4e3f69603aea3f3ab9269a4 --- /dev/null +++ b/crates/zed/src/languages/ocaml/highlights.scm @@ -0,0 +1,142 @@ +; Modules +;-------- + +[(module_name) (module_type_name)] @title + +; Types +;------ + +[(class_name) (class_type_name) (type_constructor)] @type + +[(constructor_name) (tag)] @constructor + +; Functions +;---------- + +(let_binding + pattern: (value_name) @function + (parameter)) + +(let_binding + pattern: (value_name) @function + body: [(fun_expression) (function_expression)]) + +(value_specification (value_name) @function) + +(external (value_name) @function) + +(method_name) @function + +(infix_expression + left: (value_path (value_name) @function) + operator: (concat_operator) @operator + (#eq? @operator "@@")) + +(infix_expression + operator: (rel_operator) @operator + right: (value_path (value_name) @function) + (#eq? @operator "|>")) + +(application_expression + function: (value_path (value_name) @function)) + +; Variables +;---------- + +[(type_variable) (value_pattern)] @variable + +; Properties +;----------- + +[(label_name) (field_name) (instance_variable_name)] @property + +; Constants +;---------- + +(boolean) @boolean + +[(number) (signed_number)] @number + +[(string) (character)] @string + +(quoted_string "{" @string "}" @string) @string +(quoted_string_content) @string + + +(escape_sequence) @string.escape + +[ + (conversion_specification) + (pretty_printing_indication) +] @punctuation.special + +; Operators +;---------- + +(match_expression (match_operator) @keyword) + +(value_definition [(let_operator) (let_and_operator)] @keyword) + +[ + (prefix_operator) + (sign_operator) + (pow_operator) + (mult_operator) + (add_operator) + (concat_operator) + (rel_operator) + (and_operator) + (or_operator) + (assign_operator) + (hash_operator) + (indexing_operator) + (let_operator) + (let_and_operator) + (match_operator) +] @operator + +["*" "#" "::" "<-"] @operator + +; Keywords +;--------- + +[ + "and" "as" "assert" "begin" "class" "constraint" "do" "done" "downto" "else" + "end" "exception" "external" "for" "fun" "function" "functor" "if" "in" + "include" "inherit" "initializer" "lazy" "let" "match" "method" "module" + "mutable" "new" "nonrec" "object" "of" "open" "private" "rec" "sig" "struct" + "then" "to" "try" "type" "val" "virtual" "when" "while" "with" +] @keyword + +; Punctuation +;------------ + +["(" ")" "[" "]" "{" "}" "[|" "|]" "[<" "[>"] @punctuation.bracket + +(object_type ["<" ">"] @punctuation.bracket) + +[ + "," "." ";" ":" "=" "|" "~" "?" "+" "-" "!" ">" "&" + "->" ";;" ":>" "+=" ":=" ".." +] @punctuation.delimiter + +; Attributes +;----------- + +[ + (attribute) + (item_attribute) + (floating_attribute) + (extension) + (item_extension) + (quoted_extension) + (quoted_item_extension) + "%" +] @attribute + +(attribute_id) @tag + +; Comments +;--------- + +[(comment) (line_number_directive) (directive) (shebang)] @comment diff --git a/crates/zed/src/languages/ocaml/indents.scm b/crates/zed/src/languages/ocaml/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..807495dae1a3033aebc46a953cde6b4bb9e2167f --- /dev/null +++ b/crates/zed/src/languages/ocaml/indents.scm @@ -0,0 +1,43 @@ +[ + (let_binding) + (type_binding) + + (method_definition) + + (external) + (value_specification) + (method_specification) + + (match_case) + + (function_expression) + + (field_declaration) + (field_expression) +] @indent + +(_ "[" "]" @end) @indent +(_ "[|" "|]" @end) @indent +(_ "<" ">" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent + +(_ "object" @start "end" @end) @indent + +(structure + "struct" @start + "end" @end) @indent + +(signature + "sig" @start + "end" @end) @indent + +(parenthesized_expression + "begin" @start + "end") @indent + +(do_clause + "do" @start + "done" @end) @indent + +";;" @outdent diff --git a/crates/zed/src/languages/ocaml/outline.scm b/crates/zed/src/languages/ocaml/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..16f449664ae50975e3938003828a2b711b0fc438 --- /dev/null +++ b/crates/zed/src/languages/ocaml/outline.scm @@ -0,0 +1,59 @@ +(_structure_item/value_definition + "let" @context + (let_binding + pattern: (_) @name)) @item + +(_structure_item/exception_definition + "exception" @context + (constructor_declaration + (constructor_name) @name)) @item + +(_structure_item/module_definition + "module" @context + (module_binding + name: (module_name) @name)) @item + +(module_type_definition + "module" @context + "type" @context + name: (_) @name) @item + +(type_definition + "type" @context + (type_binding name: (_) @name)) @item + +(value_specification + "val" @context + (value_name) @name) @item + +(class_definition + "class" @context + (class_binding + "virtual"? @context + name: (_) @name)) @item + +(class_type_definition + "class" @context + "type" @context + (class_type_binding + "virtual"? @context + name: (_) @name)) @item + +(instance_variable_definition + "val" @context + "method"? @context + name: (_) @name) @item + +(method_specification + "method" @context + "virtual"? @context + (method_name) @name) @item + +(method_definition + "method" @context + "virtual"? @context + name: (_) @name) @item + +(external + "external" @context + (value_name) @name) @item diff --git a/docs/src/languages/ocaml.md b/docs/src/languages/ocaml.md new file mode 100644 index 0000000000000000000000000000000000000000..553b5dbdd3f87450b1ded1b3967a48aff1d836ea --- /dev/null +++ b/docs/src/languages/ocaml.md @@ -0,0 +1,31 @@ +# OCaml + +- Tree Sitter: [tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml) +- Language Server: [ocamllsp](https://github.com/ocaml/ocaml-lsp) + +## Setup Instructions +If you have the development environment already setup, you can skip to [Launching Zed](#launching-zed) + +### Using OPAM +Opam is the official package manager for OCaml and is highly recommended for getting started with OCaml. To get started using Opam, please follow the instructions provided [here](https://opam.ocaml.org/doc/Install.html). + +Once you install opam and setup a switch with your development environment as per the instructions, you can proceed. + +### Launching Zed +By now you should have `ocamllsp` installed, you can verify so by running + +```sh +$ ocamllsp --help +``` + +in your terminal. If you get a help message, you're good to go. If not, please revisit the installation instructions for `ocamllsp` and ensure it's properly installed. + +With that aside, we can now launch Zed. Given how the OCaml package manager works, we require you to run Zed from the terminal, so please make sure you install the [Zed cli](https://zed.dev/features#cli) if you haven't already. + +Once you have the cli, simply from a terminal, navigate to your project and run + +```sh +$ zed . +``` + +Voila! You should have Zed running with OCaml support, no additional setup required. From ec9f44727e2d5ed8e735583746006bfa60712212 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 16:35:58 +0100 Subject: [PATCH 010/101] Fix Go tests failing on main (#7285) Follow-up to #7276 in which I broke tests. Release Notes: - N/A --- crates/zed/src/languages/go.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 12a63c3efee18f784c3c8bdc40713a51547b8952..2fb0d35cf06cc8cf42847a713095ac701c73cbfb 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -398,7 +398,6 @@ mod tests { let highlight_type = grammar.highlight_id_for_name("type").unwrap(); let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap(); let highlight_number = grammar.highlight_id_for_name("number").unwrap(); - let highlight_field = grammar.highlight_id_for_name("property").unwrap(); assert_eq!( language @@ -454,7 +453,7 @@ mod tests { Some(CodeLabel { text: "two.Three a.Bcd".to_string(), filter_range: 0..9, - runs: vec![(4..9, highlight_field), (12..15, highlight_type)], + runs: vec![(12..15, highlight_type)], }) ); } From 01ddf840f5516d90980288c24abb8b3e6e445744 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 16:42:43 +0100 Subject: [PATCH 011/101] completions: do not render empty multi-line documentation (#7279) I ran into this a lot with Go code: the documentation would be empty so we'd display a big box with nothing in it. I think it's better if we only display the box if we have documentation. Release Notes: - Fixed documentation box in showing up when using auto-complete even if documentation was empty. ## Before ![screenshot-2024-02-02-14 00 18@2x](https://github.com/zed-industries/zed/assets/1185253/e4915d51-a573-41f4-aa5d-21de6d1b0ff1) ## After ![screenshot-2024-02-02-14 01 58@2x](https://github.com/zed-industries/zed/assets/1185253/74b244af-3fc7-45e9-8cb3-7264e34b7ab7) --- crates/editor/src/editor.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 86090b0f055880ee6808cada23e6338975af1fd3..b244ba6d125f3d8d63ce4441af510754202888a4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -857,9 +857,15 @@ impl CompletionsMenu { Some(Documentation::MultiLinePlainText(text)) => { Some(div().child(SharedString::from(text.clone()))) } - Some(Documentation::MultiLineMarkdown(parsed)) => Some(div().child( - render_parsed_markdown("completions_markdown", parsed, &style, workspace, cx), - )), + Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => { + Some(div().child(render_parsed_markdown( + "completions_markdown", + parsed, + &style, + workspace, + cx, + ))) + } _ => None, }; multiline_docs.map(|div| { From 11bd28870aa41876eea178e9d65ffd3df1f0ad6d Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 16:48:04 +0100 Subject: [PATCH 012/101] editor: Add MoveUpByLines and MoveDownByLines actions (#7208) This adds four new actions: - `editor::MoveUpByLines` - `editor::MoveDownByLines` - `editor::SelectUpByLines` - `editor::SelectDownByLines` They all take a count by which to move the cursor up and down. (Requested by Adam here: https://twitter.com/adamwathan/status/1753017094248018302) Example `keymap.json` entries: ```json { "context": "Editor", "bindings": [ "alt-up": [ "editor::MoveUpByLines", { "lines": 3 } ], "alt-down": [ "editor::MoveDownByLines", { "lines": 3 } ], "alt-shift-up": [ "editor::SelectUpByLines", { "lines": 3 } ], "alt-shift-down": [ "editor::SelectDownByLines", { "lines": 3 } ] ] } ``` They are *not* bound by default, so as to not conflict with the `alt-up/down` bindings that already exist. Release Notes: - Added four new actions: `editor::MoveUpByLines`, `editor::MoveDownByLines`, `editor::SelectUpByLines`, `editor::SelectDownByLines` that can take a line count configuration and move the cursor up by the count. ### Demo https://github.com/zed-industries/zed/assets/1185253/e78d4077-5bd5-4d72-a806-67695698af5d https://github.com/zed-industries/zed/assets/1185253/0b086ec9-eb90-40a2-9009-844a215e6378 --- crates/editor/src/actions.rs | 30 +++++++++++++- crates/editor/src/editor.rs | 80 ++++++++++++++++++++++++++++++++++++ crates/editor/src/element.rs | 4 ++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index dd3735c6a99f37571e7172e062539bbb806ff808..049c152e2cf90a3e1aa3b0a4411a45a774135cd7 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -70,6 +70,30 @@ pub struct FoldAt { pub struct UnfoldAt { pub buffer_row: u32, } + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct MoveUpByLines { + #[serde(default)] + pub(super) lines: u32, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct MoveDownByLines { + #[serde(default)] + pub(super) lines: u32, +} +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectUpByLines { + #[serde(default)] + pub(super) lines: u32, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectDownByLines { + #[serde(default)] + pub(super) lines: u32, +} + impl_actions!( editor, [ @@ -84,7 +108,11 @@ impl_actions!( ConfirmCodeAction, ToggleComments, FoldAt, - UnfoldAt + UnfoldAt, + MoveUpByLines, + MoveDownByLines, + SelectUpByLines, + SelectDownByLines, ] ); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b244ba6d125f3d8d63ce4441af510754202888a4..21092ca085c9e68451a1af616beb1ad51b2d3c3b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5379,6 +5379,86 @@ impl Editor { }) } + pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext) { + if self.take_rename(true, cx).is_some() { + return; + } + + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } + + let text_layout_details = &self.text_layout_details(cx); + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if !selection.is_empty() && !line_mode { + selection.goal = SelectionGoal::None; + } + let (cursor, goal) = movement::up_by_rows( + map, + selection.start, + action.lines, + selection.goal, + false, + &text_layout_details, + ); + selection.collapse_to(cursor, goal); + }); + }) + } + + pub fn move_down_by_lines(&mut self, action: &MoveDownByLines, cx: &mut ViewContext) { + if self.take_rename(true, cx).is_some() { + return; + } + + if matches!(self.mode, EditorMode::SingleLine) { + cx.propagate(); + return; + } + + let text_layout_details = &self.text_layout_details(cx); + + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + let line_mode = s.line_mode; + s.move_with(|map, selection| { + if !selection.is_empty() && !line_mode { + selection.goal = SelectionGoal::None; + } + let (cursor, goal) = movement::down_by_rows( + map, + selection.start, + action.lines, + selection.goal, + false, + &text_layout_details, + ); + selection.collapse_to(cursor, goal); + }); + }) + } + + pub fn select_down_by_lines(&mut self, action: &SelectDownByLines, cx: &mut ViewContext) { + let text_layout_details = &self.text_layout_details(cx); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, goal| { + movement::down_by_rows(map, head, action.lines, goal, false, &text_layout_details) + }) + }) + } + + pub fn select_up_by_lines(&mut self, action: &SelectUpByLines, cx: &mut ViewContext) { + let text_layout_details = &self.text_layout_details(cx); + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_heads_with(|map, head, goal| { + movement::up_by_rows(map, head, action.lines, goal, false, &text_layout_details) + }) + }) + } + pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext) { if self.take_rename(true, cx).is_some() { return; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b96ec9e068f29eb71af278209523ac6c2f0e5568..7e546cc2725f2c02544f28f8697b55969370b36f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -147,7 +147,11 @@ impl EditorElement { register_action(view, cx, Editor::move_left); register_action(view, cx, Editor::move_right); register_action(view, cx, Editor::move_down); + register_action(view, cx, Editor::move_down_by_lines); + register_action(view, cx, Editor::select_down_by_lines); register_action(view, cx, Editor::move_up); + register_action(view, cx, Editor::move_up_by_lines); + register_action(view, cx, Editor::select_up_by_lines); register_action(view, cx, Editor::cancel); register_action(view, cx, Editor::newline); register_action(view, cx, Editor::newline_above); From 33e5ba6278a9f202f28e6065551f6816324fa98f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 2 Feb 2024 11:02:10 -0500 Subject: [PATCH 013/101] Revert "Add YAML file type icon (#7185)" (#7286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR reverts the addition of the YAML icon, as it doesn't look good at the moment: Screenshot 2024-02-02 at 10 55 16 AM This reverts commit a853a80634149ad5d069c48c4a0ece0bd584b5b1. Release Notes: - N/A --- assets/icons/file_icons/file_types.json | 7 ++----- assets/icons/file_icons/yaml.svg | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 assets/icons/file_icons/yaml.svg diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 29967deb8d48b0da6e7dcaeed3116ac9883fc28d..5dd0cbb91567a6f18a6ee436acfd4d55a70d4a60 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -118,8 +118,8 @@ "xlsx": "document", "xml": "template", "xrl": "erlang", - "yaml": "yaml", - "yml": "yaml", + "yaml": "settings", + "yml": "settings", "yrl": "erlang", "zlogin": "terminal", "zsh": "terminal", @@ -190,9 +190,6 @@ "php": { "icon": "icons/file_icons/php.svg" }, - "yaml": { - "icon": "icons/file_icons/yaml.svg" - }, "prettier": { "icon": "icons/file_icons/prettier.svg" }, diff --git a/assets/icons/file_icons/yaml.svg b/assets/icons/file_icons/yaml.svg deleted file mode 100644 index eb5987878b72da82e98acffc27d47e6d079d6bd8..0000000000000000000000000000000000000000 --- a/assets/icons/file_icons/yaml.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From bacb2a266ae47de0dd5ded9d8f33f7a7ab13d283 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 2 Feb 2024 18:04:23 +0200 Subject: [PATCH 014/101] Add more data into LSP header parsing error contexts (#7282) Follow-up of https://github.com/zed-industries/zed/pull/6929 Release Notes: - N/A --- crates/lsp/src/lsp.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index ee5fb3a1d2b7ba3b4303eff5b465d385419a190f..b1582fbd8d373eb1963afa2ba8489202f4d6e2e5 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -336,38 +336,40 @@ impl LanguageServer { let message_len: usize = segments .next() - .context("unable to find the first line of the LSP message header")? + .with_context(|| { + format!("unable to find the first line of the LSP message header `{header}`") + })? .strip_prefix(CONTENT_LEN_HEADER) - .context("invalid LSP message header")? + .with_context(|| format!("invalid LSP message header `{header}`"))? .parse() .with_context(|| { - format!( - "failed to parse Content-Length of LSP message header: {}", - header - ) + format!("failed to parse Content-Length of LSP message header: `{header}`") })?; if let Some(second_segment) = segments.next() { match second_segment { "" => (), // Header end - header_field if header_field.starts_with("Content-Type:") => { - stdout.read_until(b'\n', &mut buffer).await?; - } - _ => { - anyhow::bail!( - "expected a Content-Type header field or a header ending CRLF, got {second_segment:?}" - ); + header_field => { + if header_field.starts_with("Content-Type:") { + stdout.read_until(b'\n', &mut buffer).await?; + } else { + anyhow::bail!( + "inside `{header}`, expected a Content-Type header field or a header ending CRLF, got `{second_segment:?}`" + ) + } } } } else { - anyhow::bail!("unable to find the second line of the LSP message header"); + anyhow::bail!( + "unable to find the second line of the LSP message header `{header}`" + ); } buffer.resize(message_len, 0); stdout.read_exact(&mut buffer).await?; if let Ok(message) = str::from_utf8(&buffer) { - log::trace!("incoming message: {}", message); + log::trace!("incoming message: {message}"); for handler in io_handlers.lock().values_mut() { handler(IoKind::StdOut, message); } From cf4f3ed79acc361e470ad8cff11ea1d98e20ae81 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:04:43 +0100 Subject: [PATCH 015/101] Fix padding for notes label (#7280) Fixes the padding of the "notes" button in collab-ui. I'm not sure why the previous code used `div().h_7().w_full()`. Am I missing something here? ### Before ![Screenshot 2024-02-02 at 15 26 44](https://github.com/zed-industries/zed/assets/53836821/0577b706-b433-4e02-802b-b6eccefb3acd) ### After ![Screenshot 2024-02-02 at 15 27 26](https://github.com/zed-industries/zed/assets/53836821/00fd81d8-82a1-4936-829c-022554259fd5) Release Notes: - Fixed padding of the "notes" button in the collaboration panel. Co-authored-by: Marshall Bowers --- crates/collab_ui/src/collab_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index f33c6a13ef3003440469732af95b841b1f8f9cdf..ed5f426c143a16388f62c9dd55a618c7d71feeac 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -957,7 +957,7 @@ impl CollabPanel { .child(render_tree_branch(false, true, cx)) .child(IconButton::new(0, IconName::File)), ) - .child(div().h_7().w_full().child(Label::new("notes"))) + .child(Label::new("notes")) .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx)) } From 3d76ed96f5e7837e410a6d11da3ff2cc237c9cb0 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 2 Feb 2024 17:09:58 +0100 Subject: [PATCH 016/101] Use `command_buffer.wait_until_scheduled` in metal renderer (#7283) This commit goes back to using `wait_until_scheduled` as opposed to `wait_until_completed`. What this means, however, is that another draw could take place before the previous one finished. When that happens we don't want to reuse the same instance buffer because the GPU is actively reading from it, so we use a pool instead. Release Notes: - Fixed a bug that caused inconsistent frame rate when scrolling on certain hardware. --------- Co-authored-by: Antonio Scandurra Co-authored-by: Antonio --- .../gpui/src/platform/mac/metal_renderer.rs | 219 +++++++++++------- 1 file changed, 134 insertions(+), 85 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 68027ceff6834842d3e4450c2ab1fe85996fdea6..0100559d4b30de6ba94c19bf48e4dd93a935b264 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -3,6 +3,7 @@ use crate::{ Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, }; +use block::ConcreteBlock; use cocoa::{ base::{NO, YES}, foundation::NSUInteger, @@ -15,7 +16,13 @@ use media::core_video::CVMetalTextureCache; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use objc::{self, msg_send, sel, sel_impl}; use smallvec::SmallVec; -use std::{ffi::c_void, mem, ptr, sync::Arc}; +use std::{ + cell::{Cell, RefCell}, + ffi::c_void, + mem, ptr, + rc::Rc, + sync::Arc, +}; #[cfg(not(feature = "runtime_shaders"))] const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); @@ -25,6 +32,7 @@ const SHADERS_SOURCE_FILE: &'static str = const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...) pub(crate) struct MetalRenderer { + device: metal::Device, layer: metal::MetalLayer, command_queue: CommandQueue, paths_rasterization_pipeline_state: metal::RenderPipelineState, @@ -36,7 +44,7 @@ pub(crate) struct MetalRenderer { polychrome_sprites_pipeline_state: metal::RenderPipelineState, surfaces_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, - instances: metal::Buffer, + instance_buffers: Rc>>, sprite_atlas: Arc, core_video_texture_cache: CVMetalTextureCache, } @@ -93,10 +101,6 @@ impl MetalRenderer { mem::size_of_val(&unit_vertices) as u64, MTLResourceOptions::StorageModeManaged, ); - let instances = device.new_buffer( - INSTANCE_BUFFER_SIZE as u64, - MTLResourceOptions::StorageModeManaged, - ); let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state( &device, @@ -165,8 +169,11 @@ impl MetalRenderer { let command_queue = device.new_command_queue(); let sprite_atlas = Arc::new(MetalAtlas::new(device.clone())); + let core_video_texture_cache = + unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() }; Self { + device, layer, command_queue, paths_rasterization_pipeline_state, @@ -178,9 +185,9 @@ impl MetalRenderer { polychrome_sprites_pipeline_state, surfaces_pipeline_state, unit_vertices, - instances, + instance_buffers: Rc::default(), sprite_atlas, - core_video_texture_cache: unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() }, + core_video_texture_cache, } } @@ -208,13 +215,22 @@ impl MetalRenderer { ); return; }; + let mut instance_buffer = self.instance_buffers.borrow_mut().pop().unwrap_or_else(|| { + self.device.new_buffer( + INSTANCE_BUFFER_SIZE as u64, + MTLResourceOptions::StorageModeManaged, + ) + }); let command_queue = self.command_queue.clone(); let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; - let Some(path_tiles) = - self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer) - else { + let Some(path_tiles) = self.rasterize_paths( + scene.paths(), + &mut instance_buffer, + &mut instance_offset, + command_buffer, + ) else { panic!("failed to rasterize {} paths", scene.paths().len()); }; @@ -243,22 +259,29 @@ impl MetalRenderer { let ok = match batch { PrimitiveBatch::Shadows(shadows) => self.draw_shadows( shadows, + &mut instance_buffer, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Quads(quads) => self.draw_quads( + quads, + &mut instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), - PrimitiveBatch::Quads(quads) => { - self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder) - } PrimitiveBatch::Paths(paths) => self.draw_paths( paths, &path_tiles, + &mut instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Underlines(underlines) => self.draw_underlines( underlines, + &mut instance_buffer, &mut instance_offset, viewport_size, command_encoder, @@ -269,6 +292,7 @@ impl MetalRenderer { } => self.draw_monochrome_sprites( texture_id, sprites, + &mut instance_buffer, &mut instance_offset, viewport_size, command_encoder, @@ -279,12 +303,14 @@ impl MetalRenderer { } => self.draw_polychrome_sprites( texture_id, sprites, + &mut instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( surfaces, + &mut instance_buffer, &mut instance_offset, viewport_size, command_encoder, @@ -306,22 +332,32 @@ impl MetalRenderer { command_encoder.end_encoding(); - self.instances.did_modify_range(NSRange { + instance_buffer.did_modify_range(NSRange { location: 0, length: instance_offset as NSUInteger, }); + let instance_buffers = self.instance_buffers.clone(); + let instance_buffer = Cell::new(Some(instance_buffer)); + let block = ConcreteBlock::new(move |_| { + if let Some(instance_buffer) = instance_buffer.take() { + instance_buffers.borrow_mut().push(instance_buffer); + } + }); + let block = block.copy(); + command_buffer.add_completed_handler(&block); command_buffer.commit(); self.sprite_atlas.clear_textures(AtlasTextureKind::Path); - command_buffer.wait_until_completed(); + command_buffer.wait_until_scheduled(); drawable.present(); } fn rasterize_paths( &mut self, paths: &[Path], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, command_buffer: &metal::CommandBufferRef, ) -> Option> { let mut tiles = HashMap::default(); @@ -347,9 +383,9 @@ impl MetalRenderer { } for (texture_id, vertices) in vertices_by_texture_id { - align_offset(offset); + align_offset(instance_offset); let vertices_bytes_len = mem::size_of_val(vertices.as_slice()); - let next_offset = *offset + vertices_bytes_len; + let next_offset = *instance_offset + vertices_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return None; } @@ -369,8 +405,8 @@ impl MetalRenderer { command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state); command_encoder.set_vertex_buffer( PathRasterizationInputIndex::Vertices as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); let texture_size = Size { width: DevicePixels::from(texture.width()), @@ -382,7 +418,8 @@ impl MetalRenderer { &texture_size as *const Size as *const _, ); - let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + let buffer_contents = + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; unsafe { ptr::copy_nonoverlapping( vertices.as_ptr() as *const u8, @@ -397,7 +434,7 @@ impl MetalRenderer { vertices.len() as u64, ); command_encoder.end_encoding(); - *offset = next_offset; + *instance_offset = next_offset; } Some(tiles) @@ -406,14 +443,15 @@ impl MetalRenderer { fn draw_shadows( &mut self, shadows: &[Shadow], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { if shadows.is_empty() { return true; } - align_offset(offset); + align_offset(instance_offset); command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state); command_encoder.set_vertex_buffer( @@ -423,13 +461,13 @@ impl MetalRenderer { ); command_encoder.set_vertex_buffer( ShadowInputIndex::Shadows as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_fragment_buffer( ShadowInputIndex::Shadows as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( @@ -439,9 +477,10 @@ impl MetalRenderer { ); let shadow_bytes_len = mem::size_of_val(shadows); - let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + let buffer_contents = + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; - let next_offset = *offset + shadow_bytes_len; + let next_offset = *instance_offset + shadow_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } @@ -460,21 +499,22 @@ impl MetalRenderer { 6, shadows.len() as u64, ); - *offset = next_offset; + *instance_offset = next_offset; true } fn draw_quads( &mut self, quads: &[Quad], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { if quads.is_empty() { return true; } - align_offset(offset); + align_offset(instance_offset); command_encoder.set_render_pipeline_state(&self.quads_pipeline_state); command_encoder.set_vertex_buffer( @@ -484,13 +524,13 @@ impl MetalRenderer { ); command_encoder.set_vertex_buffer( QuadInputIndex::Quads as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_fragment_buffer( QuadInputIndex::Quads as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( @@ -500,9 +540,10 @@ impl MetalRenderer { ); let quad_bytes_len = mem::size_of_val(quads); - let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + let buffer_contents = + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; - let next_offset = *offset + quad_bytes_len; + let next_offset = *instance_offset + quad_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } @@ -517,7 +558,7 @@ impl MetalRenderer { 6, quads.len() as u64, ); - *offset = next_offset; + *instance_offset = next_offset; true } @@ -525,7 +566,8 @@ impl MetalRenderer { &mut self, paths: &[Path], tiles_by_path_id: &HashMap, - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { @@ -573,7 +615,7 @@ impl MetalRenderer { if sprites.is_empty() { break; } else { - align_offset(offset); + align_offset(instance_offset); let texture_id = prev_texture_id.take().unwrap(); let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id); let texture_size = size( @@ -583,8 +625,8 @@ impl MetalRenderer { command_encoder.set_vertex_buffer( SpriteInputIndex::Sprites as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( SpriteInputIndex::AtlasTextureSize as u64, @@ -593,20 +635,20 @@ impl MetalRenderer { ); command_encoder.set_fragment_buffer( SpriteInputIndex::Sprites as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); let sprite_bytes_len = mem::size_of_val(sprites.as_slice()); - let next_offset = *offset + sprite_bytes_len; + let next_offset = *instance_offset + sprite_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } let buffer_contents = - unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; unsafe { ptr::copy_nonoverlapping( @@ -622,7 +664,7 @@ impl MetalRenderer { 6, sprites.len() as u64, ); - *offset = next_offset; + *instance_offset = next_offset; sprites.clear(); } } @@ -632,14 +674,15 @@ impl MetalRenderer { fn draw_underlines( &mut self, underlines: &[Underline], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { if underlines.is_empty() { return true; } - align_offset(offset); + align_offset(instance_offset); command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state); command_encoder.set_vertex_buffer( @@ -649,13 +692,13 @@ impl MetalRenderer { ); command_encoder.set_vertex_buffer( UnderlineInputIndex::Underlines as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_fragment_buffer( UnderlineInputIndex::Underlines as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( @@ -665,9 +708,10 @@ impl MetalRenderer { ); let underline_bytes_len = mem::size_of_val(underlines); - let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + let buffer_contents = + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; - let next_offset = *offset + underline_bytes_len; + let next_offset = *instance_offset + underline_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } @@ -686,7 +730,7 @@ impl MetalRenderer { 6, underlines.len() as u64, ); - *offset = next_offset; + *instance_offset = next_offset; true } @@ -694,14 +738,15 @@ impl MetalRenderer { &mut self, texture_id: AtlasTextureId, sprites: &[MonochromeSprite], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { if sprites.is_empty() { return true; } - align_offset(offset); + align_offset(instance_offset); let texture = self.sprite_atlas.metal_texture(texture_id); let texture_size = size( @@ -716,8 +761,8 @@ impl MetalRenderer { ); command_encoder.set_vertex_buffer( SpriteInputIndex::Sprites as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( SpriteInputIndex::ViewportSize as u64, @@ -731,15 +776,16 @@ impl MetalRenderer { ); command_encoder.set_fragment_buffer( SpriteInputIndex::Sprites as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); let sprite_bytes_len = mem::size_of_val(sprites); - let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + let buffer_contents = + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; - let next_offset = *offset + sprite_bytes_len; + let next_offset = *instance_offset + sprite_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } @@ -758,7 +804,7 @@ impl MetalRenderer { 6, sprites.len() as u64, ); - *offset = next_offset; + *instance_offset = next_offset; true } @@ -766,14 +812,15 @@ impl MetalRenderer { &mut self, texture_id: AtlasTextureId, sprites: &[PolychromeSprite], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { if sprites.is_empty() { return true; } - align_offset(offset); + align_offset(instance_offset); let texture = self.sprite_atlas.metal_texture(texture_id); let texture_size = size( @@ -788,8 +835,8 @@ impl MetalRenderer { ); command_encoder.set_vertex_buffer( SpriteInputIndex::Sprites as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( SpriteInputIndex::ViewportSize as u64, @@ -803,15 +850,16 @@ impl MetalRenderer { ); command_encoder.set_fragment_buffer( SpriteInputIndex::Sprites as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); let sprite_bytes_len = mem::size_of_val(sprites); - let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + let buffer_contents = + unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) }; - let next_offset = *offset + sprite_bytes_len; + let next_offset = *instance_offset + sprite_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } @@ -830,14 +878,15 @@ impl MetalRenderer { 6, sprites.len() as u64, ); - *offset = next_offset; + *instance_offset = next_offset; true } fn draw_surfaces( &mut self, surfaces: &[Surface], - offset: &mut usize, + instance_buffer: &mut metal::Buffer, + instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { @@ -889,16 +938,16 @@ impl MetalRenderer { .unwrap() }; - align_offset(offset); - let next_offset = *offset + mem::size_of::(); + align_offset(instance_offset); + let next_offset = *instance_offset + mem::size_of::(); if next_offset > INSTANCE_BUFFER_SIZE { return false; } command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, - Some(&self.instances), - *offset as u64, + Some(instance_buffer), + *instance_offset as u64, ); command_encoder.set_vertex_bytes( SurfaceInputIndex::TextureSize as u64, @@ -915,8 +964,8 @@ impl MetalRenderer { ); unsafe { - let buffer_contents = - (self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds; + let buffer_contents = (instance_buffer.contents() as *mut u8).add(*instance_offset) + as *mut SurfaceBounds; ptr::write( buffer_contents, SurfaceBounds { @@ -927,7 +976,7 @@ impl MetalRenderer { } command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6); - *offset = next_offset; + *instance_offset = next_offset; } true } From 6f6cb53fad43892d9529e5a970d958d503d9c395 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 2 Feb 2024 11:41:47 -0500 Subject: [PATCH 017/101] Tweak pull request template to (hopefully) make it clearer (#7287) I keep seeing people leave the wrapping parentheses, like `(Added)` in their release notes. This PR tries making it clearer that we only want *one* of "Added", "Fixed", or "Improved". Release Notes: - N/A --- .github/pull_request_template.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 443e1b58da06154e9990e5dc6dac11a137a282c7..ce8a08f5b8925d112d4f3909c02f4c722c369a7c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,4 +2,8 @@ Release Notes: -- (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/zed/issues/)). +- Added/Fixed/Improved ... ([#](https://github.com/zed-industries/zed/issues/)). + +**or** + +- N/A From 074acacdf70962b2a96f2f92f50a9e42f0ff4119 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 2 Feb 2024 09:52:30 -0700 Subject: [PATCH 018/101] Do more on channel join (#7268) This change makes it so that if you are the first to join a channel, your project is automatically shared. It also makes it so that if you join a channel via a link and there are no shared projects, you open the notes instead of an empty workspace with nothing. This is to try and address the discoverability of project sharing: we've had two reviews that have talked about channels, but not talked about sharing projects into them, which makes me suspect they didn't know about the feature. Release Notes: - Added a setting `share_on_join` (defaulting to true). When set, and you join an empty channel, your project is automatically shared. --- assets/settings/default.json | 6 +++-- crates/call/src/call_settings.rs | 6 +++++ crates/call/src/room.rs | 6 ++++- crates/collab_ui/src/collab_panel.rs | 15 ++++++++++- crates/workspace/src/workspace.rs | 40 +++++++++++++++++++++++++++- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 74dfa6de2c61f74eb40891adf2e4227001f12e35..4345be1b50083304f3f508ee3e3402df624816d3 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -104,8 +104,10 @@ "show_whitespaces": "selection", // Settings related to calls in Zed "calls": { - // Join calls with the microphone muted by default - "mute_on_join": false + // Join calls with the microphone live by default + "mute_on_join": false, + // Share your project when you are the first to join a channel + "share_on_join": true }, // Scrollbar related settings "scrollbar": { diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 441323ad5ffcf4d6d525525139905278bd15ca0b..6aa42536891fbf7b8d57ef4744b3f9cd3347821c 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -7,6 +7,7 @@ use settings::Settings; #[derive(Deserialize, Debug)] pub struct CallSettings { pub mute_on_join: bool, + pub share_on_join: bool, } /// Configuration of voice calls in Zed. @@ -16,6 +17,11 @@ pub struct CallSettingsContent { /// /// Default: false pub mute_on_join: Option, + + /// Whether your current project should be shared when joining an empty channel. + /// + /// Default: true + pub share_on_join: Option, } impl Settings for CallSettings { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 3e89238b0c8b16bfb17a185a1adec446bde473b6..cd8af385ed8bc30b24958eee3e53790027a15755 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -617,6 +617,10 @@ impl Room { self.local_participant.role == proto::ChannelRole::Admin } + pub fn local_participant_is_guest(&self) -> bool { + self.local_participant.role == proto::ChannelRole::Guest + } + pub fn set_participant_role( &mut self, user_id: u64, @@ -1202,7 +1206,7 @@ impl Room { }) } - pub(crate) fn share_project( + pub fn share_project( &mut self, project: Model, cx: &mut ModelContext, diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index ed5f426c143a16388f62c9dd55a618c7d71feeac..6730203ecaa7daaf47b22c87c992aff9750cc92f 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -40,7 +40,7 @@ use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt}, - Workspace, + OpenChannelNotes, Workspace, }; actions!( @@ -69,6 +69,19 @@ pub fn init(cx: &mut AppContext) { workspace.register_action(|workspace, _: &ToggleFocus, cx| { workspace.toggle_panel_focus::(cx); }); + workspace.register_action(|_, _: &OpenChannelNotes, cx| { + let channel_id = ActiveCall::global(cx) + .read(cx) + .room() + .and_then(|room| room.read(cx).channel_id()); + + if let Some(channel_id) = channel_id { + let workspace = cx.view().clone(); + cx.window_context().defer(move |cx| { + ChannelView::open(channel_id, workspace, cx).detach_and_log_err(cx) + }); + } + }); }) .detach(); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ada481675365f19ce94ab639ba43963328200e47..af8608776f28a1a2e7cbcac2b2d94ec6b990b5bb 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -12,7 +12,7 @@ mod toolbar; mod workspace_settings; use anyhow::{anyhow, Context as _, Result}; -use call::ActiveCall; +use call::{call_settings::CallSettings, ActiveCall}; use client::{ proto::{self, ErrorCode, PeerId}, Client, ErrorExt, Status, TypedEnvelope, UserStore, @@ -3974,6 +3974,8 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } +actions!(collab, [OpenChannelNotes]); + async fn join_channel_internal( channel_id: u64, app_state: &Arc, @@ -4075,6 +4077,36 @@ async fn join_channel_internal( return Some(join_remote_project(project, host, app_state.clone(), cx)); } + // if you are the first to join a channel, share your project + if room.remote_participants().len() == 0 && !room.local_participant_is_guest() { + if let Some(workspace) = requesting_window { + let project = workspace.update(cx, |workspace, cx| { + if !CallSettings::get_global(cx).share_on_join { + return None; + } + let project = workspace.project.read(cx); + if project.is_local() + && project.visible_worktrees(cx).any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) + { + Some(workspace.project.clone()) + } else { + None + } + }); + if let Ok(Some(project)) = project { + return Some(cx.spawn(|room, mut cx| async move { + room.update(&mut cx, |room, cx| room.share_project(project, cx))? + .await?; + Ok(()) + })); + } + } + } + None })?; if let Some(task) = task { @@ -4117,6 +4149,12 @@ pub fn join_channel( })? .await?; + if result.is_ok() { + cx.update(|cx| { + cx.dispatch_action(&OpenChannelNotes); + }).log_err(); + } + active_window = Some(window_handle); } From 659423a4a1fabc7a0e5af10a2f5954b9e2d27089 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 2 Feb 2024 09:53:07 -0700 Subject: [PATCH 019/101] Use `Mutex` instead of a `RefCell` to acquire/release instance buffers (#7291) This fixes a panic happening when releasing an instance buffer. Releasing the buffer happens on a different thread but the borrow checker was not catching it because the metal buffer completion handler API doesn't have a `Send` marker on it. Release Notes: - N/A --- crates/gpui/src/platform/mac/metal_renderer.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 0100559d4b30de6ba94c19bf48e4dd93a935b264..aa9992dfda02ccc3e115ff3e369a457d37b6620b 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -15,14 +15,9 @@ use foreign_types::ForeignType; use media::core_video::CVMetalTextureCache; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use objc::{self, msg_send, sel, sel_impl}; +use parking_lot::Mutex; use smallvec::SmallVec; -use std::{ - cell::{Cell, RefCell}, - ffi::c_void, - mem, ptr, - rc::Rc, - sync::Arc, -}; +use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc}; #[cfg(not(feature = "runtime_shaders"))] const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); @@ -44,7 +39,8 @@ pub(crate) struct MetalRenderer { polychrome_sprites_pipeline_state: metal::RenderPipelineState, surfaces_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, - instance_buffers: Rc>>, + #[allow(clippy::arc_with_non_send_sync)] + instance_buffers: Arc>>, sprite_atlas: Arc, core_video_texture_cache: CVMetalTextureCache, } @@ -185,7 +181,7 @@ impl MetalRenderer { polychrome_sprites_pipeline_state, surfaces_pipeline_state, unit_vertices, - instance_buffers: Rc::default(), + instance_buffers: Arc::default(), sprite_atlas, core_video_texture_cache, } @@ -215,7 +211,7 @@ impl MetalRenderer { ); return; }; - let mut instance_buffer = self.instance_buffers.borrow_mut().pop().unwrap_or_else(|| { + let mut instance_buffer = self.instance_buffers.lock().pop().unwrap_or_else(|| { self.device.new_buffer( INSTANCE_BUFFER_SIZE as u64, MTLResourceOptions::StorageModeManaged, @@ -341,7 +337,7 @@ impl MetalRenderer { let instance_buffer = Cell::new(Some(instance_buffer)); let block = ConcreteBlock::new(move |_| { if let Some(instance_buffer) = instance_buffer.take() { - instance_buffers.borrow_mut().push(instance_buffer); + instance_buffers.lock().push(instance_buffer); } }); let block = block.copy(); From 3995c22414d9103f1816bdf50d718cafea7e1cba Mon Sep 17 00:00:00 2001 From: James Roberts <82052595+contrast-jproberts@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:08:15 -0500 Subject: [PATCH 020/101] Use async-native-tls for websockets (#7254) This change switches from using async_tungstenite::async_tls to async_tungstenite::async_std with the async-native-tls feature. The previous feature, async_tls, used async-tls which wraps rustls. rustls bundles webpki-roots, which is a copy of Mozilla's root certificates. These certificates are used by default, and manual configuration is required to support custom certificates, such as those required by web security gateways in enterprise environments. Instead of introducing a new configuration option to Zed, async-native-tls integrates with the platform-native certificate store to support enterprise environments out-of-the-box. For MacOS, this adds support for Security.framework TLS. This integration is provided through openssl-sys, which is also the SSL certificate provider for isahc, the library underlying Zed's HTTP client. Making websockets and HTTP communications use the same SSL provider should keep Zed consistent operations and make the project easier to maintain. Release Notes: - Fixed WebSocket communications using custom TLS certificates ([#4759](https://github.com/zed-industries/zed/issues/4759)). --- Cargo.lock | 77 +++++++++---------------------------- crates/client/Cargo.toml | 2 +- crates/client/src/client.rs | 2 +- 3 files changed, 20 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d735cac63be106491a2619f3d9485336f06a159..b5751e0560dfe3ee59e89c0e9bd1a63838274436 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,6 +441,18 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-native-tls" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" +dependencies = [ + "async-std", + "native-tls", + "thiserror", + "url", +] + [[package]] name = "async-net" version = "1.7.0" @@ -570,19 +582,6 @@ version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" -[[package]] -name = "async-tls" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400" -dependencies = [ - "futures-core", - "futures-io", - "rustls 0.19.1", - "webpki", - "webpki-roots 0.21.1", -] - [[package]] name = "async-trait" version = "0.1.73" @@ -600,7 +599,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5682ea0913e5c20780fe5785abacb85a411e7437bf52a1bedb93ddb3972cb8dd" dependencies = [ - "async-tls", + "async-native-tls", + "async-std", "futures-io", "futures-util", "log", @@ -6731,19 +6731,6 @@ dependencies = [ "rustix 0.38.30", ] -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki", -] - [[package]] name = "rustls" version = "0.21.7" @@ -6752,7 +6739,7 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "ring", "rustls-webpki", - "sct 0.7.0", + "sct", ] [[package]] @@ -6895,16 +6882,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sct" version = "0.7.0" @@ -7626,7 +7603,7 @@ dependencies = [ "paste", "percent-encoding", "rust_decimal", - "rustls 0.21.7", + "rustls", "rustls-pemfile", "serde", "serde_json", @@ -7640,7 +7617,7 @@ dependencies = [ "tracing", "url", "uuid 1.4.1", - "webpki-roots 0.24.0", + "webpki-roots", ] [[package]] @@ -9138,6 +9115,7 @@ dependencies = [ "http", "httparse", "log", + "native-tls", "rand 0.8.5", "sha-1 0.9.8", "thiserror", @@ -9845,25 +9823,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.24.0" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 9bf04caa71140357b978326e1a4881edc8c6315e..f405b1a74d7558a0030f8e5174f9b47334fa2906 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -27,7 +27,7 @@ sum_tree = { path = "../sum_tree" } anyhow.workspace = true async-recursion = "0.3" -async-tungstenite = { version = "0.16", features = ["async-tls"] } +async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] } futures.workspace = true image = "0.23" lazy_static.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index dc95d0ca67a2d47fd15ce16dd0ebbb95346294f8..ff8adc96607b854b2345a5b2b9fa25f8088d6bec 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1040,7 +1040,7 @@ impl Client { rpc_url.set_scheme("wss").unwrap(); let request = request.uri(rpc_url.as_str()).body(())?; let (stream, _) = - async_tungstenite::async_tls::client_async_tls(request, stream).await?; + async_tungstenite::async_std::client_async_tls(request, stream).await?; Ok(Connection::new( stream .map_err(|error| anyhow!(error)) From 5360c0ea285329e0a79a6e214b6aa82725ac9165 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 2 Feb 2024 12:09:05 -0500 Subject: [PATCH 021/101] theme_importer: Make VS Code theme parsing more lenient (#7292) This PR updates the `theme_importer` to use `serde_json_lenient` to parse VS Code themes. This should allow us to parse themes that have trailing commas and such, in addition to the comment support that we already had. Release Notes: - N/A --- Cargo.lock | 8 +------- crates/theme_importer/Cargo.toml | 2 +- crates/theme_importer/src/main.rs | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5751e0560dfe3ee59e89c0e9bd1a63838274436..6500eacea55f87a2bb89c9541011d93ae8b89862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3867,12 +3867,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json_comments" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" - [[package]] name = "jwt" version = "0.16.0" @@ -8228,7 +8222,6 @@ dependencies = [ "gpui", "indexmap 1.9.3", "indoc", - "json_comments", "log", "palette", "pathfinder_color", @@ -8236,6 +8229,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "serde_json_lenient", "simplelog", "strum", "theme", diff --git a/crates/theme_importer/Cargo.toml b/crates/theme_importer/Cargo.toml index 2214af85c740f1472e9807ec809b84c4fdaed42e..ddb9433f16d3e32f24bec3ed3f1d9741b9edf4e5 100644 --- a/crates/theme_importer/Cargo.toml +++ b/crates/theme_importer/Cargo.toml @@ -13,7 +13,6 @@ convert_case = "0.6.0" gpui = { path = "../gpui" } indexmap = { version = "1.6.2", features = ["serde"] } indoc.workspace = true -json_comments = "0.2.2" log.workspace = true palette = { version = "0.7.3", default-features = false, features = ["std"] } pathfinder_color = "0.5" @@ -21,6 +20,7 @@ rust-embed.workspace = true schemars = { workspace = true, features = ["indexmap"] } serde.workspace = true serde_json.workspace = true +serde_json_lenient.workspace = true simplelog = "0.9" strum = { version = "0.25.0", features = ["derive"] } theme = { path = "../theme" } diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index a8cc8a1bdf78a6edbc523d4947b173c1a7829710..c689d7f7f9914105dd592cf05ff3cb0b5a6b90f8 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -9,7 +9,6 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use indexmap::IndexMap; -use json_comments::StripComments; use log::LevelFilter; use schemars::schema_for; use serde::Deserialize; @@ -132,8 +131,7 @@ fn main() -> Result<()> { } }; - let theme_without_comments = StripComments::new(theme_file); - let vscode_theme: VsCodeTheme = serde_json::from_reader(theme_without_comments) + let vscode_theme: VsCodeTheme = serde_json_lenient::from_reader(theme_file) .context(format!("failed to parse theme {theme_file_path:?}"))?; let theme_metadata = ThemeMetadata { From a0b52cc69a2dee14e0d3139a8e6132549ab5a71b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 2 Feb 2024 10:11:20 -0700 Subject: [PATCH 022/101] Scope line layout cache to each window (#7235) This improves a performance problem we were observing when having multiple windows updating at the same time, where each window would invalidate the other window's layout cache. Release Notes: - Improved performance when having multiple Zed windows open. Co-authored-by: Max Brunsfeld --- crates/editor/src/movement.rs | 4 +- crates/gpui/src/text_system.rs | 154 +++++++++++-------- crates/gpui/src/text_system/line_wrapper.rs | 4 +- crates/gpui/src/window.rs | 10 +- crates/terminal_view/src/terminal_element.rs | 6 +- 5 files changed, 102 insertions(+), 76 deletions(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index fa5e5f1655c6d5da687432596474d2bf2d9e450c..5e37cb8be263419c3bf12ad54137964731615ffd 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -3,7 +3,7 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint}; -use gpui::{px, Pixels, TextSystem}; +use gpui::{px, Pixels, WindowTextSystem}; use language::Point; use std::{ops::Range, sync::Arc}; @@ -22,7 +22,7 @@ pub enum FindRange { /// TextLayoutDetails encompasses everything we need to move vertically /// taking into account variable width characters. pub struct TextLayoutDetails { - pub(crate) text_system: Arc, + pub(crate) text_system: Arc, pub(crate) editor_style: EditorStyle, pub(crate) rem_size: Pixels, pub anchor: Anchor, diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index d5180e30c07fb05a9f8f19bcb786487d130821cb..12242e26c245be91167c3f34242e9e7a8b3d01e5 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -15,6 +15,7 @@ use crate::{ use anyhow::anyhow; use collections::{BTreeSet, FxHashMap, FxHashSet}; use core::fmt; +use derive_more::Deref; use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::{smallvec, SmallVec}; @@ -38,9 +39,8 @@ pub struct FontFamilyId(pub usize); pub(crate) const SUBPIXEL_VARIANTS: u8 = 4; -/// The GPUI text layout and rendering sub system. +/// The GPUI text rendering sub system. pub struct TextSystem { - line_layout_cache: Arc, platform_text_system: Arc, font_ids_by_font: RwLock>>, font_metrics: RwLock>, @@ -53,7 +53,6 @@ pub struct TextSystem { impl TextSystem { pub(crate) fn new(platform_text_system: Arc) -> Self { TextSystem { - line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())), platform_text_system, font_metrics: RwLock::default(), raster_bounds: RwLock::default(), @@ -234,43 +233,66 @@ impl TextSystem { } } - pub(crate) fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { - self.line_layout_cache.with_view(view_id, f) + /// Returns a handle to a line wrapper, for the given font and font size. + pub fn line_wrapper(self: &Arc, font: Font, font_size: Pixels) -> LineWrapperHandle { + let lock = &mut self.wrapper_pool.lock(); + let font_id = self.resolve_font(&font); + let wrappers = lock + .entry(FontIdWithSize { font_id, font_size }) + .or_default(); + let wrapper = wrappers.pop().unwrap_or_else(|| { + LineWrapper::new(font_id, font_size, self.platform_text_system.clone()) + }); + + LineWrapperHandle { + wrapper: Some(wrapper), + text_system: self.clone(), + } } - /// Layout the given line of text, at the given font_size. - /// Subsets of the line can be styled independently with the `runs` parameter. - /// Generally, you should prefer to use `TextLayout::shape_line` instead, which - /// can be painted directly. - pub fn layout_line( - &self, - text: &str, - font_size: Pixels, - runs: &[TextRun], - ) -> Result> { - let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); - for run in runs.iter() { - let font_id = self.resolve_font(&run.font); - if let Some(last_run) = font_runs.last_mut() { - if last_run.font_id == font_id { - last_run.len += run.len; - continue; - } - } - font_runs.push(FontRun { - len: run.len, - font_id, - }); + /// Get the rasterized size and location of a specific, rendered glyph. + pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result> { + let raster_bounds = self.raster_bounds.upgradable_read(); + if let Some(bounds) = raster_bounds.get(params) { + Ok(*bounds) + } else { + let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds); + let bounds = self.platform_text_system.glyph_raster_bounds(params)?; + raster_bounds.insert(params.clone(), bounds); + Ok(bounds) } + } - let layout = self - .line_layout_cache - .layout_line(text, font_size, &font_runs); + pub(crate) fn rasterize_glyph( + &self, + params: &RenderGlyphParams, + ) -> Result<(Size, Vec)> { + let raster_bounds = self.raster_bounds(params)?; + self.platform_text_system + .rasterize_glyph(params, raster_bounds) + } +} - font_runs.clear(); - self.font_runs_pool.lock().push(font_runs); +/// The GPUI text layout subsystem. +#[derive(Deref)] +pub struct WindowTextSystem { + line_layout_cache: Arc, + #[deref] + text_system: Arc, +} - Ok(layout) +impl WindowTextSystem { + pub(crate) fn new(text_system: Arc) -> Self { + Self { + line_layout_cache: Arc::new(LineLayoutCache::new( + text_system.platform_text_system.clone(), + )), + text_system, + } + } + + pub(crate) fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { + self.line_layout_cache.with_view(view_id, f) } /// Shape the given line, at the given font_size, for painting to the screen. @@ -429,43 +451,39 @@ impl TextSystem { self.line_layout_cache.finish_frame(reused_views) } - /// Returns a handle to a line wrapper, for the given font and font size. - pub fn line_wrapper(self: &Arc, font: Font, font_size: Pixels) -> LineWrapperHandle { - let lock = &mut self.wrapper_pool.lock(); - let font_id = self.resolve_font(&font); - let wrappers = lock - .entry(FontIdWithSize { font_id, font_size }) - .or_default(); - let wrapper = wrappers.pop().unwrap_or_else(|| { - LineWrapper::new(font_id, font_size, self.platform_text_system.clone()) - }); - - LineWrapperHandle { - wrapper: Some(wrapper), - text_system: self.clone(), + /// Layout the given line of text, at the given font_size. + /// Subsets of the line can be styled independently with the `runs` parameter. + /// Generally, you should prefer to use `TextLayout::shape_line` instead, which + /// can be painted directly. + pub fn layout_line( + &self, + text: &str, + font_size: Pixels, + runs: &[TextRun], + ) -> Result> { + let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); + for run in runs.iter() { + let font_id = self.resolve_font(&run.font); + if let Some(last_run) = font_runs.last_mut() { + if last_run.font_id == font_id { + last_run.len += run.len; + continue; + } + } + font_runs.push(FontRun { + len: run.len, + font_id, + }); } - } - /// Get the rasterized size and location of a specific, rendered glyph. - pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result> { - let raster_bounds = self.raster_bounds.upgradable_read(); - if let Some(bounds) = raster_bounds.get(params) { - Ok(*bounds) - } else { - let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds); - let bounds = self.platform_text_system.glyph_raster_bounds(params)?; - raster_bounds.insert(params.clone(), bounds); - Ok(bounds) - } - } + let layout = self + .line_layout_cache + .layout_line(text, font_size, &font_runs); - pub(crate) fn rasterize_glyph( - &self, - params: &RenderGlyphParams, - ) -> Result<(Size, Vec)> { - let raster_bounds = self.raster_bounds(params)?; - self.platform_text_system - .rasterize_glyph(params, raster_bounds) + font_runs.clear(); + self.font_runs_pool.lock().push(font_runs); + + Ok(layout) } } diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 4950d7e20e863bd7dc31dfe34e65043fb7734bea..0abe31352d58753b09030303f6a0d9de94ffab0b 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -143,7 +143,7 @@ impl Boundary { #[cfg(test)] mod tests { use super::*; - use crate::{font, TestAppContext, TestDispatcher, TextRun, WrapBoundary}; + use crate::{font, TestAppContext, TestDispatcher, TextRun, WindowTextSystem, WrapBoundary}; use rand::prelude::*; #[test] @@ -218,7 +218,7 @@ mod tests { #[crate::test] fn test_wrap_shaped_line(cx: &mut TestAppContext) { cx.update(|cx| { - let text_system = cx.text_system().clone(); + let text_system = WindowTextSystem::new(cx.text_system().clone()); let normal = TextRun { len: 0, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index d64feb56a446b9885b0ed6bae62a1cea1ffe2f47..c1be6925428f302a25f008b3a34e6615ad3c0390 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -7,7 +7,7 @@ use crate::{ MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds, - WindowOptions, + WindowOptions, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -251,6 +251,7 @@ pub struct Window { pub(crate) platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, + text_system: Arc, pub(crate) rem_size: Pixels, pub(crate) viewport_size: Size, layout_engine: Option, @@ -337,6 +338,7 @@ impl Window { let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); let bounds = platform_window.bounds(); + let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); platform_window.on_request_frame(Box::new({ let mut cx = cx.to_async(); @@ -393,6 +395,7 @@ impl Window { platform_window, display_id, sprite_atlas, + text_system, rem_size: px(16.), viewport_size: content_size, layout_engine: Some(TaffyLayoutEngine::new()), @@ -530,6 +533,11 @@ impl<'a> WindowContext<'a> { self.window.focus_enabled = false; } + /// Accessor for the text system. + pub fn text_system(&self) -> &Arc { + &self.window.text_system + } + /// Dispatch the given action on the currently focused element. pub fn dispatch_action(&mut self, action: Box) { let focus_handle = self.focused(); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index c691d74754ddb4d83473d6de6d01ce911d427638..73d9bb27f539bce4ec25ea4ff2e04753bd11e96a 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -4,8 +4,8 @@ use gpui::{ ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent, - Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, - UnderlineStyle, WeakView, WhiteSpace, WindowContext, + Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, + UnderlineStyle, WeakView, WhiteSpace, WindowContext, WindowTextSystem, }; use itertools::Itertools; use language::CursorShape; @@ -185,7 +185,7 @@ impl TerminalElement { grid: &Vec, text_style: &TextStyle, // terminal_theme: &TerminalStyle, - text_system: &TextSystem, + text_system: &WindowTextSystem, hyperlink: Option<(HighlightStyle, &RangeInclusive)>, cx: &WindowContext<'_>, ) -> (Vec, Vec) { From 1f6bd6760fec123000321096819d211d8bd4bed4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 2 Feb 2024 10:21:45 -0700 Subject: [PATCH 023/101] Fix main (#7293) Release Notes: - N/A --- crates/collab_ui/src/collab_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 6730203ecaa7daaf47b22c87c992aff9750cc92f..9460be3e954aa6fef86b5ca9bea6b1164d92ec68 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -78,7 +78,7 @@ pub fn init(cx: &mut AppContext) { if let Some(channel_id) = channel_id { let workspace = cx.view().clone(); cx.window_context().defer(move |cx| { - ChannelView::open(channel_id, workspace, cx).detach_and_log_err(cx) + ChannelView::open(channel_id, None, workspace, cx).detach_and_log_err(cx) }); } }); From 115f0672fb4c67675e46d1fd99d4e4489c3672de Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 2 Feb 2024 13:13:35 -0500 Subject: [PATCH 024/101] gpui: Add support for observing window appearance (#7294) This PR adds support to GPUI for observing when the appearance of a window changes. Based on the initial work done in https://github.com/zed-industries/zed/pull/6881. Release Notes: - N/A --- crates/gpui/src/platform/test/window.rs | 6 ++-- crates/gpui/src/window.rs | 45 +++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index b310084fc490de79b41d8b4284b46332a1b12b8e..d5d62e0155513bcdf57e2a77905e623b7007cd73 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -171,7 +171,7 @@ impl PlatformWindow for TestWindow { } fn appearance(&self) -> WindowAppearance { - unimplemented!() + WindowAppearance::Light } fn display(&self) -> std::rc::Rc { @@ -276,9 +276,7 @@ impl PlatformWindow for TestWindow { unimplemented!() } - fn on_appearance_changed(&self, _callback: Box) { - unimplemented!() - } + fn on_appearance_changed(&self, _callback: Box) {} fn is_topmost_for_position(&self, _position: crate::Point) -> bool { unimplemented!() diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index c1be6925428f302a25f008b3a34e6615ad3c0390..fbea1ba8dd4948d42183cf8110bc02f0e4abaf42 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -6,8 +6,8 @@ use crate::{ KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, - Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds, - WindowOptions, WindowTextSystem, + Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, + WindowBounds, WindowOptions, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -269,6 +269,8 @@ pub struct Window { scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, + appearance: WindowAppearance, + appearance_observers: SubscriberSet<(), AnyObserver>, active: bool, pub(crate) dirty: bool, pub(crate) refreshing: bool, @@ -338,6 +340,7 @@ impl Window { let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); let bounds = platform_window.bounds(); + let appearance = platform_window.appearance(); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); platform_window.on_request_frame(Box::new({ @@ -364,6 +367,14 @@ impl Window { .log_err(); } })); + platform_window.on_appearance_changed(Box::new({ + let mut cx = cx.to_async(); + move || { + handle + .update(&mut cx, |_, cx| cx.appearance_changed()) + .log_err(); + } + })); platform_window.on_active_status_change(Box::new({ let mut cx = cx.to_async(); move |active| { @@ -413,6 +424,8 @@ impl Window { scale_factor, bounds, bounds_observers: SubscriberSet::new(), + appearance, + appearance_observers: SubscriberSet::new(), active: false, dirty: false, refreshing: false, @@ -742,6 +755,20 @@ impl<'a> WindowContext<'a> { self.window.bounds } + fn appearance_changed(&mut self) { + self.window.appearance = self.window.platform_window.appearance(); + + self.window + .appearance_observers + .clone() + .retain(&(), |callback| callback(self)); + } + + /// Returns the appearance of the current window. + pub fn appearance(&self) -> WindowAppearance { + self.window.appearance + } + /// Returns the size of the drawable area within the window. pub fn viewport_size(&self) -> Size { self.window.viewport_size @@ -2066,6 +2093,20 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Registers a callback to be invoked when the window appearance changes. + pub fn observe_window_appearance( + &mut self, + mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let (subscription, activate) = self.window.appearance_observers.insert( + (), + Box::new(move |cx| view.update(cx, |view, cx| callback(view, cx)).is_ok()), + ); + activate(); + subscription + } + /// Register a listener to be called when the given focus handle receives focus. /// Returns a subscription and persists until the subscription is dropped. pub fn on_focus( From 2d58226a9b90bef9bc46c0a8e6d32bc34418f5bb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 2 Feb 2024 20:47:45 +0200 Subject: [PATCH 025/101] Avoid logging errors about missing themes dir (#7290) --- crates/zed/src/main.rs | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 1666b23f07a76ca7107c9bf09553354dbe1fffe1..2f5db82dd6b87ac76125e34dfa8a4d91318458b7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -892,27 +892,37 @@ fn load_user_themes_in_background(fs: Arc, cx: &mut AppContext) { if let Some(theme_registry) = cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err() { - if let Some(()) = theme_registry - .load_user_themes(&paths::THEMES_DIR.clone(), fs) + let themes_dir = paths::THEMES_DIR.as_ref(); + match fs + .metadata(themes_dir) .await - .log_err() + .ok() + .flatten() + .map(|m| m.is_dir) { - cx.update(|cx| { - let mut theme_settings = ThemeSettings::get_global(cx).clone(); - - if let Some(requested_theme) = theme_settings.requested_theme.clone() { - if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx) - { - ThemeSettings::override_global(theme_settings, cx); - } - } - }) - .log_err(); + Some(is_dir) => { + anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory") + } + None => { + fs.create_dir(themes_dir).await.with_context(|| { + format!("Failed to create themes dir at path {themes_dir:?}") + })?; + } } + theme_registry.load_user_themes(themes_dir, fs).await?; + cx.update(|cx| { + let mut theme_settings = ThemeSettings::get_global(cx).clone(); + if let Some(requested_theme) = theme_settings.requested_theme.clone() { + if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx) { + ThemeSettings::override_global(theme_settings, cx); + } + } + })?; } + anyhow::Ok(()) } }) - .detach(); + .detach_and_log_err(cx); } /// Spawns a background task to watch the themes directory for changes. From f2ba969d5b409d3ed5637ae36b49ed1f6a542d3e Mon Sep 17 00:00:00 2001 From: tomholford <16504501+tomholford@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:52:28 -0800 Subject: [PATCH 026/101] Dismiss update notification when viewing releases notes (#7297) After updating zed, a notification is shown in the bottom right with the new version number, a link to the release notes, and an 'x' to dismiss the dialog. Before this PR, clicking the link to the release notes would not dismiss the modal. So, a user returning to the IDE after viewing the notes in the browser would still see the notification. With this change, clicking 'View release notes' also dismisses the notification. Co-authored-by: tomholford Release Notes: - Made update notification to dismiss when viewing releases notes --- crates/auto_update/src/update_notification.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index eece0c105a1ecaa4d85a533a59fed01c01368681..1dbee718061385dd057e040c201bd83d04582dcf 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -40,10 +40,11 @@ impl Render for UpdateNotification { .id("notes") .child(Label::new("View the release notes")) .cursor_pointer() - .on_click(|_, cx| { + .on_click(cx.listener(|this, _, cx| { crate::view_release_notes(&Default::default(), cx); - }), - ) + this.dismiss(&menu::Cancel, cx) + })), + ); } } From 15edc46827a08f4befeaecf0a7f6be692b8391a4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 2 Feb 2024 16:42:46 -0700 Subject: [PATCH 027/101] Maintain smooth frame rates when ProMotion and direct mode are enabled (#7305) This is achieved by starting a `CADisplayLink` that will invoke the `on_request_frame` callback at the refresh interval of the display. We only actually draw frames when the window was dirty, or for 2 extra seconds after the last input event to ensure ProMotion doesn't downclock the refresh rate when the user is actively interacting with the window. Release Notes: - Improved performance when using a ProMotion display with fast key repeat rates. --------- Co-authored-by: Nathan Sobo --- crates/gpui/src/app.rs | 13 ++----- crates/gpui/src/platform.rs | 1 - crates/gpui/src/platform/mac/window.rs | 20 ++++++----- crates/gpui/src/platform/test/window.rs | 2 -- crates/gpui/src/window.rs | 47 ++++++++++++++++++------- 5 files changed, 48 insertions(+), 35 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9f924450458bb328c13183116ba8f4b70445aa24..56d66f9b0b6cb570bb4247adeceacd97a3fae6c9 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -652,27 +652,20 @@ impl AppContext { } } } else { - for window in self.windows.values() { - if let Some(window) = window.as_ref() { - if window.dirty { - window.platform_window.invalidate(); - } - } - } - #[cfg(any(test, feature = "test-support"))] for window in self .windows .values() .filter_map(|window| { let window = window.as_ref()?; - (window.dirty || window.focus_invalidated).then_some(window.handle) + (window.dirty.get() || window.focus_invalidated).then_some(window.handle) }) .collect::>() { self.update_window(window, |_, cx| cx.draw()).unwrap(); } + #[allow(clippy::collapsible_else_if)] if self.pending_effects.is_empty() { break; } @@ -749,7 +742,7 @@ impl AppContext { fn apply_refresh_effect(&mut self) { for window in self.windows.values_mut() { if let Some(window) = window.as_mut() { - window.dirty = true; + window.dirty.set(true); } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index df886fc4d6ad315c91984914c4c7f85bee059d63..62ac543319417207a454f2fa31f987c2289f4ad3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -175,7 +175,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn on_close(&self, callback: Box); fn on_appearance_changed(&self, callback: Box); fn is_topmost_for_position(&self, position: Point) -> bool; - fn invalidate(&self); fn draw(&self, scene: &Scene); fn sprite_atlas(&self) -> Arc; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index ce25c4d6eb57e3709243763c5a360cd2ea2c1c7e..0e12dc4af00e312890259e0acb807c3a9004aa8d 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -16,8 +16,8 @@ use cocoa::{ }, base::{id, nil}, foundation::{ - NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect, - NSSize, NSString, NSUInteger, + NSArray, NSAutoreleasePool, NSDefaultRunLoopMode, NSDictionary, NSFastEnumeration, + NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger, }, }; use core_graphics::display::CGRect; @@ -321,6 +321,7 @@ struct MacWindowState { executor: ForegroundExecutor, native_window: id, native_view: NonNull, + display_link: id, renderer: MetalRenderer, kind: WindowKind, request_frame_callback: Option>, @@ -522,6 +523,10 @@ impl MacWindow { let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); + let display_link: id = msg_send![class!(CADisplayLink), displayLinkWithTarget: native_view selector: sel!(displayLayer:)]; + let main_run_loop: id = msg_send![class!(NSRunLoop), mainRunLoop]; + let _: () = + msg_send![display_link, addToRunLoop: main_run_loop forMode: NSDefaultRunLoopMode]; assert!(!native_view.is_null()); let window = Self(Arc::new(Mutex::new(MacWindowState { @@ -529,6 +534,7 @@ impl MacWindow { executor, native_window, native_view: NonNull::new_unchecked(native_view as *mut _), + display_link, renderer: MetalRenderer::new(true), kind: options.kind, request_frame_callback: None, @@ -687,6 +693,9 @@ impl Drop for MacWindow { fn drop(&mut self) { let this = self.0.lock(); let window = this.native_window; + unsafe { + let _: () = msg_send![this.display_link, invalidate]; + } this.executor .spawn(async move { unsafe { @@ -1000,13 +1009,6 @@ impl PlatformWindow for MacWindow { } } - fn invalidate(&self) { - let this = self.0.lock(); - unsafe { - let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES]; - } - } - fn draw(&self, scene: &crate::Scene) { let mut this = self.0.lock(); this.renderer.draw(scene); diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index d5d62e0155513bcdf57e2a77905e623b7007cd73..d56851c8b9909af2ffc1fe73e8cac8047458f8fc 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -282,8 +282,6 @@ impl PlatformWindow for TestWindow { unimplemented!() } - fn invalidate(&self) {} - fn draw(&self, _scene: &crate::Scene) {} fn sprite_atlas(&self) -> sync::Arc { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index fbea1ba8dd4948d42183cf8110bc02f0e4abaf42..afc2235fe6425cc8d1db14cd0c7901978a5fd0bf 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -22,7 +22,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - cell::RefCell, + cell::{Cell, RefCell}, collections::hash_map::Entry, fmt::{Debug, Display}, future::Future, @@ -34,7 +34,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::Duration, + time::{Duration, Instant}, }; use util::{measure, ResultExt}; @@ -272,7 +272,8 @@ pub struct Window { appearance: WindowAppearance, appearance_observers: SubscriberSet<(), AnyObserver>, active: bool, - pub(crate) dirty: bool, + pub(crate) dirty: Rc>, + pub(crate) last_input_timestamp: Rc>, pub(crate) refreshing: bool, pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, @@ -342,13 +343,28 @@ impl Window { let bounds = platform_window.bounds(); let appearance = platform_window.appearance(); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); + let dirty = Rc::new(Cell::new(false)); + let last_input_timestamp = Rc::new(Cell::new(Instant::now())); platform_window.on_request_frame(Box::new({ let mut cx = cx.to_async(); + let dirty = dirty.clone(); + let last_input_timestamp = last_input_timestamp.clone(); move || { - measure("frame duration", || { - handle.update(&mut cx, |_, cx| cx.draw()).log_err(); - }) + if dirty.get() { + measure("frame duration", || { + handle + .update(&mut cx, |_, cx| { + cx.draw(); + cx.present(); + }) + .log_err(); + }) + } else if last_input_timestamp.get().elapsed() < Duration::from_secs(2) { + // Keep presenting the current scene for 2 extra seconds since the + // last input to prevent the display from underclocking the refresh rate. + handle.update(&mut cx, |_, cx| cx.present()).log_err(); + } } })); platform_window.on_resize(Box::new({ @@ -427,7 +443,8 @@ impl Window { appearance, appearance_observers: SubscriberSet::new(), active: false, - dirty: false, + dirty, + last_input_timestamp, refreshing: false, drawing: false, activation_observers: SubscriberSet::new(), @@ -488,7 +505,7 @@ impl<'a> WindowContext<'a> { pub fn refresh(&mut self) { if !self.window.drawing { self.window.refreshing = true; - self.window.dirty = true; + self.window.dirty.set(true); } } @@ -962,9 +979,10 @@ impl<'a> WindowContext<'a> { &self.window.next_frame.z_index_stack } - /// Draw pixels to the display for this window based on the contents of its scene. + /// Produces a new frame and assigns it to `rendered_frame`. To actually show + /// the contents of the new [Scene], use [present]. pub(crate) fn draw(&mut self) { - self.window.dirty = false; + self.window.dirty.set(false); self.window.drawing = true; #[cfg(any(test, feature = "test-support"))] @@ -1105,16 +1123,19 @@ impl<'a> WindowContext<'a> { .clone() .retain(&(), |listener| listener(&event, self)); } + self.window.refreshing = false; + self.window.drawing = false; + } + fn present(&self) { self.window .platform_window .draw(&self.window.rendered_frame.scene); - self.window.refreshing = false; - self.window.drawing = false; } /// Dispatch a mouse or keyboard event on the window. pub fn dispatch_event(&mut self, event: PlatformInput) -> bool { + self.window.last_input_timestamp.set(Instant::now()); // Handlers may set this to false by calling `stop_propagation`. self.app.propagate_event = true; // Handlers may set this to true by calling `prevent_default`. @@ -2058,7 +2079,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } if !self.window.drawing { - self.window_cx.window.dirty = true; + self.window_cx.window.dirty.set(true); self.window_cx.app.push_effect(Effect::Notify { emitter: self.view.model.entity_id, }); From 430f5d5d535f1a65a98c9839d09388551709e430 Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Sat, 3 Feb 2024 07:44:47 +0800 Subject: [PATCH 028/101] vim: Convert from visual mode to normal mode with a single click (#6985) Release Notes: - Fixed #4319 Here is a demo after the fix: https://github.com/zed-industries/zed/assets/22256154/a690f146-73c9-4b0e-8527-e4faf690cca2 Actually I'm not really sure should I submit my idea to discussion, since it's not a large change, and it's something like a bug fix. So I directly create a pr here. --------- Co-authored-by: Conrad Irwin --- crates/vim/src/vim.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index d35a2b9a6faf1b01c1347998f4ef2147d074ca7f..41ae6c4f4c8527b226b9b0a89968746f79b0467d 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -204,7 +204,8 @@ impl Vim { let editor = editor.read(cx); if editor.leader_peer_id().is_none() { let newest = editor.selections.newest::(cx); - local_selections_changed(newest, cx); + let is_multicursor = editor.selections.count() > 1; + local_selections_changed(newest, is_multicursor, cx); } } EditorEvent::InputIgnored { text } => { @@ -626,13 +627,24 @@ impl Settings for VimModeSetting { } } -fn local_selections_changed(newest: Selection, cx: &mut WindowContext) { +fn local_selections_changed( + newest: Selection, + is_multicursor: bool, + cx: &mut WindowContext, +) { Vim::update(cx, |vim, cx| { - if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() { - if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) { - vim.switch_mode(Mode::VisualBlock, false, cx); - } else { - vim.switch_mode(Mode::Visual, false, cx) + if vim.enabled { + if vim.state().mode == Mode::Normal && !newest.is_empty() { + if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) { + vim.switch_mode(Mode::VisualBlock, false, cx); + } else { + vim.switch_mode(Mode::Visual, false, cx) + } + } else if newest.is_empty() + && !is_multicursor + && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&vim.state().mode) + { + vim.switch_mode(Mode::Normal, true, cx) } } }) From e1efa7298e1bbf48fdbc8b9149b70943040b2b85 Mon Sep 17 00:00:00 2001 From: Tom Planche <58936594+TomPlanche@users.noreply.github.com> Date: Sat, 3 Feb 2024 01:15:31 +0100 Subject: [PATCH 029/101] Update configuring_zed__key_bindings.md (#7310) Added explanations for binding `null` to a keyboard binding. Release Notes: - N/A --- docs/src/configuring_zed__key_bindings.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/src/configuring_zed__key_bindings.md b/docs/src/configuring_zed__key_bindings.md index 27728e97616788b6e588e4f4226f102c005efcca..1e493691d03f04362bd8bd8ac67e444bf33b4f48 100644 --- a/docs/src/configuring_zed__key_bindings.md +++ b/docs/src/configuring_zed__key_bindings.md @@ -33,6 +33,25 @@ You can see more examples in Zed's [`default.json`](https://zed.dev/ref/default. _There are some key bindings that can't be overridden; we are working on an issue surrounding this._ +## Special Keyboard Layouts +Some people have unique and custom keyboard layouts. + +For example, [@TomPlanche](https://github.com/TomPlanche) having a [French keyboard](https%3A%2F%2Fcdn.shopify.com%2Fs%2Ffiles%2F1%2F0810%2F3669%2Ffiles%2Ffrench-azerty-mac-keyboard-layout-2021-keyshorts.png&f=1&nofb=1&ipt=f53a06c5e60a20b621082410aa699c8cceff269a11ff90b3b5a35c6124dbf827&ipo=images), had to type `Shift-Alt-(` in order to have a simple `[` so he made a simple layout with those 'rules': +`ù -> [`, `backtick -> ]`, `Alt-[ (where [ is the old ù) -> {`, `Alt-] -> }`. +But, it was impossible to take into account the `{` and `}` when he was typing so now, in order to ignore a binding, he can add `null` to the binding: +```json +[ + { + "context": "Editor", + "bindings": { + "alt-[": null, + "alt-]": null, + } + } +] +``` + + ## All key bindings ### Global From f09da1a1c8f94d01c358817f00d9e49bb88dc3e8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 2 Feb 2024 19:24:36 -0700 Subject: [PATCH 030/101] vim hml (#7298) - Add a setting for `vertical_scroll_offset` - Fix H/M/L in vim with scrolloff Release Notes: - Added a settings for `vertical_scroll_offset` - vim: Fix H/M/L with various values of vertical_scroll_offset --------- Co-authored-by: Vbhavsar Co-authored-by: fdionisi --- assets/settings/default.json | 2 ++ crates/editor/src/display_map.rs | 3 +- crates/editor/src/editor.rs | 7 ++-- crates/editor/src/editor_settings.rs | 6 ++++ crates/editor/src/movement.rs | 7 ++-- crates/editor/src/scroll.rs | 12 +++---- crates/vim/src/motion.rs | 32 +++++++++++++++---- crates/vim/src/normal/scroll.rs | 10 +++--- .../src/test/neovim_backed_test_context.rs | 6 ++-- 9 files changed, 57 insertions(+), 28 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 4345be1b50083304f3f508ee3e3402df624816d3..4a81cfbe6928e4f99295af853618a215b5162904 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -133,6 +133,8 @@ // Whether to show diagnostic indicators in the scrollbar. "diagnostics": true }, + // The number of lines to keep above/below the cursor when scrolling. + "vertical_scroll_margin": 3, "relative_line_numbers": false, // When to populate a new search's query based on the text under the cursor. // This setting can take the following three values: diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 787fb4590fa6965a44a2b84644f2d6c4206be4d6..4ad43ef0cf555253ab53898a33868809587754ca 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -586,8 +586,9 @@ impl DisplaySnapshot { text_system, editor_style, rem_size, - anchor: _, + scroll_anchor: _, visible_rows: _, + vertical_scroll_margin: _, }: &TextLayoutDetails, ) -> Arc { let mut runs = Vec::new(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 21092ca085c9e68451a1af616beb1ad51b2d3c3b..40f385be9b90abe0b0793a0d2457053b96940886 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1430,7 +1430,7 @@ impl Editor { buffer: buffer.clone(), display_map: display_map.clone(), selections, - scroll_manager: ScrollManager::new(), + scroll_manager: ScrollManager::new(cx), columnar_selection_tail: None, add_selections_state: None, select_next_state: None, @@ -3086,8 +3086,9 @@ impl Editor { text_system: cx.text_system().clone(), editor_style: self.style.clone().unwrap(), rem_size: cx.rem_size(), - anchor: self.scroll_manager.anchor().anchor, + scroll_anchor: self.scroll_manager.anchor(), visible_rows: self.visible_line_count(), + vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin, } } @@ -8762,6 +8763,8 @@ impl Editor { )), cx, ); + self.scroll_manager.vertical_scroll_margin = + EditorSettings::get_global(cx).vertical_scroll_margin; cx.notify(); } diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index e9ecbe13d404e4c5ca3bbb3f3e831a795ddd2824..ddbcd6e4a009c31119ca9e1779ad0ce2614a1c38 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -11,6 +11,7 @@ pub struct EditorSettings { pub completion_documentation_secondary_query_debounce: u64, pub use_on_type_format: bool, pub scrollbar: Scrollbar, + pub vertical_scroll_margin: f32, pub relative_line_numbers: bool, pub seed_search_query_from_cursor: SeedQuerySetting, pub redact_private_values: bool, @@ -87,6 +88,11 @@ pub struct EditorSettingsContent { pub use_on_type_format: Option, /// Scrollbar related settings pub scrollbar: Option, + + /// The number of lines to keep above/below the cursor when auto-scrolling. + /// + /// Default: 3. + pub vertical_scroll_margin: Option, /// Whether the line numbers on editors gutter are relative or not. /// /// Default: false diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 5e37cb8be263419c3bf12ad54137964731615ffd..4aacc7e4e7e3500926e73172022aff8fa116cc6d 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -2,14 +2,12 @@ //! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate. use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; -use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint}; +use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint}; use gpui::{px, Pixels, WindowTextSystem}; use language::Point; use std::{ops::Range, sync::Arc}; -use multi_buffer::Anchor; - /// Defines search strategy for items in `movement` module. /// `FindRange::SingeLine` only looks for a match on a single line at a time, whereas /// `FindRange::MultiLine` keeps going until the end of a string. @@ -25,8 +23,9 @@ pub struct TextLayoutDetails { pub(crate) text_system: Arc, pub(crate) editor_style: EditorStyle, pub(crate) rem_size: Pixels, - pub anchor: Anchor, + pub scroll_anchor: ScrollAnchor, pub visible_rows: Option, + pub vertical_scroll_margin: f32, } /// Returns a column to the left of the current point, wrapping diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 46af2da37192ed47727060507dbc28cdf98d7e6d..c354f98150afd046bf81cb84aa2618184242c9e4 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -6,13 +6,14 @@ use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, persistence::DB, - Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, InlayHintRefreshReason, + Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, EditorSettings, InlayHintRefreshReason, MultiBufferSnapshot, ToPoint, }; pub use autoscroll::{Autoscroll, AutoscrollStrategy}; -use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext}; +use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext, WindowContext}; use language::{Bias, Point}; pub use scroll_amount::ScrollAmount; +use settings::Settings; use std::{ cmp::Ordering, time::{Duration, Instant}, @@ -21,7 +22,6 @@ use util::ResultExt; use workspace::{ItemId, WorkspaceId}; pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28); -pub const VERTICAL_SCROLL_MARGIN: f32 = 3.; const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); #[derive(Default)] @@ -128,7 +128,7 @@ impl OngoingScroll { } pub struct ScrollManager { - vertical_scroll_margin: f32, + pub(crate) vertical_scroll_margin: f32, anchor: ScrollAnchor, ongoing: OngoingScroll, autoscroll_request: Option<(Autoscroll, bool)>, @@ -140,9 +140,9 @@ pub struct ScrollManager { } impl ScrollManager { - pub fn new() -> Self { + pub fn new(cx: &mut WindowContext) -> Self { ScrollManager { - vertical_scroll_margin: VERTICAL_SCROLL_MARGIN, + vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin, anchor: ScrollAnchor::new(), ongoing: OngoingScroll::new(), autoscroll_request: None, diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 944db88a7ceb9c142605908fc9b0ab167e2033e8..67abd5836c9ac35c9dc52c787f4fbfc11bb318e2 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1044,9 +1044,17 @@ fn window_top( map: &DisplaySnapshot, point: DisplayPoint, text_layout_details: &TextLayoutDetails, - times: usize, + mut times: usize, ) -> (DisplayPoint, SelectionGoal) { - let first_visible_line = text_layout_details.anchor.to_display_point(map); + let first_visible_line = text_layout_details + .scroll_anchor + .anchor + .to_display_point(map); + + if first_visible_line.row() != 0 && text_layout_details.vertical_scroll_margin as usize > times + { + times = text_layout_details.vertical_scroll_margin.ceil() as usize; + } if let Some(visible_rows) = text_layout_details.visible_rows { let bottom_row = first_visible_line.row() + visible_rows as u32; @@ -1070,7 +1078,10 @@ fn window_middle( text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { if let Some(visible_rows) = text_layout_details.visible_rows { - let first_visible_line = text_layout_details.anchor.to_display_point(map); + let first_visible_line = text_layout_details + .scroll_anchor + .anchor + .to_display_point(map); let max_rows = (visible_rows as u32).min(map.max_buffer_row()); let new_row = first_visible_line.row() + (max_rows.div_euclid(2)); let new_col = point.column().min(map.line_len(new_row)); @@ -1085,11 +1096,20 @@ fn window_bottom( map: &DisplaySnapshot, point: DisplayPoint, text_layout_details: &TextLayoutDetails, - times: usize, + mut times: usize, ) -> (DisplayPoint, SelectionGoal) { if let Some(visible_rows) = text_layout_details.visible_rows { - let first_visible_line = text_layout_details.anchor.to_display_point(map); - let bottom_row = first_visible_line.row() + (visible_rows) as u32; + let first_visible_line = text_layout_details + .scroll_anchor + .anchor + .to_display_point(map); + let bottom_row = first_visible_line.row() + + (visible_rows + text_layout_details.scroll_anchor.offset.y - 1.).floor() as u32; + if bottom_row < map.max_buffer_row() + && text_layout_details.vertical_scroll_margin as usize > times + { + times = text_layout_details.vertical_scroll_margin.ceil() as usize; + } let bottom_row_capped = bottom_row.min(map.max_buffer_row()); let new_row = if bottom_row_capped.saturating_sub(times as u32) < first_visible_line.row() { first_visible_line.row() diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs index 8c061582315a907c9a3dbf46b0a8ed0ce9cf3e1c..0bccf24977aaefc6508b9b55fb3f5e09f9abca0f 100644 --- a/crates/vim/src/normal/scroll.rs +++ b/crates/vim/src/normal/scroll.rs @@ -1,11 +1,10 @@ use crate::Vim; use editor::{ - display_map::ToDisplayPoint, - scroll::{ScrollAmount, VERTICAL_SCROLL_MARGIN}, - DisplayPoint, Editor, + display_map::ToDisplayPoint, scroll::ScrollAmount, DisplayPoint, Editor, EditorSettings, }; use gpui::{actions, ViewContext}; use language::Bias; +use settings::Settings; use workspace::Workspace; actions!( @@ -77,6 +76,7 @@ fn scroll_editor( }; let top_anchor = editor.scroll_manager.anchor().anchor; + let vertical_scroll_margin = EditorSettings::get_global(cx).vertical_scroll_margin; editor.change_selections(None, cx, |s| { s.move_with(|map, selection| { @@ -88,8 +88,8 @@ fn scroll_editor( let new_row = top.row() + selection.head().row() - old_top.row(); head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left) } - let min_row = top.row() + VERTICAL_SCROLL_MARGIN as u32; - let max_row = top.row() + visible_rows - VERTICAL_SCROLL_MARGIN as u32 - 1; + let min_row = top.row() + vertical_scroll_margin as u32; + let max_row = top.row() + visible_rows - vertical_scroll_margin as u32 - 1; let new_head = if head.row() < min_row { map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left) diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index 977d6aa7c6b1b3c5c2f65e8e4e9b9ded17eb3a6b..0c12d64f58981fc1eb73fd55e3afcdf55ba71b16 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -1,4 +1,4 @@ -use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle}; +use editor::test::editor_test_context::ContextHandle; use gpui::{px, size, Context}; use indoc::indoc; use settings::SettingsStore; @@ -155,9 +155,7 @@ impl NeovimBackedTestContext { pub async fn set_scroll_height(&mut self, rows: u32) { // match Zed's scrolling behavior - self.neovim - .set_option(&format!("scrolloff={}", VERTICAL_SCROLL_MARGIN)) - .await; + self.neovim.set_option(&format!("scrolloff={}", 3)).await; // +2 to account for the vim command UI at the bottom. self.neovim.set_option(&format!("lines={}", rows + 2)).await; let (line_height, visible_line_count) = self.editor(|editor, cx| { From fcbc220408ab13e04910f33fe0a20addd8737542 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 2 Feb 2024 19:24:49 -0700 Subject: [PATCH 031/101] Don't log errors on main (#7289) Release Notes: - N/A --- crates/client/src/telemetry.rs | 19 +++++++++++++------ script/zed-local | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 331683d5d1e0a1b5bf848a50a2138a4209087cef..9bdf038b261670faa7f35fda08baa5066a515667 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -145,11 +145,14 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1); #[cfg(not(debug_assertions))] const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5); -static ZED_CLIENT_CHECKSUM_SEED: Lazy> = Lazy::new(|| { +static ZED_CLIENT_CHECKSUM_SEED: Lazy>> = Lazy::new(|| { option_env!("ZED_CLIENT_CHECKSUM_SEED") - .unwrap_or("development-checksum-seed") - .as_bytes() - .into() + .map(|s| s.as_bytes().into()) + .or_else(|| { + env::var("ZED_CLIENT_CHECKSUM_SEED") + .ok() + .map(|s| s.as_bytes().into()) + }) }); impl Telemetry { @@ -510,6 +513,10 @@ impl Telemetry { return; } + let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else { + return; + }; + let this = self.clone(); self.executor .spawn( @@ -551,9 +558,9 @@ impl Telemetry { } let mut summer = Sha256::new(); - summer.update(&*ZED_CLIENT_CHECKSUM_SEED); + summer.update(checksum_seed); summer.update(&json_bytes); - summer.update(&*ZED_CLIENT_CHECKSUM_SEED); + summer.update(checksum_seed); let mut checksum = String::new(); for byte in summer.finalize().as_slice() { use std::fmt::Write; diff --git a/script/zed-local b/script/zed-local index 068235730d2f30a70219eedea63be22ec4a5271f..1b1852043b4f31837cfb2160e43cb7b24b144fdc 100755 --- a/script/zed-local +++ b/script/zed-local @@ -142,6 +142,7 @@ setTimeout(() => { ZED_RPC_URL: "http://localhost:8080/rpc", ZED_ADMIN_API_TOKEN: "secret", ZED_WINDOW_SIZE: size, + ZED_CLIENT_CHECKSUM_SEED: "development-checksum-seed", PATH: process.env.PATH, RUST_LOG: process.env.RUST_LOG || "info", }, From 583273b6ee67a477a5a4e834d53a3eaa0c773c23 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 2 Feb 2024 20:39:51 -0700 Subject: [PATCH 032/101] Bump alacritty to fix some panics (#7313) Release Notes: - Fixed some panics in the Terminal ([#6835](https://github.com/zed-industries/zed/issues/6835)). --- Cargo.lock | 5 ++--- crates/terminal/Cargo.toml | 3 ++- crates/terminal/src/terminal.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6500eacea55f87a2bb89c9541011d93ae8b89862..2604753f71f9a7498ddd8d81e86316e3767a82c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,9 +103,8 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35229555d7cc7e83392dfc27c96bec560b1076d756184893296cd60125f4a264" +version = "0.20.1-dev" +source = "git+https://github.com/alacritty/alacritty?rev=2d2b894c3b869fadc78fce9d72cb5c8d2b764cac#2d2b894c3b869fadc78fce9d72cb5c8d2b764cac" dependencies = [ "base64 0.21.4", "bitflags 2.4.1", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index baad46b2617ed8b7dbc809fb500141998e39b191..670510356d577c386da3aabb281dded17a010e33 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -11,7 +11,8 @@ doctest = false [dependencies] -alacritty_terminal = "0.21" +# needed for "a few weeks" until alacritty 0.13.2 is out +alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "2d2b894c3b869fadc78fce9d72cb5c8d2b764cac" } anyhow.workspace = true db = { path = "../db" } dirs = "4.0.0" diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index be8dc9ed01144dc41d7f40d46322bc19e654fcdf..5bc8d00e52f8799bae08c17acfa84737cc507163 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -364,7 +364,7 @@ impl TerminalBuilder { pty, pty_options.hold, false, - ); + )?; //Kick things off let pty_tx = event_loop.channel(); From 1a82470897c141ef320e1ec0eb60f55d799141bb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 2 Feb 2024 22:05:28 -0700 Subject: [PATCH 033/101] Open URLs with cmd-click (#7312) Release Notes: - Added ability to cmd-click on URLs in all buffers --------- Co-authored-by: fdionisi --- Cargo.lock | 10 + crates/editor/Cargo.toml | 5 +- crates/editor/src/display_map.rs | 4 +- crates/editor/src/display_map/inlay_map.rs | 2 +- crates/editor/src/editor.rs | 57 +- crates/editor/src/element.rs | 128 +-- ...ink_go_to_definition.rs => hover_links.rs} | 809 ++++++++---------- crates/editor/src/hover_popover.rs | 4 +- crates/editor/src/items.rs | 9 +- crates/editor/src/test/editor_test_context.rs | 30 +- crates/gpui/src/app/test_context.rs | 42 +- crates/gpui/src/platform/keystroke.rs | 30 + crates/gpui/src/platform/test/platform.rs | 6 +- 13 files changed, 543 insertions(+), 593 deletions(-) rename crates/editor/src/{link_go_to_definition.rs => hover_links.rs} (63%) diff --git a/Cargo.lock b/Cargo.lock index 2604753f71f9a7498ddd8d81e86316e3767a82c7..14b5882ff902f40edb82317de62444d97fa3587f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2416,6 +2416,7 @@ dependencies = [ "itertools 0.10.5", "language", "lazy_static", + "linkify", "log", "lsp", "multi_buffer", @@ -4134,6 +4135,15 @@ dependencies = [ "safemem", ] +[[package]] +name = "linkify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +dependencies = [ + "memchr", +] + [[package]] name = "linkme" version = "0.3.17" diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 3e6b4d709c1dd912d98b864740500b43c860772d..be8745e4c225bae875d42999ffe0e7b450ad985a 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -20,7 +20,7 @@ test-support = [ "util/test-support", "workspace/test-support", "tree-sitter-rust", - "tree-sitter-typescript" + "tree-sitter-typescript", ] [dependencies] @@ -33,13 +33,14 @@ convert_case = "0.6.0" copilot = { path = "../copilot" } db = { path = "../db" } futures.workspace = true -fuzzy = { path = "../fuzzy" } +fuzzy = { path = "../fuzzy" } git = { path = "../git" } gpui = { path = "../gpui" } indoc = "1.0.4" itertools = "0.10" language = { path = "../language" } lazy_static.workspace = true +linkify = "0.10.0" log.workspace = true lsp = { path = "../lsp" } multi_buffer = { path = "../multi_buffer" } diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4ad43ef0cf555253ab53898a33868809587754ca..23507eab3e4a5012626c54c178b4260d0a62b5cd 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -25,8 +25,8 @@ mod wrap_map; use crate::EditorStyle; use crate::{ - link_go_to_definition::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, - InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, + hover_links::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, InlayId, + MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; pub use block_map::{BlockMap, BlockPoint}; use collections::{BTreeMap, HashMap, HashSet}; diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index c0d5198ddd9734c5a22cde9ee22e9ca837c23ebc..f1f52b49275fe2e3946957443eba29cc9db2fa07 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1168,7 +1168,7 @@ mod tests { use super::*; use crate::{ display_map::{InlayHighlights, TextHighlights}, - link_go_to_definition::InlayHighlight, + hover_links::InlayHighlight, InlayId, MultiBuffer, }; use gpui::AppContext; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 40f385be9b90abe0b0793a0d2457053b96940886..9c84dd0fe9cad2059969aa7e82207f97f4eea2d2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22,9 +22,9 @@ mod inlay_hint_cache; mod debounced_delay; mod git; mod highlight_matching_bracket; +mod hover_links; mod hover_popover; pub mod items; -mod link_go_to_definition; mod mouse_context_menu; pub mod movement; mod persistence; @@ -77,7 +77,7 @@ use language::{ Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, }; -use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; +use hover_links::{HoverLink, HoveredLinkState, InlayHighlight}; use lsp::{DiagnosticSeverity, LanguageServerId}; use mouse_context_menu::MouseContextMenu; use movement::TextLayoutDetails; @@ -402,7 +402,7 @@ pub struct Editor { remote_id: Option, hover_state: HoverState, gutter_hovered: bool, - link_go_to_definition_state: LinkGoToDefinitionState, + hovered_link_state: Option, copilot_state: CopilotState, inlay_hint_cache: InlayHintCache, next_inlay_id: usize, @@ -1477,7 +1477,7 @@ impl Editor { leader_peer_id: None, remote_id: None, hover_state: Default::default(), - link_go_to_definition_state: Default::default(), + hovered_link_state: Default::default(), copilot_state: Default::default(), inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, @@ -7243,11 +7243,8 @@ impl Editor { cx.spawn(|editor, mut cx| async move { let definitions = definitions.await?; editor.update(&mut cx, |editor, cx| { - editor.navigate_to_definitions( - definitions - .into_iter() - .map(GoToDefinitionLink::Text) - .collect(), + editor.navigate_to_hover_links( + definitions.into_iter().map(HoverLink::Text).collect(), split, cx, ); @@ -7257,9 +7254,9 @@ impl Editor { .detach_and_log_err(cx); } - pub fn navigate_to_definitions( + pub fn navigate_to_hover_links( &mut self, - mut definitions: Vec, + mut definitions: Vec, split: bool, cx: &mut ViewContext, ) { @@ -7271,10 +7268,14 @@ impl Editor { if definitions.len() == 1 { let definition = definitions.pop().unwrap(); let target_task = match definition { - GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))), - GoToDefinitionLink::InlayHint(lsp_location, server_id) => { + HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))), + HoverLink::InlayHint(lsp_location, server_id) => { self.compute_target_location(lsp_location, server_id, cx) } + HoverLink::Url(url) => { + cx.open_url(&url); + Task::ready(Ok(None)) + } }; cx.spawn(|editor, mut cx| async move { let target = target_task.await.context("target resolution task")?; @@ -7325,29 +7326,27 @@ impl Editor { let title = definitions .iter() .find_map(|definition| match definition { - GoToDefinitionLink::Text(link) => { - link.origin.as_ref().map(|origin| { - let buffer = origin.buffer.read(cx); - format!( - "Definitions for {}", - buffer - .text_for_range(origin.range.clone()) - .collect::() - ) - }) - } - GoToDefinitionLink::InlayHint(_, _) => None, + HoverLink::Text(link) => link.origin.as_ref().map(|origin| { + let buffer = origin.buffer.read(cx); + format!( + "Definitions for {}", + buffer + .text_for_range(origin.range.clone()) + .collect::() + ) + }), + HoverLink::InlayHint(_, _) => None, + HoverLink::Url(_) => None, }) .unwrap_or("Definitions".to_string()); let location_tasks = definitions .into_iter() .map(|definition| match definition { - GoToDefinitionLink::Text(link) => { - Task::Ready(Some(Ok(Some(link.target)))) - } - GoToDefinitionLink::InlayHint(lsp_location, server_id) => { + HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))), + HoverLink::InlayHint(lsp_location, server_id) => { editor.compute_target_location(lsp_location, server_id, cx) } + HoverLink::Url(_) => Task::ready(Ok(None)), }) .collect::>(); (title, location_tasks) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7e546cc2725f2c02544f28f8697b55969370b36f..46c85ddd104b70d850ade75ea4a222b0f492ebcc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9,11 +9,6 @@ use crate::{ self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, items::BufferSearchHighlights, - link_go_to_definition::{ - go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition, - update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger, - LinkGoToDefinitionState, - }, mouse_context_menu, scroll::scroll_amount::ScrollAmount, CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, @@ -337,7 +332,14 @@ impl EditorElement { register_action(view, cx, Editor::display_cursor_names); } - fn register_key_listeners(&self, cx: &mut ElementContext) { + fn register_key_listeners( + &self, + cx: &mut ElementContext, + text_bounds: Bounds, + layout: &LayoutState, + ) { + let position_map = layout.position_map.clone(); + let stacking_order = cx.stacking_order().clone(); cx.on_key_event({ let editor = self.editor.clone(); move |event: &ModifiersChangedEvent, phase, cx| { @@ -345,46 +347,41 @@ impl EditorElement { return; } - if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) { - cx.stop_propagation(); - } + editor.update(cx, |editor, cx| { + Self::modifiers_changed( + editor, + event, + &position_map, + text_bounds, + &stacking_order, + cx, + ) + }) } }); } - pub(crate) fn modifiers_changed( + fn modifiers_changed( editor: &mut Editor, event: &ModifiersChangedEvent, + position_map: &PositionMap, + text_bounds: Bounds, + stacking_order: &StackingOrder, cx: &mut ViewContext, - ) -> bool { - let pending_selection = editor.has_pending_selection(); - - if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point { - if event.command && !pending_selection { - let point = point.clone(); - let snapshot = editor.snapshot(cx); - let kind = point.definition_kind(event.shift); - - show_link_definition(kind, editor, point, snapshot, cx); - return false; - } - } - + ) { + let mouse_position = cx.mouse_position(); + if !text_bounds.contains(&mouse_position) + || !cx.was_top_layer(&mouse_position, stacking_order) { - if editor.link_go_to_definition_state.symbol_range.is_some() - || !editor.link_go_to_definition_state.definitions.is_empty() - { - editor.link_go_to_definition_state.symbol_range.take(); - editor.link_go_to_definition_state.definitions.clear(); - cx.notify(); - } - - editor.link_go_to_definition_state.task = None; - - editor.clear_highlights::(cx); + return; } - false + editor.update_hovered_link( + position_map.point_for_position(text_bounds, mouse_position), + &position_map.snapshot, + event.modifiers, + cx, + ) } fn mouse_left_down( @@ -485,13 +482,7 @@ impl EditorElement { && cx.was_top_layer(&event.position, stacking_order) { let point = position_map.point_for_position(text_bounds, event.position); - let could_be_inlay = point.as_valid().is_none(); - let split = event.modifiers.alt; - if event.modifiers.shift || could_be_inlay { - go_to_fetched_type_definition(editor, point, split, cx); - } else { - go_to_fetched_definition(editor, point, split, cx); - } + editor.handle_click_hovered_link(point, event.modifiers, cx); cx.stop_propagation(); } else if end_selection { @@ -564,31 +555,14 @@ impl EditorElement { if text_hovered && was_top { let point_for_position = position_map.point_for_position(text_bounds, event.position); - match point_for_position.as_valid() { - Some(point) => { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(point)), - modifiers.command, - modifiers.shift, - cx, - ); - hover_at(editor, Some(point), cx); - Self::update_visible_cursor(editor, point, position_map, cx); - } - None => { - update_inlay_link_and_hover_points( - &position_map.snapshot, - point_for_position, - editor, - modifiers.command, - modifiers.shift, - cx, - ); - } + editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx); + + if let Some(point) = point_for_position.as_valid() { + hover_at(editor, Some(point), cx); + Self::update_visible_cursor(editor, point, position_map, cx); } } else { - update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx); + editor.hide_hovered_link(cx); hover_at(editor, None, cx); if gutter_hovered && was_top { cx.stop_propagation(); @@ -930,13 +904,13 @@ impl EditorElement { if self .editor .read(cx) - .link_go_to_definition_state - .definitions - .is_empty() + .hovered_link_state + .as_ref() + .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty()) { - cx.set_cursor_style(CursorStyle::IBeam); - } else { cx.set_cursor_style(CursorStyle::PointingHand); + } else { + cx.set_cursor_style(CursorStyle::IBeam); } } @@ -3105,9 +3079,9 @@ impl Element for EditorElement { let key_context = self.editor.read(cx).key_context(cx); cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { self.register_actions(cx); - self.register_key_listeners(cx); cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + self.register_key_listeners(cx, text_bounds, &layout); cx.handle_input( &focus_handle, ElementInputHandler::new(bounds, self.editor.clone()), @@ -3224,16 +3198,6 @@ pub struct PointForPosition { } impl PointForPosition { - #[cfg(test)] - pub fn valid(valid: DisplayPoint) -> Self { - Self { - previous_valid: valid, - next_valid: valid, - exact_unclipped: valid, - column_overshoot_after_line_end: 0, - } - } - pub fn as_valid(&self) -> Option { if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped { Some(self.previous_valid) diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/hover_links.rs similarity index 63% rename from crates/editor/src/link_go_to_definition.rs rename to crates/editor/src/hover_links.rs index c4da7fcd38729599e5782e34ca71020e62c5e1e1..955aafde24ea2fd330035a4afbbed8205f6dceac 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/hover_links.rs @@ -1,12 +1,11 @@ use crate::{ - display_map::DisplaySnapshot, element::PointForPosition, hover_popover::{self, InlayHover}, - Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, - SelectPhase, + Anchor, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, SelectPhase, }; -use gpui::{px, Task, ViewContext}; +use gpui::{px, AsyncWindowContext, Model, Modifiers, Task, ViewContext}; use language::{Bias, ToOffset}; +use linkify::{LinkFinder, LinkKind}; use lsp::LanguageServerId; use project::{ HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, @@ -16,12 +15,12 @@ use std::ops::Range; use theme::ActiveTheme as _; use util::TryFutureExt; -#[derive(Debug, Default)] -pub struct LinkGoToDefinitionState { - pub last_trigger_point: Option, +#[derive(Debug)] +pub struct HoveredLinkState { + pub last_trigger_point: TriggerPoint, + pub preferred_kind: LinkDefinitionKind, pub symbol_range: Option, - pub kind: Option, - pub definitions: Vec, + pub links: Vec, pub task: Option>>, } @@ -56,14 +55,9 @@ impl RangeInEditor { } } -#[derive(Debug)] -pub enum GoToDefinitionTrigger { - Text(DisplayPoint), - InlayHint(InlayHighlight, lsp::Location, LanguageServerId), -} - #[derive(Debug, Clone)] -pub enum GoToDefinitionLink { +pub enum HoverLink { + Url(String), Text(LocationLink), InlayHint(lsp::Location, LanguageServerId), } @@ -75,26 +69,13 @@ pub(crate) struct InlayHighlight { pub range: Range, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum TriggerPoint { Text(Anchor), InlayHint(InlayHighlight, lsp::Location, LanguageServerId), } impl TriggerPoint { - pub fn definition_kind(&self, shift: bool) -> LinkDefinitionKind { - match self { - TriggerPoint::Text(_) => { - if shift { - LinkDefinitionKind::Type - } else { - LinkDefinitionKind::Symbol - } - } - TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type, - } - } - fn anchor(&self) -> &Anchor { match self { TriggerPoint::Text(anchor) => anchor, @@ -103,69 +84,88 @@ impl TriggerPoint { } } -pub fn update_go_to_definition_link( - editor: &mut Editor, - origin: Option, - cmd_held: bool, - shift_held: bool, - cx: &mut ViewContext, -) { - let pending_nonempty_selection = editor.has_pending_nonempty_selection(); - - // Store new mouse point as an anchor - let snapshot = editor.snapshot(cx); - let trigger_point = match origin { - Some(GoToDefinitionTrigger::Text(p)) => { - Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before( - p.to_offset(&snapshot.display_snapshot, Bias::Left), - ))) - } - Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => { - Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id)) +impl Editor { + pub(crate) fn update_hovered_link( + &mut self, + point_for_position: PointForPosition, + snapshot: &EditorSnapshot, + modifiers: Modifiers, + cx: &mut ViewContext, + ) { + if !modifiers.command || self.has_pending_selection() { + self.hide_hovered_link(cx); + return; } - None => None, - }; - // If the new point is the same as the previously stored one, return early - if let (Some(a), Some(b)) = ( - &trigger_point, - &editor.link_go_to_definition_state.last_trigger_point, - ) { - match (a, b) { - (TriggerPoint::Text(anchor_a), TriggerPoint::Text(anchor_b)) => { - if anchor_a.cmp(anchor_b, &snapshot.buffer_snapshot).is_eq() { - return; - } + match point_for_position.as_valid() { + Some(point) => { + let trigger_point = TriggerPoint::Text( + snapshot + .buffer_snapshot + .anchor_before(point.to_offset(&snapshot.display_snapshot, Bias::Left)), + ); + + show_link_definition(modifiers.shift, self, trigger_point, snapshot, cx); } - (TriggerPoint::InlayHint(range_a, _, _), TriggerPoint::InlayHint(range_b, _, _)) => { - if range_a == range_b { - return; - } + None => { + update_inlay_link_and_hover_points( + &snapshot, + point_for_position, + self, + modifiers.command, + modifiers.shift, + cx, + ); } - _ => {} } } - editor.link_go_to_definition_state.last_trigger_point = trigger_point.clone(); - - if pending_nonempty_selection { - hide_link_definition(editor, cx); - return; + pub(crate) fn hide_hovered_link(&mut self, cx: &mut ViewContext) { + self.hovered_link_state.take(); + self.clear_highlights::(cx); } - if cmd_held { - if let Some(trigger_point) = trigger_point { - let kind = trigger_point.definition_kind(shift_held); - show_link_definition(kind, editor, trigger_point, snapshot, cx); - return; + pub(crate) fn handle_click_hovered_link( + &mut self, + point: PointForPosition, + modifiers: Modifiers, + cx: &mut ViewContext, + ) { + if let Some(hovered_link_state) = self.hovered_link_state.take() { + self.hide_hovered_link(cx); + if !hovered_link_state.links.is_empty() { + if !self.focus_handle.is_focused(cx) { + cx.focus(&self.focus_handle); + } + + self.navigate_to_hover_links(hovered_link_state.links, modifiers.alt, cx); + return; + } } - } - hide_link_definition(editor, cx); + // We don't have the correct kind of link cached, set the selection on + // click and immediately trigger GoToDefinition. + self.select( + SelectPhase::Begin { + position: point.next_valid, + add: false, + click_count: 1, + }, + cx, + ); + + if point.as_valid().is_some() { + if modifiers.shift { + self.go_to_type_definition(&GoToTypeDefinition, cx) + } else { + self.go_to_definition(&GoToDefinition, cx) + } + } + } } pub fn update_inlay_link_and_hover_points( - snapshot: &DisplaySnapshot, + snapshot: &EditorSnapshot, point_for_position: PointForPosition, editor: &mut Editor, cmd_held: bool, @@ -306,18 +306,20 @@ pub fn update_inlay_link_and_hover_points( if let Some((language_server_id, location)) = hovered_hint_part.location { - go_to_definition_updated = true; - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::InlayHint( - highlight, - location, - language_server_id, - )), - cmd_held, - shift_held, - cx, - ); + if cmd_held && !editor.has_pending_nonempty_selection() { + go_to_definition_updated = true; + show_link_definition( + shift_held, + editor, + TriggerPoint::InlayHint( + highlight, + location, + language_server_id, + ), + snapshot, + cx, + ); + } } } } @@ -330,7 +332,7 @@ pub fn update_inlay_link_and_hover_points( } if !go_to_definition_updated { - update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); + editor.hide_hovered_link(cx) } if !hover_updated { hover_popover::hover_at(editor, None, cx); @@ -344,113 +346,149 @@ pub enum LinkDefinitionKind { } pub fn show_link_definition( - definition_kind: LinkDefinitionKind, + shift_held: bool, editor: &mut Editor, trigger_point: TriggerPoint, - snapshot: EditorSnapshot, + snapshot: &EditorSnapshot, cx: &mut ViewContext, ) { - let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind); - if !same_kind { - hide_link_definition(editor, cx); - } + let preferred_kind = match trigger_point { + TriggerPoint::Text(_) if !shift_held => LinkDefinitionKind::Symbol, + _ => LinkDefinitionKind::Type, + }; + + let (mut hovered_link_state, is_cached) = + if let Some(existing) = editor.hovered_link_state.take() { + (existing, true) + } else { + ( + HoveredLinkState { + last_trigger_point: trigger_point.clone(), + symbol_range: None, + preferred_kind, + links: vec![], + task: None, + }, + false, + ) + }; if editor.pending_rename.is_some() { return; } let trigger_anchor = trigger_point.anchor(); - let (buffer, buffer_position) = if let Some(output) = editor + let Some((buffer, buffer_position)) = editor .buffer .read(cx) .text_anchor_for_position(trigger_anchor.clone(), cx) - { - output - } else { + else { return; }; - let excerpt_id = if let Some((excerpt_id, _, _)) = editor + let Some((excerpt_id, _, _)) = editor .buffer() .read(cx) .excerpt_containing(trigger_anchor.clone(), cx) - { - excerpt_id - } else { + else { return; }; - let project = if let Some(project) = editor.project.clone() { - project - } else { + let Some(project) = editor.project.clone() else { return; }; - // Don't request again if the location is within the symbol region of a previous request with the same kind - if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range { - if same_kind && symbol_range.point_within_range(&trigger_point, &snapshot) { + let same_kind = hovered_link_state.preferred_kind == preferred_kind + || hovered_link_state + .links + .first() + .is_some_and(|d| matches!(d, HoverLink::Url(_))); + + if same_kind { + if is_cached && (&hovered_link_state.last_trigger_point == &trigger_point) + || hovered_link_state + .symbol_range + .as_ref() + .is_some_and(|symbol_range| { + symbol_range.point_within_range(&trigger_point, &snapshot) + }) + { + editor.hovered_link_state = Some(hovered_link_state); return; } + } else { + editor.hide_hovered_link(cx) } - let task = cx.spawn(|this, mut cx| { + let snapshot = snapshot.buffer_snapshot.clone(); + hovered_link_state.task = Some(cx.spawn(|this, mut cx| { async move { let result = match &trigger_point { TriggerPoint::Text(_) => { - // query the LSP for definition info - project - .update(&mut cx, |project, cx| match definition_kind { - LinkDefinitionKind::Symbol => { - project.definition(&buffer, buffer_position, cx) - } - - LinkDefinitionKind::Type => { - project.type_definition(&buffer, buffer_position, cx) - } - })? - .await - .ok() - .map(|definition_result| { + if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) { + this.update(&mut cx, |_, _| { + let start = + snapshot.anchor_in_excerpt(excerpt_id.clone(), url_range.start); + let end = snapshot.anchor_in_excerpt(excerpt_id.clone(), url_range.end); ( - definition_result.iter().find_map(|link| { - link.origin.as_ref().map(|origin| { - let start = snapshot.buffer_snapshot.anchor_in_excerpt( - excerpt_id.clone(), - origin.range.start, - ); - let end = snapshot.buffer_snapshot.anchor_in_excerpt( - excerpt_id.clone(), - origin.range.end, - ); - RangeInEditor::Text(start..end) - }) - }), - definition_result - .into_iter() - .map(GoToDefinitionLink::Text) - .collect(), + Some(RangeInEditor::Text(start..end)), + vec![HoverLink::Url(url)], ) }) + .ok() + } else { + // query the LSP for definition info + project + .update(&mut cx, |project, cx| match preferred_kind { + LinkDefinitionKind::Symbol => { + project.definition(&buffer, buffer_position, cx) + } + + LinkDefinitionKind::Type => { + project.type_definition(&buffer, buffer_position, cx) + } + })? + .await + .ok() + .map(|definition_result| { + ( + definition_result.iter().find_map(|link| { + link.origin.as_ref().map(|origin| { + let start = snapshot.anchor_in_excerpt( + excerpt_id.clone(), + origin.range.start, + ); + let end = snapshot.anchor_in_excerpt( + excerpt_id.clone(), + origin.range.end, + ); + RangeInEditor::Text(start..end) + }) + }), + definition_result.into_iter().map(HoverLink::Text).collect(), + ) + }) + } } TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some(( Some(RangeInEditor::Inlay(highlight.clone())), - vec![GoToDefinitionLink::InlayHint( - lsp_location.clone(), - *server_id, - )], + vec![HoverLink::InlayHint(lsp_location.clone(), *server_id)], )), }; this.update(&mut cx, |this, cx| { // Clear any existing highlights - this.clear_highlights::(cx); - this.link_go_to_definition_state.kind = Some(definition_kind); - this.link_go_to_definition_state.symbol_range = result + this.clear_highlights::(cx); + let Some(hovered_link_state) = this.hovered_link_state.as_mut() else { + return; + }; + hovered_link_state.preferred_kind = preferred_kind; + hovered_link_state.symbol_range = result .as_ref() .and_then(|(symbol_range, _)| symbol_range.clone()); if let Some((symbol_range, definitions)) = result { - this.link_go_to_definition_state.definitions = definitions.clone(); + hovered_link_state.links = definitions.clone(); let buffer_snapshot = buffer.read(cx).snapshot(); @@ -459,7 +497,7 @@ pub fn show_link_definition( let any_definition_does_not_contain_current_location = definitions.iter().any(|definition| { match &definition { - GoToDefinitionLink::Text(link) => { + HoverLink::Text(link) => { if link.target.buffer == buffer { let range = &link.target.range; // Expand range by one character as lsp definition ranges include positions adjacent @@ -481,7 +519,8 @@ pub fn show_link_definition( true } } - GoToDefinitionLink::InlayHint(_, _) => true, + HoverLink::InlayHint(_, _) => true, + HoverLink::Url(_) => true, } }); @@ -497,7 +536,6 @@ pub fn show_link_definition( let highlight_range = symbol_range.unwrap_or_else(|| match &trigger_point { TriggerPoint::Text(trigger_anchor) => { - let snapshot = &snapshot.buffer_snapshot; // If no symbol range returned from language server, use the surrounding word. let (offset_range, _) = snapshot.surrounding_word(*trigger_anchor); @@ -512,21 +550,14 @@ pub fn show_link_definition( }); match highlight_range { - RangeInEditor::Text(text_range) => this - .highlight_text::( - vec![text_range], - style, - cx, - ), + RangeInEditor::Text(text_range) => { + this.highlight_text::(vec![text_range], style, cx) + } RangeInEditor::Inlay(highlight) => this - .highlight_inlays::( - vec![highlight], - style, - cx, - ), + .highlight_inlays::(vec![highlight], style, cx), } } else { - hide_link_definition(this, cx); + this.hide_hovered_link(cx); } } })?; @@ -534,78 +565,68 @@ pub fn show_link_definition( Ok::<_, anyhow::Error>(()) } .log_err() - }); + })); - editor.link_go_to_definition_state.task = Some(task); + editor.hovered_link_state = Some(hovered_link_state); } -pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { - if editor.link_go_to_definition_state.symbol_range.is_some() - || !editor.link_go_to_definition_state.definitions.is_empty() - { - editor.link_go_to_definition_state.symbol_range.take(); - editor.link_go_to_definition_state.definitions.clear(); - cx.notify(); - } +fn find_url( + buffer: &Model, + position: text::Anchor, + mut cx: AsyncWindowContext, +) -> Option<(Range, String)> { + const LIMIT: usize = 2048; - editor.link_go_to_definition_state.task = None; - - editor.clear_highlights::(cx); -} - -pub fn go_to_fetched_definition( - editor: &mut Editor, - point: PointForPosition, - split: bool, - cx: &mut ViewContext, -) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx); -} + let Ok(snapshot) = buffer.update(&mut cx, |buffer, _| buffer.snapshot()) else { + return None; + }; -pub fn go_to_fetched_type_definition( - editor: &mut Editor, - point: PointForPosition, - split: bool, - cx: &mut ViewContext, -) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx); -} + let offset = position.to_offset(&snapshot); + let mut token_start = offset; + let mut token_end = offset; + let mut found_start = false; + let mut found_end = false; -fn go_to_fetched_definition_of_kind( - kind: LinkDefinitionKind, - editor: &mut Editor, - point: PointForPosition, - split: bool, - cx: &mut ViewContext, -) { - let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); - hide_link_definition(editor, cx); - let cached_definitions_kind = editor.link_go_to_definition_state.kind; - - let is_correct_kind = cached_definitions_kind == Some(kind); - if !cached_definitions.is_empty() && is_correct_kind { - if !editor.focus_handle.is_focused(cx) { - cx.focus(&editor.focus_handle); + for ch in snapshot.reversed_chars_at(offset).take(LIMIT) { + if ch.is_whitespace() { + found_start = true; + break; } + token_start -= ch.len_utf8(); + } + if !found_start { + return None; + } - editor.navigate_to_definitions(cached_definitions, split, cx); - } else { - editor.select( - SelectPhase::Begin { - position: point.next_valid, - add: false, - click_count: 1, - }, - cx, - ); + for ch in snapshot + .chars_at(offset) + .take(LIMIT - (offset - token_start)) + { + if ch.is_whitespace() { + found_end = true; + break; + } + token_end += ch.len_utf8(); + } + if !found_end { + return None; + } - if point.as_valid().is_some() { - match kind { - LinkDefinitionKind::Symbol => editor.go_to_definition(&GoToDefinition, cx), - LinkDefinitionKind::Type => editor.go_to_type_definition(&GoToTypeDefinition, cx), - } + let mut finder = LinkFinder::new(); + finder.kinds(&[LinkKind::Url]); + let input = snapshot + .text_for_range(token_start..token_end) + .collect::(); + + let relative_offset = offset - token_start; + for link in finder.links(&input) { + if link.start() <= relative_offset && link.end() >= relative_offset { + let range = snapshot.anchor_before(token_start + link.start()) + ..snapshot.anchor_after(token_start + link.end()); + return Some((range, link.as_str().to_string())); } } + None } #[cfg(test)] @@ -616,16 +637,18 @@ mod tests { editor_tests::init_test, inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, test::editor_lsp_test_context::EditorLspTestContext, + DisplayPoint, }; use futures::StreamExt; - use gpui::{Modifiers, ModifiersChangedEvent}; + use gpui::Modifiers; use indoc::indoc; use language::language_settings::InlayHintSettings; use lsp::request::{GotoDefinition, GotoTypeDefinition}; use util::assert_set_eq; + use workspace::item::Item; #[gpui::test] - async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { + async fn test_hover_type_links(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( @@ -642,12 +665,9 @@ mod tests { struct A; let vˇariable = A; "}); + let screen_coord = cx.editor(|editor, cx| editor.pixel_position_of_cursor(cx)); // Basic hold cmd+shift, expect highlight in region if response contains type definition - let hover_point = cx.display_point(indoc! {" - struct A; - let vˇariable = A; - "}); let symbol_range = cx.lsp_range(indoc! {" struct A; let «variable» = A; @@ -657,6 +677,8 @@ mod tests { let variable = A; "}); + cx.run_until_parked(); + let mut requests = cx.handle_request::(move |url, _, _| async move { Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ @@ -669,70 +691,28 @@ mod tests { ]))) }); - // Press cmd+shift to trigger highlight - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - true, - cx, - ); - }); + cx.cx + .cx + .simulate_mouse_move(screen_coord.unwrap(), Modifiers::command_shift()); + requests.next().await; - cx.background_executor.run_until_parked(); - cx.assert_editor_text_highlights::(indoc! {" + cx.run_until_parked(); + cx.assert_editor_text_highlights::(indoc! {" struct A; let «variable» = A; "}); - // Unpress shift causes highlight to go away (normal goto-definition is not valid here) - cx.update_editor(|editor, cx| { - crate::element::EditorElement::modifiers_changed( - editor, - &ModifiersChangedEvent { - modifiers: Modifiers { - command: true, - ..Default::default() - }, - ..Default::default() - }, - cx, - ); - }); + cx.simulate_modifiers_change(Modifiers::command()); + cx.run_until_parked(); // Assert no link highlights - cx.assert_editor_text_highlights::(indoc! {" - struct A; - let variable = A; - "}); - - // Cmd+shift click without existing definition requests and jumps - let hover_point = cx.display_point(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" struct A; - let vˇariable = A; - "}); - let target_range = cx.lsp_range(indoc! {" - struct «A»; let variable = A; "}); - let mut requests = - cx.handle_request::(move |url, _, _| async move { - Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ - lsp::LocationLink { - origin_selection_range: None, - target_uri: url, - target_range, - target_selection_range: target_range, - }, - ]))) - }); - - cx.update_editor(|editor, cx| { - go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx); - }); - requests.next().await; - cx.background_executor.run_until_parked(); + cx.cx + .cx + .simulate_click(screen_coord.unwrap(), Modifiers::command_shift()); cx.assert_editor_state(indoc! {" struct «Aˇ»; @@ -741,7 +721,7 @@ mod tests { } #[gpui::test] - async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { + async fn test_hover_links(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( @@ -759,7 +739,7 @@ mod tests { "}); // Basic hold cmd, expect highlight in region if response contains definition - let hover_point = cx.display_point(indoc! {" + let hover_point = cx.pixel_position(indoc! {" fn test() { do_wˇork(); } fn do_work() { test(); } "}); @@ -783,65 +763,42 @@ mod tests { ]))) }); - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - false, - cx, - ); - }); + cx.simulate_mouse_move(hover_point, Modifiers::command()); requests.next().await; cx.background_executor.run_until_parked(); - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { «do_work»(); } fn do_work() { test(); } "}); // Unpress cmd causes highlight to go away - cx.update_editor(|editor, cx| { - crate::element::EditorElement::modifiers_changed(editor, &Default::default(), cx); - }); - - // Assert no link highlights - cx.assert_editor_text_highlights::(indoc! {" + cx.simulate_modifiers_change(Modifiers::none()); + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } "}); - // Response without source range still highlights word - cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None); let mut requests = cx.handle_request::(move |url, _, _| async move { Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ lsp::LocationLink { - // No origin range - origin_selection_range: None, + origin_selection_range: Some(symbol_range), target_uri: url.clone(), target_range, target_selection_range: target_range, }, ]))) }); - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - false, - cx, - ); - }); + + cx.simulate_mouse_move(hover_point, Modifiers::command()); requests.next().await; cx.background_executor.run_until_parked(); - - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { «do_work»(); } fn do_work() { test(); } "}); // Moving mouse to location with no response dismisses highlight - let hover_point = cx.display_point(indoc! {" + let hover_point = cx.pixel_position(indoc! {" fˇn test() { do_work(); } fn do_work() { test(); } "}); @@ -851,42 +808,26 @@ mod tests { // No definitions returned Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) }); - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - false, - cx, - ); - }); + cx.simulate_mouse_move(hover_point, Modifiers::command()); + requests.next().await; cx.background_executor.run_until_parked(); // Assert no link highlights - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } "}); - // Move mouse without cmd and then pressing cmd triggers highlight - let hover_point = cx.display_point(indoc! {" + // // Move mouse without cmd and then pressing cmd triggers highlight + let hover_point = cx.pixel_position(indoc! {" fn test() { do_work(); } fn do_work() { teˇst(); } "}); - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - false, - false, - cx, - ); - }); - cx.background_executor.run_until_parked(); + cx.simulate_mouse_move(hover_point, Modifiers::none()); // Assert no link highlights - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } "}); @@ -910,73 +851,44 @@ mod tests { }, ]))) }); - cx.update_editor(|editor, cx| { - crate::element::EditorElement::modifiers_changed( - editor, - &ModifiersChangedEvent { - modifiers: Modifiers { - command: true, - ..Default::default() - }, - }, - cx, - ); - }); + + cx.simulate_modifiers_change(Modifiers::command()); + requests.next().await; cx.background_executor.run_until_parked(); - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { «test»(); } "}); - cx.cx.cx.deactivate_window(); - cx.assert_editor_text_highlights::(indoc! {" + cx.deactivate_window(); + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } "}); - // Moving the mouse restores the highlights. - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - false, - cx, - ); - }); + cx.simulate_mouse_move(hover_point, Modifiers::command()); cx.background_executor.run_until_parked(); - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { «test»(); } "}); // Moving again within the same symbol range doesn't re-request - let hover_point = cx.display_point(indoc! {" + let hover_point = cx.pixel_position(indoc! {" fn test() { do_work(); } fn do_work() { tesˇt(); } "}); - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - false, - cx, - ); - }); + cx.simulate_mouse_move(hover_point, Modifiers::command()); cx.background_executor.run_until_parked(); - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { «test»(); } "}); // Cmd click with existing definition doesn't re-request and dismisses highlight - cx.update_editor(|editor, cx| { - go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); - }); - // Assert selection moved to to definition + cx.simulate_click(hover_point, Modifiers::command()); cx.lsp .handle_request::(move |_, _| async move { // Empty definition response to make sure we aren't hitting the lsp and using @@ -990,13 +902,13 @@ mod tests { "}); // Assert no link highlights after jump - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } "}); // Cmd click without existing definition requests and jumps - let hover_point = cx.display_point(indoc! {" + let hover_point = cx.pixel_position(indoc! {" fn test() { do_wˇork(); } fn do_work() { test(); } "}); @@ -1015,9 +927,7 @@ mod tests { }, ]))) }); - cx.update_editor(|editor, cx| { - go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); - }); + cx.simulate_click(hover_point, Modifiers::command()); requests.next().await; cx.background_executor.run_until_parked(); cx.assert_editor_state(indoc! {" @@ -1027,7 +937,7 @@ mod tests { // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens // 2. Selection is completed, hovering - let hover_point = cx.display_point(indoc! {" + let hover_point = cx.pixel_position(indoc! {" fn test() { do_wˇork(); } fn do_work() { test(); } "}); @@ -1060,18 +970,10 @@ mod tests { s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) }); }); - cx.update_editor(|editor, cx| { - update_go_to_definition_link( - editor, - Some(GoToDefinitionTrigger::Text(hover_point)), - true, - false, - cx, - ); - }); + cx.simulate_mouse_move(hover_point, Modifiers::command()); cx.background_executor.run_until_parked(); assert!(requests.try_next().is_err()); - cx.assert_editor_text_highlights::(indoc! {" + cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } "}); @@ -1079,7 +981,7 @@ mod tests { } #[gpui::test] - async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) { + async fn test_inlay_hover_links(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, @@ -1167,39 +1069,25 @@ mod tests { .get(0) .cloned() .unwrap(); - let hint_hover_position = cx.update_editor(|editor, cx| { + let midpoint = cx.update_editor(|editor, cx| { let snapshot = editor.snapshot(cx); let previous_valid = inlay_range.start.to_display_point(&snapshot); let next_valid = inlay_range.end.to_display_point(&snapshot); assert_eq!(previous_valid.row(), next_valid.row()); assert!(previous_valid.column() < next_valid.column()); - let exact_unclipped = DisplayPoint::new( + DisplayPoint::new( previous_valid.row(), previous_valid.column() + (hint_label.len() / 2) as u32, - ); - PointForPosition { - previous_valid, - next_valid, - exact_unclipped, - column_overshoot_after_line_end: 0, - } + ) }); // Press cmd to trigger highlight - cx.update_editor(|editor, cx| { - update_inlay_link_and_hover_points( - &editor.snapshot(cx), - hint_hover_position, - editor, - true, - false, - cx, - ); - }); + let hover_point = cx.pixel_position_for(midpoint); + cx.simulate_mouse_move(hover_point, Modifiers::command()); cx.background_executor.run_until_parked(); cx.update_editor(|editor, cx| { let snapshot = editor.snapshot(cx); let actual_highlights = snapshot - .inlay_highlights::() + .inlay_highlights::() .into_iter() .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight)) .collect::>(); @@ -1213,57 +1101,21 @@ mod tests { assert_set_eq!(actual_highlights, vec![&expected_highlight]); }); - // Unpress cmd causes highlight to go away - cx.update_editor(|editor, cx| { - crate::element::EditorElement::modifiers_changed( - editor, - &ModifiersChangedEvent { - modifiers: Modifiers { - command: false, - ..Default::default() - }, - ..Default::default() - }, - cx, - ); - }); + cx.simulate_mouse_move(hover_point, Modifiers::none()); // Assert no link highlights cx.update_editor(|editor, cx| { let snapshot = editor.snapshot(cx); let actual_ranges = snapshot - .text_highlight_ranges::() + .text_highlight_ranges::() .map(|ranges| ranges.as_ref().clone().1) .unwrap_or_default(); assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}"); }); - // Cmd+click without existing definition requests and jumps - cx.update_editor(|editor, cx| { - crate::element::EditorElement::modifiers_changed( - editor, - &ModifiersChangedEvent { - modifiers: Modifiers { - command: true, - ..Default::default() - }, - ..Default::default() - }, - cx, - ); - update_inlay_link_and_hover_points( - &editor.snapshot(cx), - hint_hover_position, - editor, - true, - false, - cx, - ); - }); + cx.simulate_modifiers_change(Modifiers::command()); cx.background_executor.run_until_parked(); - cx.update_editor(|editor, cx| { - go_to_fetched_type_definition(editor, hint_hover_position, false, cx); - }); + cx.simulate_click(hover_point, Modifiers::command()); cx.background_executor.run_until_parked(); cx.assert_editor_state(indoc! {" struct «TestStructˇ»; @@ -1273,4 +1125,35 @@ mod tests { } "}); } + + #[gpui::test] + async fn test_urls(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + Let's test a [complex](https://zed.dev/channel/had-(oops)) caseˇ. + "}); + + let screen_coord = cx.pixel_position(indoc! {" + Let's test a [complex](https://zed.dev/channel/had-(ˇoops)) case. + "}); + + cx.simulate_mouse_move(screen_coord, Modifiers::command()); + cx.assert_editor_text_highlights::(indoc! {" + Let's test a [complex](«https://zed.dev/channel/had-(oops)ˇ») case. + "}); + + cx.simulate_click(screen_coord, Modifiers::command()); + assert_eq!( + cx.opened_url(), + Some("https://zed.dev/channel/had-(oops)".into()) + ); + } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 668d00f1aabcf67cd9293e1bfaf1d3abe5c57091..b4253e074b1a75a3de36a24bcce74c4f5691fc0c 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{InlayHighlight, RangeInEditor}, + hover_links::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, Hover, RangeToAnchorExt, }; @@ -605,8 +605,8 @@ mod tests { use crate::{ editor_tests::init_test, element::PointForPosition, + hover_links::update_inlay_link_and_hover_points, inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, - link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, InlayId, }; diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ac55ed8546cea07ea263cf313c6a5f3745771a2f..e95d958db5a7f9eddf3c33d5616f3039c29fc811 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,7 +1,7 @@ use crate::{ - editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition, - persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, - ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, + editor_settings::SeedQuerySetting, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, + Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, + NavigationData, ToPoint as _, }; use anyhow::{anyhow, Context as _, Result}; use collections::HashSet; @@ -682,8 +682,7 @@ impl Item for Editor { } fn workspace_deactivated(&mut self, cx: &mut ViewContext) { - hide_link_definition(self, cx); - self.link_go_to_definition_state.last_trigger_point = None; + self.hide_hovered_link(cx); } fn is_dirty(&self, cx: &AppContext) -> bool { diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 3289471e81c51c72586e21af1747c6ab8d376930..9ad08390884a1c9d4e7a16c2c91585eebd7ffe06 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -4,7 +4,8 @@ use crate::{ use collections::BTreeMap; use futures::Future; use gpui::{ - AnyWindowHandle, AppContext, Keystroke, ModelContext, View, ViewContext, VisualTestContext, + AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext, + VisualTestContext, }; use indoc::indoc; use itertools::Itertools; @@ -187,6 +188,31 @@ impl EditorTestContext { ranges[0].start.to_display_point(&snapshot) } + pub fn pixel_position(&mut self, marked_text: &str) -> Point { + let display_point = self.display_point(marked_text); + self.pixel_position_for(display_point) + } + + pub fn pixel_position_for(&mut self, display_point: DisplayPoint) -> Point { + self.update_editor(|editor, cx| { + let newest_point = editor.selections.newest_display(cx).head(); + let pixel_position = editor.pixel_position_of_newest_cursor.unwrap(); + let line_height = editor + .style() + .unwrap() + .text + .line_height_in_pixels(cx.rem_size()); + let snapshot = editor.snapshot(cx); + let details = editor.text_layout_details(cx); + + let y = pixel_position.y + + line_height * (display_point.row() as f32 - newest_point.row() as f32); + let x = pixel_position.x + snapshot.x_for_display_point(display_point, &details) + - snapshot.x_for_display_point(newest_point, &details); + Point::new(x, y) + }) + } + // Returns anchors for the current buffer using `«` and `»` pub fn text_anchor_range(&mut self, marked_text: &str) -> Range { let ranges = self.ranges(marked_text); @@ -343,7 +369,7 @@ impl EditorTestContext { } impl Deref for EditorTestContext { - type Target = gpui::TestAppContext; + type Target = gpui::VisualTestContext; fn deref(&self) -> &Self::Target { &self.cx diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index bf213e281885275ced8376678d63305e4c61a972..0f64a0690fc0e42f4a7aba3d0ed30c0c4058e5a3 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,9 +1,10 @@ use crate::{ Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, - ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, - Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, - ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, + ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers, + ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, + Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, + TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -236,6 +237,11 @@ impl TestAppContext { self.test_platform.has_pending_prompt() } + /// All the urls that have been opened with cx.open_url() during this test. + pub fn opened_url(&self) -> Option { + self.test_platform.opened_url.borrow().clone() + } + /// Simulates the user resizing the window to the new size. pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size) { self.test_window(window_handle).simulate_resize(size); @@ -625,6 +631,36 @@ impl<'a> VisualTestContext { self.cx.simulate_input(self.window, input) } + /// Simulate a mouse move event to the given point + pub fn simulate_mouse_move(&mut self, position: Point, modifiers: Modifiers) { + self.simulate_event(MouseMoveEvent { + position, + modifiers, + pressed_button: None, + }) + } + + /// Simulate a primary mouse click at the given point + pub fn simulate_click(&mut self, position: Point, modifiers: Modifiers) { + self.simulate_event(MouseDownEvent { + position, + modifiers, + button: MouseButton::Left, + click_count: 1, + }); + self.simulate_event(MouseUpEvent { + position, + modifiers, + button: MouseButton::Left, + click_count: 1, + }); + } + + /// Simulate a modifiers changed event + pub fn simulate_modifiers_change(&mut self, modifiers: Modifiers) { + self.simulate_event(ModifiersChangedEvent { modifiers }) + } + /// Simulates the user resizing the window to the new size. pub fn simulate_resize(&self, size: Size) { self.simulate_window_resize(self.window, size) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 2e1acfa630627da23d939ae8eb1c1e11eebce425..9cdffd5e6136a06ec915d2c4027cbf7e67ab6b27 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -170,4 +170,34 @@ impl Modifiers { pub fn modified(&self) -> bool { self.control || self.alt || self.shift || self.command || self.function } + + /// helper method for Modifiers with no modifiers + pub fn none() -> Modifiers { + Default::default() + } + + /// helper method for Modifiers with just command + pub fn command() -> Modifiers { + Modifiers { + command: true, + ..Default::default() + } + } + + /// helper method for Modifiers with just shift + pub fn shift() -> Modifiers { + Modifiers { + shift: true, + ..Default::default() + } + } + + /// helper method for Modifiers with command + shift + pub fn command_shift() -> Modifiers { + Modifiers { + shift: true, + command: true, + ..Default::default() + } + } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 5aadc4b760aeacf9bb03ee3bef4694a4d73fea4d..4d954b32669f653e70e14d60279532e8cdfc0d1c 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -25,6 +25,7 @@ pub(crate) struct TestPlatform { active_cursor: Mutex, current_clipboard_item: Mutex>, pub(crate) prompts: RefCell, + pub opened_url: RefCell>, weak: Weak, } @@ -45,6 +46,7 @@ impl TestPlatform { active_window: Default::default(), current_clipboard_item: Mutex::new(None), weak: weak.clone(), + opened_url: Default::default(), }) } @@ -188,8 +190,8 @@ impl Platform for TestPlatform { fn stop_display_link(&self, _display_id: DisplayId) {} - fn open_url(&self, _url: &str) { - unimplemented!() + fn open_url(&self, url: &str) { + *self.opened_url.borrow_mut() = Some(url.to_string()) } fn on_open_urls(&self, _callback: Box)>) { From d08d4174a59b0b09d1295ee065ee48ff2b216212 Mon Sep 17 00:00:00 2001 From: Rashid Almheiri <69181766+huwaireb@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:04:15 +0400 Subject: [PATCH 034/101] Modify the default tab size of OCaml & OCaml Interface to 2 (#7315) Thanks @pseudomata for the heads up. Release Notes: - N/A --- assets/settings/default.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/settings/default.json b/assets/settings/default.json index 4a81cfbe6928e4f99295af853618a215b5162904..3e1e35cc47d3037750156fc2008fbb080a200248 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -502,6 +502,12 @@ }, "JSON": { "tab_size": 2 + }, + "OCaml": { + "tab_size": 2 + }, + "OCaml Interface": { + "tab_size": 2 } }, // Zed's Prettier integration settings. From c906fd232df79799f0f416229f428b19b5ed9992 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 3 Feb 2024 05:27:08 -0700 Subject: [PATCH 035/101] Reduce GPU memory usage (#7319) This pull request decreases the size of each instance buffer and shares instance buffers across windows. Release Notes: - Improved GPU memory usage. --------- Co-authored-by: Nathan Sobo --- .../gpui/src/platform/mac/metal_renderer.rs | 24 ++++++++++--------- crates/gpui/src/platform/mac/platform.rs | 10 +++++++- crates/gpui/src/platform/mac/window.rs | 3 ++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index aa9992dfda02ccc3e115ff3e369a457d37b6620b..13db1bfff1cd93a7040165b41eb67a99c00acbdc 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -24,7 +24,7 @@ const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shader #[cfg(feature = "runtime_shaders")] const SHADERS_SOURCE_FILE: &'static str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal")); -const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...) +const INSTANCE_BUFFER_SIZE: usize = 2 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...) pub(crate) struct MetalRenderer { device: metal::Device, @@ -40,13 +40,13 @@ pub(crate) struct MetalRenderer { surfaces_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, #[allow(clippy::arc_with_non_send_sync)] - instance_buffers: Arc>>, + instance_buffer_pool: Arc>>, sprite_atlas: Arc, core_video_texture_cache: CVMetalTextureCache, } impl MetalRenderer { - pub fn new(is_opaque: bool) -> Self { + pub fn new(instance_buffer_pool: Arc>>) -> Self { let device: metal::Device = if let Some(device) = metal::Device::system_default() { device } else { @@ -58,7 +58,7 @@ impl MetalRenderer { layer.set_device(&device); layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_presents_with_transaction(true); - layer.set_opaque(is_opaque); + layer.set_opaque(true); unsafe { let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO]; let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES]; @@ -181,7 +181,7 @@ impl MetalRenderer { polychrome_sprites_pipeline_state, surfaces_pipeline_state, unit_vertices, - instance_buffers: Arc::default(), + instance_buffer_pool, sprite_atlas, core_video_texture_cache, } @@ -211,7 +211,7 @@ impl MetalRenderer { ); return; }; - let mut instance_buffer = self.instance_buffers.lock().pop().unwrap_or_else(|| { + let mut instance_buffer = self.instance_buffer_pool.lock().pop().unwrap_or_else(|| { self.device.new_buffer( INSTANCE_BUFFER_SIZE as u64, MTLResourceOptions::StorageModeManaged, @@ -227,7 +227,8 @@ impl MetalRenderer { &mut instance_offset, command_buffer, ) else { - panic!("failed to rasterize {} paths", scene.paths().len()); + log::error!("failed to rasterize {} paths", scene.paths().len()); + return; }; let render_pass_descriptor = metal::RenderPassDescriptor::new(); @@ -314,7 +315,7 @@ impl MetalRenderer { }; if !ok { - panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", + log::error!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", scene.paths.len(), scene.shadows.len(), scene.quads.len(), @@ -322,7 +323,8 @@ impl MetalRenderer { scene.monochrome_sprites.len(), scene.polychrome_sprites.len(), scene.surfaces.len(), - ) + ); + break; } } @@ -333,11 +335,11 @@ impl MetalRenderer { length: instance_offset as NSUInteger, }); - let instance_buffers = self.instance_buffers.clone(); + let instance_buffer_pool = self.instance_buffer_pool.clone(); let instance_buffer = Cell::new(Some(instance_buffer)); let block = ConcreteBlock::new(move |_| { if let Some(instance_buffer) = instance_buffer.take() { - instance_buffers.lock().push(instance_buffer); + instance_buffer_pool.lock().push(instance_buffer); } }); let block = block.copy(); diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 7c6f3df26603693df416a99e174d7ea151fbae78..0e3864065f1675cb9da15f145410010859405738 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -146,6 +146,7 @@ pub(crate) struct MacPlatformState { foreground_executor: ForegroundExecutor, text_system: Arc, display_linker: MacDisplayLinker, + instance_buffer_pool: Arc>>, pasteboard: id, text_hash_pasteboard_type: id, metadata_pasteboard_type: id, @@ -176,6 +177,7 @@ impl MacPlatform { foreground_executor: ForegroundExecutor::new(dispatcher), text_system: Arc::new(MacTextSystem::new()), display_linker: MacDisplayLinker::new(), + instance_buffer_pool: Arc::default(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") }, metadata_pasteboard_type: unsafe { ns_string("zed-metadata") }, @@ -494,7 +496,13 @@ impl Platform for MacPlatform { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { - Box::new(MacWindow::open(handle, options, self.foreground_executor())) + let instance_buffer_pool = self.0.lock().instance_buffer_pool.clone(); + Box::new(MacWindow::open( + handle, + options, + self.foreground_executor(), + instance_buffer_pool, + )) } fn set_display_link_output_callback( diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 0e12dc4af00e312890259e0acb807c3a9004aa8d..bb8c08a8794cd616988dd6e385a2a901594c004a 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -459,6 +459,7 @@ impl MacWindow { handle: AnyWindowHandle, options: WindowOptions, executor: ForegroundExecutor, + instance_buffer_pool: Arc>>, ) -> Self { unsafe { let pool = NSAutoreleasePool::new(nil); @@ -535,7 +536,7 @@ impl MacWindow { native_window, native_view: NonNull::new_unchecked(native_view as *mut _), display_link, - renderer: MetalRenderer::new(true), + renderer: MetalRenderer::new(instance_buffer_pool), kind: options.kind, request_frame_callback: None, event_callback: None, From 54aecd21ecc80642eed28a897b5e4760a69e89cd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 3 Feb 2024 05:41:58 -0700 Subject: [PATCH 036/101] Remove unnecessary `focus_invalidated` field (#7320) I believe at some point this was used for tests but it doesn't seem necessary anymore. Release Notes: - N/A --- crates/gpui/src/app.rs | 2 +- crates/gpui/src/window.rs | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 56d66f9b0b6cb570bb4247adeceacd97a3fae6c9..8d6f70f27e2a25095e1296b8853f4c9cba00cec6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -658,7 +658,7 @@ impl AppContext { .values() .filter_map(|window| { let window = window.as_ref()?; - (window.dirty.get() || window.focus_invalidated).then_some(window.handle) + window.dirty.get().then_some(window.handle) }) .collect::>() { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index afc2235fe6425cc8d1db14cd0c7901978a5fd0bf..19ed1c90993e36c846ced747716ec2035e9954b8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -280,9 +280,6 @@ pub struct Window { pub(crate) focus: Option, focus_enabled: bool, pending_input: Option, - - #[cfg(any(test, feature = "test-support"))] - pub(crate) focus_invalidated: bool, } #[derive(Default, Debug)] @@ -451,9 +448,6 @@ impl Window { focus: None, focus_enabled: true, pending_input: None, - - #[cfg(any(test, feature = "test-support"))] - focus_invalidated: false, } } } @@ -538,12 +532,6 @@ impl<'a> WindowContext<'a> { .rendered_frame .dispatch_tree .clear_pending_keystrokes(); - - #[cfg(any(test, feature = "test-support"))] - { - self.window.focus_invalidated = true; - } - self.refresh(); } @@ -985,11 +973,6 @@ impl<'a> WindowContext<'a> { self.window.dirty.set(false); self.window.drawing = true; - #[cfg(any(test, feature = "test-support"))] - { - self.window.focus_invalidated = false; - } - if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() { let input_handler = self.window.platform_window.take_input_handler(); From 06674a21f9fd891e22a1130a17f27a04a06fc2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Pf=C3=A4ffle?= <67913738+rpfaeffle@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:04:27 +0100 Subject: [PATCH 037/101] Add support for relative terminal links (#7303) Allow opening file paths relative to terminal's cwd https://github.com/zed-industries/zed/assets/67913738/413a1107-541e-4c25-ae7c-cbe45469d452 Release Notes: - Added support for opening file paths relative to terminal's cwd ([#7144](https://github.com/zed-industries/zed/issues/7144)). --------- Co-authored-by: Kirill --- Cargo.lock | 1 + crates/terminal/src/terminal.rs | 27 ++- crates/terminal_view/Cargo.toml | 1 + crates/terminal_view/src/terminal_view.rs | 216 ++++++++++++++-------- crates/util/src/paths.rs | 2 +- 5 files changed, 168 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14b5882ff902f40edb82317de62444d97fa3587f..242e3d4704540ecc6f49f7d16fbc00a471bf3a97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8133,6 +8133,7 @@ version = "0.1.0" dependencies = [ "anyhow", "client", + "collections", "db", "dirs 4.0.0", "editor", diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5bc8d00e52f8799bae08c17acfa84737cc507163..ad9587a3267732d66295b667f4a63e4437fc9915 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -86,6 +86,15 @@ pub enum Event { Open(MaybeNavigationTarget), } +#[derive(Clone, Debug)] +pub struct PathLikeTarget { + /// File system path, absolute or relative, existing or not. + /// Might have line and column number(s) attached as `file.rs:1:23` + pub maybe_path: String, + /// Current working directory of the terminal + pub terminal_dir: Option, +} + /// A string inside terminal, potentially useful as a URI that can be opened. #[derive(Clone, Debug)] pub enum MaybeNavigationTarget { @@ -93,7 +102,7 @@ pub enum MaybeNavigationTarget { Url(String), /// File system path, absolute or relative, existing or not. /// Might have line and column number(s) attached as `file.rs:1:23` - PathLike(String), + PathLike(PathLikeTarget), } #[derive(Clone)] @@ -626,6 +635,12 @@ impl Terminal { } } + fn get_cwd(&self) -> Option { + self.foreground_process_info + .as_ref() + .map(|info| info.cwd.clone()) + } + ///Takes events from Alacritty and translates them to behavior on this view fn process_terminal_event( &mut self, @@ -800,7 +815,10 @@ impl Terminal { let target = if is_url { MaybeNavigationTarget::Url(maybe_url_or_path) } else { - MaybeNavigationTarget::PathLike(maybe_url_or_path) + MaybeNavigationTarget::PathLike(PathLikeTarget { + maybe_path: maybe_url_or_path, + terminal_dir: self.get_cwd(), + }) }; cx.emit(Event::Open(target)); } else { @@ -852,7 +870,10 @@ impl Terminal { let navigation_target = if is_url { MaybeNavigationTarget::Url(word) } else { - MaybeNavigationTarget::PathLike(word) + MaybeNavigationTarget::PathLike(PathLikeTarget { + maybe_path: word, + terminal_dir: self.get_cwd(), + }) }; cx.emit(Event::NewNavigationTarget(Some(navigation_target))); } diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index dfffe3824f806203af89ab9d65618a51dc5297bc..134f9f08dd8a6d1706b2dddb3d8b6d54bc8cbe04 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -12,6 +12,7 @@ doctest = false [dependencies] anyhow.workspace = true db = { path = "../db" } +collections = { path = "../collections" } dirs = "4.0.0" editor = { path = "../editor" } futures.workspace = true diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index c0074cf53ad752f58e1e3e33ed2d2cef2cc489ac..6c7270d9b4df076a19aae24fc4715d3dc780b23c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -2,7 +2,9 @@ mod persistence; pub mod terminal_element; pub mod terminal_panel; +use collections::HashSet; use editor::{scroll::Autoscroll, Editor}; +use futures::{stream::FuturesUnordered, StreamExt}; use gpui::{ div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, Pixels, @@ -10,7 +12,7 @@ use gpui::{ }; use language::Bias; use persistence::TERMINAL_DB; -use project::{search::SearchQuery, LocalWorktree, Project}; +use project::{search::SearchQuery, Fs, LocalWorktree, Metadata, Project}; use terminal::{ alacritty_terminal::{ index::Point, @@ -177,8 +179,21 @@ impl TerminalView { Event::NewNavigationTarget(maybe_navigation_target) => { this.can_navigate_to_selected_word = match maybe_navigation_target { Some(MaybeNavigationTarget::Url(_)) => true, - Some(MaybeNavigationTarget::PathLike(maybe_path)) => { - !possible_open_targets(&workspace, maybe_path, cx).is_empty() + Some(MaybeNavigationTarget::PathLike(path_like_target)) => { + if let Ok(fs) = workspace.update(cx, |workspace, cx| { + workspace.project().read(cx).fs().clone() + }) { + let valid_files_to_open_task = possible_open_targets( + fs, + &workspace, + &path_like_target.terminal_dir, + &path_like_target.maybe_path, + cx, + ); + smol::block_on(valid_files_to_open_task).len() > 0 + } else { + false + } } None => false, } @@ -187,57 +202,60 @@ impl TerminalView { Event::Open(maybe_navigation_target) => match maybe_navigation_target { MaybeNavigationTarget::Url(url) => cx.open_url(url), - MaybeNavigationTarget::PathLike(maybe_path) => { + MaybeNavigationTarget::PathLike(path_like_target) => { if !this.can_navigate_to_selected_word { return; } - let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx); - if let Some(path) = potential_abs_paths.into_iter().next() { - let task_workspace = workspace.clone(); - cx.spawn(|_, mut cx| async move { - let fs = task_workspace.update(&mut cx, |workspace, cx| { - workspace.project().read(cx).fs().clone() - })?; - let is_dir = fs - .metadata(&path.path_like) - .await? - .with_context(|| { - format!("Missing metadata for file {:?}", path.path_like) - })? - .is_dir; - let opened_items = task_workspace - .update(&mut cx, |workspace, cx| { - workspace.open_paths( - vec![path.path_like], - OpenVisible::OnlyDirectories, - None, - cx, - ) - }) - .context("workspace update")? - .await; - anyhow::ensure!( - opened_items.len() == 1, - "For a single path open, expected single opened item" - ); - let opened_item = opened_items - .into_iter() - .next() - .unwrap() - .transpose() - .context("path open")?; - if is_dir { - task_workspace.update(&mut cx, |workspace, cx| { - workspace.project().update(cx, |_, cx| { - cx.emit(project::Event::ActivateProjectPanel); - }) - })?; - } else { + let task_workspace = workspace.clone(); + let Some(fs) = workspace + .update(cx, |workspace, cx| { + workspace.project().read(cx).fs().clone() + }) + .ok() + else { + return; + }; + + let path_like_target = path_like_target.clone(); + cx.spawn(|terminal_view, mut cx| async move { + let valid_files_to_open = terminal_view + .update(&mut cx, |_, cx| { + possible_open_targets( + fs, + &task_workspace, + &path_like_target.terminal_dir, + &path_like_target.maybe_path, + cx, + ) + })? + .await; + let paths_to_open = valid_files_to_open + .iter() + .map(|(p, _)| p.path_like.clone()) + .collect(); + let opened_items = task_workspace + .update(&mut cx, |workspace, cx| { + workspace.open_paths( + paths_to_open, + OpenVisible::OnlyDirectories, + None, + cx, + ) + }) + .context("workspace update")? + .await; + + let mut has_dirs = false; + for ((path, metadata), opened_item) in valid_files_to_open + .into_iter() + .zip(opened_items.into_iter()) + { + if metadata.is_dir { + has_dirs = true; + } else if let Some(Ok(opened_item)) = opened_item { if let Some(row) = path.row { let col = path.column.unwrap_or(0); - if let Some(active_editor) = - opened_item.and_then(|item| item.downcast::()) - { + if let Some(active_editor) = opened_item.downcast::() { active_editor .downgrade() .update(&mut cx, |editor, cx| { @@ -259,10 +277,19 @@ impl TerminalView { } } } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - } + } + + if has_dirs { + task_workspace.update(&mut cx, |workspace, cx| { + workspace.project().update(cx, |_, cx| { + cx.emit(project::Event::ActivateProjectPanel); + }) + })?; + } + + anyhow::Ok(()) + }) + .detach_and_log_err(cx) } }, Event::BreadcrumbsChanged => cx.emit(ItemEvent::UpdateBreadcrumbs), @@ -554,48 +581,87 @@ impl TerminalView { } } +fn possible_open_paths_metadata( + fs: Arc, + row: Option, + column: Option, + potential_paths: HashSet, + cx: &mut ViewContext, +) -> Task, Metadata)>> { + cx.background_executor().spawn(async move { + let mut paths_with_metadata = Vec::with_capacity(potential_paths.len()); + + let mut fetch_metadata_tasks = potential_paths + .into_iter() + .map(|potential_path| async { + let metadata = fs.metadata(&potential_path).await.ok().flatten(); + ( + PathLikeWithPosition { + path_like: potential_path, + row, + column, + }, + metadata, + ) + }) + .collect::>(); + + while let Some((path, metadata)) = fetch_metadata_tasks.next().await { + if let Some(metadata) = metadata { + paths_with_metadata.push((path, metadata)); + } + } + + paths_with_metadata + }) +} + fn possible_open_targets( + fs: Arc, workspace: &WeakView, + cwd: &Option, maybe_path: &String, - cx: &mut ViewContext<'_, TerminalView>, -) -> Vec> { + cx: &mut ViewContext, +) -> Task, Metadata)>> { let path_like = PathLikeWithPosition::parse_str(maybe_path.as_str(), |path_str| { Ok::<_, std::convert::Infallible>(Path::new(path_str).to_path_buf()) }) .expect("infallible"); + let row = path_like.row; + let column = path_like.column; let maybe_path = path_like.path_like; let potential_abs_paths = if maybe_path.is_absolute() { - vec![maybe_path] + HashSet::from_iter([maybe_path]) } else if maybe_path.starts_with("~") { if let Some(abs_path) = maybe_path .strip_prefix("~") .ok() .and_then(|maybe_path| Some(dirs::home_dir()?.join(maybe_path))) { - vec![abs_path] + HashSet::from_iter([abs_path]) } else { - Vec::new() + HashSet::default() } - } else if let Some(workspace) = workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - workspace - .worktrees(cx) - .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path)) - .collect() - }) } else { - Vec::new() + // First check cwd and then workspace + let mut potential_cwd_and_workspace_paths = HashSet::default(); + if let Some(cwd) = cwd { + potential_cwd_and_workspace_paths.insert(Path::join(cwd, &maybe_path)); + } + if let Some(workspace) = workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + for potential_worktree_path in workspace + .worktrees(cx) + .map(|worktree| worktree.read(cx).abs_path().join(&maybe_path)) + { + potential_cwd_and_workspace_paths.insert(potential_worktree_path); + } + }); + } + potential_cwd_and_workspace_paths }; - potential_abs_paths - .into_iter() - .filter(|path| path.exists()) - .map(|path| PathLikeWithPosition { - path_like: path, - row: path_like.row, - column: path_like.column, - }) - .collect() + possible_open_paths_metadata(fs, row, column, potential_abs_paths, cx) } pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option { diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 6e7fc3f65368b01e5d6fdb0742ffab101ade5081..cd839ae50e82e510ed481bdc9acfdd0b91e76318 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -121,7 +121,7 @@ pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; /// A representation of a path-like string with optional row and column numbers. /// Matching values example: `te`, `test.rs:22`, `te:22:5`, etc. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct PathLikeWithPosition

{ pub path_like: P, pub row: Option, From 1ab0af2fa35d1a576ea1094089f9cd3ba3eceeda Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 3 Feb 2024 18:32:56 +0200 Subject: [PATCH 038/101] Revert the commit that broke Zed display capabilities (#7326) --- crates/gpui/src/app.rs | 13 +++++-- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/window.rs | 20 +++++------ crates/gpui/src/platform/test/window.rs | 2 ++ crates/gpui/src/window.rs | 47 +++++++------------------ 5 files changed, 35 insertions(+), 48 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8d6f70f27e2a25095e1296b8853f4c9cba00cec6..9f924450458bb328c13183116ba8f4b70445aa24 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -652,20 +652,27 @@ impl AppContext { } } } else { + for window in self.windows.values() { + if let Some(window) = window.as_ref() { + if window.dirty { + window.platform_window.invalidate(); + } + } + } + #[cfg(any(test, feature = "test-support"))] for window in self .windows .values() .filter_map(|window| { let window = window.as_ref()?; - window.dirty.get().then_some(window.handle) + (window.dirty || window.focus_invalidated).then_some(window.handle) }) .collect::>() { self.update_window(window, |_, cx| cx.draw()).unwrap(); } - #[allow(clippy::collapsible_else_if)] if self.pending_effects.is_empty() { break; } @@ -742,7 +749,7 @@ impl AppContext { fn apply_refresh_effect(&mut self) { for window in self.windows.values_mut() { if let Some(window) = window.as_mut() { - window.dirty.set(true); + window.dirty = true; } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 62ac543319417207a454f2fa31f987c2289f4ad3..df886fc4d6ad315c91984914c4c7f85bee059d63 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -175,6 +175,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn on_close(&self, callback: Box); fn on_appearance_changed(&self, callback: Box); fn is_topmost_for_position(&self, position: Point) -> bool; + fn invalidate(&self); fn draw(&self, scene: &Scene); fn sprite_atlas(&self) -> Arc; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bb8c08a8794cd616988dd6e385a2a901594c004a..ac3ce4c5758a6e5d1d1b8e67aeb4a3fdcc80a236 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -16,8 +16,8 @@ use cocoa::{ }, base::{id, nil}, foundation::{ - NSArray, NSAutoreleasePool, NSDefaultRunLoopMode, NSDictionary, NSFastEnumeration, - NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger, + NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect, + NSSize, NSString, NSUInteger, }, }; use core_graphics::display::CGRect; @@ -321,7 +321,6 @@ struct MacWindowState { executor: ForegroundExecutor, native_window: id, native_view: NonNull, - display_link: id, renderer: MetalRenderer, kind: WindowKind, request_frame_callback: Option>, @@ -524,10 +523,6 @@ impl MacWindow { let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); - let display_link: id = msg_send![class!(CADisplayLink), displayLinkWithTarget: native_view selector: sel!(displayLayer:)]; - let main_run_loop: id = msg_send![class!(NSRunLoop), mainRunLoop]; - let _: () = - msg_send![display_link, addToRunLoop: main_run_loop forMode: NSDefaultRunLoopMode]; assert!(!native_view.is_null()); let window = Self(Arc::new(Mutex::new(MacWindowState { @@ -535,7 +530,6 @@ impl MacWindow { executor, native_window, native_view: NonNull::new_unchecked(native_view as *mut _), - display_link, renderer: MetalRenderer::new(instance_buffer_pool), kind: options.kind, request_frame_callback: None, @@ -694,9 +688,6 @@ impl Drop for MacWindow { fn drop(&mut self) { let this = self.0.lock(); let window = this.native_window; - unsafe { - let _: () = msg_send![this.display_link, invalidate]; - } this.executor .spawn(async move { unsafe { @@ -1010,6 +1001,13 @@ impl PlatformWindow for MacWindow { } } + fn invalidate(&self) { + let this = self.0.lock(); + unsafe { + let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES]; + } + } + fn draw(&self, scene: &crate::Scene) { let mut this = self.0.lock(); this.renderer.draw(scene); diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index d56851c8b9909af2ffc1fe73e8cac8047458f8fc..d5d62e0155513bcdf57e2a77905e623b7007cd73 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -282,6 +282,8 @@ impl PlatformWindow for TestWindow { unimplemented!() } + fn invalidate(&self) {} + fn draw(&self, _scene: &crate::Scene) {} fn sprite_atlas(&self) -> sync::Arc { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 19ed1c90993e36c846ced747716ec2035e9954b8..8b10f5d8f44606afb5930ab87471910ccee9f5a5 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -22,7 +22,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - cell::{Cell, RefCell}, + cell::RefCell, collections::hash_map::Entry, fmt::{Debug, Display}, future::Future, @@ -34,7 +34,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::{Duration, Instant}, + time::Duration, }; use util::{measure, ResultExt}; @@ -272,8 +272,7 @@ pub struct Window { appearance: WindowAppearance, appearance_observers: SubscriberSet<(), AnyObserver>, active: bool, - pub(crate) dirty: Rc>, - pub(crate) last_input_timestamp: Rc>, + pub(crate) dirty: bool, pub(crate) refreshing: bool, pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, @@ -340,28 +339,13 @@ impl Window { let bounds = platform_window.bounds(); let appearance = platform_window.appearance(); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); - let dirty = Rc::new(Cell::new(false)); - let last_input_timestamp = Rc::new(Cell::new(Instant::now())); platform_window.on_request_frame(Box::new({ let mut cx = cx.to_async(); - let dirty = dirty.clone(); - let last_input_timestamp = last_input_timestamp.clone(); move || { - if dirty.get() { - measure("frame duration", || { - handle - .update(&mut cx, |_, cx| { - cx.draw(); - cx.present(); - }) - .log_err(); - }) - } else if last_input_timestamp.get().elapsed() < Duration::from_secs(2) { - // Keep presenting the current scene for 2 extra seconds since the - // last input to prevent the display from underclocking the refresh rate. - handle.update(&mut cx, |_, cx| cx.present()).log_err(); - } + measure("frame duration", || { + handle.update(&mut cx, |_, cx| cx.draw()).log_err(); + }) } })); platform_window.on_resize(Box::new({ @@ -440,8 +424,7 @@ impl Window { appearance, appearance_observers: SubscriberSet::new(), active: false, - dirty, - last_input_timestamp, + dirty: false, refreshing: false, drawing: false, activation_observers: SubscriberSet::new(), @@ -499,7 +482,7 @@ impl<'a> WindowContext<'a> { pub fn refresh(&mut self) { if !self.window.drawing { self.window.refreshing = true; - self.window.dirty.set(true); + self.window.dirty = true; } } @@ -967,10 +950,9 @@ impl<'a> WindowContext<'a> { &self.window.next_frame.z_index_stack } - /// Produces a new frame and assigns it to `rendered_frame`. To actually show - /// the contents of the new [Scene], use [present]. + /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) { - self.window.dirty.set(false); + self.window.dirty = false; self.window.drawing = true; if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() @@ -1106,19 +1088,16 @@ impl<'a> WindowContext<'a> { .clone() .retain(&(), |listener| listener(&event, self)); } - self.window.refreshing = false; - self.window.drawing = false; - } - fn present(&self) { self.window .platform_window .draw(&self.window.rendered_frame.scene); + self.window.refreshing = false; + self.window.drawing = false; } /// Dispatch a mouse or keyboard event on the window. pub fn dispatch_event(&mut self, event: PlatformInput) -> bool { - self.window.last_input_timestamp.set(Instant::now()); // Handlers may set this to false by calling `stop_propagation`. self.app.propagate_event = true; // Handlers may set this to true by calling `prevent_default`. @@ -2062,7 +2041,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } if !self.window.drawing { - self.window_cx.window.dirty.set(true); + self.window_cx.window.dirty = true; self.window_cx.app.push_effect(Effect::Notify { emitter: self.view.model.entity_id, }); From c9a53b63a762ac6ca4a8bacfa3ff09409228bff2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 3 Feb 2024 18:52:03 +0200 Subject: [PATCH 039/101] Fix the compilation error (#7328) Follow-up of https://github.com/zed-industries/zed/pull/7326 Release Notes: - N/A --- crates/gpui/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9f924450458bb328c13183116ba8f4b70445aa24..4101c0b4cf6049f8cbd60f280b8e9c9941685d4f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -666,7 +666,7 @@ impl AppContext { .values() .filter_map(|window| { let window = window.as_ref()?; - (window.dirty || window.focus_invalidated).then_some(window.handle) + window.dirty.then_some(window.handle) }) .collect::>() { From 55185c159ba236a8b071fa4d915091343267731a Mon Sep 17 00:00:00 2001 From: Rashid Almheiri <69181766+huwaireb@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:35:57 +0400 Subject: [PATCH 040/101] Support documentation as a resolvable property (#7306) Closes #7288 Screenshot 2024-02-03 at 01 56 14 Release Notes: - Improved LSP `completionItem/resolve` request by supporting `documentation` as a resolvable property --- crates/lsp/src/lsp.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b1582fbd8d373eb1963afa2ba8489202f4d6e2e5..e4b95bfb21654e2aadef3cb5932515c53bc40270 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -560,7 +560,10 @@ impl LanguageServer { completion_item: Some(CompletionItemCapability { snippet_support: Some(true), resolve_support: Some(CompletionItemCapabilityResolveSupport { - properties: vec!["additionalTextEdits".to_string()], + properties: vec![ + "documentation".to_string(), + "additionalTextEdits".to_string(), + ], }), ..Default::default() }), From 8da6e629143715196cb4755b4969c24a30623373 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Sat, 3 Feb 2024 23:40:54 +0300 Subject: [PATCH 041/101] Editor toolbar configuration (#7338) Adds settings for hiding breadcrumbs and quick action bar from the editor toolbar. If both elements are hidden, the toolbar disappears completely. Example: ```json "toolbar": { "breadcrumbs": true, "quick_actions": false } ``` - It intentionally doesn't hide breadcrumbs in other views (for instance, in the search result window) because their usage there differ from the main editor. - The editor controls how breadcrumbs are displayed in the toolbar, so implementation differs a bit for breadcrumbs and quick actions bar. Release Notes: - Added support for configuring the editor toolbar ([4756](https://github.com/zed-industries/zed/issues/4756)) --- Cargo.lock | 1 + assets/settings/default.json | 7 ++ crates/editor/src/editor.rs | 7 +- crates/editor/src/editor_settings.rs | 22 +++++ crates/editor/src/items.rs | 6 +- crates/quick_action_bar/Cargo.toml | 1 + .../quick_action_bar/src/quick_action_bar.rs | 88 ++++++++++++------- crates/workspace/src/toolbar.rs | 2 +- crates/zed/src/zed.rs | 2 +- docs/src/configuring_zed.md | 17 ++++ 10 files changed, 114 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 242e3d4704540ecc6f49f7d16fbc00a471bf3a97..ad58e4442533570bfc90c1fbefb5c70a6b1f1422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6064,6 +6064,7 @@ dependencies = [ "editor", "gpui", "search", + "settings", "ui", "workspace", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index 3e1e35cc47d3037750156fc2008fbb080a200248..9387c58f1dce90c4c592032ebbc28069947731f0 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -109,6 +109,13 @@ // Share your project when you are the first to join a channel "share_on_join": true }, + // Toolbar related settings + "toolbar": { + // Whether to show breadcrumbs. + "breadcrumbs": true, + // Whether to show quick action buttons. + "quick_actions": true + }, // Scrollbar related settings "scrollbar": { // When to show the scrollbar in the editor. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c84dd0fe9cad2059969aa7e82207f97f4eea2d2..9039cc4cf03c2cc62a523ebf0f353880eff9bf4c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -374,6 +374,7 @@ pub struct Editor { hovered_cursors: HashMap>, pub show_local_selections: bool, mode: EditorMode, + show_breadcrumbs: bool, show_gutter: bool, show_wrap_guides: Option, placeholder_text: Option>, @@ -1448,6 +1449,7 @@ impl Editor { blink_manager: blink_manager.clone(), show_local_selections: true, mode, + show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs, show_gutter: mode == EditorMode::Full, show_wrap_guides: None, placeholder_text: None, @@ -8762,8 +8764,9 @@ impl Editor { )), cx, ); - self.scroll_manager.vertical_scroll_margin = - EditorSettings::get_global(cx).vertical_scroll_margin; + let editor_settings = EditorSettings::get_global(cx); + self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin; + self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs; cx.notify(); } diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index ddbcd6e4a009c31119ca9e1779ad0ce2614a1c38..074003492fa02ea828f09b729143ba279c156e2c 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -10,6 +10,7 @@ pub struct EditorSettings { pub show_completion_documentation: bool, pub completion_documentation_secondary_query_debounce: u64, pub use_on_type_format: bool, + pub toolbar: Toolbar, pub scrollbar: Scrollbar, pub vertical_scroll_margin: f32, pub relative_line_numbers: bool, @@ -29,6 +30,12 @@ pub enum SeedQuerySetting { Never, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct Toolbar { + pub breadcrumbs: bool, + pub quick_actions: bool, +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Scrollbar { pub show: ShowScrollbar, @@ -86,6 +93,8 @@ pub struct EditorSettingsContent { /// /// Default: true pub use_on_type_format: Option, + /// Toolbar related settings + pub toolbar: Option, /// Scrollbar related settings pub scrollbar: Option, @@ -110,6 +119,19 @@ pub struct EditorSettingsContent { pub redact_private_values: Option, } +// Toolbar related settings +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct ToolbarContent { + /// Whether to display breadcrumbs in the editor toolbar. + /// + /// Default: true + pub breadcrumbs: Option, + /// Whether to display quik action buttons in the editor toolbar. + /// + /// Default: true + pub quick_actions: Option, +} + /// Scrollbar related settings #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarContent { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e95d958db5a7f9eddf3c33d5616f3039c29fc811..418307df82db6e34134c385829f548e4c2fb31bc 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -800,7 +800,11 @@ impl Item for Editor { } fn breadcrumb_location(&self) -> ToolbarItemLocation { - ToolbarItemLocation::PrimaryLeft + if self.show_breadcrumbs { + ToolbarItemLocation::PrimaryLeft + } else { + ToolbarItemLocation::Hidden + } } fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option> { diff --git a/crates/quick_action_bar/Cargo.toml b/crates/quick_action_bar/Cargo.toml index 56c92be8a1c2bc613bdd89b4d7bc36e9a9fa3100..07e081acd39a5a711758808287ceeb9b14bc3e1b 100644 --- a/crates/quick_action_bar/Cargo.toml +++ b/crates/quick_action_bar/Cargo.toml @@ -14,6 +14,7 @@ assistant = { path = "../assistant" } editor = { path = "../editor" } gpui = { path = "../gpui" } search = { path = "../search" } +settings = { path = "../settings" } ui = { path = "../ui" } workspace = { path = "../workspace" } diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index 3e49328c133231ef06bad3123467cd25af4fe97a..b7c783b344b17c0342aad03a5a4211e8c8caec56 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -1,11 +1,12 @@ use assistant::{AssistantPanel, InlineAssist}; -use editor::Editor; +use editor::{Editor, EditorSettings}; use gpui::{ Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView, }; use search::{buffer_search, BufferSearchBar}; +use settings::{Settings, SettingsStore}; use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip}; use workspace::{ item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -16,16 +17,26 @@ pub struct QuickActionBar { active_item: Option>, _inlay_hints_enabled_subscription: Option, workspace: WeakView, + show: bool, } impl QuickActionBar { - pub fn new(buffer_search_bar: View, workspace: &Workspace) -> Self { - Self { + pub fn new( + buffer_search_bar: View, + workspace: &Workspace, + cx: &mut ViewContext, + ) -> Self { + let mut this = Self { buffer_search_bar, active_item: None, _inlay_hints_enabled_subscription: None, workspace: workspace.weak_handle(), - } + show: true, + }; + this.apply_settings(cx); + cx.observe_global::(|this, cx| this.apply_settings(cx)) + .detach(); + this } fn active_editor(&self) -> Option> { @@ -33,6 +44,24 @@ impl QuickActionBar { .as_ref() .and_then(|item| item.downcast::()) } + + fn apply_settings(&mut self, cx: &mut ViewContext) { + let new_show = EditorSettings::get_global(cx).toolbar.quick_actions; + if new_show != self.show { + self.show = new_show; + cx.emit(ToolbarItemEvent::ChangeLocation( + self.get_toolbar_item_location(), + )); + } + } + + fn get_toolbar_item_location(&self) -> ToolbarItemLocation { + if self.show && self.active_editor().is_some() { + ToolbarItemLocation::PrimaryRight + } else { + ToolbarItemLocation::Hidden + } + } } impl Render for QuickActionBar { @@ -40,7 +69,6 @@ impl Render for QuickActionBar { let Some(editor) = self.active_editor() else { return div().id("empty quick action bar"); }; - let inlay_hints_button = Some(QuickActionBarButton::new( "toggle inlay hints", IconName::InlayHint, @@ -155,36 +183,28 @@ impl ToolbarItemView for QuickActionBar { active_pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) -> ToolbarItemLocation { - match active_pane_item { - Some(active_item) => { - self.active_item = Some(active_item.boxed_clone()); - self._inlay_hints_enabled_subscription.take(); - - if let Some(editor) = active_item.downcast::() { - let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled(); - let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx); - self._inlay_hints_enabled_subscription = - Some(cx.observe(&editor, move |_, editor, cx| { - let editor = editor.read(cx); - let new_inlay_hints_enabled = editor.inlay_hints_enabled(); - let new_supports_inlay_hints = editor.supports_inlay_hints(cx); - let should_notify = inlay_hints_enabled != new_inlay_hints_enabled - || supports_inlay_hints != new_supports_inlay_hints; - inlay_hints_enabled = new_inlay_hints_enabled; - supports_inlay_hints = new_supports_inlay_hints; - if should_notify { - cx.notify() - } - })); - ToolbarItemLocation::PrimaryRight - } else { - ToolbarItemLocation::Hidden - } - } - None => { - self.active_item = None; - ToolbarItemLocation::Hidden + self.active_item = active_pane_item.map(ItemHandle::boxed_clone); + if let Some(active_item) = active_pane_item { + self._inlay_hints_enabled_subscription.take(); + + if let Some(editor) = active_item.downcast::() { + let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled(); + let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx); + self._inlay_hints_enabled_subscription = + Some(cx.observe(&editor, move |_, editor, cx| { + let editor = editor.read(cx); + let new_inlay_hints_enabled = editor.inlay_hints_enabled(); + let new_supports_inlay_hints = editor.supports_inlay_hints(cx); + let should_notify = inlay_hints_enabled != new_inlay_hints_enabled + || supports_inlay_hints != new_supports_inlay_hints; + inlay_hints_enabled = new_inlay_hints_enabled; + supports_inlay_hints = new_supports_inlay_hints; + if should_notify { + cx.notify() + } + })); } } + self.get_toolbar_item_location() } } diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index b127de8de5bea535658750e95bc181d5883cc1e2..d2b042668ed1d6721a5e4347b6c3ce321d93b657 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -127,7 +127,7 @@ impl Render for Toolbar { h_flex() // We're using `flex_none` here to prevent some flickering that can occur when the // size of the left items container changes. - .flex_none() + .when_else(has_left_items, Div::flex_none, Div::flex_auto) .justify_end() .children(self.right_items().map(|item| item.to_any())), ) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d48b9438229071e2c121dfb9b2fb3fac6cf3ae46..443adac50d254a4014561ae666b701d8d7faf286 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -353,7 +353,7 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View, cx: &mut ViewCo toolbar.add_item(buffer_search_bar.clone(), cx); let quick_action_bar = - cx.new_view(|_| QuickActionBar::new(buffer_search_bar, workspace)); + cx.new_view(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx)); toolbar.add_item(quick_action_bar, cx); let diagnostic_editor_controls = cx.new_view(|_| diagnostics::ToolbarControls::new()); toolbar.add_item(diagnostic_editor_controls, cx); diff --git a/docs/src/configuring_zed.md b/docs/src/configuring_zed.md index 173fab8d690a11ec2bee5c339565ddd819232740..0a72e384ce0095f26e1c51a06894f5211bf8db3a 100644 --- a/docs/src/configuring_zed.md +++ b/docs/src/configuring_zed.md @@ -190,6 +190,23 @@ List of `string` values 2. Position the dock to the right of the workspace like a side panel: `right` 3. Position the dock full screen over the entire workspace: `expanded` +## Editor Toolbar + +- Description: Whether or not to show various elements in the editor toolbar. +- Setting: `toolbar` +- Default: + +```json +"toolbar": { + "breadcrumbs": true, + "quick_actions": true +}, +``` + +**Options** + +Each option controls displaying of a particular toolbar element. If all elements are hidden, the editor toolbar is not displayed. + ## Enable Language Server - Description: Whether or not to use language servers to provide code intelligence. From ae2c23bd8e6a5bc34d9e87f1429b31187c97d8de Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 3 Feb 2024 16:33:08 -0700 Subject: [PATCH 042/101] Reintroduce ProMotion support (#7347) This re-introduces the changes of #7305 but this time we create a display link using the `NSScreen` associated with the window. We're hoping we'll get these frame requests more reliably, and this seems supported by the fact that awakening my laptop restores the frame requests. Release Notes: - See #7305. Co-authored-by: Nathan --- crates/gpui/src/app.rs | 13 ++--- crates/gpui/src/platform.rs | 1 - crates/gpui/src/platform/mac/window.rs | 66 ++++++++++++++++++++----- crates/gpui/src/platform/test/window.rs | 2 - crates/gpui/src/window.rs | 47 +++++++++++++----- 5 files changed, 90 insertions(+), 39 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 4101c0b4cf6049f8cbd60f280b8e9c9941685d4f..8d6f70f27e2a25095e1296b8853f4c9cba00cec6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -652,27 +652,20 @@ impl AppContext { } } } else { - for window in self.windows.values() { - if let Some(window) = window.as_ref() { - if window.dirty { - window.platform_window.invalidate(); - } - } - } - #[cfg(any(test, feature = "test-support"))] for window in self .windows .values() .filter_map(|window| { let window = window.as_ref()?; - window.dirty.then_some(window.handle) + window.dirty.get().then_some(window.handle) }) .collect::>() { self.update_window(window, |_, cx| cx.draw()).unwrap(); } + #[allow(clippy::collapsible_else_if)] if self.pending_effects.is_empty() { break; } @@ -749,7 +742,7 @@ impl AppContext { fn apply_refresh_effect(&mut self) { for window in self.windows.values_mut() { if let Some(window) = window.as_mut() { - window.dirty = true; + window.dirty.set(true); } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index df886fc4d6ad315c91984914c4c7f85bee059d63..62ac543319417207a454f2fa31f987c2289f4ad3 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -175,7 +175,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn on_close(&self, callback: Box); fn on_appearance_changed(&self, callback: Box); fn is_topmost_for_position(&self, position: Point) -> bool; - fn invalidate(&self); fn draw(&self, scene: &Scene); fn sprite_atlas(&self) -> Arc; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index ac3ce4c5758a6e5d1d1b8e67aeb4a3fdcc80a236..eb66954f44d992166a8b9fda2ab8a6f797387c7f 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -16,8 +16,8 @@ use cocoa::{ }, base::{id, nil}, foundation::{ - NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect, - NSSize, NSString, NSUInteger, + NSArray, NSAutoreleasePool, NSDefaultRunLoopMode, NSDictionary, NSFastEnumeration, + NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger, }, }; use core_graphics::display::CGRect; @@ -168,6 +168,7 @@ unsafe fn build_classes() { sel!(displayLayer:), display_layer as extern "C" fn(&Object, Sel, id), ); + decl.add_method(sel!(step:), step as extern "C" fn(&Object, Sel, id)); decl.add_protocol(Protocol::get("NSTextInputClient").unwrap()); decl.add_method( @@ -260,6 +261,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowDidMove:), window_did_move as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowDidChangeScreen:), + window_did_change_screen as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowDidBecomeKey:), window_did_change_key_status as extern "C" fn(&Object, Sel, id), @@ -320,7 +325,8 @@ struct MacWindowState { handle: AnyWindowHandle, executor: ForegroundExecutor, native_window: id, - native_view: NonNull, + native_view: NonNull, + display_link: id, renderer: MetalRenderer, kind: WindowKind, request_frame_callback: Option>, @@ -522,14 +528,16 @@ impl MacWindow { let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); - assert!(!native_view.is_null()); + let display_link = start_display_link(native_window, native_view); + let window = Self(Arc::new(Mutex::new(MacWindowState { handle, executor, native_window, - native_view: NonNull::new_unchecked(native_view as *mut _), + native_view: NonNull::new_unchecked(native_view), + display_link, renderer: MetalRenderer::new(instance_buffer_pool), kind: options.kind, request_frame_callback: None, @@ -664,6 +672,7 @@ impl MacWindow { } window.0.lock().move_traffic_light(); + pool.drain(); window @@ -684,10 +693,19 @@ impl MacWindow { } } +unsafe fn start_display_link(native_screen: id, native_view: id) -> id { + let display_link: id = + msg_send![native_screen, displayLinkWithTarget: native_view selector: sel!(step:)]; + let main_run_loop: id = msg_send![class!(NSRunLoop), mainRunLoop]; + let _: () = msg_send![display_link, addToRunLoop: main_run_loop forMode: NSDefaultRunLoopMode]; + display_link +} + impl Drop for MacWindow { fn drop(&mut self) { - let this = self.0.lock(); + let mut this = self.0.lock(); let window = this.native_window; + this.display_link = nil; this.executor .spawn(async move { unsafe { @@ -1001,13 +1019,6 @@ impl PlatformWindow for MacWindow { } } - fn invalidate(&self) { - let this = self.0.lock(); - unsafe { - let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES]; - } - } - fn draw(&self, scene: &crate::Scene) { let mut this = self.0.lock(); this.renderer.draw(scene); @@ -1354,6 +1365,19 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { } } +extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut lock = window_state.as_ref().lock(); + unsafe { + let screen = lock.native_window.screen(); + if screen != nil { + lock.display_link = start_display_link(screen, lock.native_view.as_ptr()); + } else { + lock.display_link = nil; + } + } +} + extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; let lock = window_state.lock(); @@ -1503,6 +1527,22 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) { } } +extern "C" fn step(this: &Object, _: Sel, display_link: id) { + let window_state = unsafe { get_window_state(this) }; + let mut lock = window_state.lock(); + if lock.display_link == display_link { + if let Some(mut callback) = lock.request_frame_callback.take() { + drop(lock); + callback(); + window_state.lock().request_frame_callback = Some(callback); + } + } else { + unsafe { + let _: () = msg_send![display_link, invalidate]; + } + } +} + extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id { unsafe { msg_send![class!(NSArray), array] } } diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index d5d62e0155513bcdf57e2a77905e623b7007cd73..d56851c8b9909af2ffc1fe73e8cac8047458f8fc 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -282,8 +282,6 @@ impl PlatformWindow for TestWindow { unimplemented!() } - fn invalidate(&self) {} - fn draw(&self, _scene: &crate::Scene) {} fn sprite_atlas(&self) -> sync::Arc { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8b10f5d8f44606afb5930ab87471910ccee9f5a5..1cfd01b52e2d43546f6dbc3a6424a2a6b7fa1e83 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -22,7 +22,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, - cell::RefCell, + cell::{Cell, RefCell}, collections::hash_map::Entry, fmt::{Debug, Display}, future::Future, @@ -34,7 +34,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::Duration, + time::{Duration, Instant}, }; use util::{measure, ResultExt}; @@ -272,7 +272,8 @@ pub struct Window { appearance: WindowAppearance, appearance_observers: SubscriberSet<(), AnyObserver>, active: bool, - pub(crate) dirty: bool, + pub(crate) dirty: Rc>, + pub(crate) last_input_timestamp: Rc>, pub(crate) refreshing: bool, pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, @@ -339,13 +340,28 @@ impl Window { let bounds = platform_window.bounds(); let appearance = platform_window.appearance(); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); + let dirty = Rc::new(Cell::new(false)); + let last_input_timestamp = Rc::new(Cell::new(Instant::now())); platform_window.on_request_frame(Box::new({ let mut cx = cx.to_async(); + let dirty = dirty.clone(); + let last_input_timestamp = last_input_timestamp.clone(); move || { - measure("frame duration", || { - handle.update(&mut cx, |_, cx| cx.draw()).log_err(); - }) + if dirty.get() { + measure("frame duration", || { + handle + .update(&mut cx, |_, cx| { + cx.draw(); + cx.present(); + }) + .log_err(); + }) + } else if last_input_timestamp.get().elapsed() < Duration::from_secs(1) { + // Keep presenting the current scene for 1 extra second since the + // last input to prevent the display from underclocking the refresh rate. + handle.update(&mut cx, |_, cx| cx.present()).log_err(); + } } })); platform_window.on_resize(Box::new({ @@ -424,7 +440,8 @@ impl Window { appearance, appearance_observers: SubscriberSet::new(), active: false, - dirty: false, + dirty, + last_input_timestamp, refreshing: false, drawing: false, activation_observers: SubscriberSet::new(), @@ -482,7 +499,7 @@ impl<'a> WindowContext<'a> { pub fn refresh(&mut self) { if !self.window.drawing { self.window.refreshing = true; - self.window.dirty = true; + self.window.dirty.set(true); } } @@ -950,9 +967,10 @@ impl<'a> WindowContext<'a> { &self.window.next_frame.z_index_stack } - /// Draw pixels to the display for this window based on the contents of its scene. + /// Produces a new frame and assigns it to `rendered_frame`. To actually show + /// the contents of the new [Scene], use [present]. pub(crate) fn draw(&mut self) { - self.window.dirty = false; + self.window.dirty.set(false); self.window.drawing = true; if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() @@ -1088,16 +1106,19 @@ impl<'a> WindowContext<'a> { .clone() .retain(&(), |listener| listener(&event, self)); } + self.window.refreshing = false; + self.window.drawing = false; + } + fn present(&self) { self.window .platform_window .draw(&self.window.rendered_frame.scene); - self.window.refreshing = false; - self.window.drawing = false; } /// Dispatch a mouse or keyboard event on the window. pub fn dispatch_event(&mut self, event: PlatformInput) -> bool { + self.window.last_input_timestamp.set(Instant::now()); // Handlers may set this to false by calling `stop_propagation`. self.app.propagate_event = true; // Handlers may set this to true by calling `prevent_default`. @@ -2041,7 +2062,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } if !self.window.drawing { - self.window_cx.window.dirty = true; + self.window_cx.window.dirty.set(true); self.window_cx.app.push_effect(Effect::Notify { emitter: self.view.model.entity_id, }); From ac74a72a9ef80a6cc50e7b001e163e76fbd9fa3d Mon Sep 17 00:00:00 2001 From: Andrey Kuzmin Date: Sun, 4 Feb 2024 00:57:24 +0100 Subject: [PATCH 043/101] Improve elm-language-server configuration (#7342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hi folks! @absynce and I paired a bit to improve the `elm-language-server` configuration. We have realised that sometimes `elm-language-server` settings were being reset to default. We had been configuring `elm-language-server` like this: ```json "lsp": { "elm-language-server": { "initialization_options": { "disableElmLSDiagnostics": true, "onlyUpdateDiagnosticsOnSave": true, "elmReviewDiagnostics": "warning" } } } ``` And then we noticed that the following communication happened: ``` // Send: {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{}}} // Receive: {"jsonrpc":"2.0","id":5,"method":"workspace/configuration","params":{"items":[{"section":"elmLS"}]}} // Send: {"jsonrpc":"2.0","id":5,"result":[null],"error":null} ``` In `elm-language-server` the settings from `didChangeConfiguration` [replace the initial settings](https://github.com/elm-tooling/elm-language-server/blob/edd68133883f3902f8940a6e5e2834a9cd627dc1/src/common/providers/diagnostics/diagnosticsProvider.ts#L188). Setting the value to `{}` effectively resets the configuration options to defaults. In Zed, `initialization_options` and `workspace_configuration` are two different things, but in `elm-language-server` they are coupled. Additionally, `elm-language-server` is requesting workspace configuration for the `elmLS` section that doesn't exist. This PR: 1. Fixes settings reset on `didChangeConfiguration` by populating `workspace_configuration` from `initialization_options` 2. Makes workspace configuration requests work by inserting an extra copy of the settings under the `elmLS` key in `workspace_configuration` — this is a bit ugly, but we're not sure how to make both kinds of configuration messages work in the current setup. This is how communication looks like after the proposed changes: ``` // Send: { "jsonrpc": "2.0", "method": "workspace/didChangeConfiguration", "params": { "settings": { "disableElmLSDiagnostics": true, "onlyUpdateDiagnosticsOnSave": true, "elmReviewDiagnostics": "warning", "elmLS": { "disableElmLSDiagnostics": true, "onlyUpdateDiagnosticsOnSave": true, "elmReviewDiagnostics": "warning" } } } } // Receive: { "jsonrpc": "2.0", "id": 4, "method": "workspace/configuration", "params": { "items": [ { "section": "elmLS" } ] } } // Send: { "jsonrpc": "2.0", "id": 4, "result": [ { "disableElmLSDiagnostics": true, "onlyUpdateDiagnosticsOnSave": true, "elmReviewDiagnostics": "warning" } ], "error": null } ``` Things we have considered: 1. Extracting the `elm-language-server` settings into a separate section: we haven't found this being widely used in Zed, seems that all language server configuration should fall under the top level `lsp` section 2. Changing the way `elm-language-server` configuration works: `elm-language-server` has got integrations with multiple editors, changing the configuration behaviour would mean updating all the existing integrations. Plus we are not exactly sure if it's doing anything wrong. Release Notes: - Improved elm-language-server configuration options Co-authored-by: Jared M. Smith --- crates/zed/src/languages/elm.rs | 28 +++++++++++++++++++++++++++- docs/src/languages/elm.md | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/languages/elm.rs b/crates/zed/src/languages/elm.rs index 6eb56451026c3df5825c12b1149308efeaead2c5..9c50184498de36c153f4503288c513a3de272c66 100644 --- a/crates/zed/src/languages/elm.rs +++ b/crates/zed/src/languages/elm.rs @@ -1,9 +1,13 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; +use gpui::AppContext; use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; +use project::project_settings::ProjectSettings; +use serde_json::Value; +use settings::Settings; use smol::fs; use std::{ any::Any, @@ -13,6 +17,7 @@ use std::{ }; use util::ResultExt; +const SERVER_NAME: &'static str = "elm-language-server"; const SERVER_PATH: &'static str = "node_modules/@elm-tooling/elm-language-server/out/node/index.js"; fn server_binary_arguments(server_path: &Path) -> Vec { @@ -32,7 +37,7 @@ impl ElmLspAdapter { #[async_trait] impl LspAdapter for ElmLspAdapter { fn name(&self) -> LanguageServerName { - LanguageServerName("elm-language-server".into()) + LanguageServerName(SERVER_NAME.into()) } fn short_name(&self) -> &'static str { @@ -88,6 +93,27 @@ impl LspAdapter for ElmLspAdapter { ) -> Option { get_cached_server_binary(container_dir, &*self.node).await } + + fn workspace_configuration(&self, _workspace_root: &Path, cx: &mut AppContext) -> Value { + // elm-language-server expects workspace didChangeConfiguration notification + // params to be the same as lsp initialization_options + let override_options = ProjectSettings::get_global(cx) + .lsp + .get(SERVER_NAME) + .and_then(|s| s.initialization_options.clone()) + .unwrap_or_default(); + + match override_options.clone().as_object_mut() { + Some(op) => { + // elm-language-server requests workspace configuration + // for the `elmLS` section, so we have to nest + // another copy of initialization_options there + op.insert("elmLS".into(), override_options); + serde_json::to_value(op).unwrap_or_default() + } + None => override_options, + } + } } async fn get_cached_server_binary( diff --git a/docs/src/languages/elm.md b/docs/src/languages/elm.md index ec6aa46c7a4d18238fc6507a9b128a3b30e2302b..02bd4a88a6481878ee1529f98f6f2d08d0e860b7 100644 --- a/docs/src/languages/elm.md +++ b/docs/src/languages/elm.md @@ -2,3 +2,24 @@ - Tree Sitter: [tree-sitter-elm](https://github.com/elm-tooling/tree-sitter-elm) - Language Server: [elm-language-server](https://github.com/elm-tooling/elm-language-server) + +### Setting up `elm-language-server` + +Elm language server can be configured in your `settings.json`, e.g.: + +```json +{ + "lsp": { + "elm-language-server": { + "initialization_options": { + "disableElmLSDiagnostics": true, + "onlyUpdateDiagnosticsOnSave": false, + "elmReviewDiagnostics": "warning" + } + } + } +} +``` + +`elm-format`, `elm-review` and `elm` need to be installed and made available in the environment +or configured in the settings. See the [full list of server settings here](https://github.com/elm-tooling/elm-language-server?tab=readme-ov-file#server-settings). From 87d3f5951529fd41aaabadad8c8aeaddd33422ec Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 5 Feb 2024 11:43:16 +0100 Subject: [PATCH 044/101] Do not show completion documentation if disabled (#7372) This fixes #7348 by not rendering completions if they are disabled in the settings. Previously we only checked that setting when starting or not starting background threads to fetch documentation. But in case we already have documentation, this stops it from being rendered. Release Notes: - Fixed documentation in completions showing up even when disabled via `show_completion_documentation` ([#7348](https://github.com/zed-industries/zed/issues/7348)) --- crates/editor/src/editor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9039cc4cf03c2cc62a523ebf0f353880eff9bf4c..e5e5ae9c286abf79577205f9239f9819d8f7b348 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -852,7 +852,7 @@ impl CompletionsMenu { let selected_item = self.selected_item; let style = style.clone(); - let multiline_docs = { + let multiline_docs = if show_completion_documentation { let mat = &self.matches[selected_item]; let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation { Some(Documentation::MultiLinePlainText(text)) => { @@ -883,6 +883,8 @@ impl CompletionsMenu { // because that would move the cursor. .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) }) + } else { + None }; let list = uniform_list( From 0755ce64869ca0a9f2241fe4dc4b08353ef0c575 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:40:12 +0100 Subject: [PATCH 045/101] picker: Outline Editor::new That way crates that use Picker::new do not have to codegen constructor of Editor; tl;dr, 10% of LLVM shaved off of crates like vcs_menu or theme_selector in release mode. --- crates/picker/src/picker.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 3b8788246fed1efc59bc78d871861e3e8b92956f..3db2f135c23b956c940f30b87f58669364c138ee 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -58,13 +58,16 @@ impl FocusableView for Picker { } } +fn create_editor(placeholder: Arc, cx: &mut WindowContext<'_>) -> View { + cx.new_view(|cx| { + let mut editor = Editor::single_line(cx); + editor.set_placeholder_text(placeholder, cx); + editor + }) +} impl Picker { pub fn new(delegate: D, cx: &mut ViewContext) -> Self { - let editor = cx.new_view(|cx| { - let mut editor = Editor::single_line(cx); - editor.set_placeholder_text(delegate.placeholder_text(), cx); - editor - }); + let editor = create_editor(delegate.placeholder_text(), cx); cx.subscribe(&editor, Self::on_input_editor_event).detach(); let mut this = Self { delegate, From 45429a452883da56b87e8238b20bd347f9f0d7ba Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 5 Feb 2024 15:46:48 +0200 Subject: [PATCH 046/101] Fix inlay hints using stale editor data (#7376) Refactor the hint query code to pass along an actual `cx` instead of its potentially stale, cloned version. Release Notes: - Fixed occasional duplicate hints inserted and offset-related panics when concurrently editing the buffer and querying multiple its ranges for hints --- crates/editor/src/inlay_hint_cache.rs | 401 +++++++++++++------------- 1 file changed, 200 insertions(+), 201 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index ae5a0ef9a3115c2f1410c69517a5cdd971b5e62b..714f3d1789095a38df6d7234af76cb00bbddb3ef 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -19,7 +19,7 @@ use crate::{ use anyhow::Context; use clock::Global; use futures::future; -use gpui::{Model, ModelContext, Task, ViewContext}; +use gpui::{AsyncWindowContext, Model, ModelContext, Task, ViewContext}; use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use parking_lot::RwLock; use project::{InlayHint, ResolveState}; @@ -29,7 +29,7 @@ use language::language_settings::InlayHintSettings; use smol::lock::Semaphore; use sum_tree::Bias; use text::{BufferId, ToOffset, ToPoint}; -use util::post_inc; +use util::{post_inc, ResultExt}; pub struct InlayHintCache { hints: HashMap>>, @@ -78,6 +78,7 @@ pub(super) enum InvalidationStrategy { /// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes. /// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead. /// Splice is picked to help avoid extra hint flickering and "jumps" on the screen. +#[derive(Debug)] pub(super) struct InlaySplice { pub to_remove: Vec, pub to_insert: Vec, @@ -619,7 +620,6 @@ fn spawn_new_update_tasks( update_cache_version: usize, cx: &mut ViewContext<'_, Editor>, ) { - let visible_hints = Arc::new(editor.visible_inlay_hints(cx)); for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in excerpts_to_query { @@ -636,8 +636,7 @@ fn spawn_new_update_tasks( continue; } - let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); - if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + if let Some(cached_excerpt_hints) = editor.inlay_hint_cache.hints.get(&excerpt_id) { let cached_excerpt_hints = cached_excerpt_hints.read(); let cached_buffer_version = &cached_excerpt_hints.buffer_version; if cached_excerpt_hints.version > update_cache_version @@ -647,20 +646,15 @@ fn spawn_new_update_tasks( } }; - let (multi_buffer_snapshot, Some(query_ranges)) = - editor.buffer.update(cx, |multi_buffer, cx| { - ( - multi_buffer.snapshot(cx), - determine_query_ranges( - multi_buffer, - excerpt_id, - &excerpt_buffer, - excerpt_visible_range, - cx, - ), - ) - }) - else { + let Some(query_ranges) = editor.buffer.update(cx, |multi_buffer, cx| { + determine_query_ranges( + multi_buffer, + excerpt_id, + &excerpt_buffer, + excerpt_visible_range, + cx, + ) + }) else { return; }; let query = ExcerptQuery { @@ -671,18 +665,8 @@ fn spawn_new_update_tasks( reason, }; - let new_update_task = |query_ranges| { - new_update_task( - query, - query_ranges, - multi_buffer_snapshot, - buffer_snapshot.clone(), - Arc::clone(&visible_hints), - cached_excerpt_hints, - Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter), - cx, - ) - }; + let mut new_update_task = + |query_ranges| new_update_task(query, query_ranges, excerpt_buffer.clone(), cx); match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { hash_map::Entry::Occupied(mut o) => { @@ -790,62 +774,55 @@ const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 400; fn new_update_task( query: ExcerptQuery, query_ranges: QueryRanges, - multi_buffer_snapshot: MultiBufferSnapshot, - buffer_snapshot: BufferSnapshot, - visible_hints: Arc>, - cached_excerpt_hints: Option>>, - lsp_request_limiter: Arc, + excerpt_buffer: Model, cx: &mut ViewContext<'_, Editor>, ) -> Task<()> { - cx.spawn(|editor, mut cx| async move { - let closure_cx = cx.clone(); - let fetch_and_update_hints = |invalidate, range| { - fetch_and_update_hints( - editor.clone(), - multi_buffer_snapshot.clone(), - buffer_snapshot.clone(), - Arc::clone(&visible_hints), - cached_excerpt_hints.as_ref().map(Arc::clone), - query, - invalidate, - range, - Arc::clone(&lsp_request_limiter), - closure_cx.clone(), - ) - }; - let visible_range_update_results = future::join_all(query_ranges.visible.into_iter().map( - |visible_range| async move { - ( - visible_range.clone(), - fetch_and_update_hints(query.invalidate.should_invalidate(), visible_range) - .await, - ) - }, - )) + cx.spawn(move |editor, mut cx| async move { + let visible_range_update_results = future::join_all( + query_ranges + .visible + .into_iter() + .filter_map(|visible_range| { + let fetch_task = editor + .update(&mut cx, |_, cx| { + fetch_and_update_hints( + excerpt_buffer.clone(), + query, + visible_range.clone(), + query.invalidate.should_invalidate(), + cx, + ) + }) + .log_err()?; + Some(async move { (visible_range, fetch_task.await) }) + }), + ) .await; let hint_delay = cx.background_executor().timer(Duration::from_millis( INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS, )); - let mut query_range_failed = |range: &Range, e: anyhow::Error| { - log::error!("inlay hint update task for range {range:?} failed: {e:#}"); - editor - .update(&mut cx, |editor, _| { - if let Some(task_ranges) = editor - .inlay_hint_cache - .update_tasks - .get_mut(&query.excerpt_id) - { - task_ranges.invalidate_range(&buffer_snapshot, &range); - } - }) - .ok() - }; + let query_range_failed = + |range: &Range, e: anyhow::Error, cx: &mut AsyncWindowContext| { + log::error!("inlay hint update task for range {range:?} failed: {e:#}"); + editor + .update(cx, |editor, cx| { + if let Some(task_ranges) = editor + .inlay_hint_cache + .update_tasks + .get_mut(&query.excerpt_id) + { + let buffer_snapshot = excerpt_buffer.read(cx).snapshot(); + task_ranges.invalidate_range(&buffer_snapshot, &range); + } + }) + .ok() + }; for (range, result) in visible_range_update_results { if let Err(e) = result { - query_range_failed(&range, e); + query_range_failed(&range, e, &mut cx); } } @@ -855,149 +832,171 @@ fn new_update_task( .before_visible .into_iter() .chain(query_ranges.after_visible.into_iter()) - .map(|invisible_range| async move { - ( - invisible_range.clone(), - fetch_and_update_hints(false, invisible_range).await, - ) + .filter_map(|invisible_range| { + let fetch_task = editor + .update(&mut cx, |_, cx| { + fetch_and_update_hints( + excerpt_buffer.clone(), + query, + invisible_range.clone(), + false, // visible screen request already invalidated the entries + cx, + ) + }) + .log_err()?; + Some(async move { (invisible_range, fetch_task.await) }) }), ) .await; for (range, result) in invisible_range_update_results { if let Err(e) = result { - query_range_failed(&range, e); + query_range_failed(&range, e, &mut cx); } } }) } -async fn fetch_and_update_hints( - editor: gpui::WeakView, - multi_buffer_snapshot: MultiBufferSnapshot, - buffer_snapshot: BufferSnapshot, - visible_hints: Arc>, - cached_excerpt_hints: Option>>, +fn fetch_and_update_hints( + excerpt_buffer: Model, query: ExcerptQuery, - invalidate: bool, fetch_range: Range, - lsp_request_limiter: Arc, - mut cx: gpui::AsyncWindowContext, -) -> anyhow::Result<()> { - let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() { - (None, false) - } else { - match lsp_request_limiter.try_acquire() { - Some(guard) => (Some(guard), false), - None => (Some(lsp_request_limiter.acquire().await), true), - } - }; - let fetch_range_to_log = - fetch_range.start.to_point(&buffer_snapshot)..fetch_range.end.to_point(&buffer_snapshot); - let inlay_hints_fetch_task = editor - .update(&mut cx, |editor, cx| { - if got_throttled { - let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) { - Some((_, _, current_visible_range)) => { - let visible_offset_length = current_visible_range.len(); - let double_visible_range = current_visible_range - .start - .saturating_sub(visible_offset_length) - ..current_visible_range - .end - .saturating_add(visible_offset_length) - .min(buffer_snapshot.len()); - !double_visible_range - .contains(&fetch_range.start.to_offset(&buffer_snapshot)) - && !double_visible_range - .contains(&fetch_range.end.to_offset(&buffer_snapshot)) - }, - None => true, - }; - if query_not_around_visible_range { - log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping."); - if let Some(task_ranges) = editor - .inlay_hint_cache - .update_tasks - .get_mut(&query.excerpt_id) - { - task_ranges.invalidate_range(&buffer_snapshot, &fetch_range); + invalidate: bool, + cx: &mut ViewContext, +) -> Task> { + cx.spawn(|editor, mut cx| async move { + let buffer_snapshot = excerpt_buffer.update(&mut cx, |buffer, _| buffer.snapshot())?; + let (lsp_request_limiter, multi_buffer_snapshot) = editor.update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let lsp_request_limiter = Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter); + (lsp_request_limiter, multi_buffer_snapshot) + })?; + + let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() { + (None, false) + } else { + match lsp_request_limiter.try_acquire() { + Some(guard) => (Some(guard), false), + None => (Some(lsp_request_limiter.acquire().await), true), + } + }; + let fetch_range_to_log = + fetch_range.start.to_point(&buffer_snapshot)..fetch_range.end.to_point(&buffer_snapshot); + let inlay_hints_fetch_task = editor + .update(&mut cx, |editor, cx| { + if got_throttled { + let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) { + Some((_, _, current_visible_range)) => { + let visible_offset_length = current_visible_range.len(); + let double_visible_range = current_visible_range + .start + .saturating_sub(visible_offset_length) + ..current_visible_range + .end + .saturating_add(visible_offset_length) + .min(buffer_snapshot.len()); + !double_visible_range + .contains(&fetch_range.start.to_offset(&buffer_snapshot)) + && !double_visible_range + .contains(&fetch_range.end.to_offset(&buffer_snapshot)) + }, + None => true, + }; + if query_not_around_visible_range { + log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping."); + if let Some(task_ranges) = editor + .inlay_hint_cache + .update_tasks + .get_mut(&query.excerpt_id) + { + task_ranges.invalidate_range(&buffer_snapshot, &fetch_range); + } + return None; } - return None; } - } + editor + .buffer() + .read(cx) + .buffer(query.buffer_id) + .and_then(|buffer| { + let project = editor.project.as_ref()?; + Some(project.update(cx, |project, cx| { + project.inlay_hints(buffer, fetch_range.clone(), cx) + })) + }) + }) + .ok() + .flatten(); + + let cached_excerpt_hints = editor.update(&mut cx, |editor, _| { editor - .buffer() - .read(cx) - .buffer(query.buffer_id) - .and_then(|buffer| { - let project = editor.project.as_ref()?; - Some(project.update(cx, |project, cx| { - project.inlay_hints(buffer, fetch_range.clone(), cx) - })) - }) - }) - .ok() - .flatten(); - let new_hints = match inlay_hints_fetch_task { - Some(fetch_task) => { - log::debug!( - "Fetching inlay hints for range {fetch_range_to_log:?}, reason: {query_reason}, invalidate: {invalidate}", - query_reason = query.reason, - ); - log::trace!( - "Currently visible hints: {visible_hints:?}, cached hints present: {}", - cached_excerpt_hints.is_some(), - ); - fetch_task.await.context("inlay hint fetch task")? - } - None => return Ok(()), - }; - drop(lsp_request_guard); - log::debug!( - "Fetched {} hints for range {fetch_range_to_log:?}", - new_hints.len() - ); - log::trace!("Fetched hints: {new_hints:?}"); - - let background_task_buffer_snapshot = buffer_snapshot.clone(); - let background_fetch_range = fetch_range.clone(); - let new_update = cx - .background_executor() - .spawn(async move { - calculate_hint_updates( - query.excerpt_id, - invalidate, - background_fetch_range, - new_hints, - &background_task_buffer_snapshot, - cached_excerpt_hints, - &visible_hints, - ) - }) - .await; - if let Some(new_update) = new_update { + .inlay_hint_cache + .hints + .get(&query.excerpt_id) + .cloned() + })?; + + let visible_hints = editor.update(&mut cx, |editor, cx| editor.visible_inlay_hints(cx))?; + let new_hints = match inlay_hints_fetch_task { + Some(fetch_task) => { + log::debug!( + "Fetching inlay hints for range {fetch_range_to_log:?}, reason: {query_reason}, invalidate: {invalidate}", + query_reason = query.reason, + ); + log::trace!( + "Currently visible hints: {visible_hints:?}, cached hints present: {}", + cached_excerpt_hints.is_some(), + ); + fetch_task.await.context("inlay hint fetch task")? + } + None => return Ok(()), + }; + drop(lsp_request_guard); log::debug!( - "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}", - new_update.remove_from_visible.len(), - new_update.remove_from_cache.len(), - new_update.add_to_cache.len() + "Fetched {} hints for range {fetch_range_to_log:?}", + new_hints.len() ); - log::trace!("New update: {new_update:?}"); - editor - .update(&mut cx, |editor, cx| { - apply_hint_update( - editor, - new_update, - query, + log::trace!("Fetched hints: {new_hints:?}"); + + let background_task_buffer_snapshot = buffer_snapshot.clone(); + let background_fetch_range = fetch_range.clone(); + let new_update = cx + .background_executor() + .spawn(async move { + calculate_hint_updates( + query.excerpt_id, invalidate, - buffer_snapshot, - multi_buffer_snapshot, - cx, - ); + background_fetch_range, + new_hints, + &background_task_buffer_snapshot, + cached_excerpt_hints, + &visible_hints, + ) }) - .ok(); - } - Ok(()) + .await; + if let Some(new_update) = new_update { + log::debug!( + "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}", + new_update.remove_from_visible.len(), + new_update.remove_from_cache.len(), + new_update.add_to_cache.len() + ); + log::trace!("New update: {new_update:?}"); + editor + .update(&mut cx, |editor, cx| { + apply_hint_update( + editor, + new_update, + query, + invalidate, + buffer_snapshot, + multi_buffer_snapshot, + cx, + ); + }) + .ok(); + } + anyhow::Ok(()) + }) } fn calculate_hint_updates( @@ -2436,7 +2435,7 @@ pub mod tests { }); } - #[gpui::test(iterations = 10)] + #[gpui::test(iterations = 30)] async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) { init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { From 8030e8cf4751bb6f930aeacbce3f95e824c8bea9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 5 Feb 2024 10:45:41 -0500 Subject: [PATCH 047/101] Improve the contrast of the default `search_match_background` colors (#7382) This PR improves the contrast of the default `search_match_background` colors. Release Notes: - Improved the contrast of the default `search.match_background` colors. --- crates/theme/src/default_colors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 755ece62f98a28238b4d1da97d755e5effa716f2..4e369e917d8f07f69d66526513b71be49f6b3449 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -54,7 +54,7 @@ impl ThemeColors { tab_bar_background: neutral().light().step_2(), tab_inactive_background: neutral().light().step_2(), tab_active_background: neutral().light().step_1(), - search_match_background: neutral().light().step_2(), + search_match_background: neutral().light().step_5(), panel_background: neutral().light().step_2(), panel_focused_border: blue().light().step_5(), pane_focused_border: blue().light().step_5(), @@ -148,7 +148,7 @@ impl ThemeColors { tab_bar_background: neutral().dark().step_2(), tab_inactive_background: neutral().dark().step_2(), tab_active_background: neutral().dark().step_1(), - search_match_background: neutral().dark().step_2(), + search_match_background: neutral().dark().step_5(), panel_background: neutral().dark().step_2(), panel_focused_border: blue().dark().step_5(), pane_focused_border: blue().dark().step_5(), From 3bf412feff7d2d5c7c8609289eb5b521a1befa27 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 5 Feb 2024 10:46:07 -0500 Subject: [PATCH 048/101] Use window's screen rather than window itself to start display link Co-Authored-By: Antonio Scandurra Co-Authored-By: Nathan Sobo --- crates/gpui/src/platform/mac/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index eb66954f44d992166a8b9fda2ab8a6f797387c7f..ca6a3f8d62f799c8dfd1eb791e73a179e48e3397 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -530,7 +530,7 @@ impl MacWindow { let native_view = NSView::init(native_view); assert!(!native_view.is_null()); - let display_link = start_display_link(native_window, native_view); + let display_link = start_display_link(native_window.screen(), native_view); let window = Self(Arc::new(Mutex::new(MacWindowState { handle, From d742b3bfac0c4e11590dd2d57b101099ac280f6e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 5 Feb 2024 09:09:49 -0700 Subject: [PATCH 049/101] Mark the window as dirty when first opening it (#7384) Otherwise we won't display anything if the window never notifies. Release Notes: - N/A Co-authored-by: Nathan --- crates/gpui/src/platform/mac/window.rs | 6 +++--- crates/gpui/src/window.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index ca6a3f8d62f799c8dfd1eb791e73a179e48e3397..946e608a7a686d2680b98b1388c652e2cedcf3f5 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1370,10 +1370,10 @@ extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) { let mut lock = window_state.as_ref().lock(); unsafe { let screen = lock.native_window.screen(); - if screen != nil { - lock.display_link = start_display_link(screen, lock.native_view.as_ptr()); - } else { + if screen == nil { lock.display_link = nil; + } else { + lock.display_link = start_display_link(screen, lock.native_view.as_ptr()); } } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 1cfd01b52e2d43546f6dbc3a6424a2a6b7fa1e83..c9c672557f56368a75079a6565c0e9a434b368b2 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -340,7 +340,7 @@ impl Window { let bounds = platform_window.bounds(); let appearance = platform_window.appearance(); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); - let dirty = Rc::new(Cell::new(false)); + let dirty = Rc::new(Cell::new(true)); let last_input_timestamp = Rc::new(Cell::new(Instant::now())); platform_window.on_request_frame(Box::new({ From 223f45c65bac52dc783ca01038c85218826ed40b Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 5 Feb 2024 17:12:35 +0100 Subject: [PATCH 050/101] Remove default keybinds for navigating between docks/editors (#7381) Turns out that these keybindings are active in Vim *and* in non-Vim mode and they shadow `Ctrl-w` in terminals. We should fix `Ctrl-w` being shadowed, but until then let's remove the default keybindings. Release Notes: - N/A Co-authored-by: Kirill Co-authored-by: Conrad --- assets/keymaps/vim.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 70e900a75df2bbe47053397af94368836efcd918..bf68ee10e4d19a3dbe048dd8eb0f8792c35bef27 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -502,18 +502,5 @@ "enter": "vim::SearchSubmit", "escape": "buffer_search::Dismiss" } - }, - { - "context": "Dock", - "bindings": { - "ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"], - "ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"], - "ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"], - "ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"], - "ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"], - "ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"], - "ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"], - "ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"] - } } ] From 47329f4489a001a8938bf83e6fa9e9e1d83859f9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 5 Feb 2024 11:13:38 -0500 Subject: [PATCH 051/101] Add `search.match_background` colors to bundled themes (#7385) This PR populates the `search.match_background` colors in the bundled themes, using the values from the Zed1 themes. Release Notes: - Added theme-specific `search.match_background` colors to built-in themes. --- assets/themes/andromeda/andromeda.json | 2 +- assets/themes/atelier/atelier.json | 40 ++++++++++++------------ assets/themes/ayu/ayu.json | 6 ++-- assets/themes/gruvbox/gruvbox.json | 12 +++---- assets/themes/one/one.json | 4 +-- assets/themes/rose_pine/rose_pine.json | 6 ++-- assets/themes/sandcastle/sandcastle.json | 2 +- assets/themes/solarized/solarized.json | 4 +-- assets/themes/summercamp/summercamp.json | 2 +- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/assets/themes/andromeda/andromeda.json b/assets/themes/andromeda/andromeda.json index 2fd3417150bb78691b0e28e7ac004b9245a9af35..a9144907cdf30aa6232d070ea25769e0038be355 100644 --- a/assets/themes/andromeda/andromeda.json +++ b/assets/themes/andromeda/andromeda.json @@ -42,7 +42,7 @@ "tab_bar.background": "#21242bff", "tab.inactive_background": "#21242bff", "tab.active_background": "#1e2025ff", - "search.match_background": null, + "search.match_background": "#11a79366", "panel.background": "#21242bff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/atelier/atelier.json b/assets/themes/atelier/atelier.json index 2a857c69b334858b2135d3f336705dddb72a3cc9..7f8bdfbaf3d64b0d30d285412c8c84f5d8a50d99 100644 --- a/assets/themes/atelier/atelier.json +++ b/assets/themes/atelier/atelier.json @@ -42,7 +42,7 @@ "tab_bar.background": "#221f26ff", "tab.inactive_background": "#221f26ff", "tab.active_background": "#19171cff", - "search.match_background": null, + "search.match_background": "#576dda66", "panel.background": "#221f26ff", "panel.focused_border": null, "pane.focused_border": null, @@ -426,7 +426,7 @@ "tab_bar.background": "#e6e3ebff", "tab.inactive_background": "#e6e3ebff", "tab.active_background": "#efecf4ff", - "search.match_background": null, + "search.match_background": "#586dda66", "panel.background": "#e6e3ebff", "panel.focused_border": null, "pane.focused_border": null, @@ -810,7 +810,7 @@ "tab_bar.background": "#262622ff", "tab.inactive_background": "#262622ff", "tab.active_background": "#20201dff", - "search.match_background": null, + "search.match_background": "#6684e066", "panel.background": "#262622ff", "panel.focused_border": null, "pane.focused_border": null, @@ -1194,7 +1194,7 @@ "tab_bar.background": "#eeebd7ff", "tab.inactive_background": "#eeebd7ff", "tab.active_background": "#fefbecff", - "search.match_background": null, + "search.match_background": "#6784e066", "panel.background": "#eeebd7ff", "panel.focused_border": null, "pane.focused_border": null, @@ -1578,7 +1578,7 @@ "tab_bar.background": "#2c2b23ff", "tab.inactive_background": "#2c2b23ff", "tab.active_background": "#22221bff", - "search.match_background": null, + "search.match_background": "#37a16666", "panel.background": "#2c2b23ff", "panel.focused_border": null, "pane.focused_border": null, @@ -1962,7 +1962,7 @@ "tab_bar.background": "#ebeae3ff", "tab.inactive_background": "#ebeae3ff", "tab.active_background": "#f4f3ecff", - "search.match_background": null, + "search.match_background": "#38a16666", "panel.background": "#ebeae3ff", "panel.focused_border": null, "pane.focused_border": null, @@ -2346,7 +2346,7 @@ "tab_bar.background": "#27211eff", "tab.inactive_background": "#27211eff", "tab.active_background": "#1b1918ff", - "search.match_background": null, + "search.match_background": "#417ee666", "panel.background": "#27211eff", "panel.focused_border": null, "pane.focused_border": null, @@ -2730,7 +2730,7 @@ "tab_bar.background": "#e9e6e4ff", "tab.inactive_background": "#e9e6e4ff", "tab.active_background": "#f0eeedff", - "search.match_background": null, + "search.match_background": "#417ee666", "panel.background": "#e9e6e4ff", "panel.focused_border": null, "pane.focused_border": null, @@ -3114,7 +3114,7 @@ "tab_bar.background": "#252025ff", "tab.inactive_background": "#252025ff", "tab.active_background": "#1b181bff", - "search.match_background": null, + "search.match_background": "#526aeb66", "panel.background": "#252025ff", "panel.focused_border": null, "pane.focused_border": null, @@ -3498,7 +3498,7 @@ "tab_bar.background": "#e0d5e0ff", "tab.inactive_background": "#e0d5e0ff", "tab.active_background": "#f7f3f7ff", - "search.match_background": null, + "search.match_background": "#526aeb66", "panel.background": "#e0d5e0ff", "panel.focused_border": null, "pane.focused_border": null, @@ -3882,7 +3882,7 @@ "tab_bar.background": "#1c2529ff", "tab.inactive_background": "#1c2529ff", "tab.active_background": "#161b1dff", - "search.match_background": null, + "search.match_background": "#277fad66", "panel.background": "#1c2529ff", "panel.focused_border": null, "pane.focused_border": null, @@ -4266,7 +4266,7 @@ "tab_bar.background": "#cdeaf9ff", "tab.inactive_background": "#cdeaf9ff", "tab.active_background": "#ebf8ffff", - "search.match_background": null, + "search.match_background": "#277fad66", "panel.background": "#cdeaf9ff", "panel.focused_border": null, "pane.focused_border": null, @@ -4650,7 +4650,7 @@ "tab_bar.background": "#252020ff", "tab.inactive_background": "#252020ff", "tab.active_background": "#1b1818ff", - "search.match_background": null, + "search.match_background": "#7272ca66", "panel.background": "#252020ff", "panel.focused_border": null, "pane.focused_border": null, @@ -5034,7 +5034,7 @@ "tab_bar.background": "#ebe3e3ff", "tab.inactive_background": "#ebe3e3ff", "tab.active_background": "#f4ececff", - "search.match_background": null, + "search.match_background": "#7372ca66", "panel.background": "#ebe3e3ff", "panel.focused_border": null, "pane.focused_border": null, @@ -5418,7 +5418,7 @@ "tab_bar.background": "#1f2621ff", "tab.inactive_background": "#1f2621ff", "tab.active_background": "#171c19ff", - "search.match_background": null, + "search.match_background": "#478c9066", "panel.background": "#1f2621ff", "panel.focused_border": null, "pane.focused_border": null, @@ -5802,7 +5802,7 @@ "tab_bar.background": "#e3ebe6ff", "tab.inactive_background": "#e3ebe6ff", "tab.active_background": "#ecf4eeff", - "search.match_background": null, + "search.match_background": "#488c9066", "panel.background": "#e3ebe6ff", "panel.focused_border": null, "pane.focused_border": null, @@ -6186,7 +6186,7 @@ "tab_bar.background": "#1f231fff", "tab.inactive_background": "#1f231fff", "tab.active_background": "#131513ff", - "search.match_background": null, + "search.match_background": "#3e62f466", "panel.background": "#1f231fff", "panel.focused_border": null, "pane.focused_border": null, @@ -6570,7 +6570,7 @@ "tab_bar.background": "#daeedaff", "tab.inactive_background": "#daeedaff", "tab.active_background": "#f3faf3ff", - "search.match_background": null, + "search.match_background": "#3f62f466", "panel.background": "#daeedaff", "panel.focused_border": null, "pane.focused_border": null, @@ -6954,7 +6954,7 @@ "tab_bar.background": "#262f51ff", "tab.inactive_background": "#262f51ff", "tab.active_background": "#202646ff", - "search.match_background": null, + "search.match_background": "#3e8fd066", "panel.background": "#262f51ff", "panel.focused_border": null, "pane.focused_border": null, @@ -7338,7 +7338,7 @@ "tab_bar.background": "#e5e8f5ff", "tab.inactive_background": "#e5e8f5ff", "tab.active_background": "#f5f7ffff", - "search.match_background": null, + "search.match_background": "#3f8fd066", "panel.background": "#e5e8f5ff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/ayu/ayu.json b/assets/themes/ayu/ayu.json index 0a3f5f776e1a0ba078cfa1ccb7a0f02826ffe76d..40b426220427725a4a579c1bd4e78a15b17f2971 100644 --- a/assets/themes/ayu/ayu.json +++ b/assets/themes/ayu/ayu.json @@ -42,7 +42,7 @@ "tab_bar.background": "#1f2127ff", "tab.inactive_background": "#1f2127ff", "tab.active_background": "#0d1016ff", - "search.match_background": null, + "search.match_background": "#5ac2fe66", "panel.background": "#1f2127ff", "panel.focused_border": null, "pane.focused_border": null, @@ -411,7 +411,7 @@ "tab_bar.background": "#ececedff", "tab.inactive_background": "#ececedff", "tab.active_background": "#fcfcfcff", - "search.match_background": null, + "search.match_background": "#3b9ee566", "panel.background": "#ececedff", "panel.focused_border": null, "pane.focused_border": null, @@ -780,7 +780,7 @@ "tab_bar.background": "#353944ff", "tab.inactive_background": "#353944ff", "tab.active_background": "#242835ff", - "search.match_background": null, + "search.match_background": "#73cffe66", "panel.background": "#353944ff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/gruvbox/gruvbox.json b/assets/themes/gruvbox/gruvbox.json index f32387c3ab7033fdb227e11b2767424a42469662..eddcae6ff89bb4e19c974c1ce151de69c312a591 100644 --- a/assets/themes/gruvbox/gruvbox.json +++ b/assets/themes/gruvbox/gruvbox.json @@ -42,7 +42,7 @@ "tab_bar.background": "#3a3735ff", "tab.inactive_background": "#3a3735ff", "tab.active_background": "#282828ff", - "search.match_background": null, + "search.match_background": "#83a59866", "panel.background": "#3a3735ff", "panel.focused_border": null, "pane.focused_border": null, @@ -416,7 +416,7 @@ "tab_bar.background": "#393634ff", "tab.inactive_background": "#393634ff", "tab.active_background": "#1d2021ff", - "search.match_background": null, + "search.match_background": "#83a59866", "panel.background": "#393634ff", "panel.focused_border": null, "pane.focused_border": null, @@ -790,7 +790,7 @@ "tab_bar.background": "#3b3735ff", "tab.inactive_background": "#3b3735ff", "tab.active_background": "#32302fff", - "search.match_background": null, + "search.match_background": "#83a59866", "panel.background": "#3b3735ff", "panel.focused_border": null, "pane.focused_border": null, @@ -1164,7 +1164,7 @@ "tab_bar.background": "#ecddb4ff", "tab.inactive_background": "#ecddb4ff", "tab.active_background": "#fbf1c7ff", - "search.match_background": null, + "search.match_background": "#0b667866", "panel.background": "#ecddb4ff", "panel.focused_border": null, "pane.focused_border": null, @@ -1538,7 +1538,7 @@ "tab_bar.background": "#ecddb5ff", "tab.inactive_background": "#ecddb5ff", "tab.active_background": "#f9f5d7ff", - "search.match_background": null, + "search.match_background": "#0b667866", "panel.background": "#ecddb5ff", "panel.focused_border": null, "pane.focused_border": null, @@ -1912,7 +1912,7 @@ "tab_bar.background": "#ecdcb3ff", "tab.inactive_background": "#ecdcb3ff", "tab.active_background": "#f2e5bcff", - "search.match_background": null, + "search.match_background": "#0b667866", "panel.background": "#ecdcb3ff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/one/one.json b/assets/themes/one/one.json index 8be973677a7e96767f7fe4b6e581a0d9edb093a6..b8953f95f4797beb8118750d85f7b835587da29a 100644 --- a/assets/themes/one/one.json +++ b/assets/themes/one/one.json @@ -42,7 +42,7 @@ "tab_bar.background": "#2f343eff", "tab.inactive_background": "#2f343eff", "tab.active_background": "#282c33ff", - "search.match_background": null, + "search.match_background": "#74ade866", "panel.background": "#2f343eff", "panel.focused_border": null, "pane.focused_border": null, @@ -416,7 +416,7 @@ "tab_bar.background": "#ebebecff", "tab.inactive_background": "#ebebecff", "tab.active_background": "#fafafaff", - "search.match_background": null, + "search.match_background": "#5c79e266", "panel.background": "#ebebecff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/rose_pine/rose_pine.json b/assets/themes/rose_pine/rose_pine.json index 5878f1125a23aad32648ae8208f34ee0c317cadb..69b89f8005aed1635bfaea6dc63f35d8e6d32505 100644 --- a/assets/themes/rose_pine/rose_pine.json +++ b/assets/themes/rose_pine/rose_pine.json @@ -42,7 +42,7 @@ "tab_bar.background": "#1c1b2aff", "tab.inactive_background": "#1c1b2aff", "tab.active_background": "#191724ff", - "search.match_background": null, + "search.match_background": "#57949f66", "panel.background": "#1c1b2aff", "panel.focused_border": null, "pane.focused_border": null, @@ -421,7 +421,7 @@ "tab_bar.background": "#fef9f2ff", "tab.inactive_background": "#fef9f2ff", "tab.active_background": "#faf4edff", - "search.match_background": null, + "search.match_background": "#9cced766", "panel.background": "#fef9f2ff", "panel.focused_border": null, "pane.focused_border": null, @@ -800,7 +800,7 @@ "tab_bar.background": "#28253cff", "tab.inactive_background": "#28253cff", "tab.active_background": "#232136ff", - "search.match_background": null, + "search.match_background": "#9cced766", "panel.background": "#28253cff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/sandcastle/sandcastle.json b/assets/themes/sandcastle/sandcastle.json index 502723c92af0c9b1776aab32e2a67a1321f7e259..6c42fee3c6fa6c7e865a304ba499ee8fff5e7d6d 100644 --- a/assets/themes/sandcastle/sandcastle.json +++ b/assets/themes/sandcastle/sandcastle.json @@ -42,7 +42,7 @@ "tab_bar.background": "#2b3038ff", "tab.inactive_background": "#2b3038ff", "tab.active_background": "#282c33ff", - "search.match_background": null, + "search.match_background": "#528b8b66", "panel.background": "#2b3038ff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/solarized/solarized.json b/assets/themes/solarized/solarized.json index 84811aa71403f8020da0cef4e9d9ec7277f38876..217dddf4f7db2f019a2f31bde1ab420ca39c52db 100644 --- a/assets/themes/solarized/solarized.json +++ b/assets/themes/solarized/solarized.json @@ -42,7 +42,7 @@ "tab_bar.background": "#04313bff", "tab.inactive_background": "#04313bff", "tab.active_background": "#002a35ff", - "search.match_background": null, + "search.match_background": "#288bd166", "panel.background": "#04313bff", "panel.focused_border": null, "pane.focused_border": null, @@ -411,7 +411,7 @@ "tab_bar.background": "#f3eddaff", "tab.inactive_background": "#f3eddaff", "tab.active_background": "#fdf6e3ff", - "search.match_background": null, + "search.match_background": "#298bd166", "panel.background": "#f3eddaff", "panel.focused_border": null, "pane.focused_border": null, diff --git a/assets/themes/summercamp/summercamp.json b/assets/themes/summercamp/summercamp.json index fff6108a71f15364ed4bd1a93b42c5f6f0c7a8bd..187d1fd23f649b56d0879a3962f1a812d7b43b1a 100644 --- a/assets/themes/summercamp/summercamp.json +++ b/assets/themes/summercamp/summercamp.json @@ -42,7 +42,7 @@ "tab_bar.background": "#231f16ff", "tab.inactive_background": "#231f16ff", "tab.active_background": "#1b1810ff", - "search.match_background": null, + "search.match_background": "#499bef66", "panel.background": "#231f16ff", "panel.focused_border": null, "pane.focused_border": null, From 583ce443592ac2bd2f92001a795c64bfefe53f54 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 5 Feb 2024 18:55:27 +0100 Subject: [PATCH 052/101] Fix cmd+k in terminal and fix sporadic keybind misses (#7388) This fixes `cmd+k` in the terminal taking 1s to have an effect. It is now immediate. It also fixes #7270 by ensuring that we don't set a bad state when matching keybindings. It matches keybindings per context and if it finds a match on a lower context it doesn't keep pending keystrokes. If it finds two matches on the same context level, requiring more keystrokes, then it waits. Release Notes: - Fixed `cmd-k` in terminal taking 1s to have an effect. Also fixed sporadic non-matching of keybindings if there are overlapping keybindings. ([#7270](https://github.com/zed-industries/zed/issues/7270)). --------- Co-authored-by: Conrad Co-authored-by: Conrad Irwin --- assets/keymaps/default.json | 22 ++--- crates/collab/src/tests/integration_tests.rs | 2 +- crates/gpui/src/key_dispatch.rs | 20 ++--- crates/gpui/src/window.rs | 89 +++++++++++--------- crates/gpui/src/window/element_cx.rs | 19 ++--- crates/terminal_view/src/terminal_element.rs | 1 - 6 files changed, 71 insertions(+), 82 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 8679296733559a03c51a94d1f133c6bd922eadf9..06358dab225af5c06555c5a2b2d49838c8fb08ad 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -415,7 +415,15 @@ "cmd-?": "assistant::ToggleFocus", "cmd-alt-s": "workspace::SaveAll", "cmd-k m": "language_selector::Toggle", - "escape": "workspace::Unfollow" + "escape": "workspace::Unfollow", + "cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"], + "cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"], + "cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"], + "cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"], + "cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"], + "cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"], + "cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"], + "cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"] } }, // Bindings from Sublime Text @@ -441,18 +449,6 @@ "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" } }, - { - "bindings": { - "cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"], - "cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"], - "cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"], - "cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"], - "cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"], - "cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"], - "cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"], - "cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"] - } - }, // Bindings from Atom { "context": "Pane", diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 625544e4a1572b67ccb09cb52a256631cb5ec88b..eb0beb6f6653d7f1a5846e8584fc522ab44aad53 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -5967,6 +5967,6 @@ async fn test_cmd_k_left(cx: &mut TestAppContext) { cx.executor().advance_clock(Duration::from_secs(2)); cx.simulate_keystrokes("left"); workspace.update(cx, |workspace, cx| { - assert!(workspace.items(cx).collect::>().len() == 3); + assert!(workspace.items(cx).collect::>().len() == 2); }); } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c6a2e1788453cc1a5c654f0e7c2f4fef70cfabb4..8f9eb16a62584d31465256b5e397f29a90079e8d 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -62,16 +62,6 @@ use std::{ rc::Rc, }; -/// KeymatchMode controls how keybindings are resolved in the case of conflicting pending keystrokes. -/// When `Sequenced`, gpui will wait for 1s for sequences to complete. -/// When `Immediate`, gpui will immediately resolve the keybinding. -#[derive(Default, PartialEq)] -pub enum KeymatchMode { - #[default] - Sequenced, - Immediate, -} - #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub(crate) struct DispatchNodeId(usize); @@ -84,7 +74,6 @@ pub(crate) struct DispatchTree { keystroke_matchers: FxHashMap, KeystrokeMatcher>, keymap: Rc>, action_registry: Rc, - pub(crate) keymatch_mode: KeymatchMode, } #[derive(Default)] @@ -116,7 +105,6 @@ impl DispatchTree { keystroke_matchers: FxHashMap::default(), keymap, action_registry, - keymatch_mode: KeymatchMode::Sequenced, } } @@ -127,7 +115,6 @@ impl DispatchTree { self.focusable_node_ids.clear(); self.view_node_ids.clear(); self.keystroke_matchers.clear(); - self.keymatch_mode = KeymatchMode::Sequenced; } pub fn push_node( @@ -335,7 +322,7 @@ impl DispatchTree { .collect() } - // dispatch_key pushses the next keystroke into any key binding matchers. + // dispatch_key pushes the next keystroke into any key binding matchers. // any matching bindings are returned in the order that they should be dispatched: // * First by length of binding (so if you have a binding for "b" and "ab", the "ab" binding fires first) // * Secondly by depth in the tree (so if Editor has a binding for "b" and workspace a @@ -364,6 +351,11 @@ impl DispatchTree { .or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone())); let result = keystroke_matcher.match_keystroke(keystroke, &context_stack); + if result.pending && !pending && !bindings.is_empty() { + context_stack.pop(); + continue; + } + pending = result.pending || pending; for new_binding in result.bindings { match bindings diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index c9c672557f56368a75079a6565c0e9a434b368b2..69982cce6813a0b15108b4ffce0ec92a726acba8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2,12 +2,12 @@ use crate::{ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, - Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchMode, - KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, - MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, - PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, - Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, - WindowBounds, WindowOptions, WindowTextSystem, + Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult, + Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent, + MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, + PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds, + WindowOptions, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -291,10 +291,6 @@ struct PendingInput { } impl PendingInput { - fn is_noop(&self) -> bool { - self.bindings.is_empty() && (self.keystrokes.iter().all(|k| k.ime_key.is_none())) - } - fn input(&self) -> String { self.keystrokes .iter() @@ -1282,21 +1278,12 @@ impl<'a> WindowContext<'a> { .dispatch_path(node_id); if let Some(key_down_event) = event.downcast_ref::() { - let KeymatchResult { - bindings, - mut pending, - } = self + let KeymatchResult { bindings, pending } = self .window .rendered_frame .dispatch_tree .dispatch_key(&key_down_event.keystroke, &dispatch_path); - if self.window.rendered_frame.dispatch_tree.keymatch_mode == KeymatchMode::Immediate - && !bindings.is_empty() - { - pending = false; - } - if pending { let mut currently_pending = self.window.pending_input.take().unwrap_or_default(); if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus @@ -1311,22 +1298,17 @@ impl<'a> WindowContext<'a> { currently_pending.bindings.push(binding); } - // for vim compatibility, we also should check "is input handler enabled" - if !currently_pending.is_noop() { - currently_pending.timer = Some(self.spawn(|mut cx| async move { - cx.background_executor.timer(Duration::from_secs(1)).await; - cx.update(move |cx| { - cx.clear_pending_keystrokes(); - let Some(currently_pending) = cx.window.pending_input.take() else { - return; - }; - cx.replay_pending_input(currently_pending) - }) - .log_err(); - })); - } else { - currently_pending.timer = None; - } + currently_pending.timer = Some(self.spawn(|mut cx| async move { + cx.background_executor.timer(Duration::from_secs(1)).await; + cx.update(move |cx| { + cx.clear_pending_keystrokes(); + let Some(currently_pending) = cx.window.pending_input.take() else { + return; + }; + cx.replay_pending_input(currently_pending) + }) + .log_err(); + })); self.window.pending_input = Some(currently_pending); self.propagate_event = false; @@ -1354,8 +1336,21 @@ impl<'a> WindowContext<'a> { } } + self.dispatch_key_down_up_event(event, &dispatch_path); + if !self.propagate_event { + return; + } + + self.dispatch_keystroke_observers(event, None); + } + + fn dispatch_key_down_up_event( + &mut self, + event: &dyn Any, + dispatch_path: &SmallVec<[DispatchNodeId; 32]>, + ) { // Capture phase - for node_id in &dispatch_path { + for node_id in dispatch_path { let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for key_listener in node.key_listeners.clone() { @@ -1381,8 +1376,6 @@ impl<'a> WindowContext<'a> { } } } - - self.dispatch_keystroke_observers(event, None); } /// Determine whether a potential multi-stroke key binding is in progress on this window. @@ -1419,6 +1412,24 @@ impl<'a> WindowContext<'a> { } } + let dispatch_path = self + .window + .rendered_frame + .dispatch_tree + .dispatch_path(node_id); + + for keystroke in currently_pending.keystrokes { + let event = KeyDownEvent { + keystroke, + is_held: false, + }; + + self.dispatch_key_down_up_event(&event, &dispatch_path); + if !self.propagate_event { + return; + } + } + if !input.is_empty() { if let Some(mut input_handler) = self.window.platform_window.take_input_handler() { input_handler.flush_pending_input(&input, self); diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index c7814fc10100763bb8162bc247e775172a198f89..8ba3fc5c4fc94c27629d18efee3c45e023b4a8a1 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -31,11 +31,11 @@ use crate::{ prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox, EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, - InputHandler, IsZero, KeyContext, KeyEvent, KeymatchMode, LayoutId, MonochromeSprite, - MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, - RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, - StackingContext, StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, - Window, WindowContext, SUBPIXEL_VARIANTS, + InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad, + Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams, + RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext, + StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, Window, + WindowContext, SUBPIXEL_VARIANTS, }; type AnyMouseListener = Box; @@ -1143,15 +1143,6 @@ impl<'a> ElementContext<'a> { } } - /// keymatch mode immediate instructs GPUI to prefer shorter action bindings. - /// In the case that you have a keybinding of `"cmd-k": "terminal::Clear"` and - /// `"cmd-k left": "workspace::MoveLeft"`, GPUI will by default wait for 1s after - /// you type cmd-k to see if you're going to type left. - /// This is problematic in the terminal - pub fn keymatch_mode_immediate(&mut self) { - self.window.next_frame.dispatch_tree.keymatch_mode = KeymatchMode::Immediate; - } - /// Register a mouse event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 73d9bb27f539bce4ec25ea4ff2e04753bd11e96a..66ef42bc46ebb09244ff4ae45c7cb26bbd6b3ead 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -776,7 +776,6 @@ impl Element for TerminalElement { self.interactivity .paint(bounds, bounds.size, state, cx, |_, _, cx| { cx.handle_input(&self.focus, terminal_input_handler); - cx.keymatch_mode_immediate(); cx.on_key_event({ let this = self.terminal.clone(); From 4195f27964aa85470209dfbe061b7c935b90993b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 5 Feb 2024 10:57:53 -0700 Subject: [PATCH 053/101] Fix hover links in channel buffers (#7393) Release Notes: - N/A --- crates/editor/src/editor.rs | 17 +++++++++++------ crates/editor/src/hover_links.rs | 9 ++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e5e5ae9c286abf79577205f9239f9819d8f7b348..4ed8f1370053db8035aedc2073e0c56bec16bdd9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7264,10 +7264,6 @@ impl Editor { split: bool, cx: &mut ViewContext, ) { - let Some(workspace) = self.workspace() else { - return; - }; - let pane = workspace.read(cx).active_pane().clone(); // If there is one definition, just open it directly if definitions.len() == 1 { let definition = definitions.pop().unwrap(); @@ -7285,6 +7281,11 @@ impl Editor { let target = target_task.await.context("target resolution task")?; if let Some(target) = target { editor.update(&mut cx, |editor, cx| { + let Some(workspace) = editor.workspace() else { + return; + }; + let pane = workspace.read(cx).active_pane().clone(); + let range = target.range.to_offset(target.buffer.read(cx)); let range = editor.range_for_match(&range); if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() { @@ -7325,7 +7326,7 @@ impl Editor { } else if !definitions.is_empty() { let replica_id = self.replica_id(cx); cx.spawn(|editor, mut cx| async move { - let (title, location_tasks) = editor + let (title, location_tasks, workspace) = editor .update(&mut cx, |editor, cx| { let title = definitions .iter() @@ -7353,7 +7354,7 @@ impl Editor { HoverLink::Url(_) => Task::ready(Ok(None)), }) .collect::>(); - (title, location_tasks) + (title, location_tasks, editor.workspace().clone()) }) .context("location tasks preparation")?; @@ -7363,6 +7364,10 @@ impl Editor { .filter_map(|location| location.transpose()) .collect::>() .context("location tasks")?; + + let Some(workspace) = workspace else { + return Ok(()); + }; workspace .update(&mut cx, |workspace, cx| { Self::open_locations_in_multibuffer( diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 955aafde24ea2fd330035a4afbbed8205f6dceac..5834ae5a2bc9dd8a3da98cb6c44a2eb77d363e0d 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -394,10 +394,6 @@ pub fn show_link_definition( return; }; - let Some(project) = editor.project.clone() else { - return; - }; - let same_kind = hovered_link_state.preferred_kind == preferred_kind || hovered_link_state .links @@ -419,6 +415,7 @@ pub fn show_link_definition( } else { editor.hide_hovered_link(cx) } + let project = editor.project.clone(); let snapshot = snapshot.buffer_snapshot.clone(); hovered_link_state.task = Some(cx.spawn(|this, mut cx| { @@ -436,7 +433,7 @@ pub fn show_link_definition( ) }) .ok() - } else { + } else if let Some(project) = project { // query the LSP for definition info project .update(&mut cx, |project, cx| match preferred_kind { @@ -468,6 +465,8 @@ pub fn show_link_definition( definition_result.into_iter().map(HoverLink::Text).collect(), ) }) + } else { + None } } TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some(( From e2e8e52ec4eb4c053b4148c063a775bed30c7d94 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 5 Feb 2024 11:07:26 -0700 Subject: [PATCH 054/101] Beancount syntax highlighting (#7389) Release Notes: - Added syntax highlighting for [Beancount](https://beancount.github.io) --- Cargo.lock | 10 +++++++++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + .../zed/src/languages/beancount/config.toml | 3 +++ .../src/languages/beancount/highlights.scm | 21 +++++++++++++++++++ 6 files changed, 37 insertions(+) create mode 100644 crates/zed/src/languages/beancount/config.toml create mode 100644 crates/zed/src/languages/beancount/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index ad58e4442533570bfc90c1fbefb5c70a6b1f1422..40d594ff42b109a7def4f89fedb2f16ccda05d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8729,6 +8729,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-beancount" +version = "2.2.0" +source = "git+https://github.com/polarmutex/tree-sitter-beancount?rev=da1bf8c6eb0ae7a97588affde7227630bcd678b6#da1bf8c6eb0ae7a97588affde7227630bcd678b6" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-c" version = "0.20.6" @@ -10369,6 +10378,7 @@ dependencies = [ "toml", "tree-sitter", "tree-sitter-bash", + "tree-sitter-beancount", "tree-sitter-c", "tree-sitter-c-sharp", "tree-sitter-cpp", diff --git a/Cargo.toml b/Cargo.toml index 5a276ef8b3b54b22e7ddeefb1a664f765b991400..edfc968ffb2c2c27e144965b61f7eb54135d5dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,7 @@ time = { version = "0.3", features = ["serde", "serde-well-known"] } toml = "0.5" tree-sitter = { version = "0.20", features = ["wasm"] } tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" } +tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" } tree-sitter-c = "0.20.1" tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" } tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 61df53a9fb27c0fb4f48119eb2fe7ea0e2a7c45b..f3ba0825c7a04a49bfe0208155502413e4ce14b8 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -108,6 +108,7 @@ thiserror.workspace = true tiny_http = "0.8" toml.workspace = true tree-sitter-bash.workspace = true +tree-sitter-beancount.workspace = true tree-sitter-c-sharp.workspace = true tree-sitter-c.workspace = true tree-sitter-cpp.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 15bd180a2ebeac074d9c0bf00ea134b8b5c92fc3..248ff3c7fde6b6e047260bdd0b1709fc2354d8c1 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -67,6 +67,7 @@ pub fn init( }; language("bash", tree_sitter_bash::language(), vec![]); + language("beancount", tree_sitter_beancount::language(), vec![]); language( "c", tree_sitter_c::language(), diff --git a/crates/zed/src/languages/beancount/config.toml b/crates/zed/src/languages/beancount/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..722b8a39b74bb54eb71148a7d1e93d3134448dd2 --- /dev/null +++ b/crates/zed/src/languages/beancount/config.toml @@ -0,0 +1,3 @@ +name = "Beancount" +path_suffixes = ["beancount"] +brackets = [{ start = "\"", end = "\"", close = false, newline = false }] diff --git a/crates/zed/src/languages/beancount/highlights.scm b/crates/zed/src/languages/beancount/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..87a383ddbf306b14556b49f8059a64131496c926 --- /dev/null +++ b/crates/zed/src/languages/beancount/highlights.scm @@ -0,0 +1,21 @@ +(comment) @comment +(headline) @comment +[ + (payee) + (narration) + (string) +] @string + +(number) @number +(date) @function +(currency) @constant +(account) @identifier + +[ + (option) + (include) + (open) + (balance) + (pad) + (close) +] @keyword From 91303a50210aecba7e7f5baffb29a0b521ec6c88 Mon Sep 17 00:00:00 2001 From: Dairon M Date: Mon, 5 Feb 2024 13:12:29 -0500 Subject: [PATCH 055/101] Do not run scheduled CI jobs on forks (#7394) Runs scheduled CI jobs only on the main repository, not on forks. These jobs fail on forks and generate unnecessary noise on contributor emails. Release Notes: - N/A --- .github/workflows/release_nightly.yml | 3 +++ .github/workflows/update_all_top_ranking_issues.yml | 1 + .github/workflows/update_weekly_top_ranking_issues.yml | 1 + 3 files changed, 5 insertions(+) diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 80a012e560050ee7f9a7be9051549057ff26afdd..b7221c97fd031873d7b64a066369a8d782239817 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -16,6 +16,7 @@ env: jobs: style: name: Check formatting and Clippy lints + if: github.repository_owner == 'zed-industries' runs-on: - self-hosted - test @@ -32,6 +33,7 @@ jobs: tests: name: Run tests + if: github.repository_owner == 'zed-industries' runs-on: - self-hosted - test @@ -48,6 +50,7 @@ jobs: bundle: name: Bundle app + if: github.repository_owner == 'zed-industries' runs-on: - self-hosted - bundle diff --git a/.github/workflows/update_all_top_ranking_issues.yml b/.github/workflows/update_all_top_ranking_issues.yml index 0ffdb6aeb25c24392396e0d6667ab773627dcb3d..32941ea220d9abc6e31c6940bf24e6c24e6259d7 100644 --- a/.github/workflows/update_all_top_ranking_issues.yml +++ b/.github/workflows/update_all_top_ranking_issues.yml @@ -6,6 +6,7 @@ on: jobs: update_top_ranking_issues: runs-on: ubuntu-latest + if: github.repository_owner == 'zed-industries' steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 diff --git a/.github/workflows/update_weekly_top_ranking_issues.yml b/.github/workflows/update_weekly_top_ranking_issues.yml index fa9a33bdf5cf3b78b7345ffe96080be3f20c9290..de1b4d21bc3d273728a24d4f239397313dae74c8 100644 --- a/.github/workflows/update_weekly_top_ranking_issues.yml +++ b/.github/workflows/update_weekly_top_ranking_issues.yml @@ -6,6 +6,7 @@ on: jobs: update_top_ranking_issues: runs-on: ubuntu-latest + if: github.repository_owner == 'zed-industries' steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 From 2ed45d72d8aa8f6e9e64ea41982b01dbc2d4c1a1 Mon Sep 17 00:00:00 2001 From: Andrew Lygin Date: Mon, 5 Feb 2024 21:12:47 +0300 Subject: [PATCH 056/101] File finder UI enhancement (#7364) File finder looks and feels a little bulky now. It duplicates file names and consumes too much space for each file. This PR makes it more compact: - File name is trimmed from the path, removing duplication - Path is placed to the right of the file name, improving space usage - Path is muted and printed in small size to not distract attention from the main information (file names) It makes search results easier to look through, consistent with the editor tabs, and closer in terms of usage to mature editors. Release Notes: - File finder UI enhancement --- crates/file_finder/src/file_finder.rs | 19 ++++++++++++++++--- crates/file_finder/src/file_finder_tests.rs | 4 ++-- .../src/components/label/highlighted_label.rs | 4 +++- crates/ui/src/components/label/label_like.rs | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index be3d2feb83098f6bd10aa936a32466717937179b..d222682e807531e25dbee1f0a9f4423895c74674 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -566,7 +566,7 @@ impl FileFinderDelegate { let path = &path_match.path; let path_string = path.to_string_lossy(); let full_path = [path_match.path_prefix.as_ref(), path_string.as_ref()].join(""); - let path_positions = path_match.positions.clone(); + let mut path_positions = path_match.positions.clone(); let file_name = path.file_name().map_or_else( || path_match.path_prefix.to_string(), @@ -584,6 +584,14 @@ impl FileFinderDelegate { }) .collect(); + // Trim file name from the full path + let full_path = if full_path.len() > file_name.len() { + full_path[..full_path.len() - file_name.len() - 1].to_string() + } else { + "".to_string() + }; + path_positions.retain(|idx| *idx < full_path.len()); + (file_name, file_name_positions, full_path, path_positions) } @@ -868,9 +876,14 @@ impl PickerDelegate for FileFinderDelegate { .inset(true) .selected(selected) .child( - v_flex() + h_flex() + .gap_2() .child(HighlightedLabel::new(file_name, file_name_positions)) - .child(HighlightedLabel::new(full_path, full_path_positions)), + .child( + HighlightedLabel::new(full_path, full_path_positions) + .size(LabelSize::Small) + .color(Color::Muted), + ), ), ) } diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index 450d034614a859d13eb89afe9f6858c5386c6494..f0e1626bd4e8d47341758104998d67afdcbe8e3e 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -490,8 +490,8 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) { 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"); - assert_eq!(full_path_positions, &[0, 1, 4]); + assert_eq!(full_path, ""); + assert_eq!(full_path_positions, &[0; 0]); }); // Since the worktree root is a file, searching for its name followed by a slash does diff --git a/crates/ui/src/components/label/highlighted_label.rs b/crates/ui/src/components/label/highlighted_label.rs index d70c2d1b51c82e426a508fb413a35d4f4b9e3a70..41866bd10af00cd86632a9deb7ca41a625faa2e3 100644 --- a/crates/ui/src/components/label/highlighted_label.rs +++ b/crates/ui/src/components/label/highlighted_label.rs @@ -79,6 +79,8 @@ impl RenderOnce for HighlightedLabel { let mut text_style = cx.text_style().clone(); text_style.color = self.base.color.color(cx); - LabelLike::new().child(StyledText::new(self.label).with_highlights(&text_style, highlights)) + LabelLike::new() + .size(self.base.size) + .child(StyledText::new(self.label).with_highlights(&text_style, highlights)) } } diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index cddd849b89d7d15e6ebfa6367ad5829bfe4a709a..f08ff1bf833e80c1c1fed0b78066a48d350758a8 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -36,7 +36,7 @@ pub trait LabelCommon { #[derive(IntoElement)] pub struct LabelLike { - size: LabelSize, + pub(crate) size: LabelSize, line_height_style: LineHeightStyle, pub(crate) color: Color, strikethrough: bool, From 8911e1b365d2c731c8c3747020f3b9f6e40d054b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 5 Feb 2024 20:19:50 +0200 Subject: [PATCH 057/101] Make inlay hints test less flacky (#7396) Suppresses a flacky inlay hints test failures like https://github.com/zed-industries/zed/actions/runs/7788741514a for now. Release Notes: - N/A --- crates/editor/src/inlay_hint_cache.rs | 1130 +++++++++++++------------ 1 file changed, 610 insertions(+), 520 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 714f3d1789095a38df6d7234af76cb00bbddb3ef..1f34bdde20506c70df8c92fff19ab4907bd55125 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1307,49 +1307,55 @@ pub mod tests { cx.executor().run_until_parked(); let mut edits_made = 1; - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get its first hints when opening the editor" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, allowed_hint_kinds, - "Cache should use editor settings to get the allowed hint kinds" - ); - assert_eq!( - inlay_cache.version, edits_made, - "The editor update the cache version after every cache/view change" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }) + .unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input("some change", cx); - edits_made += 1; - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("some change", cx); + edits_made += 1; + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string(), "1".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get new hints after an edit" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, allowed_hint_kinds, - "Cache should use editor settings to get the allowed hint kinds" - ); - assert_eq!( - inlay_cache.version, edits_made, - "The editor update the cache version after every cache/view change" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string(), "1".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get new hints after an edit" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }) + .unwrap(); fake_server .request::(()) @@ -1357,24 +1363,26 @@ pub mod tests { .expect("inlay refresh request failed"); edits_made += 1; cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get new hints after hint refresh/ request" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, allowed_hint_kinds, - "Cache should use editor settings to get the allowed hint kinds" - ); - assert_eq!( - inlay_cache.version, edits_made, - "The editor update the cache version after every cache/view change" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get new hints after hint refresh/ request" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }) + .unwrap(); } #[gpui::test] @@ -1417,20 +1425,22 @@ pub mod tests { cx.executor().run_until_parked(); let mut edits_made = 1; - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get its first hints when opening the editor" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - edits_made, - "The editor update the cache version after every cache/view change" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + edits_made, + "The editor update the cache version after every cache/view change" + ); + }) + .unwrap(); let progress_token = "test_progress_token"; fake_server @@ -1448,20 +1458,22 @@ pub mod tests { }); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should not update hints while the work task is running" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - edits_made, - "Should not update the cache while the work task is running" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should not update hints while the work task is running" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + edits_made, + "Should not update the cache while the work task is running" + ); + }) + .unwrap(); fake_server.notify::(lsp::ProgressParams { token: lsp::ProgressToken::String(progress_token.to_string()), @@ -1472,20 +1484,22 @@ pub mod tests { cx.executor().run_until_parked(); edits_made += 1; - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["1".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "New hints should be queried after the work task is done" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - edits_made, - "Cache version should update once after the work task is done" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["1".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "New hints should be queried after the work task is done" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + edits_made, + "Cache version should update once after the work task is done" + ); + }) + .unwrap(); } #[gpui::test] @@ -1577,20 +1591,22 @@ pub mod tests { .next() .await; cx.executor().run_until_parked(); - _ = rs_editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get its first hints when opening the editor" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - 1, - "Rust editor update the cache version after every cache/view change" - ); - }); + rs_editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + 1, + "Rust editor update the cache version after every cache/view change" + ); + }) + .unwrap(); cx.executor().run_until_parked(); let md_buffer = project @@ -1628,72 +1644,86 @@ pub mod tests { .next() .await; cx.executor().run_until_parked(); - _ = md_editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Markdown editor should have a separate version, repeating Rust editor rules" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, 1); - }); + md_editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Markdown editor should have a separate version, repeating Rust editor rules" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!(editor.inlay_hint_cache().version, 1); + }) + .unwrap(); - _ = rs_editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input("some rs change", cx); - }); + rs_editor + .update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("some rs change", cx); + }) + .unwrap(); cx.executor().run_until_parked(); - _ = rs_editor.update(cx, |editor, cx| { - let expected_hints = vec!["1".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Rust inlay cache should change after the edit" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - 2, - "Every time hint cache changes, cache version should be incremented" - ); - }); - _ = md_editor.update(cx, |editor, cx| { - let expected_hints = vec!["0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Markdown editor should not be affected by Rust editor changes" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, 1); - }); + rs_editor + .update(cx, |editor, cx| { + let expected_hints = vec!["1".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Rust inlay cache should change after the edit" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + 2, + "Every time hint cache changes, cache version should be incremented" + ); + }) + .unwrap(); + md_editor + .update(cx, |editor, cx| { + let expected_hints = vec!["0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Markdown editor should not be affected by Rust editor changes" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!(editor.inlay_hint_cache().version, 1); + }) + .unwrap(); - _ = md_editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input("some md change", cx); - }); + md_editor + .update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("some md change", cx); + }) + .unwrap(); cx.executor().run_until_parked(); - _ = md_editor.update(cx, |editor, cx| { - let expected_hints = vec!["1".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Rust editor should not be affected by Markdown editor changes" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, 2); - }); - _ = rs_editor.update(cx, |editor, cx| { - let expected_hints = vec!["1".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Markdown editor should also change independently" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, 2); - }); + md_editor + .update(cx, |editor, cx| { + let expected_hints = vec!["1".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Rust editor should not be affected by Markdown editor changes" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!(editor.inlay_hint_cache().version, 2); + }) + .unwrap(); + rs_editor + .update(cx, |editor, cx| { + let expected_hints = vec!["1".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Markdown editor should also change independently" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!(editor.inlay_hint_cache().version, 2); + }) + .unwrap(); } #[gpui::test] @@ -1759,66 +1789,70 @@ pub mod tests { cx.executor().run_until_parked(); let mut edits_made = 1; - _ = editor.update(cx, |editor, cx| { - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 1, - "Should query new hints once" - ); - assert_eq!( - vec![ - "other hint".to_string(), - "parameter hint".to_string(), - "type hint".to_string(), - ], - cached_hint_labels(editor), - "Should get its first hints when opening the editor" - ); - assert_eq!( - vec!["other hint".to_string(), "type hint".to_string()], - visible_hint_labels(editor, cx) - ); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, allowed_hint_kinds, - "Cache should use editor settings to get the allowed hint kinds" - ); - assert_eq!( - inlay_cache.version, edits_made, - "The editor update the cache version after every cache/view change" - ); - }); + editor + .update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 1, + "Should query new hints once" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!( + vec!["other hint".to_string(), "type hint".to_string()], + visible_hint_labels(editor, cx) + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }) + .unwrap(); fake_server .request::(()) .await .expect("inlay refresh request failed"); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 2, - "Should load new hints twice" - ); - assert_eq!( - vec![ - "other hint".to_string(), - "parameter hint".to_string(), - "type hint".to_string(), - ], - cached_hint_labels(editor), - "Cached hints should not change due to allowed hint kinds settings update" - ); - assert_eq!( - vec!["other hint".to_string(), "type hint".to_string()], - visible_hint_labels(editor, cx) - ); - assert_eq!( - editor.inlay_hint_cache().version, - edits_made, - "Should not update cache version due to new loaded hints being the same" - ); - }); + editor + .update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should load new hints twice" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Cached hints should not change due to allowed hint kinds settings update" + ); + assert_eq!( + vec!["other hint".to_string(), "type hint".to_string()], + visible_hint_labels(editor, cx) + ); + assert_eq!( + editor.inlay_hint_cache().version, + edits_made, + "Should not update cache version due to new loaded hints being the same" + ); + }) + .unwrap(); for (new_allowed_hint_kinds, expected_visible_hints) in [ (HashSet::from_iter([None]), vec!["other hint".to_string()]), @@ -1866,7 +1900,7 @@ pub mod tests { }) }); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { assert_eq!( lsp_request_count.load(Ordering::Relaxed), 2, @@ -1895,7 +1929,7 @@ pub mod tests { inlay_cache.version, edits_made, "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change" ); - }); + }).unwrap(); } edits_made += 1; @@ -1910,37 +1944,39 @@ pub mod tests { }) }); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 2, - "Should not load new hints when hints got disabled" - ); - assert!( - cached_hint_labels(editor).is_empty(), - "Should clear the cache when hints got disabled" - ); - assert!( - visible_hint_labels(editor, cx).is_empty(), - "Should clear visible hints when hints got disabled" - ); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds, - "Should update its allowed hint kinds even when hints got disabled" - ); - assert_eq!( - inlay_cache.version, edits_made, - "The editor should update the cache version after hints got disabled" - ); - }); + editor + .update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints when hints got disabled" + ); + assert!( + cached_hint_labels(editor).is_empty(), + "Should clear the cache when hints got disabled" + ); + assert!( + visible_hint_labels(editor, cx).is_empty(), + "Should clear visible hints when hints got disabled" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds, + "Should update its allowed hint kinds even when hints got disabled" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should update the cache version after hints got disabled" + ); + }) + .unwrap(); fake_server .request::(()) .await .expect("inlay refresh request failed"); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { assert_eq!( lsp_request_count.load(Ordering::Relaxed), 2, @@ -1952,7 +1988,7 @@ pub mod tests { editor.inlay_hint_cache().version, edits_made, "The editor should not update the cache version after /refresh query without updates" ); - }); + }).unwrap(); let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); edits_made += 1; @@ -1966,62 +2002,66 @@ pub mod tests { }) }); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 3, - "Should query for new hints when they got re-enabled" - ); - assert_eq!( - vec![ - "other hint".to_string(), - "parameter hint".to_string(), - "type hint".to_string(), - ], - cached_hint_labels(editor), - "Should get its cached hints fully repopulated after the hints got re-enabled" - ); - assert_eq!( - vec!["parameter hint".to_string()], - visible_hint_labels(editor, cx), - "Should get its visible hints repopulated and filtered after the h" - ); - 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 re-enabled" - ); - assert_eq!( - inlay_cache.version, edits_made, - "Cache should update its version after hints got re-enabled" - ); - }); + editor + .update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 3, + "Should query for new hints when they got re-enabled" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its cached hints fully repopulated after the hints got re-enabled" + ); + assert_eq!( + vec!["parameter hint".to_string()], + visible_hint_labels(editor, cx), + "Should get its visible hints repopulated and filtered after the h" + ); + 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 re-enabled" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Cache should update its version after hints got re-enabled" + ); + }) + .unwrap(); fake_server .request::(()) .await .expect("inlay refresh request failed"); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 4, - "Should query for new hints again" - ); - assert_eq!( - vec![ - "other hint".to_string(), - "parameter hint".to_string(), - "type hint".to_string(), - ], - cached_hint_labels(editor), - ); - assert_eq!( - vec!["parameter hint".to_string()], - visible_hint_labels(editor, cx), - ); - assert_eq!(editor.inlay_hint_cache().version, edits_made); - }); + editor + .update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 4, + "Should query for new hints again" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + ); + assert_eq!( + vec!["parameter hint".to_string()], + visible_hint_labels(editor, cx), + ); + assert_eq!(editor.inlay_hint_cache().version, edits_made); + }) + .unwrap(); } #[gpui::test] @@ -2069,16 +2109,18 @@ pub mod tests { "initial change #2", "initial change #3", ] { - _ = editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input(change_after_opening, cx); - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(change_after_opening, cx); + }) + .unwrap(); expected_changes.push(change_after_opening); } cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let current_text = editor.text(cx); for change in &expected_changes { assert!( @@ -2102,7 +2144,7 @@ pub mod tests { editor.inlay_hint_cache().version, 1, "Only one update should be registered in the cache after all cancellations" ); - }); + }).unwrap(); let mut edits = Vec::new(); for async_later_change in [ @@ -2113,41 +2155,45 @@ pub mod tests { expected_changes.push(async_later_change); let task_editor = editor.clone(); edits.push(cx.spawn(|mut cx| async move { - _ = task_editor.update(&mut cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input(async_later_change, cx); - }); + task_editor + .update(&mut cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(async_later_change, cx); + }) + .unwrap(); })); } let _ = future::join_all(edits).await; cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let current_text = editor.text(cx); - for change in &expected_changes { - assert!( - current_text.contains(change), - "Should apply all changes made" + editor + .update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); + } + assert_eq!( + lsp_request_count.load(Ordering::SeqCst), + 3, + "Should query new hints one more time, for the last edit only" ); - } - assert_eq!( - lsp_request_count.load(Ordering::SeqCst), - 3, - "Should query new hints one more time, for the last edit only" - ); - let expected_hints = vec!["3".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get hints from the last edit landed only" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - 2, - "Should update the cache version once more, for the new change" - ); - }); + let expected_hints = vec!["3".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit landed only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + 2, + "Should update the cache version once more, for the new change" + ); + }) + .unwrap(); } #[gpui::test(iterations = 10)] @@ -2280,7 +2326,7 @@ pub mod tests { lsp::Position::new(initial_visible_range.end.row * 2, 2); let mut expected_invisible_query_start = lsp_initial_visible_range.end; expected_invisible_query_start.character += 1; - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let ranges = lsp_request_ranges.lock().drain(..).collect::>(); assert_eq!(ranges.len(), 2, "When scroll is at the edge of a big document, its visible part and the same range further should be queried in order, but got: {ranges:?}"); @@ -2305,12 +2351,14 @@ pub mod tests { editor.inlay_hint_cache().version, requests_count, "LSP queries should've bumped the cache version" ); - }); + }).unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.scroll_screen(&ScrollAmount::Page(1.0), cx); - editor.scroll_screen(&ScrollAmount::Page(1.0), cx); - }); + editor + .update(cx, |editor, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + }) + .unwrap(); cx.executor().advance_clock(Duration::from_millis( INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, )); @@ -2377,16 +2425,18 @@ pub mod tests { }) .unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::center()), cx, |s| { - s.select_ranges([selection_in_cached_range..selection_in_cached_range]) - }); - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::center()), cx, |s| { + s.select_ranges([selection_in_cached_range..selection_in_cached_range]) + }); + }) + .unwrap(); cx.executor().advance_clock(Duration::from_millis( INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, )); cx.executor().run_until_parked(); - _ = editor.update(cx, |_, _| { + editor.update(cx, |_, _| { let ranges = lsp_request_ranges .lock() .drain(..) @@ -2394,16 +2444,18 @@ pub mod tests { .collect::>(); assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints"); assert_eq!(lsp_request_count.load(Ordering::Acquire), 4); - }); + }).unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.handle_input("++++more text++++", cx); - }); + editor + .update(cx, |editor, cx| { + editor.handle_input("++++more text++++", cx); + }) + .unwrap(); cx.executor().advance_clock(Duration::from_millis( INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, )); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); ranges.sort_by_key(|r| r.start); @@ -2432,7 +2484,7 @@ pub mod tests { "Should have hints from the new LSP response after the edit"); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added"); - }); + }).unwrap(); } #[gpui::test(iterations = 30)] @@ -2631,7 +2683,7 @@ pub mod tests { .await; cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let expected_hints = vec![ "main hint #0".to_string(), "main hint #1".to_string(), @@ -2647,21 +2699,23 @@ pub mod tests { ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version"); - }); + }).unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::Next), cx, |s| { - s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) - }); - editor.change_selections(Some(Autoscroll::Next), cx, |s| { - s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) - }); - editor.change_selections(Some(Autoscroll::Next), cx, |s| { - s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) - }); - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) + }); + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let expected_hints = vec![ "main hint #0".to_string(), "main hint #1".to_string(), @@ -2678,13 +2732,15 @@ pub mod tests { assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Due to every excerpt having one hint, we update cache per new excerpt scrolled"); - }); + }).unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::Next), cx, |s| { - s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) - }); - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) + }); + }) + .unwrap(); cx.executor().advance_clock(Duration::from_millis( INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, )); @@ -2711,13 +2767,15 @@ pub mod tests { expected_hints.len() }).unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::Next), cx, |s| { - s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) - }); - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let expected_hints = vec![ "main hint #0".to_string(), "main hint #1".to_string(), @@ -2736,18 +2794,19 @@ pub mod tests { "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 scrolled buffer"); - }); + }).unwrap(); editor_edited.store(true, Ordering::Release); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| { - // TODO if this gets set to hint boundary (e.g. 56) we sometimes get an extra cache version bump, why? - s.select_ranges([Point::new(57, 0)..Point::new(57, 0)]) - }); - editor.handle_input("++++more text++++", cx); - }); + editor + .update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(57, 0)..Point::new(57, 0)]) + }); + editor.handle_input("++++more text++++", cx); + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let expected_hints = vec![ "main hint(edited) #0".to_string(), "main hint(edited) #1".to_string(), @@ -2767,12 +2826,13 @@ pub mod tests { assert_eq!(expected_hints, visible_hint_labels(editor, cx)); let current_cache_version = editor.inlay_hint_cache().version; - assert_eq!( - current_cache_version, - last_scroll_update_version + expected_hints.len(), - "We should have updated cache N times == N of new hints arrived (separately from each excerpt)" + let expected_version = last_scroll_update_version + expected_hints.len(); + assert!( + current_cache_version == expected_version || current_cache_version == expected_version + 1 , + // TODO we sometimes get an extra cache version bump, why? + "We should have updated cache N times == N of new hints arrived (separately from each excerpt), or hit a bug and do that one extra time" ); - }); + }).unwrap(); } #[gpui::test] @@ -2929,45 +2989,51 @@ pub mod tests { .await; cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert_eq!( - vec!["main hint #0".to_string(), "other hint #0".to_string()], - cached_hint_labels(editor), - "Cache should update for both excerpts despite hints display was disabled" - ); - assert!( + editor + .update(cx, |editor, cx| { + assert_eq!( + vec!["main hint #0".to_string(), "other hint #0".to_string()], + cached_hint_labels(editor), + "Cache should update for both excerpts despite hints display was disabled" + ); + assert!( visible_hint_labels(editor, cx).is_empty(), "All hints are disabled and should not be shown despite being present in the cache" ); - assert_eq!( - editor.inlay_hint_cache().version, - 2, - "Cache should update once per excerpt query" - ); - }); + assert_eq!( + editor.inlay_hint_cache().version, + 2, + "Cache should update once per excerpt query" + ); + }) + .unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.buffer().update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts(buffer_2_excerpts, cx) + editor + .update(cx, |editor, cx| { + editor.buffer().update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts(buffer_2_excerpts, cx) + }) }) - }); + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert_eq!( - vec!["main hint #0".to_string()], - cached_hint_labels(editor), - "For the removed excerpt, should clean corresponding cached hints" - ); - assert!( + editor + .update(cx, |editor, cx| { + assert_eq!( + vec!["main hint #0".to_string()], + cached_hint_labels(editor), + "For the removed excerpt, should clean corresponding cached hints" + ); + assert!( visible_hint_labels(editor, cx).is_empty(), "All hints are disabled and should not be shown despite being present in the cache" ); - assert_eq!( - editor.inlay_hint_cache().version, - 3, - "Excerpt removal should trigger a cache update" - ); - }); + assert_eq!( + editor.inlay_hint_cache().version, + 3, + "Excerpt removal should trigger a cache update" + ); + }) + .unwrap(); update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { @@ -2978,24 +3044,26 @@ pub mod tests { }) }); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["main hint #0".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Hint display settings change should not change the cache" - ); - assert_eq!( - expected_hints, - visible_hint_labels(editor, cx), - "Settings change should make cached hints visible" - ); - assert_eq!( - editor.inlay_hint_cache().version, - 4, - "Settings change should trigger a cache update" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["main hint #0".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Hint display settings change should not change the cache" + ); + assert_eq!( + expected_hints, + visible_hint_labels(editor, cx), + "Settings change should make cached hints visible" + ); + assert_eq!( + editor.inlay_hint_cache().version, + 4, + "Settings change should trigger a cache update" + ); + }) + .unwrap(); } #[gpui::test] @@ -3075,18 +3143,22 @@ pub mod tests { .await; cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| { - s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) + editor + .update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) + }) }) - }); + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["1".to_string()]; - assert_eq!(expected_hints, cached_hint_labels(editor)); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, 1); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["1".to_string()]; + assert_eq!(expected_hints, cached_hint_labels(editor)); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!(editor.inlay_hint_cache().version, 1); + }) + .unwrap(); } #[gpui::test] @@ -3102,9 +3174,11 @@ pub mod tests { let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; - _ = editor.update(cx, |editor, cx| { - editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) - }); + editor + .update(cx, |editor, cx| { + editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) + }) + .unwrap(); cx.executor().start_waiting(); let lsp_request_count = Arc::new(AtomicU32::new(0)); let closure_lsp_request_count = Arc::clone(&lsp_request_count); @@ -3133,33 +3207,39 @@ pub mod tests { .next() .await; cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["1".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should display inlays after toggle despite them disabled in settings" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!( - editor.inlay_hint_cache().version, - 1, - "First toggle should be cache's first update" - ); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["1".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should display inlays after toggle despite them disabled in settings" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!( + editor.inlay_hint_cache().version, + 1, + "First toggle should be cache's first update" + ); + }) + .unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) - }); + editor + .update(cx, |editor, cx| { + editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert!( - cached_hint_labels(editor).is_empty(), - "Should clear hints after 2nd toggle" - ); - assert!(visible_hint_labels(editor, cx).is_empty()); - assert_eq!(editor.inlay_hint_cache().version, 2); - }); + editor + .update(cx, |editor, cx| { + assert!( + cached_hint_labels(editor).is_empty(), + "Should clear hints after 2nd toggle" + ); + assert!(visible_hint_labels(editor, cx).is_empty()); + assert_eq!(editor.inlay_hint_cache().version, 2); + }) + .unwrap(); update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { @@ -3170,35 +3250,43 @@ pub mod tests { }) }); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - let expected_hints = vec!["2".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should query LSP hints for the 2nd time after enabling hints in settings" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, 3); - }); + editor + .update(cx, |editor, cx| { + let expected_hints = vec!["2".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should query LSP hints for the 2nd time after enabling hints in settings" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + assert_eq!(editor.inlay_hint_cache().version, 3); + }) + .unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) - }); + editor + .update(cx, |editor, cx| { + editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { - assert!( - cached_hint_labels(editor).is_empty(), - "Should clear hints after enabling in settings and a 3rd toggle" - ); - assert!(visible_hint_labels(editor, cx).is_empty()); - assert_eq!(editor.inlay_hint_cache().version, 4); - }); + editor + .update(cx, |editor, cx| { + assert!( + cached_hint_labels(editor).is_empty(), + "Should clear hints after enabling in settings and a 3rd toggle" + ); + assert!(visible_hint_labels(editor, cx).is_empty()); + assert_eq!(editor.inlay_hint_cache().version, 4); + }) + .unwrap(); - _ = editor.update(cx, |editor, cx| { - editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) - }); + editor + .update(cx, |editor, cx| { + editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx) + }) + .unwrap(); cx.executor().run_until_parked(); - _ = editor.update(cx, |editor, cx| { + editor.update(cx, |editor, cx| { let expected_hints = vec!["3".to_string()]; assert_eq!( expected_hints, @@ -3207,7 +3295,7 @@ pub mod tests { ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(editor.inlay_hint_cache().version, 5); - }); + }).unwrap(); } pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { @@ -3258,7 +3346,7 @@ pub mod tests { .await; let project = Project::test(fs, ["/a".as_ref()], cx).await; - _ = project.update(cx, |project, _| project.languages().add(Arc::new(language))); + project.update(cx, |project, _| project.languages().add(Arc::new(language))); let buffer = project .update(cx, |project, cx| { project.open_local_buffer("/a/main.rs", cx) @@ -3270,11 +3358,13 @@ pub mod tests { let fake_server = fake_servers.next().await.unwrap(); let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx)); - _ = editor.update(cx, |editor, cx| { - assert!(cached_hint_labels(editor).is_empty()); - assert!(visible_hint_labels(editor, cx).is_empty()); - assert_eq!(editor.inlay_hint_cache().version, 0); - }); + editor + .update(cx, |editor, cx| { + assert!(cached_hint_labels(editor).is_empty()); + assert!(visible_hint_labels(editor, cx).is_empty()); + assert_eq!(editor.inlay_hint_cache().version, 0); + }) + .unwrap(); ("/a/main.rs", editor, fake_server) } From ce62404e24e019247538220af42748de28b93038 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 5 Feb 2024 13:21:07 -0500 Subject: [PATCH 058/101] Correctly use the base element in `HighlightedLabel` (#7397) This PR updates the `HighlightedLabel` to correctly render its base element, which is the one that receives the styling properties, instead of rendering a new `LabelLike`. Release Notes: - N/A --- crates/ui/src/components/label/highlighted_label.rs | 3 +-- crates/ui/src/components/label/label_like.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/components/label/highlighted_label.rs b/crates/ui/src/components/label/highlighted_label.rs index 41866bd10af00cd86632a9deb7ca41a625faa2e3..88174f489599c1cc77868a9f29921c3664af6a4f 100644 --- a/crates/ui/src/components/label/highlighted_label.rs +++ b/crates/ui/src/components/label/highlighted_label.rs @@ -79,8 +79,7 @@ impl RenderOnce for HighlightedLabel { let mut text_style = cx.text_style().clone(); text_style.color = self.base.color.color(cx); - LabelLike::new() - .size(self.base.size) + self.base .child(StyledText::new(self.label).with_highlights(&text_style, highlights)) } } diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index f08ff1bf833e80c1c1fed0b78066a48d350758a8..cddd849b89d7d15e6ebfa6367ad5829bfe4a709a 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -36,7 +36,7 @@ pub trait LabelCommon { #[derive(IntoElement)] pub struct LabelLike { - pub(crate) size: LabelSize, + size: LabelSize, line_height_style: LineHeightStyle, pub(crate) color: Color, strikethrough: bool, From 21797bad4d939af85b10fba9f615b07cd4cb5f5f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 5 Feb 2024 13:26:42 -0500 Subject: [PATCH 059/101] file_finder: Simplify removal of file name from file path (#7398) This PR simplifies the removal of the file name from the file path when computing the file finder matches. Release Notes: - N/A --- crates/file_finder/src/file_finder.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index d222682e807531e25dbee1f0a9f4423895c74674..521682e6b3dea37aa9bbbc0c80b197b77ca6209b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -584,12 +584,7 @@ impl FileFinderDelegate { }) .collect(); - // Trim file name from the full path - let full_path = if full_path.len() > file_name.len() { - full_path[..full_path.len() - file_name.len() - 1].to_string() - } else { - "".to_string() - }; + let full_path = full_path.trim_end_matches(&file_name).to_string(); path_positions.retain(|idx| *idx < full_path.len()); (file_name, file_name_positions, full_path, path_positions) From 6863b9263ec31ef20406ae381ab63ce2bf493e7b Mon Sep 17 00:00:00 2001 From: Caius Durling Date: Mon, 5 Feb 2024 19:38:30 +0000 Subject: [PATCH 060/101] Add Terraform & HCL syntax highlighting (#6882) Terraform and HCL are almost the same language, but not quite so proposing them as separate languages within Zed. (Terraform is an extension of HCL, with a different formatter.) This is just adding the language definition, parsing and highlighting functionality, not any LSP or formatting beyond that for either language. I've taken a bunch of inspiration from Neovim for having the separate languages, and also lifted some of their `scm` files (with attribution comments in this codebase) as the tree-sitter repo doesn't contain them. (Neovim's code is Apache-2.0 licensed, so should be fine here with attribution from reading Zed's licenses files.) I've then amended to make sure the capture groups are named for things Zed understands. I'd love someone from Zed to confirm that's okay, or if I should clean-room implement the `scm` files. Highlighting in Terraform & HCL with a moderate amount of syntax in a file (Terraform on left, HCL on right.) Screenshot 2024-01-31 at 18 07 45 Release Notes: - (|Improved) ... ([#5098](https://github.com/zed-industries/zed/issues/5098)). --- Cargo.lock | 10 ++ Cargo.toml | 1 + assets/settings/default.json | 3 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 2 + crates/zed/src/languages/hcl/config.toml | 13 ++ crates/zed/src/languages/hcl/highlights.scm | 117 +++++++++++++ crates/zed/src/languages/hcl/indents.scm | 11 ++ crates/zed/src/languages/hcl/injections.scm | 6 + .../zed/src/languages/terraform/config.toml | 13 ++ .../src/languages/terraform/highlights.scm | 159 ++++++++++++++++++ .../zed/src/languages/terraform/indents.scm | 14 ++ .../src/languages/terraform/injections.scm | 9 + 13 files changed, 359 insertions(+) create mode 100644 crates/zed/src/languages/hcl/config.toml create mode 100644 crates/zed/src/languages/hcl/highlights.scm create mode 100644 crates/zed/src/languages/hcl/indents.scm create mode 100644 crates/zed/src/languages/hcl/injections.scm create mode 100644 crates/zed/src/languages/terraform/config.toml create mode 100644 crates/zed/src/languages/terraform/highlights.scm create mode 100644 crates/zed/src/languages/terraform/indents.scm create mode 100644 crates/zed/src/languages/terraform/injections.scm diff --git a/Cargo.lock b/Cargo.lock index 40d594ff42b109a7def4f89fedb2f16ccda05d4c..ef68faac45b47479046f1894e365670a0e4fcb2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8876,6 +8876,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-hcl" +version = "0.0.1" +source = "git+https://github.com/MichaHoffmann/tree-sitter-hcl?rev=v1.1.0#636dbe70301ecbab8f353c8c78b3406fe4f185f5" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-heex" version = "0.0.1" @@ -10394,6 +10403,7 @@ dependencies = [ "tree-sitter-gomod", "tree-sitter-gowork", "tree-sitter-haskell", + "tree-sitter-hcl", "tree-sitter-heex", "tree-sitter-html", "tree-sitter-json 0.20.0", diff --git a/Cargo.toml b/Cargo.toml index edfc968ffb2c2c27e144965b61f7eb54135d5dfd..2676f5b6589f8aa8e1a42f66ddd59f78c1edf112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,7 @@ tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" } tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" } tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "cf98de23e4285b8e6bcb57b050ef2326e2cc284b" } +tree-sitter-hcl = {git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "v1.1.0"} tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } tree-sitter-html = "0.19.0" tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } diff --git a/assets/settings/default.json b/assets/settings/default.json index 9387c58f1dce90c4c592032ebbc28069947731f0..3cb6082991b43e6e74d5084b4fee0642f3c2f5e1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -498,6 +498,9 @@ "JavaScript": { "tab_size": 2 }, + "Terraform": { + "tab_size": 2 + }, "TypeScript": { "tab_size": 2 }, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f3ba0825c7a04a49bfe0208155502413e4ce14b8..46025a59bbf800c9aa06be4ff222d0a4109aea0f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -124,6 +124,7 @@ tree-sitter-go.workspace = true tree-sitter-gomod.workspace = true tree-sitter-gowork.workspace = true tree-sitter-haskell.workspace = true +tree-sitter-hcl.workspace = true tree-sitter-heex.workspace = true tree-sitter-html.workspace = true tree-sitter-json.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 248ff3c7fde6b6e047260bdd0b1709fc2354d8c1..1250aa92c74bfecfbc5c6fe1fec55a7393409e65 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -322,6 +322,8 @@ pub fn init( vec![Arc::new(uiua::UiuaLanguageServer {})], ); language("proto", tree_sitter_proto::language(), vec![]); + language("terraform", tree_sitter_hcl::language(), vec![]); + language("hcl", tree_sitter_hcl::language(), vec![]); if let Ok(children) = std::fs::read_dir(&*PLUGINS_DIR) { for child in children { diff --git a/crates/zed/src/languages/hcl/config.toml b/crates/zed/src/languages/hcl/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..c7c2ebf6baba57d6f1a3efe7d00798073bd37659 --- /dev/null +++ b/crates/zed/src/languages/hcl/config.toml @@ -0,0 +1,13 @@ +name = "HCL" +path_suffixes = ["hcl"] +line_comments = ["# ", "// "] +block_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 = false, not_in = ["comment", "string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] }, + { start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] }, +] diff --git a/crates/zed/src/languages/hcl/highlights.scm b/crates/zed/src/languages/hcl/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..a1713b3e3f509b7872102142333904dc4d927f6c --- /dev/null +++ b/crates/zed/src/languages/hcl/highlights.scm @@ -0,0 +1,117 @@ +; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/hcl/highlights.scm +; highlights.scm +[ + "!" + "\*" + "/" + "%" + "\+" + "-" + ">" + ">=" + "<" + "<=" + "==" + "!=" + "&&" + "||" +] @operator + +[ + "{" + "}" + "[" + "]" + "(" + ")" +] @punctuation.bracket + +[ + "." + ".*" + "," + "[*]" +] @punctuation.delimiter + +[ + (ellipsis) + "\?" + "=>" +] @punctuation.special + +[ + ":" + "=" +] @punctuation + +[ + "for" + "endfor" + "in" + "if" + "else" + "endif" +] @keyword + +[ + (quoted_template_start) ; " + (quoted_template_end) ; " + (template_literal) ; non-interpolation/directive content +] @string + +[ + (heredoc_identifier) ; END + (heredoc_start) ; << or <<- +] @punctuation.delimiter + +[ + (template_interpolation_start) ; ${ + (template_interpolation_end) ; } + (template_directive_start) ; %{ + (template_directive_end) ; } + (strip_marker) ; ~ +] @punctuation.special + +(numeric_lit) @number + +(bool_lit) @boolean + +(null_lit) @constant + +(comment) @comment + +(identifier) @variable + +(body + (block + (identifier) @keyword)) + +(body + (block + (body + (block + (identifier) @type)))) + +(function_call + (identifier) @function) + +(attribute + (identifier) @variable) + +; { key: val } +; +; highlight identifier keys as though they were block attributes +(object_elem + key: + (expression + (variable_expr + (identifier) @variable))) + +; var.foo, data.bar +; +; first element in get_attr is a variable.builtin or a reference to a variable.builtin +(expression + (variable_expr + (identifier) @variable) + (get_attr + (identifier) @variable)) diff --git a/crates/zed/src/languages/hcl/indents.scm b/crates/zed/src/languages/hcl/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..74edb66bdf28d80cc9bf25069283c9fff7fd7dee --- /dev/null +++ b/crates/zed/src/languages/hcl/indents.scm @@ -0,0 +1,11 @@ +; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/indents.scm +[ + (block) + (object) + (tuple) + (function_call) +] @indent + +(_ "[" "]" @end) @indent +(_ "(" ")" @end) @indent +(_ "{" "}" @end) @indent diff --git a/crates/zed/src/languages/hcl/injections.scm b/crates/zed/src/languages/hcl/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..8617e9fc2ef439c28abd691572ed94d96d3e1e63 --- /dev/null +++ b/crates/zed/src/languages/hcl/injections.scm @@ -0,0 +1,6 @@ +; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/injections.scm + +(heredoc_template + (template_literal) @content + (heredoc_identifier) @language + (#downcase! @language)) diff --git a/crates/zed/src/languages/terraform/config.toml b/crates/zed/src/languages/terraform/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..9ea58960014d1a6b936ec2c11fc8b762bc0039d6 --- /dev/null +++ b/crates/zed/src/languages/terraform/config.toml @@ -0,0 +1,13 @@ +name = "Terraform" +path_suffixes = ["tf", "tfvars"] +line_comments = ["# ", "// "] +block_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 = false, not_in = ["comment", "string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] }, + { start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] }, +] diff --git a/crates/zed/src/languages/terraform/highlights.scm b/crates/zed/src/languages/terraform/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..f123c3232ddc61e1ba208b083aa635222fcb8acf --- /dev/null +++ b/crates/zed/src/languages/terraform/highlights.scm @@ -0,0 +1,159 @@ +; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/hcl/highlights.scm +; highlights.scm +[ + "!" + "\*" + "/" + "%" + "\+" + "-" + ">" + ">=" + "<" + "<=" + "==" + "!=" + "&&" + "||" +] @operator + +[ + "{" + "}" + "[" + "]" + "(" + ")" +] @punctuation.bracket + +[ + "." + ".*" + "," + "[*]" +] @punctuation.delimiter + +[ + (ellipsis) + "\?" + "=>" +] @punctuation.special + +[ + ":" + "=" +] @punctuation + +[ + "for" + "endfor" + "in" + "if" + "else" + "endif" +] @keyword + +[ + (quoted_template_start) ; " + (quoted_template_end) ; " + (template_literal) ; non-interpolation/directive content +] @string + +[ + (heredoc_identifier) ; END + (heredoc_start) ; << or <<- +] @punctuation.delimiter + +[ + (template_interpolation_start) ; ${ + (template_interpolation_end) ; } + (template_directive_start) ; %{ + (template_directive_end) ; } + (strip_marker) ; ~ +] @punctuation.special + +(numeric_lit) @number + +(bool_lit) @boolean + +(null_lit) @constant + +(comment) @comment + +(identifier) @variable + +(body + (block + (identifier) @keyword)) + +(body + (block + (body + (block + (identifier) @type)))) + +(function_call + (identifier) @function) + +(attribute + (identifier) @variable) + +; { key: val } +; +; highlight identifier keys as though they were block attributes +(object_elem + key: + (expression + (variable_expr + (identifier) @variable))) + +; var.foo, data.bar +; +; first element in get_attr is a variable.builtin or a reference to a variable.builtin +(expression + (variable_expr + (identifier) @variable) + (get_attr + (identifier) @variable)) + +; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/terraform/highlights.scm +; Terraform specific references +; +; +; local/module/data/var/output +(expression + (variable_expr + (identifier) @variable + (#any-of? @variable "data" "var" "local" "module" "output")) + (get_attr + (identifier) @variable)) + +; path.root/cwd/module +(expression + (variable_expr + (identifier) @type + (#eq? @type "path")) + (get_attr + (identifier) @variable + (#any-of? @variable "root" "cwd" "module"))) + +; terraform.workspace +(expression + (variable_expr + (identifier) @type + (#eq? @type "terraform")) + (get_attr + (identifier) @variable + (#any-of? @variable "workspace"))) + +; Terraform specific keywords +; FIXME: ideally only for identifiers under a `variable` block to minimize false positives +((identifier) @type + (#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any")) + +(object_elem + val: + (expression + (variable_expr + (identifier) @type + (#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any")))) diff --git a/crates/zed/src/languages/terraform/indents.scm b/crates/zed/src/languages/terraform/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..95ad93df1da98bee40f1c40b21c8a4558b9b1e4b --- /dev/null +++ b/crates/zed/src/languages/terraform/indents.scm @@ -0,0 +1,14 @@ +; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/indents.scm +[ + (block) + (object) + (tuple) + (function_call) +] @indent + +(_ "[" "]" @end) @indent +(_ "(" ")" @end) @indent +(_ "{" "}" @end) @indent + +; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/terraform/indents.scm +; inherits: hcl diff --git a/crates/zed/src/languages/terraform/injections.scm b/crates/zed/src/languages/terraform/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..b41ee95d403cea90d11b2ccfe04a8d0357c2ece4 --- /dev/null +++ b/crates/zed/src/languages/terraform/injections.scm @@ -0,0 +1,9 @@ +; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/injections.scm + +(heredoc_template + (template_literal) @content + (heredoc_identifier) @language + (#downcase! @language)) + +; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/terraform/injections.scm +; inherits: hcl From 28a62affe4c0bc50c0ca6189776bfbd3f8871dea Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 5 Feb 2024 22:03:44 +0200 Subject: [PATCH 061/101] Clean up visible inlay hints that got removed from the cache (#7399) Fixes another flack in the inlay hint cache. Now that test does not fail after 500 iterations and seems to be stable at last. Release Notes: - N/A --- crates/editor/src/inlay_hint_cache.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 1f34bdde20506c70df8c92fff19ab4907bd55125..bc90b6face8e32da68c545242630d2c8f6700ce7 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -78,7 +78,7 @@ pub(super) enum InvalidationStrategy { /// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes. /// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead. /// Splice is picked to help avoid extra hint flickering and "jumps" on the screen. -#[derive(Debug)] +#[derive(Debug, Default)] pub(super) struct InlaySplice { pub to_remove: Vec, pub to_insert: Vec, @@ -87,7 +87,7 @@ pub(super) struct InlaySplice { #[derive(Debug)] struct ExcerptHintsUpdate { excerpt_id: ExcerptId, - remove_from_visible: Vec, + remove_from_visible: HashSet, remove_from_cache: HashSet, add_to_cache: Vec, } @@ -1052,7 +1052,7 @@ fn calculate_hint_updates( } } - let mut remove_from_visible = Vec::new(); + let mut remove_from_visible = HashSet::default(); let mut remove_from_cache = HashSet::default(); if invalidate { remove_from_visible.extend( @@ -1074,6 +1074,7 @@ fn calculate_hint_updates( }) .copied(), ); + remove_from_visible.extend(remove_from_cache.iter().cloned()); } } @@ -1135,10 +1136,8 @@ fn apply_hint_update( cached_excerpt_hints .hints_by_id .retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id)); - let mut splice = InlaySplice { - to_remove: new_update.remove_from_visible, - to_insert: Vec::new(), - }; + let mut splice = InlaySplice::default(); + splice.to_remove.extend(new_update.remove_from_visible); for new_hint in new_update.add_to_cache { let insert_position = match cached_excerpt_hints .ordered_hints From 46464ebe87057d46175c8baf813fd20c91a72717 Mon Sep 17 00:00:00 2001 From: N8th8n8el Date: Mon, 5 Feb 2024 21:06:43 +0100 Subject: [PATCH 062/101] terminal: Fix copy to clipboard lag (#7323) Fixes #7322 Release Notes: - Fixed terminal's copy to clipboard being non-instant ([7322](https://github.com/zed-industries/zed/issues/7322)) --- crates/terminal_view/src/terminal_view.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 6c7270d9b4df076a19aae24fc4715d3dc780b23c..006ffb9d7945c887e5ec09b2ae3a00fa29454471 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -478,7 +478,8 @@ impl TerminalView { ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { - self.terminal.update(cx, |term, _| term.copy()) + self.terminal.update(cx, |term, _| term.copy()); + cx.notify(); } ///Attempt to paste the clipboard into the terminal From b59f92593386ce58ae9df03ad66a089d313d6ecf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 5 Feb 2024 22:26:56 +0200 Subject: [PATCH 063/101] Upgrade GH actions to reduce CI warnings (#7403) Deals with one of the warnings GH shows on our actions run: https://github.com/zed-industries/zed/actions/runs/7790218555 image bufbuild/* actions seem to have no new major versions so there's nothing new to upgrade to. Release Notes: - N/A --- .github/actions/check_style/action.yml | 46 ++-- .github/actions/run_tests/action.yml | 2 +- .github/workflows/ci.yml | 278 ++++++++++++------------- .github/workflows/danger.yml | 50 ++--- .github/workflows/randomized_tests.yml | 52 ++--- .github/workflows/release_nightly.yml | 2 +- 6 files changed, 215 insertions(+), 215 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index d4e5d62f2c65fb1a8bfd102244a6c0a866ed45d1..39601e8c2599a985244a4c36d7fcfea6b8c12208 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,29 +2,29 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + using: "composite" + steps: + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - cargo clippy --release --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo - cargo clippy -p gpui + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --release --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + cargo clippy -p gpui - - name: Find modified migrations - shell: bash -euxo pipefail {0} - run: | - export SQUAWK_GITHUB_TOKEN=${{ github.token }} - . ./script/squawk + - name: Find modified migrations + shell: bash -euxo pipefail {0} + run: | + export SQUAWK_GITHUB_TOKEN=${{ github.token }} + . ./script/squawk - - uses: bufbuild/buf-setup-action@v1 - - uses: bufbuild/buf-breaking-action@v1 - with: - input: "crates/rpc/proto/" - against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,subdir=crates/rpc/proto/" + - uses: bufbuild/buf-setup-action@v1 + - uses: bufbuild/buf-breaking-action@v1 + with: + input: "crates/rpc/proto/" + against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,subdir=crates/rpc/proto/" diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index 4e6664a6fb6fa2ab810adeae478885cdeb9b3519..a37c2759d6582159bb162760f7ec8896568cb655 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -10,7 +10,7 @@ runs: cargo install cargo-nextest - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "18" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d8cf6522e44a434561e18292ff748052af5cf4d..8ad9f57fd1d5c368eace41e7679376e00b6a4b56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,149 +1,149 @@ name: CI on: - push: - branches: - - main - - "v[0-9]+.[0-9]+.x" - tags: - - "v*" - pull_request: - branches: - - "**" + push: + branches: + - main + - "v[0-9]+.[0-9]+.x" + tags: + - "v*" + pull_request: + branches: + - "**" concurrency: - # Allow only one workflow per any non-`main` branch. - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} - cancel-in-progress: true + # Allow only one workflow per any non-`main` branch. + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} + cancel-in-progress: true env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 jobs: - style: - name: Check formatting, Clippy lints, and spelling - runs-on: - - self-hosted - - test - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - clean: false - submodules: "recursive" - fetch-depth: 0 - - - name: Set up default .cargo/config.toml - run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml - - - name: Check spelling - run: | - if ! which typos > /dev/null; then - cargo install typos-cli - fi - typos - - - name: Run style checks - uses: ./.github/actions/check_style - - tests: - name: Run tests - runs-on: - - self-hosted - - test - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - clean: false - submodules: "recursive" - - - name: Run tests - uses: ./.github/actions/run_tests - - - name: Build collab - run: cargo build -p collab - - - name: Build other binaries - run: cargo build --workspace --bins --all-features - - bundle: - name: Bundle app - runs-on: - - self-hosted - - bundle - if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} - needs: tests - env: - MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} - MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} - APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} - APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} - ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} - steps: - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: "18" - - - name: Checkout repo - uses: actions/checkout@v4 - with: - clean: false - submodules: "recursive" - - - name: Limit target directory size - run: script/clear-target-dir-if-larger-than 100 - - - name: Determine version and release channel - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - run: | - set -eu - - version=$(script/get-crate-version zed) - channel=$(cat crates/zed/RELEASE_CHANNEL) - echo "Publishing version: ${version} on release channel ${channel}" - echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV - - expected_tag_name="" - case ${channel} in - stable) - expected_tag_name="v${version}";; - preview) - expected_tag_name="v${version}-pre";; - nightly) - expected_tag_name="v${version}-nightly";; - *) - echo "can't publish a release on channel ${channel}" - exit 1;; - esac - if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then - echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" - exit 1 - fi - - - name: Generate license file - run: script/generate-licenses - - - name: Create app bundle - run: script/bundle - - - name: Upload app bundle to workflow run if main branch or specific label - uses: actions/upload-artifact@v3 - if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} - with: - name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg - path: target/release/Zed.dmg - - - uses: softprops/action-gh-release@v1 - name: Upload app bundle to release - if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }} - with: - draft: true - prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }} - files: target/release/Zed.dmg - body: "" + style: + name: Check formatting, Clippy lints, and spelling + runs-on: + - self-hosted + - test + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + clean: false + submodules: "recursive" + fetch-depth: 0 + + - name: Set up default .cargo/config.toml + run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml + + - name: Check spelling + run: | + if ! which typos > /dev/null; then + cargo install typos-cli + fi + typos + + - name: Run style checks + uses: ./.github/actions/check_style + + tests: + name: Run tests + runs-on: + - self-hosted + - test + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + clean: false + submodules: "recursive" + + - name: Run tests + uses: ./.github/actions/run_tests + + - name: Build collab + run: cargo build -p collab + + - name: Build other binaries + run: cargo build --workspace --bins --all-features + + bundle: + name: Bundle app + runs-on: + - self-hosted + - bundle + if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} + needs: tests env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} + APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} + ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} + steps: + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Checkout repo + uses: actions/checkout@v4 + with: + clean: false + submodules: "recursive" + + - name: Limit target directory size + run: script/clear-target-dir-if-larger-than 100 + + - name: Determine version and release channel + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: | + set -eu + + version=$(script/get-crate-version zed) + channel=$(cat crates/zed/RELEASE_CHANNEL) + echo "Publishing version: ${version} on release channel ${channel}" + echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV + + expected_tag_name="" + case ${channel} in + stable) + expected_tag_name="v${version}";; + preview) + expected_tag_name="v${version}-pre";; + nightly) + expected_tag_name="v${version}-nightly";; + *) + echo "can't publish a release on channel ${channel}" + exit 1;; + esac + if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then + echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" + exit 1 + fi + + - name: Generate license file + run: script/generate-licenses + + - name: Create app bundle + run: script/bundle + + - name: Upload app bundle to workflow run if main branch or specific label + uses: actions/upload-artifact@v3 + if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} + with: + name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg + path: target/release/Zed.dmg + + - uses: softprops/action-gh-release@v1 + name: Upload app bundle to release + if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }} + with: + draft: true + prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }} + files: target/release/Zed.dmg + body: "" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 92d73171ea277883040d0f65051db609e594a540..ac04d3dfa227d54a3a6444bb8bc57d1e21dfe1e7 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -1,35 +1,35 @@ name: Danger on: - pull_request: - branches: [main] - types: - - opened - - synchronize - - reopened - - edited + pull_request: + branches: [main] + types: + - opened + - synchronize + - reopened + - edited jobs: - danger: - runs-on: ubuntu-latest + danger: + runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + steps: + - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2.2.4 - with: - version: 8 + - uses: pnpm/action-setup@v2.2.4 + with: + version: 8 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "pnpm" - cache-dependency-path: "script/danger/pnpm-lock.yaml" + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + cache-dependency-path: "script/danger/pnpm-lock.yaml" - - run: pnpm install --dir script/danger + - run: pnpm install --dir script/danger - - name: Run Danger - run: pnpm run --dir script/danger danger ci - env: - GITHUB_TOKEN: ${{ github.token }} + - name: Run Danger + run: pnpm run --dir script/danger danger ci + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index b629b1a806c20fcdbb1bb0126da9e7c03493cf72..3b41a9fef22bde567b632af7c7fa4f2a20b00a06 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -3,35 +3,35 @@ name: Randomized Tests concurrency: randomized-tests on: - push: - branches: - - randomized-tests-runner - # schedule: - # - cron: '0 * * * *' + push: + branches: + - randomized-tests-runner + # schedule: + # - cron: '0 * * * *' env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 - ZED_SERVER_URL: https://zed.dev + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + ZED_SERVER_URL: https://zed.dev jobs: - tests: - name: Run randomized tests - runs-on: - - self-hosted - - randomized-tests - steps: - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: "18" + tests: + name: Run randomized tests + runs-on: + - self-hosted + - randomized-tests + steps: + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: "18" - - name: Checkout repo - uses: actions/checkout@v4 - with: - clean: false - submodules: "recursive" + - name: Checkout repo + uses: actions/checkout@v4 + with: + clean: false + submodules: "recursive" - - name: Run randomized tests - run: script/randomized-test-ci + - name: Run randomized tests + run: script/randomized-test-ci diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index b7221c97fd031873d7b64a066369a8d782239817..c9b4097265a2e5a63d70d001e354d5df051bd422 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -65,7 +65,7 @@ jobs: ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} steps: - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "18" From a80a3b8706b83e70476ed4a89252b3324edfb391 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 5 Feb 2024 15:39:01 -0500 Subject: [PATCH 064/101] Add support for specifying both light and dark themes in `settings.json` (#7404) This PR adds support for configuring both a light and dark theme in `settings.json`. In addition to accepting just a theme name, the `theme` field now also accepts an object in the following form: ```jsonc { "theme": { "mode": "system", "light": "One Light", "dark": "One Dark" } } ``` Both `light` and `dark` are required, and indicate which theme should be used when the system is in light mode and dark mode, respectively. The `mode` field is optional and indicates which theme should be used: - `"system"` - Use the theme that corresponds to the system's appearance. - `"light"` - Use the theme indicated by the `light` field. - `"dark"` - Use the theme indicated by the `dark` field. Thank you to @Yesterday17 for taking a first stab at this in #6881! Release Notes: - Added support for configuring both a light and dark theme and switching between them based on system preference. --- .../incoming_call_notification.rs | 4 +- .../project_shared_notification.rs | 4 +- crates/theme/src/settings.rs | 112 ++++++++++++++++-- crates/theme/src/theme.rs | 11 +- crates/theme_selector/src/theme_selector.rs | 24 +++- crates/workspace/src/workspace.rs | 19 ++- crates/zed/src/main.rs | 17 ++- 7 files changed, 167 insertions(+), 24 deletions(-) diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index f66194c52a0006c46c37cba2514e505c4ee5ec9b..12662fe6cbc8ae6c4cc596c742e78a44eaa770c5 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -5,7 +5,7 @@ use futures::StreamExt; use gpui::{prelude::*, AppContext, WindowHandle}; use settings::Settings; use std::sync::{Arc, Weak}; -use theme::ThemeSettings; +use theme::{SystemAppearance, ThemeSettings}; use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -35,6 +35,8 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { let options = notification_window_options(screen, window_size); let window = cx .open_window(options, |cx| { + SystemAppearance::init_for_window(cx); + cx.new_view(|_| { IncomingCallNotification::new( incoming_call.clone(), diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index b8ceefcd765f4e1797bcf1584ac93594a1fffdaa..bb70fc9571684cc9ec441855449c835401eec8e7 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -6,7 +6,7 @@ use collections::HashMap; use gpui::{AppContext, Size}; use settings::Settings; use std::sync::{Arc, Weak}; -use theme::ThemeSettings; +use theme::{SystemAppearance, ThemeSettings}; use ui::{prelude::*, Button, Label}; use workspace::AppState; @@ -28,6 +28,8 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for screen in cx.displays() { let options = notification_window_options(screen, window_size); let window = cx.open_window(options, |cx| { + SystemAppearance::init_for_window(cx); + cx.new_view(|_| { ProjectSharedNotification::new( owner.clone(), diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index f7157fa139dbbf751023e0cbd47451ce219673f7..17404d7c67c6dcdd25a8fdfabf2ba9560a10724f 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -1,9 +1,10 @@ use crate::one_themes::one_dark; -use crate::{SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent}; +use crate::{Appearance, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent}; use anyhow::Result; +use derive_more::{Deref, DerefMut}; use gpui::{ px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription, - ViewContext, + ViewContext, WindowContext, }; use refineable::Refineable; use schemars::{ @@ -27,16 +28,104 @@ pub struct ThemeSettings { pub buffer_font: Font, pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, - pub requested_theme: Option, + pub theme_selection: Option, pub active_theme: Arc, pub theme_overrides: Option, } +/// The appearance of the system. +#[derive(Debug, Clone, Copy, Deref)] +pub struct SystemAppearance(pub Appearance); + +impl Default for SystemAppearance { + fn default() -> Self { + Self(Appearance::Dark) + } +} + +#[derive(Deref, DerefMut, Default)] +struct GlobalSystemAppearance(SystemAppearance); + +impl Global for GlobalSystemAppearance {} + +impl SystemAppearance { + /// Returns the global [`SystemAppearance`]. + /// + /// Inserts a default [`SystemAppearance`] if one does not yet exist. + pub(crate) fn default_global(cx: &mut AppContext) -> Self { + cx.default_global::().0 + } + + /// Initializes the [`SystemAppearance`] for the current window. + pub fn init_for_window(cx: &mut WindowContext) { + *cx.default_global::() = + GlobalSystemAppearance(SystemAppearance(cx.appearance().into())); + } + + /// Returns the global [`SystemAppearance`]. + pub fn global(cx: &AppContext) -> Self { + cx.global::().0 + } + + /// Returns a mutable reference to the global [`SystemAppearance`]. + pub fn global_mut(cx: &mut AppContext) -> &mut Self { + cx.global_mut::() + } +} + #[derive(Default)] pub(crate) struct AdjustedBufferFontSize(Pixels); impl Global for AdjustedBufferFontSize {} +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ThemeSelection { + Static(#[schemars(schema_with = "theme_name_ref")] String), + Dynamic { + #[serde(default)] + mode: ThemeMode, + #[schemars(schema_with = "theme_name_ref")] + light: String, + #[schemars(schema_with = "theme_name_ref")] + dark: String, + }, +} + +fn theme_name_ref(_: &mut SchemaGenerator) -> Schema { + Schema::new_ref("#/definitions/ThemeName".into()) +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ThemeMode { + /// Use the specified `light` theme. + Light, + + /// Use the specified `dark` theme. + Dark, + + /// Use the theme based on the system's appearance. + #[default] + System, +} + +impl ThemeSelection { + pub fn theme(&self, system_appearance: Appearance) -> &str { + match self { + Self::Static(theme) => theme, + Self::Dynamic { mode, light, dark } => match mode { + ThemeMode::Light => light, + ThemeMode::Dark => dark, + ThemeMode::System => match system_appearance { + Appearance::Light => light, + Appearance::Dark => dark, + }, + }, + } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { #[serde(default)] @@ -54,7 +143,7 @@ pub struct ThemeSettingsContent { #[serde(default)] pub buffer_font_features: Option, #[serde(default)] - pub theme: Option, + pub theme: Option, /// EXPERIMENTAL: Overrides for the current theme. /// @@ -188,6 +277,7 @@ impl settings::Settings for ThemeSettings { cx: &mut AppContext, ) -> Result { let themes = ThemeRegistry::default_global(cx); + let system_appearance = SystemAppearance::default_global(cx); let mut this = Self { ui_font_size: defaults.ui_font_size.unwrap().into(), @@ -205,9 +295,9 @@ impl settings::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - requested_theme: defaults.theme.clone(), + theme_selection: defaults.theme.clone(), active_theme: themes - .get(defaults.theme.as_ref().unwrap()) + .get(defaults.theme.as_ref().unwrap().theme(*system_appearance)) .or(themes.get(&one_dark().name)) .unwrap(), theme_overrides: None, @@ -229,9 +319,11 @@ impl settings::Settings for ThemeSettings { } if let Some(value) = &value.theme { - this.requested_theme = Some(value.clone()); + this.theme_selection = Some(value.clone()); + + let theme_name = value.theme(*system_appearance); - if let Some(theme) = themes.get(value).log_err() { + if let Some(theme) = themes.get(theme_name).log_err() { this.active_theme = theme; } } @@ -291,10 +383,6 @@ impl settings::Settings for ThemeSettings { .unwrap() .properties .extend([ - ( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - ), ( "buffer_font_family".to_owned(), Schema::new_ref("#/definitions/FontFamilies".into()), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8161217c8f3b6b07a2eef665d7917766883ba777..14cddafa7af59de9f4ba344b9436bb2432d843d9 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -27,7 +27,7 @@ pub use schema::*; pub use settings::*; pub use styles::*; -use gpui::{AppContext, AssetSource, Hsla, SharedString}; +use gpui::{AppContext, AssetSource, Hsla, SharedString, WindowAppearance}; use serde::Deserialize; #[derive(Debug, PartialEq, Clone, Copy, Deserialize)] @@ -45,6 +45,15 @@ impl Appearance { } } +impl From for Appearance { + fn from(value: WindowAppearance) -> Self { + match value { + WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark, + WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light, + } + } +} + pub enum LoadThemes { /// Only load the base theme. /// diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 21d90735704eb4bee19392fc00c04c803c05ad21..f82a0c5ac7a72dc4c692b32f41096d1bf0cc65eb 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -9,7 +9,9 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; -use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; +use theme::{ + Appearance, Theme, ThemeMeta, ThemeMode, ThemeRegistry, ThemeSelection, ThemeSettings, +}; use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; @@ -167,8 +169,26 @@ impl PickerDelegate for ThemeSelectorDelegate { self.telemetry .report_setting_event("theme", theme_name.to_string()); + let appearance = Appearance::from(cx.appearance()); + update_settings_file::(self.fs.clone(), cx, move |settings| { - settings.theme = Some(theme_name.to_string()); + if let Some(selection) = settings.theme.as_mut() { + let theme_to_update = match selection { + ThemeSelection::Static(theme) => theme, + ThemeSelection::Dynamic { mode, light, dark } => match mode { + ThemeMode::Light => light, + ThemeMode::Dark => dark, + ThemeMode::System => match appearance { + Appearance::Light => light, + Appearance::Dark => dark, + }, + }, + }; + + *theme_to_update = theme_name.to_string(); + } else { + settings.theme = Some(ThemeSelection::Static(theme_name.to_string())); + } }); self.view diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index af8608776f28a1a2e7cbcac2b2d94ec6b990b5bb..23c9b84f010a7b6f78b6a861339f2f33acd90a49 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -64,7 +64,7 @@ use std::{ sync::{atomic::AtomicUsize, Arc}, time::Duration, }; -use theme::{ActiveTheme, ThemeSettings}; +use theme::{ActiveTheme, SystemAppearance, ThemeSettings}; pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub use ui; use ui::Label; @@ -682,6 +682,21 @@ impl Workspace { } cx.notify(); }), + cx.observe_window_appearance(|_, cx| { + let window_appearance = cx.appearance(); + + *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into()); + + let mut theme_settings = ThemeSettings::get_global(cx).clone(); + + if let Some(theme_selection) = theme_settings.theme_selection.clone() { + let theme_name = theme_selection.theme(window_appearance.into()); + + if let Some(_theme) = theme_settings.switch_theme(&theme_name, cx) { + ThemeSettings::override_global(theme_settings, cx); + } + } + }), cx.observe(&left_dock, |this, _, cx| { this.serialize_workspace(cx); cx.notify(); @@ -840,6 +855,8 @@ impl Workspace { let workspace_id = workspace_id.clone(); let project_handle = project_handle.clone(); move |cx| { + SystemAppearance::init_for_window(cx); + cx.new_view(|cx| { Workspace::new(workspace_id, project_handle, app_state, cx) }) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2f5db82dd6b87ac76125e34dfa8a4d91318458b7..ee17b16e4cc5ec3809440d0b6e8b887ba04f80d5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -43,7 +43,7 @@ use std::{ thread, time::Duration, }; -use theme::{ActiveTheme, ThemeRegistry, ThemeSettings}; +use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings}; use util::{ async_maybe, http::{self, HttpClient, ZedHttpClient}, @@ -912,8 +912,10 @@ fn load_user_themes_in_background(fs: Arc, cx: &mut AppContext) { theme_registry.load_user_themes(themes_dir, fs).await?; cx.update(|cx| { let mut theme_settings = ThemeSettings::get_global(cx).clone(); - if let Some(requested_theme) = theme_settings.requested_theme.clone() { - if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx) { + if let Some(theme_selection) = theme_settings.theme_selection.clone() { + let theme_name = theme_selection.theme(*SystemAppearance::global(cx)); + + if let Some(_theme) = theme_settings.switch_theme(&theme_name, cx) { ThemeSettings::override_global(theme_settings, cx); } } @@ -949,11 +951,14 @@ fn watch_themes(fs: Arc, cx: &mut AppContext) { cx.update(|cx| { let mut theme_settings = ThemeSettings::get_global(cx).clone(); - if let Some(requested_theme) = - theme_settings.requested_theme.clone() + if let Some(theme_selection) = + theme_settings.theme_selection.clone() { + let theme_name = + theme_selection.theme(*SystemAppearance::global(cx)); + if let Some(_theme) = - theme_settings.switch_theme(&requested_theme, cx) + theme_settings.switch_theme(&theme_name, cx) { ThemeSettings::override_global(theme_settings, cx); } From 0c34bd893530ab7d57e4648f7212dfb025ae1c89 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 5 Feb 2024 15:12:25 -0700 Subject: [PATCH 065/101] fix following bugs (#7406) - Another broken following test - Fix following between two windows Release Notes: - Fixed following when the leader has multiple Zed windows open --- crates/collab/src/tests/following_tests.rs | 81 ++++++++++++++++++++ crates/collab/src/tests/test_server.rs | 7 +- crates/collab_ui/src/collab_titlebar_item.rs | 25 ++++-- crates/workspace/src/item.rs | 1 + crates/workspace/src/workspace.rs | 26 ++++--- 5 files changed, 119 insertions(+), 21 deletions(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index bfebd0d2571ed28cc674a6d5a455cb098a0dfba0..28fcc99271a1ad0ef06c37386b96bd59e4f7936c 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -22,6 +22,8 @@ use workspace::{ SplitDirection, Workspace, }; +use super::TestClient; + #[gpui::test(iterations = 10)] async fn test_basic_following( cx_a: &mut TestAppContext, @@ -1996,3 +1998,82 @@ async fn test_following_to_channel_notes_without_a_shared_project( ); }); } + +async fn join_channel( + channel_id: u64, + client: &TestClient, + cx: &mut TestAppContext, +) -> anyhow::Result<()> { + cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, cx)) + .await +} + +async fn share_workspace( + workspace: &View, + cx: &mut VisualTestContext, +) -> anyhow::Result { + let project = workspace.update(cx, |workspace, _| workspace.project().clone()); + cx.read(ActiveCall::global) + .update(cx, |call, cx| call.share_project(project, cx)) + .await +} + +#[gpui::test] +async fn test_following_to_channel_notes_other_workspace( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let (_, client_a, client_b, channel) = TestServer::start2(cx_a, cx_b).await; + + let mut cx_a2 = cx_a.clone(); + let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await; + join_channel(channel, &client_a, cx_a).await.unwrap(); + share_workspace(&workspace_a, cx_a).await.unwrap(); + + // a opens 1.txt + cx_a.simulate_keystrokes("cmd-p 1 enter"); + cx_a.run_until_parked(); + workspace_a.update(cx_a, |workspace, cx| { + let editor = workspace.active_item(cx).unwrap(); + assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + }); + + // b joins channel and is following a + join_channel(channel, &client_b, cx_b).await.unwrap(); + cx_b.run_until_parked(); + let (workspace_b, cx_b) = client_b.active_workspace(cx_b); + workspace_b.update(cx_b, |workspace, cx| { + let editor = workspace.active_item(cx).unwrap(); + assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + }); + + // a opens a second workspace and the channel notes + let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await; + cx_a2.update(|cx| cx.activate_window()); + cx_a2 + .update(|cx| ChannelView::open(channel, None, workspace_a2, cx)) + .await + .unwrap(); + cx_a2.run_until_parked(); + + // b should follow a to the channel notes + workspace_b.update(cx_b, |workspace, cx| { + let editor = workspace.active_item_as::(cx).unwrap(); + assert_eq!(editor.read(cx).channel(cx).unwrap().id, channel); + }); + + // a returns to the shared project + cx_a.update(|cx| cx.activate_window()); + cx_a.run_until_parked(); + + workspace_a.update(cx_a, |workspace, cx| { + let editor = workspace.active_item(cx).unwrap(); + assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + }); + + // b should follow a back + workspace_b.update(cx_b, |workspace, cx| { + let editor = workspace.active_item_as::(cx).unwrap(); + assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + }); +} diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 13fd15e3f445020af324cdaba03aa0b686c70956..69e338b6ea45eb0bf298de386c2602732fa0b718 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -123,7 +123,12 @@ impl TestServer { let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let channel_id = server - .make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)]) + .make_channel( + "test-channel", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b)], + ) .await; cx_a.run_until_parked(); diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 29175983d3f6e48afe8f048fd221195fc29e3770..2d0177c34323d2e6e6c243ec5e6d7852513269d8 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -562,14 +562,23 @@ impl CollabTitlebarItem { } fn window_activation_changed(&mut self, cx: &mut ViewContext) { - let project = if cx.is_window_active() { - Some(self.project.clone()) - } else { - None - }; - ActiveCall::global(cx) - .update(cx, |call, cx| call.set_location(project.as_ref(), cx)) - .detach_and_log_err(cx); + if cx.is_window_active() { + ActiveCall::global(cx) + .update(cx, |call, cx| call.set_location(Some(&self.project), cx)) + .detach_and_log_err(cx); + return; + } + + if cx.active_window().is_none() { + ActiveCall::global(cx) + .update(cx, |call, cx| call.set_location(None, cx)) + .detach_and_log_err(cx); + } + self.workspace + .update(cx, |workspace, cx| { + workspace.update_active_view_for_followers(cx); + }) + .ok(); } fn active_call_changed(&mut self, cx: &mut ViewContext) { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 908ea1d168c22fcfbeeb0417bd40baec9f0ae1c9..d5d8aed39dd300f3e5dd9eccd549d79272b2bae9 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -666,6 +666,7 @@ pub trait ProjectItem: Item { Self: Sized; } +#[derive(Debug)] pub enum FollowEvent { Unfollow, } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 23c9b84f010a7b6f78b6a861339f2f33acd90a49..6ca74875b76f896ecc9ae8c4b8f500f5b8261bc2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2910,25 +2910,27 @@ impl Workspace { Ok(()) } - fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) { + pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) { let mut is_project_item = true; let mut update = proto::UpdateActiveView::default(); - if let Some(item) = self.active_item(cx) { - if item.focus_handle(cx).contains_focused(cx) { - if let Some(item) = item.to_followable_item_handle(cx) { - is_project_item = item.is_project_item(cx); - update = proto::UpdateActiveView { - id: item - .remote_id(&self.app_state.client, cx) - .map(|id| id.to_proto()), - leader_id: self.leader_for_pane(&self.active_pane), - }; + if cx.is_window_active() { + if let Some(item) = self.active_item(cx) { + if item.focus_handle(cx).contains_focused(cx) { + if let Some(item) = item.to_followable_item_handle(cx) { + is_project_item = item.is_project_item(cx); + update = proto::UpdateActiveView { + id: item + .remote_id(&self.app_state.client, cx) + .map(|id| id.to_proto()), + leader_id: self.leader_for_pane(&self.active_pane), + }; + } } } } - if update.id != self.last_active_view_id { + if &update.id != &self.last_active_view_id { self.last_active_view_id = update.id.clone(); self.update_followers( is_project_item, From 1a40c9f0f2cacf2981b0f0ab6f6deec3935deca0 Mon Sep 17 00:00:00 2001 From: Pseudomata Date: Mon, 5 Feb 2024 17:20:40 -0500 Subject: [PATCH 066/101] Add Haskell buffer symbol search (#7331) This PR is a follow-up from https://github.com/zed-industries/zed/pull/6786#issuecomment-1912912550 and adds an `outline.scm` file for buffer symbol search support in Haskell. Release Notes: - Added buffer symbol search support for Haskell --- crates/zed/src/languages/haskell/outline.scm | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 crates/zed/src/languages/haskell/outline.scm diff --git a/crates/zed/src/languages/haskell/outline.scm b/crates/zed/src/languages/haskell/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..db744eca6d551563ce18ea52d5ec10c263215a7c --- /dev/null +++ b/crates/zed/src/languages/haskell/outline.scm @@ -0,0 +1,26 @@ +(adt + "data" @context + name: (type) @name) @item + +(type_alias + "type" @context + name: (type) @name) @item + +(newtype + "newtype" @context + name: (type) @name) @item + +(signature + name: (variable) @name) @item + +(class + "class" @context + (class_head) @name) @item + +(instance + "instance" @context + (instance_head) @name) @item + +(foreign_import + "foreign" @context + (impent) @name) @item From d04a2866342565583d38f54929200c4bcbb6d4c9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 5 Feb 2024 15:21:18 -0700 Subject: [PATCH 067/101] Fix prompting the user to hang up when opening a workspace (#7408) Before this change if you had joined a call with an empty workspace, then we'd prompt you to hang up when you tried to open a recent project. Release Notes: - Fixed an erroneous prompt to "hang up" when opening a new project from an empty workspace. --- crates/workspace/src/workspace.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6ca74875b76f896ecc9ae8c4b8f500f5b8261bc2..181f14386de4786aac1ed5a88ed641f7e6d46b55 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1345,22 +1345,14 @@ impl Workspace { let is_remote = self.project.read(cx).is_remote(); let has_worktree = self.project.read(cx).worktrees().next().is_some(); let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx)); - let close_task = if is_remote || has_worktree || has_dirty_items { + let window_to_replace = if is_remote || has_worktree || has_dirty_items { None } else { - Some(self.prepare_to_close(false, cx)) + window }; let app_state = self.app_state.clone(); cx.spawn(|_, mut cx| async move { - let window_to_replace = if let Some(close_task) = close_task { - if !close_task.await? { - return Ok(()); - } - window - } else { - None - }; cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))? .await?; Ok(()) From c7c4166724a0542d5f0487abce11094080f53a2d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 5 Feb 2024 14:48:54 -0800 Subject: [PATCH 068/101] Disable extra frames for ProMotion when screen is not active. (#7410) This was causing an issue where windows about 1/10 of the way across the display would hang for a fully second after being deactivated. Release Notes: - N/A Co-authored-by: max Co-authored-by: nathan Co-authored-by: antonio --- crates/gpui/src/platform/mac/window.rs | 1 + crates/gpui/src/window.rs | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 946e608a7a686d2680b98b1388c652e2cedcf3f5..b15e8c8de5c47c25ffff0f744597bc2f6b6b2b89 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1530,6 +1530,7 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) { extern "C" fn step(this: &Object, _: Sel, display_link: id) { let window_state = unsafe { get_window_state(this) }; let mut lock = window_state.lock(); + if lock.display_link == display_link { if let Some(mut callback) = lock.request_frame_callback.take() { drop(lock); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 69982cce6813a0b15108b4ffce0ec92a726acba8..47068eacc724ea549f47b3726a20fe20810261b0 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -271,7 +271,7 @@ pub struct Window { bounds_observers: SubscriberSet<(), AnyObserver>, appearance: WindowAppearance, appearance_observers: SubscriberSet<(), AnyObserver>, - active: bool, + active: Rc>, pub(crate) dirty: Rc>, pub(crate) last_input_timestamp: Rc>, pub(crate) refreshing: bool, @@ -337,11 +337,13 @@ impl Window { let appearance = platform_window.appearance(); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); let dirty = Rc::new(Cell::new(true)); + let active = Rc::new(Cell::new(false)); let last_input_timestamp = Rc::new(Cell::new(Instant::now())); platform_window.on_request_frame(Box::new({ let mut cx = cx.to_async(); let dirty = dirty.clone(); + let active = active.clone(); let last_input_timestamp = last_input_timestamp.clone(); move || { if dirty.get() { @@ -353,9 +355,12 @@ impl Window { }) .log_err(); }) - } else if last_input_timestamp.get().elapsed() < Duration::from_secs(1) { - // Keep presenting the current scene for 1 extra second since the - // last input to prevent the display from underclocking the refresh rate. + } + // Keep presenting the current scene for 1 extra second since the + // last input to prevent the display from underclocking the refresh rate. + else if active.get() + && last_input_timestamp.get().elapsed() < Duration::from_secs(1) + { handle.update(&mut cx, |_, cx| cx.present()).log_err(); } } @@ -389,7 +394,7 @@ impl Window { move |active| { handle .update(&mut cx, |_, cx| { - cx.window.active = active; + cx.window.active.set(active); cx.window .activation_observers .clone() @@ -435,7 +440,7 @@ impl Window { bounds_observers: SubscriberSet::new(), appearance, appearance_observers: SubscriberSet::new(), - active: false, + active, dirty, last_input_timestamp, refreshing: false, @@ -777,7 +782,7 @@ impl<'a> WindowContext<'a> { /// Returns whether this window is focused by the operating system (receiving key events). pub fn is_window_active(&self) -> bool { - self.window.active + self.window.active.get() } /// Toggle zoom on the window. @@ -1033,7 +1038,7 @@ impl<'a> WindowContext<'a> { self.window.focus, ); self.window.next_frame.focus = self.window.focus; - self.window.next_frame.window_active = self.window.active; + self.window.next_frame.window_active = self.window.active.get(); self.window.root_view = Some(root_view); // Set the cursor only if we're the active window. @@ -2553,7 +2558,7 @@ impl WindowHandle { pub fn is_active(&self, cx: &AppContext) -> Option { cx.windows .get(self.id) - .and_then(|window| window.as_ref().map(|window| window.active)) + .and_then(|window| window.as_ref().map(|window| window.active.get())) } } From 038d2a84e85a13444f0c4578a7d7cdf33d8a8692 Mon Sep 17 00:00:00 2001 From: Oliver N Date: Tue, 6 Feb 2024 15:14:50 +0700 Subject: [PATCH 069/101] Respect RUST_LOG when started in terminal (#7425) Release Notes: - N/A --- crates/zed/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ee17b16e4cc5ec3809440d0b6e8b887ba04f80d5..136ebe2642d89247cfb55e47200b1e1ff18a8003 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -471,6 +471,7 @@ fn init_paths() { fn init_logger() { if stdout_is_a_pty() { Builder::new() + .parse_default_env() .format(|buf, record| { use env_logger::fmt::Color; From c591681bad394ee8d956b7a1940036c4791b4910 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 6 Feb 2024 10:31:01 +0100 Subject: [PATCH 070/101] Fix Terminal focus handlers not being called (#7428) This fixes #7401 and probably a few other things that seemed odd with the terminal. Turns out that `TerminalView` has `focus_in` and `focus_out` callbacks, but they were never called. The `focus_handle` on which they were set was not passed in to `TerminalView`. That meant that the `impl FocusableView for TerminalView` never returned the focus handle with the right callbacks. This change here uses the already created focus handle and passes it in, so that `focus_in` and `focus_out` are now correctly called. Release Notes: - Fixed terminal not handling focus-state correctly and, for example, not restoring cursor blinking state correctly. ([#7401](https://github.com/zed-industries/zed/issues/7401)). --- crates/terminal_view/src/terminal_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 006ffb9d7945c887e5ec09b2ae3a00fa29454471..3b9f4333bfcc343964dfc7bcb041eb04dd78e49b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -311,7 +311,7 @@ impl TerminalView { workspace: workspace_handle, has_new_content: true, has_bell: false, - focus_handle: cx.focus_handle(), + focus_handle, context_menu: None, blink_state: true, blinking_on: false, From 1446fb7632d93c9cee5a43ff8b801edf2a1a7382 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:10:15 +0100 Subject: [PATCH 071/101] Outline a bunch of methods in gpui that leaked SubscriberSet types (#7430) This takes down LLVM IR size of theme_selector from 316k to ~250k. Note that I do not care about theme_selector in particular, though it acts as a benchmark for smaller crates to me ("how much static overhead in compile time does gpui have"). The title is a bit dramatic, so just to shed some light: by leaking a type I mean forcing downstream crates to codegen it's methods/know about it's drop code. Since SubscriberSet is no longer used directly in the generic (==inlineable) methods, users no longer have to codegen `insert` and co. Release Notes: - N/A --- crates/gpui/src/app.rs | 53 ++++++++++++++++++++--------- crates/gpui/src/window.rs | 70 ++++++++++++++++++--------------------- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8d6f70f27e2a25095e1296b8853f4c9cba00cec6..45c26b0ba81029b2490274cc4cd491ad522b946f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -380,6 +380,11 @@ impl AppContext { }) } + pub(crate) fn new_observer(&mut self, key: EntityId, value: Handler) -> Subscription { + let (subscription, activate) = self.observers.insert(key, value); + self.defer(move |_| activate()); + subscription + } pub(crate) fn observe_internal( &mut self, entity: &E, @@ -391,7 +396,7 @@ impl AppContext { { let entity_id = entity.entity_id(); let handle = entity.downgrade(); - let (subscription, activate) = self.observers.insert( + self.new_observer( entity_id, Box::new(move |cx| { if let Some(handle) = E::upgrade_from(&handle) { @@ -400,9 +405,7 @@ impl AppContext { false } }), - ); - self.defer(move |_| activate()); - subscription + ) } /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type. @@ -423,6 +426,15 @@ impl AppContext { }) } + pub(crate) fn new_subscription( + &mut self, + key: EntityId, + value: (TypeId, Listener), + ) -> Subscription { + let (subscription, activate) = self.event_listeners.insert(key, value); + self.defer(move |_| activate()); + subscription + } pub(crate) fn subscribe_internal( &mut self, entity: &E, @@ -435,7 +447,7 @@ impl AppContext { { let entity_id = entity.entity_id(); let entity = entity.downgrade(); - let (subscription, activate) = self.event_listeners.insert( + self.new_subscription( entity_id, ( TypeId::of::(), @@ -448,9 +460,7 @@ impl AppContext { } }), ), - ); - self.defer(move |_| activate()); - subscription + ) } /// Returns handles to all open windows in the application. @@ -930,13 +940,22 @@ impl AppContext { self.globals_by_type.insert(global_type, lease.global); } + pub(crate) fn new_view_observer( + &mut self, + key: TypeId, + value: NewViewListener, + ) -> Subscription { + let (subscription, activate) = self.new_view_observers.insert(key, value); + activate(); + subscription + } /// Arrange for the given function to be invoked whenever a view of the specified type is created. /// The function will be passed a mutable reference to the view along with an appropriate context. pub fn observe_new_views( &mut self, on_new: impl 'static + Fn(&mut V, &mut ViewContext), ) -> Subscription { - let (subscription, activate) = self.new_view_observers.insert( + self.new_view_observer( TypeId::of::(), Box::new(move |any_view: AnyView, cx: &mut WindowContext| { any_view @@ -946,9 +965,7 @@ impl AppContext { on_new(view_state, cx); }) }), - ); - activate(); - subscription + ) } /// Observe the release of a model or view. The callback is invoked after the model or view @@ -980,9 +997,15 @@ impl AppContext { &mut self, f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static, ) -> Subscription { - let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f)); - activate(); - subscription + fn inner( + keystroke_observers: &mut SubscriberSet<(), KeystrokeObserver>, + handler: KeystrokeObserver, + ) -> Subscription { + let (subscription, activate) = keystroke_observers.insert((), handler); + activate(); + subscription + } + inner(&mut self.keystroke_observers, Box::new(f)) } pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 47068eacc724ea549f47b3726a20fe20810261b0..4b7c43e113672b9bb2d9ff431042ac0d7ea88776 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -451,6 +451,12 @@ impl Window { pending_input: None, } } + fn new_focus_listener( + &mut self, + value: AnyWindowFocusListener, + ) -> (Subscription, impl FnOnce()) { + self.focus_listeners.insert((), value) + } } /// Indicates which region of the window is visible. Content falling outside of this mask will not be @@ -635,7 +641,7 @@ impl<'a> WindowContext<'a> { let entity_id = entity.entity_id(); let entity = entity.downgrade(); let window_handle = self.window.handle; - let (subscription, activate) = self.app.event_listeners.insert( + self.app.new_subscription( entity_id, ( TypeId::of::(), @@ -653,9 +659,7 @@ impl<'a> WindowContext<'a> { .unwrap_or(false) }), ), - ); - self.app.defer(move |_| activate()); - subscription + ) } /// Creates an [`AsyncWindowContext`], which has a static lifetime and can be held across @@ -1747,13 +1751,15 @@ impl VisualContext for WindowContext<'_> { let entity = build_view_state(&mut cx); cx.entities.insert(slot, entity); - cx.new_view_observers - .clone() - .retain(&TypeId::of::(), |observer| { - let any_view = AnyView::from(view.clone()); - (observer)(any_view, self); + // Non-generic part to avoid leaking SubscriberSet to invokers of `new_view`. + fn notify_observers(cx: &mut WindowContext, tid: TypeId, view: AnyView) { + cx.new_view_observers.clone().retain(&tid, |observer| { + let any_view = view.clone(); + (observer)(any_view, cx); true }); + } + notify_observers(self, TypeId::of::(), AnyView::from(view.clone())); view } @@ -1955,7 +1961,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { let entity_id = entity.entity_id(); let entity = entity.downgrade(); let window_handle = self.window.handle; - let (subscription, activate) = self.app.observers.insert( + self.app.new_observer( entity_id, Box::new(move |cx| { window_handle @@ -1969,9 +1975,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }) .unwrap_or(false) }), - ); - self.app.defer(move |_| activate()); - subscription + ) } /// Subscribe to events emitted by another model or view. @@ -1991,7 +1995,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { let entity_id = entity.entity_id(); let handle = entity.downgrade(); let window_handle = self.window.handle; - let (subscription, activate) = self.app.event_listeners.insert( + self.app.new_subscription( entity_id, ( TypeId::of::(), @@ -2009,9 +2013,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { .unwrap_or(false) }), ), - ); - self.app.defer(move |_| activate()); - subscription + ) } /// Register a callback to be invoked when the view is released. @@ -2136,9 +2138,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) -> Subscription { let view = self.view.downgrade(); let focus_id = handle.id; - let (subscription, activate) = self.window.focus_listeners.insert( - (), - Box::new(move |event, cx| { + let (subscription, activate) = + self.window.new_focus_listener(Box::new(move |event, cx| { view.update(cx, |view, cx| { if event.previous_focus_path.last() != Some(&focus_id) && event.current_focus_path.last() == Some(&focus_id) @@ -2147,9 +2148,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } }) .is_ok() - }), - ); - self.app.defer(move |_| activate()); + })); + self.app.defer(|_| activate()); subscription } @@ -2162,9 +2162,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) -> Subscription { let view = self.view.downgrade(); let focus_id = handle.id; - let (subscription, activate) = self.window.focus_listeners.insert( - (), - Box::new(move |event, cx| { + let (subscription, activate) = + self.window.new_focus_listener(Box::new(move |event, cx| { view.update(cx, |view, cx| { if !event.previous_focus_path.contains(&focus_id) && event.current_focus_path.contains(&focus_id) @@ -2173,8 +2172,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } }) .is_ok() - }), - ); + })); self.app.defer(move |_| activate()); subscription } @@ -2188,9 +2186,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) -> Subscription { let view = self.view.downgrade(); let focus_id = handle.id; - let (subscription, activate) = self.window.focus_listeners.insert( - (), - Box::new(move |event, cx| { + let (subscription, activate) = + self.window.new_focus_listener(Box::new(move |event, cx| { view.update(cx, |view, cx| { if event.previous_focus_path.last() == Some(&focus_id) && event.current_focus_path.last() != Some(&focus_id) @@ -2199,8 +2196,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } }) .is_ok() - }), - ); + })); self.app.defer(move |_| activate()); subscription } @@ -2231,9 +2227,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) -> Subscription { let view = self.view.downgrade(); let focus_id = handle.id; - let (subscription, activate) = self.window.focus_listeners.insert( - (), - Box::new(move |event, cx| { + let (subscription, activate) = + self.window.new_focus_listener(Box::new(move |event, cx| { view.update(cx, |view, cx| { if event.previous_focus_path.contains(&focus_id) && !event.current_focus_path.contains(&focus_id) @@ -2242,8 +2237,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } }) .is_ok() - }), - ); + })); self.app.defer(move |_| activate()); subscription } From dad2df365c99559bff757532419349ab3e3ce614 Mon Sep 17 00:00:00 2001 From: Josh Taylor Date: Tue, 6 Feb 2024 18:25:46 +0800 Subject: [PATCH 072/101] Add notes about XCode also being on the Apple Developer website (#7431) Super minor edit about XCode being on the Apple Developer website, which can be easier to download and install without needing to go through the App Store. Release Notes: - N/A --------- Co-authored-by: Kirill Bulatov --- docs/src/developing_zed__building_zed.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index ba648f7d2d3d9fe07c0b93c009031dcbac364b51..e04e01029ad3f2ba502f939a74c62433f0b0fbcc 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -11,7 +11,9 @@ git submodule update --init --recursive ## Dependencies - Install [Rust](https://www.rust-lang.org/tools/install) -- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store +- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store, or from the [Apple Developer](https://developer.apple.com/download/all/) website. Note this requires a developer account. + +> Ensure you launch XCode after installing, and install the MacOS components, which is the default option. - Install [Xcode command line tools](https://developer.apple.com/xcode/resources/) @@ -74,7 +76,7 @@ error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)`* xcrun: error: unable to find utility "metal", not a developer tool or in PATH ``` -Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` +Try `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer` ### Cargo errors claiming that a dependency is using unstable features From b2ce515593e2154d99b8e43462a0fea43fe80b01 Mon Sep 17 00:00:00 2001 From: Guillem Arias Fauste Date: Tue, 6 Feb 2024 13:34:46 +0100 Subject: [PATCH 073/101] Add Prisma file icon (#7207) Add Prisma icon from https://github.com/file-icons/icons ![CleanShot 2024-02-06 at 13 17 01@2x](https://github.com/zed-industries/zed/assets/5864275/55ce9286-4e15-4125-b7f7-003e2e8d8bd5) Release Notes: - Added Prisma icon. --- assets/icons/file_icons/file_types.json | 4 ++++ assets/icons/file_icons/prisma.svg | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 assets/icons/file_icons/prisma.svg diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 5dd0cbb91567a6f18a6ee436acfd4d55a70d4a60..916ccc89cb1db63c1e383d5f69e5eeed180a41cd 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -87,6 +87,7 @@ "pptx": "document", "prettierignore": "prettier", "prettierrc": "prettier", + "prisma": "prisma", "profile": "terminal", "ps1": "terminal", "psd": "image", @@ -193,6 +194,9 @@ "prettier": { "icon": "icons/file_icons/prettier.svg" }, + "prisma": { + "icon": "icons/file_icons/prisma.svg" + }, "python": { "icon": "icons/file_icons/python.svg" }, diff --git a/assets/icons/file_icons/prisma.svg b/assets/icons/file_icons/prisma.svg new file mode 100644 index 0000000000000000000000000000000000000000..8cf1a0e84bcc055d6acf51fe9feac3b951607826 --- /dev/null +++ b/assets/icons/file_icons/prisma.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file From 56f7f18033039b789aabb5f6b661b4fb77a2b4a6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 6 Feb 2024 10:18:17 -0500 Subject: [PATCH 074/101] Hide the chat message editor when there is no active chat (#7441) This PR makes it so the chat message editor is hidden when not in an active chat. Release Notes: - Changed the chat message editor to be hidden when not in an active chat. --- crates/collab_ui/src/chat_panel.rs | 41 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 88ee461dee537e42c07bfd718da53868e9209713..804bd3acf8831834c1a255982409b0ecb6af3ec2 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -589,25 +589,28 @@ impl Render for ChatPanel { ) } })) - .child( - h_flex() - .when(!self.is_scrolled_to_bottom, |el| { - el.border_t_1().border_color(cx.theme().colors().border) - }) - .p_2() - .map(|el| { - if self.active_chat.is_some() { - el.child(self.message_editor.clone()) - } else { - el.child( - div() - .rounded_md() - .h_6() - .w_full() - .bg(cx.theme().colors().editor_background), - ) - } - }), + .children( + Some( + h_flex() + .when(!self.is_scrolled_to_bottom, |el| { + el.border_t_1().border_color(cx.theme().colors().border) + }) + .p_2() + .map(|el| { + if self.active_chat.is_some() { + el.child(self.message_editor.clone()) + } else { + el.child( + div() + .rounded_md() + .h_6() + .w_full() + .bg(cx.theme().colors().editor_background), + ) + } + }), + ) + .filter(|_| self.active_chat.is_some()), ) .into_any() } From 33d982b08aa5c6dbcce5dfb164fa2c84086a11c8 Mon Sep 17 00:00:00 2001 From: Andrey Kuzmin Date: Tue, 6 Feb 2024 16:25:38 +0100 Subject: [PATCH 075/101] Add Elm file icon (#7440) Tried to match the existing file icons in Zed as much as possible. This is how it looks: | dark | light | |---|----| | Screenshot 2024-02-06 at 15 03 57 | Screenshot 2024-02-06 at 15 03 14 | The main challenge is that the tangram is visually quite heavy and detailed. The existing icons in Zed are designed in a 14px bounding box, but are a bit smaller themselves. I guess the extra space is reserved for hanging elements, it probably doesn't make sense to occupy the whole area. Simply scaling down an available SVG of the tangram didn't work well. The individual shapes were not recognizable because the spacing between them was too thin. I tried removing the spacing and applying different opacities for each shape, but that didn't yield enough contrast between the shapes either. The second approach was to just use the outlines. It sort of worked, but looked a bit messy in the places when the outlines are denser than the tangram shapes: | dark | light | |---|----| | Screenshot 2024-02-05 at 22 55 46 | Screenshot 2024-02-05 at 22 56 05 | I then tried to remove the main outline and use the maximum space for the tangram. That let me increase the spacing between the shapes. I also rounded them a little bit, to make them look similar to other icons from Zed. The end result looks clean and the shapes are still recognisable. Approaches I tried next to an existing icon from Zed: Screenshot 2024-02-06 at 15 15 33 Release Notes: - Added file type icon for Elm --- assets/icons/file_icons/elm.svg | 9 +++++++++ assets/icons/file_icons/file_types.json | 4 ++++ 2 files changed, 13 insertions(+) create mode 100644 assets/icons/file_icons/elm.svg diff --git a/assets/icons/file_icons/elm.svg b/assets/icons/file_icons/elm.svg new file mode 100644 index 0000000000000000000000000000000000000000..3e05cf9d5ca1b02f7cd97d7fcf859e572d16a689 --- /dev/null +++ b/assets/icons/file_icons/elm.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 916ccc89cb1db63c1e383d5f69e5eeed180a41cd..88c1c650235939317a5c896898d8e96f044dbf5c 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -25,6 +25,7 @@ "doc": "document", "docx": "document", "eex": "elixir", + "elm": "elm", "erl": "erlang", "escript": "erlang", "eslintrc": "eslint", @@ -155,6 +156,9 @@ "elixir": { "icon": "icons/file_icons/elixir.svg" }, + "elm": { + "icon": "icons/file_icons/elm.svg" + }, "erlang": { "icon": "icons/file_icons/erlang.svg" }, From 068c1415597fc1bcc71f5bc1f29dd039ae104245 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 6 Feb 2024 16:44:24 +0100 Subject: [PATCH 076/101] Fix shell environment not loading for Nushell (#7442) Turns out that Nushell doesn't like `-lic` and `&&`, but works perfectly fine with `-l -i -c` and `;`, which the other shells do too. These all work: bash -l -i -c 'echo lol; /usr/bin/env -0' nu -l -i -c 'echo lol; /usr/bin/env -0' zsh -l -i -c 'echo lol; /usr/bin/env -0' fish -l -i -c 'echo lol; /usr/bin/env -0' Release Notes: - Fixed shell environment not being loaded if Nushell was set as `$SHELL`. --- crates/zed/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 136ebe2642d89247cfb55e47200b1e1ff18a8003..33fcfc040f531f6adc36f5c3601cef1cbe312ba6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -811,7 +811,7 @@ async fn load_login_shell_environment() -> Result<()> { "SHELL environment variable is not assigned so we can't source login environment variables", )?; let output = Command::new(&shell) - .args(["-lic", &format!("echo {marker} && /usr/bin/env -0")]) + .args(["-l", "-i", "-c", &format!("echo {marker}; /usr/bin/env -0")]) .output() .await .context("failed to spawn login shell to source login environment variables")?; From 960eaf6245c6ece12d42847db47373535b5a0acf Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 6 Feb 2024 16:58:10 +0100 Subject: [PATCH 077/101] Remove unused TerminalView.has_new_content (#7429) Found this last week with @osiewicz and we realized that it's unused. So I think it's fine to remove it, but I want to hear whether @mikayla-maki has some thoughts here. Release Notes: - N/A --- crates/terminal_view/src/terminal_view.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3b9f4333bfcc343964dfc7bcb041eb04dd78e49b..b1e12ae6da3f022602bfe97a93f51ab6dc6c0ccf 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -77,7 +77,6 @@ pub struct TerminalView { terminal: Model, workspace: WeakView, focus_handle: FocusHandle, - has_new_content: bool, //Currently using iTerm bell, show bell emoji in tab until input is received has_bell: bool, context_menu: Option<(View, gpui::Point, Subscription)>, @@ -142,9 +141,6 @@ impl TerminalView { cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); cx.subscribe(&terminal, move |this, _, event, cx| match event { Event::Wakeup => { - if !this.focus_handle.is_focused(cx) { - this.has_new_content = true; - } cx.notify(); cx.emit(Event::Wakeup); cx.emit(ItemEvent::UpdateTab); @@ -309,7 +305,6 @@ impl TerminalView { Self { terminal, workspace: workspace_handle, - has_new_content: true, has_bell: false, focus_handle, context_menu: None, @@ -327,10 +322,6 @@ impl TerminalView { &self.terminal } - pub fn has_new_content(&self) -> bool { - self.has_new_content - } - pub fn has_bell(&self) -> bool { self.has_bell } @@ -688,7 +679,6 @@ impl TerminalView { } fn focus_in(&mut self, cx: &mut ViewContext) { - self.has_new_content = false; self.terminal.read(cx).focus_in(); self.blink_cursors(self.blink_epoch, cx); cx.notify(); From 0b2a9d2bea5620e0e7d99097054f7a5879122dab Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 6 Feb 2024 11:37:08 -0500 Subject: [PATCH 078/101] Remove empty message editor placeholder (#7444) This PR removes the placeholder that we previously displayed for the chat message editor. With the changes in #7441 we can no longer hit this codepath. Release Notes: - N/A --- crates/collab_ui/src/chat_panel.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 804bd3acf8831834c1a255982409b0ecb6af3ec2..0ee4805a74b08280cd937eb23a95ecceaac98ab4 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -596,19 +596,7 @@ impl Render for ChatPanel { el.border_t_1().border_color(cx.theme().colors().border) }) .p_2() - .map(|el| { - if self.active_chat.is_some() { - el.child(self.message_editor.clone()) - } else { - el.child( - div() - .rounded_md() - .h_6() - .w_full() - .bg(cx.theme().colors().editor_background), - ) - } - }), + .child(self.message_editor.clone()), ) .filter(|_| self.active_chat.is_some()), ) From 792c832205e20943041bfbad2dc3427143e3ecb8 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 6 Feb 2024 12:08:11 -0500 Subject: [PATCH 079/101] Improve error handling when copying a permalink fails (#7447) This PR improves the error handling when the `editor: copy permalink to line` action fails. Right now if something goes wrong nothing happens, and we don't write anything to the logs. This PR makes it so we display a toast when the operation fails with the error message, as well as write it to the Zed logs. Release Notes: - Improved error behavior for `editor: copy permalink to line` action. --- crates/editor/src/editor.rs | 53 ++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4ed8f1370053db8035aedc2073e0c56bec16bdd9..ad4ebd2655e63ffb27312fad02eccac87ac697c1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -120,6 +120,7 @@ use ui::{ Tooltip, }; use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt}; +use workspace::Toast; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -8354,21 +8355,37 @@ impl Editor { use git::permalink::{build_permalink, BuildPermalinkParams}; let permalink = maybe!({ - let project = self.project.clone()?; + let project = self.project.clone().ok_or_else(|| anyhow!("no project"))?; let project = project.read(cx); - let worktree = project.visible_worktrees(cx).next()?; + let worktree = project + .visible_worktrees(cx) + .next() + .ok_or_else(|| anyhow!("no worktree"))?; let mut cwd = worktree.read(cx).abs_path().to_path_buf(); cwd.push(".git"); - let repo = project.fs().open_repo(&cwd)?; - let origin_url = repo.lock().remote_url("origin")?; - let sha = repo.lock().head_sha()?; - - let buffer = self.buffer().read(cx).as_singleton()?; - let file = buffer.read(cx).file().and_then(|f| f.as_local())?; - let path = file.path().to_str().map(|path| path.to_string())?; + const REMOTE_NAME: &'static str = "origin"; + let repo = project + .fs() + .open_repo(&cwd) + .ok_or_else(|| anyhow!("no Git repo"))?; + let origin_url = repo + .lock() + .remote_url(REMOTE_NAME) + .ok_or_else(|| anyhow!("remote \"{REMOTE_NAME}\" not found"))?; + let sha = repo + .lock() + .head_sha() + .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?; + + let path = maybe!({ + let buffer = self.buffer().read(cx).as_singleton()?; + let file = buffer.read(cx).file().and_then(|f| f.as_local())?; + file.path().to_str().map(|path| path.to_string()) + }) + .ok_or_else(|| anyhow!("failed to determine file path"))?; let selections = self.selections.all::(cx); let selection = selections.iter().peekable().next(); @@ -8379,11 +8396,23 @@ impl Editor { path: &path, selection: selection.map(|selection| selection.range()), }) - .log_err() }); - if let Some(permalink) = permalink { - cx.write_to_clipboard(ClipboardItem::new(permalink.to_string())); + match permalink { + Ok(permalink) => { + cx.write_to_clipboard(ClipboardItem::new(permalink.to_string())); + } + Err(err) => { + let message = format!("Failed to copy permalink: {err}"); + + Err::<(), anyhow::Error>(err).log_err(); + + if let Some(workspace) = self.workspace() { + workspace.update(cx, |workspace, cx| { + workspace.show_toast(Toast::new(0x156a5f9ee, message), cx) + }) + } + } } } From 70e7ea365ce633988d69e01afc997fb66eb56404 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:36:02 +0100 Subject: [PATCH 080/101] chore: Fix up warnings from cargo +beta check. (#7453) With upcoming release of 1.76 I did a check of current +beta (which seems to already be at 1.77). These would cause CI pipeline failures once 1.77 is out. Release Notes: - N/A --- crates/gpui/src/platform/mac/dispatcher.rs | 9 +++++++-- crates/gpui/src/scene.rs | 3 --- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/platform/mac/dispatcher.rs b/crates/gpui/src/platform/mac/dispatcher.rs index 72daa8c4404f52836d45ad39517b9b4bb3db57dd..46afc14ba836ca78c66e3c6d35957064344a4dfb 100644 --- a/crates/gpui/src/platform/mac/dispatcher.rs +++ b/crates/gpui/src/platform/mac/dispatcher.rs @@ -11,7 +11,12 @@ use objc::{ }; use parking::{Parker, Unparker}; use parking_lot::Mutex; -use std::{ffi::c_void, ptr::NonNull, sync::Arc, time::Duration}; +use std::{ + ffi::c_void, + ptr::{addr_of, NonNull}, + sync::Arc, + time::Duration, +}; /// All items in the generated file are marked as pub, so we're gonna wrap it in a separate mod to prevent /// these pub items from leaking into public API. @@ -21,7 +26,7 @@ pub(crate) mod dispatch_sys { use dispatch_sys::*; pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t { - unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t } + unsafe { addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t } } pub(crate) struct MacDispatcher { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 70e24030b1db92a5d3bfafbbe766a59e5b5fdad9..d5d717e278f3a9d0eeb01488091b491e9efe1bca 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -863,6 +863,3 @@ impl PathVertex { } } } - -#[derive(Copy, Clone, Debug)] -pub(crate) struct AtlasId(pub(crate) usize); From 743f9b345f56c5457cf678ef3371c2da04531a01 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:41:36 +0100 Subject: [PATCH 081/101] chore: Move workspace dependencies to workspace.dependencies (#7454) We should prefer referring to local deps via `.workspace = true` from now on. Release Notes: - N/A --- Cargo.toml | 86 +++++++++++++++++- crates/activity_indicator/Cargo.toml | 22 ++--- crates/ai/Cargo.toml | 8 +- crates/assets/Cargo.toml | 2 +- crates/assistant/Cargo.toml | 40 ++++----- crates/audio/Cargo.toml | 6 +- crates/auto_update/Cargo.toml | 20 ++--- crates/breadcrumbs/Cargo.toml | 28 +++--- crates/call/Cargo.toml | 38 ++++---- crates/channel/Cargo.toml | 38 ++++---- crates/cli/Cargo.toml | 2 +- crates/client/Cargo.toml | 30 +++---- crates/collab/Cargo.toml | 60 ++++++------- crates/collab_ui/Cargo.toml | 80 ++++++++--------- crates/color/Cargo.toml | 2 +- crates/command_palette/Cargo.toml | 44 ++++----- crates/copilot/Cargo.toml | 34 +++---- crates/copilot_ui/Cargo.toml | 24 ++--- crates/db/Cargo.toml | 14 +-- crates/diagnostics/Cargo.toml | 36 ++++---- crates/editor/Cargo.toml | 68 +++++++------- crates/feature_flags/Cargo.toml | 2 +- crates/feedback/Cargo.toml | 28 +++--- crates/file_finder/Cargo.toml | 36 ++++---- crates/fs/Cargo.toml | 16 ++-- crates/fuzzy/Cargo.toml | 4 +- crates/git/Cargo.toml | 10 +-- crates/go_to_line/Cargo.toml | 20 ++--- crates/gpui/Cargo.toml | 14 +-- crates/install_cli/Cargo.toml | 4 +- crates/journal/Cargo.toml | 12 +-- crates/language/Cargo.toml | 38 ++++---- crates/language_selector/Cargo.toml | 24 ++--- crates/language_tools/Cargo.toml | 32 +++---- crates/live_kit_client/Cargo.toml | 22 ++--- crates/lsp/Cargo.toml | 12 +-- crates/markdown_preview/Cargo.toml | 22 ++--- crates/menu/Cargo.toml | 2 +- crates/multi_buffer/Cargo.toml | 44 ++++----- crates/node_runtime/Cargo.toml | 2 +- crates/notifications/Cargo.toml | 36 ++++---- crates/outline/Cargo.toml | 24 ++--- crates/picker/Cargo.toml | 20 ++--- crates/plugin/Cargo.toml | 2 +- crates/prettier/Cargo.toml | 22 ++--- crates/project/Cargo.toml | 64 ++++++------- crates/project_panel/Cargo.toml | 36 ++++---- crates/project_symbols/Cargo.toml | 38 ++++---- crates/quick_action_bar/Cargo.toml | 20 ++--- crates/recent_projects/Cargo.toml | 24 ++--- crates/release_channel/Cargo.toml | 2 +- crates/rich_text/Cargo.toml | 14 +-- crates/rope/Cargo.toml | 8 +- crates/rpc/Cargo.toml | 12 +-- crates/search/Cargo.toml | 32 +++---- crates/semantic_index/Cargo.toml | 40 ++++----- crates/settings/Cargo.toml | 16 ++-- crates/sqlez/Cargo.toml | 2 +- crates/sqlez_macros/Cargo.toml | 2 +- crates/story/Cargo.toml | 2 +- crates/storybook/Cargo.toml | 26 +++--- crates/terminal/Cargo.toml | 10 +-- crates/terminal_view/Cargo.toml | 36 ++++---- crates/text/Cargo.toml | 16 ++-- crates/theme/Cargo.toml | 18 ++-- crates/theme_importer/Cargo.toml | 4 +- crates/theme_selector/Cargo.toml | 26 +++--- crates/ui/Cargo.toml | 10 +-- crates/vcs_menu/Cargo.toml | 14 +-- crates/vim/Cargo.toml | 46 +++++----- crates/welcome/Cargo.toml | 34 +++---- crates/workspace/Cargo.toml | 46 +++++----- crates/zed/Cargo.toml | 130 +++++++++++++-------------- crates/zed_actions/Cargo.toml | 2 +- 74 files changed, 972 insertions(+), 888 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2676f5b6589f8aa8e1a42f66ddd59f78c1edf112..4587e947c6c56c65ae3cc16269435eb3cf0ca92a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,90 @@ default-members = ["crates/zed"] resolver = "2" [workspace.dependencies] +activity_indicator = { path = "crates/activity_indicator" } +ai = { path = "crates/ai" } +assets = { path = "crates/assets" } +assistant = { path = "crates/assistant" } +audio = { path = "crates/audio" } +auto_update = { path = "crates/auto_update" } +breadcrumbs = { path = "crates/breadcrumbs" } +call = { path = "crates/call" } +channel = { path = "crates/channel" } +cli = { path = "crates/cli" } +client = { path = "crates/client" } +clock = { path = "crates/clock" } +collab = { path = "crates/collab" } +collab_ui = { path = "crates/collab_ui" } +collections = { path = "crates/collections" } +color = { path = "crates/color" } +command_palette = { path = "crates/command_palette" } +copilot = { path = "crates/copilot" } +copilot_ui = { path = "crates/copilot_ui" } +db = { path = "crates/db" } +diagnostics = { path = "crates/diagnostics" } +editor = { path = "crates/editor" } +feature_flags = { path = "crates/feature_flags" } +feedback = { path = "crates/feedback" } +file_finder = { path = "crates/file_finder" } +fs = { path = "crates/fs" } +fsevent = { path = "crates/fsevent" } +fuzzy = { path = "crates/fuzzy" } +git = { path = "crates/git" } +go_to_line = { path = "crates/go_to_line" } +gpui = { path = "crates/gpui" } +gpui_macros = { path = "crates/gpui_macros" } +install_cli = { path = "crates/install_cli" } +journal = { path = "crates/journal" } +language = { path = "crates/language" } +language_selector = { path = "crates/language_selector" } +language_tools = { path = "crates/language_tools" } +live_kit_client = { path = "crates/live_kit_client" } +live_kit_server = { path = "crates/live_kit_server" } +lsp = { path = "crates/lsp" } +markdown_preview = { path = "crates/markdown_preview" } +media = { path = "crates/media" } +menu = { path = "crates/menu" } +multi_buffer = { path = "crates/multi_buffer" } +node_runtime = { path = "crates/node_runtime" } +notifications = { path = "crates/notifications" } +outline = { path = "crates/outline" } +picker = { path = "crates/picker" } +plugin = { path = "crates/plugin" } +plugin_macros = { path = "crates/plugin_macros" } +prettier = { path = "crates/prettier" } +project = { path = "crates/project" } +project_panel = { path = "crates/project_panel" } +project_symbols = { path = "crates/project_symbols" } +quick_action_bar = { path = "crates/quick_action_bar" } +recent_projects = { path = "crates/recent_projects" } +release_channel = { path = "crates/release_channel" } +rich_text = { path = "crates/rich_text" } +rope = { path = "crates/rope" } +rpc = { path = "crates/rpc" } +search = { path = "crates/search" } +semantic_index = { path = "crates/semantic_index" } +settings = { path = "crates/settings" } +snippet = { path = "crates/snippet" } +sqlez = { path = "crates/sqlez" } +sqlez_macros = { path = "crates/sqlez_macros" } +story = { path = "crates/story" } +storybook = { path = "crates/storybook" } +sum_tree = { path = "crates/sum_tree" } +terminal = { path = "crates/terminal" } +terminal_view = { path = "crates/terminal_view" } +text = { path = "crates/text" } +theme = { path = "crates/theme" } +theme_importer = { path = "crates/theme_importer" } +theme_selector = { path = "crates/theme_selector" } +ui = { path = "crates/ui" } +util = { path = "crates/util" } +vcs_menu = { path = "crates/vcs_menu" } +vim = { path = "crates/vim" } +welcome = { path = "crates/welcome" } +workspace = { path = "crates/workspace" } +zed = { path = "crates/zed" } +zed_actions = { path = "crates/zed_actions" } + anyhow = "1.0.57" async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-trait = "0.1" @@ -157,7 +241,7 @@ tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" } tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" } tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "cf98de23e4285b8e6bcb57b050ef2326e2cc284b" } -tree-sitter-hcl = {git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "v1.1.0"} +tree-sitter-hcl = { git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "v1.1.0" } tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } tree-sitter-html = "0.19.0" tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 8c438db31d627f8157415d79377a5bb90e7bfa14..325c9be6e0e5d37a66d0b252174d1dbd5d42694a 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -11,18 +11,18 @@ doctest = false [dependencies] anyhow.workspace = true -auto_update = { path = "../auto_update" } -editor = { path = "../editor" } +auto_update.workspace = true +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } -project = { path = "../project" } -settings = { path = "../settings" } +gpui.workspace = true +language.workspace = true +project.workspace = true +settings.workspace = true smallvec.workspace = true -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace", package = "workspace" } +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/ai/Cargo.toml b/crates/ai/Cargo.toml index 516514e768ad3921608c11220e38951d59b8923e..de21b2b5011b30ef711ed57152e8c1b7bc0b6dfe 100644 --- a/crates/ai/Cargo.toml +++ b/crates/ai/Cargo.toml @@ -17,9 +17,9 @@ anyhow.workspace = true async-trait.workspace = true bincode = "1.3.3" futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true isahc.workspace = true -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true log.workspace = true matrixmultiply = "0.3.7" @@ -33,7 +33,7 @@ rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } serde.workspace = true serde_json.workspace = true tiktoken-rs.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml index ad9cd25b839ebbd527ca493e40b430cda940e517..19eef955dc8cdbd833215406b1d9e5e0340ba37f 100644 --- a/crates/assets/Cargo.toml +++ b/crates/assets/Cargo.toml @@ -7,5 +7,5 @@ license = "GPL-3.0-or-later" [dependencies] anyhow.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true rust-embed.workspace = true diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index dba31674b04d4fb785cd46931249a4b9720f17a2..97e1a137658d29d8a7dc35fcb63cb8e84241852d 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -10,44 +10,44 @@ path = "src/assistant.rs" doctest = false [dependencies] -ai = { path = "../ai" } +ai.workspace = true anyhow.workspace = true chrono.workspace = true -client = { path = "../client" } -collections = { path = "../collections" } -editor = { path = "../editor" } -fs = { path = "../fs" } +client.workspace = true +collections.workspace = true +editor.workspace = true +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true indoc.workspace = true isahc.workspace = true -language = { path = "../language" } +language.workspace = true log.workspace = true -menu = { path = "../menu" } -multi_buffer = { path = "../multi_buffer" } +menu.workspace = true +multi_buffer.workspace = true ordered-float.workspace = true parking_lot.workspace = true -project = { path = "../project" } +project.workspace = true regex.workspace = true schemars.workspace = true -search = { path = "../search" } -semantic_index = { path = "../semantic_index" } +search.workspace = true +semantic_index.workspace = true serde.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true -theme = { path = "../theme" } +theme.workspace = true tiktoken-rs.workspace = true -ui = { path = "../ui" } -util = { path = "../util" } +ui.workspace = true +util.workspace = true uuid.workspace = true -workspace = { path = "../workspace" } +workspace.workspace = true [dev-dependencies] -ai = { path = "../ai", features = ["test-support"] } +ai = { workspace = true, features = ["test-support"] } ctor.workspace = true -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true log.workspace = true -project = { path = "../project", features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } rand.workspace = true diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 7b9b76b6357337fe616988b1552dc0c61bc8e6fb..58ab15d910afa5b04f7220a4b3361306c6ea637f 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -11,11 +11,11 @@ doctest = false [dependencies] anyhow.workspace = true -collections = { path = "../collections" } +collections.workspace = true derive_more.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true log.workspace = true parking_lot.workspace = true rodio = { version = "0.17.1", default-features = false, features = ["wav"] } -util = { path = "../util" } +util.workspace = true diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index c89f1d40fe4416970728d90d13fef477ff6a6df8..9341fbe36932062cfbe5763d773dabc53f5ab0cc 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -11,22 +11,22 @@ doctest = false [dependencies] anyhow.workspace = true -client = { path = "../client" } -db = { path = "../db" } -gpui = { path = "../gpui" } +client.workspace = true +db.workspace = true +gpui.workspace = true isahc.workspace = true lazy_static.workspace = true log.workspace = true -menu = { path = "../menu" } -project = { path = "../project" } -release_channel = { path = "../release_channel" } +menu.workspace = true +project.workspace = true +release_channel.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true tempfile.workspace = true -theme = { path = "../theme" } -util = { path = "../util" } -workspace = { path = "../workspace" } +theme.workspace = true +util.workspace = true +workspace.workspace = true diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index a2048084fea1728a6b7735dd79157fcd503cd97a..57724663ca85b148321515fa207c296928ef9e04 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -10,20 +10,20 @@ path = "src/breadcrumbs.rs" doctest = false [dependencies] -collections = { path = "../collections" } -editor = { path = "../editor" } -gpui = { path = "../gpui" } +collections.workspace = true +editor.workspace = true +gpui.workspace = true itertools = "0.10" -language = { path = "../language" } -outline = { path = "../outline" } -project = { path = "../project" } -search = { path = "../search" } -settings = { path = "../settings" } -theme = { path = "../theme" } -ui = { path = "../ui" } -workspace = { path = "../workspace" } +language.workspace = true +outline.workspace = true +project.workspace = true +search.workspace = true +settings.workspace = true +theme.workspace = true +ui.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 9d4b51e659ebf557790370a5294caa95503b658e..74726a57a310148eb1e795ee676bf5403a4ca248 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -22,33 +22,33 @@ test-support = [ [dependencies] anyhow.workspace = true async-broadcast = "0.4" -audio = { path = "../audio" } -client = { path = "../client" } -collections = { path = "../collections" } -fs = { path = "../fs" } +audio.workspace = true +client.workspace = true +collections.workspace = true +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true image = "0.23" -language = { path = "../language" } -live_kit_client = { path = "../live_kit_client" } +language.workspace = true +live_kit_client.workspace = true log.workspace = true -media = { path = "../media" } +media.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -collections = { path = "../collections", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -live_kit_client = { path = "../live_kit_client", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } -util = { path = "../util", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +live_kit_client = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/channel/Cargo.toml b/crates/channel/Cargo.toml index afb5799b6134e3b441e2dc58fb8f040a919ebcc2..f922e8c6d048bd7514e3ef4fa59d23433a141a52 100644 --- a/crates/channel/Cargo.toml +++ b/crates/channel/Cargo.toml @@ -14,42 +14,42 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo [dependencies] anyhow.workspace = true -client = { path = "../client" } -clock = { path = "../clock" } -collections = { path = "../collections" } -db = { path = "../db" } -feature_flags = { path = "../feature_flags" } +client.workspace = true +clock.workspace = true +collections.workspace = true +db.workspace = true +feature_flags.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true image = "0.23" -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true rand.workspace = true -release_channel = { path = "../release_channel" } -rpc = { path = "../rpc" } +release_channel.workspace = true +rpc.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true smol.workspace = true -sum_tree = { path = "../sum_tree" } +sum_tree.workspace = true tempfile.workspace = true -text = { path = "../text" } +text.workspace = true thiserror.workspace = true time.workspace = true tiny_http = "0.8" url.workspace = true -util = { path = "../util" } +util.workspace = true uuid.workspace = true [dev-dependencies] -collections = { path = "../collections", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -rpc = { path = "../rpc", features = ["test-support"] } -client = { path = "../client", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } -util = { path = "../util", features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d790644e99511737750cc384157e7369617248e4..6156520034b49d83317c43731b811989fe69b47d 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,7 +20,7 @@ dirs = "3.0" ipc-channel = "0.16" serde.workspace = true serde_derive.workspace = true -util = { path = "../util" } +util.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index f405b1a74d7558a0030f8e5174f9b47334fa2906..9770ee9b8ec401625acba0d097f3251946c391a2 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -14,16 +14,16 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo [dependencies] chrono = { version = "0.4", features = ["serde"] } -collections = { path = "../collections" } -db = { path = "../db" } -gpui = { path = "../gpui" } -util = { path = "../util" } -release_channel = { path = "../release_channel" } -rpc = { path = "../rpc" } -text = { path = "../text" } -settings = { path = "../settings" } -feature_flags = { path = "../feature_flags" } -sum_tree = { path = "../sum_tree" } +collections.workspace = true +db.workspace = true +gpui.workspace = true +util.workspace = true +release_channel.workspace = true +rpc.workspace = true +text.workspace = true +settings.workspace = true +feature_flags.workspace = true +sum_tree.workspace = true anyhow.workspace = true async-recursion = "0.3" @@ -51,8 +51,8 @@ uuid.workspace = true url.workspace = true [dev-dependencies] -collections = { path = "../collections", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -rpc = { path = "../rpc", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } -util = { path = "../util", features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 0fb8cb929d8538daf67f7c01702e1e736951e598..02b2e5eb4896475e567cfe51dcc9af1932ce8778 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -22,15 +22,15 @@ axum-extra = { version = "0.3", features = ["erased-json"] } base64 = "0.13" chrono.workspace = true clap = { version = "3.1", features = ["derive"], optional = true } -clock = { path = "../clock" } -collections = { path = "../collections" } +clock.workspace = true +collections.workspace = true dashmap = "5.4" envy = "0.4.2" futures.workspace = true hyper = "0.14" lazy_static.workspace = true lipsum = { version = "0.8", optional = true } -live_kit_server = { path = "../live_kit_server" } +live_kit_server.workspace = true log.workspace = true nanoid = "0.4" parking_lot.workspace = true @@ -38,7 +38,7 @@ prometheus = "0.13" prost.workspace = true rand.workspace = true reqwest = { version = "0.11", features = ["json"], optional = true } -rpc = { path = "../rpc" } +rpc.workspace = true scrypt = "0.7" sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] } serde.workspace = true @@ -47,7 +47,7 @@ serde_json.workspace = true sha-1 = "0.9" smallvec.workspace = true sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] } -text = { path = "../text" } +text.workspace = true time.workspace = true tokio = { version = "1", features = ["full"] } tokio-tungstenite = "0.17" @@ -57,44 +57,44 @@ tower = "0.4" tracing = "0.1.34" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } -util = { path = "../util" } +util.workspace = true uuid.workspace = true [dev-dependencies] -release_channel = { path = "../release_channel" } +release_channel.workspace = true async-trait.workspace = true -audio = { path = "../audio" } -call = { path = "../call", features = ["test-support"] } -channel = { path = "../channel" } -client = { path = "../client", features = ["test-support"] } -collab_ui = { path = "../collab_ui", features = ["test-support"] } -collections = { path = "../collections", features = ["test-support"] } +audio.workspace = true +call = { workspace = true, features = ["test-support"] } +channel.workspace = true +client = { workspace = true, features = ["test-support"] } +collab_ui = { workspace = true, features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } ctor.workspace = true -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true -file_finder = { path = "../file_finder" } -fs = { path = "../fs", features = ["test-support"] } -git = { path = "../git", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } +file_finder.workspace = true +fs = { workspace = true, features = ["test-support"] } +git = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true -language = { path = "../language", features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } lazy_static.workspace = true -live_kit_client = { path = "../live_kit_client", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } -menu = { path = "../menu" } -node_runtime = { path = "../node_runtime" } -notifications = { path = "../notifications", features = ["test-support"] } +live_kit_client = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } +menu.workspace = true +node_runtime.workspace = true +notifications = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true -project = { path = "../project", features = ["test-support"] } -rpc = { path = "../rpc", features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] } serde_json.workspace = true -settings = { path = "../settings", features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } sqlx = { version = "0.7", features = ["sqlite"] } -theme = { path = "../theme" } +theme.workspace = true unindent.workspace = true -util = { path = "../util" } -workspace = { path = "../workspace", features = ["test-support"] } +util.workspace = true +workspace = { workspace = true, features = ["test-support"] } [features] seed-support = ["clap", "lipsum", "reqwest"] diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index 82de5384b30948c6671a1956ebe3205391a6e9ed..e1e1aa77981a500881c3076852aa510447652d9f 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -26,58 +26,58 @@ test-support = [ [dependencies] anyhow.workspace = true -auto_update = { path = "../auto_update" } -call = { path = "../call" } -channel = { path = "../channel" } -client = { path = "../client" } -clock = { path = "../clock" } -collections = { path = "../collections" } -db = { path = "../db" } -editor = { path = "../editor" } -feature_flags = { path = "../feature_flags" } -feedback = { path = "../feedback" } +auto_update.workspace = true +call.workspace = true +channel.workspace = true +client.workspace = true +clock.workspace = true +collections.workspace = true +db.workspace = true +editor.workspace = true +feature_flags.workspace = true +feedback.workspace = true futures.workspace = true -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -language = { path = "../language" } +fuzzy.workspace = true +gpui.workspace = true +language.workspace = true lazy_static.workspace = true log.workspace = true -menu = { path = "../menu" } -notifications = { path = "../notifications" } +menu.workspace = true +notifications.workspace = true parking_lot.workspace = true -picker = { path = "../picker" } +picker.workspace = true postage.workspace = true -project = { path = "../project" } -recent_projects = { path = "../recent_projects" } -rich_text = { path = "../rich_text" } -rpc = { path = "../rpc" } +project.workspace = true +recent_projects.workspace = true +rich_text.workspace = true +rpc.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true -story = { path = "../story", optional = true } -theme = { path = "../theme" } -theme_selector = { path = "../theme_selector" } +story = { workspace = true, optional = true } +theme.workspace = true +theme_selector.workspace = true time.workspace = true -ui = { path = "../ui" } -util = { path = "../util" } -vcs_menu = { path = "../vcs_menu" } -workspace = { path = "../workspace" } -zed_actions = { path = "../zed_actions" } +ui.workspace = true +util.workspace = true +vcs_menu.workspace = true +workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] -call = { path = "../call", features = ["test-support"] } -client = { path = "../client", features = ["test-support"] } -collections = { path = "../collections", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -notifications = { path = "../notifications", features = ["test-support"] } +call = { workspace = true, features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +notifications = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true -project = { path = "../project", features = ["test-support"] } -rpc = { path = "../rpc", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } tree-sitter-markdown.workspace = true -util = { path = "../util", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/color/Cargo.toml b/crates/color/Cargo.toml index 84fe60970318411b605ee05197843c0669b125ec..e8530befdf579729353c54d74199f6f2a46a324a 100644 --- a/crates/color/Cargo.toml +++ b/crates/color/Cargo.toml @@ -16,4 +16,4 @@ doctest = true [dependencies] itertools = { version = "0.11.0", optional = true } palette = "0.7.3" -story = { path = "../story", optional = true } +story = { workspace = true, optional = true } diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 666519340351ba2b2ef2b25730e5ca018b0674f2..cf115027411a41e073f05535fbd66de17c28c793 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -11,32 +11,32 @@ doctest = false [dependencies] anyhow.workspace = true -client = { path = "../client" } -collections = { path = "../collections" } +client.workspace = true +collections.workspace = true # HACK: We're only depending on `copilot` here for `CommandPaletteFilter`. See the attached comment on that type. -copilot = { path = "../copilot" } -editor = { path = "../editor" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -picker = { path = "../picker" } -project = { path = "../project" } -release_channel = { path = "../release_channel" } +copilot.workspace = true +editor.workspace = true +fuzzy.workspace = true +gpui.workspace = true +picker.workspace = true +project.workspace = true +release_channel.workspace = true serde.workspace = true -settings = { path = "../settings" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } -zed_actions = { path = "../zed_actions" } +settings.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] ctor.workspace = true -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true -go_to_line = { path = "../go_to_line" } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -menu = { path = "../menu" } -project = { path = "../project", features = ["test-support"] } +go_to_line.workspace = true +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +menu.workspace = true +project = { workspace = true, features = ["test-support"] } serde_json.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 73ce1c4df4a6c11ec2c40ba89557fa234c71c0f0..8ad50e898a96bf58e60dfc004cd98ece8a0c2708 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -23,28 +23,28 @@ test-support = [ anyhow.workspace = true async-compression.workspace = true async-tar = "0.4.2" -collections = { path = "../collections" } +collections.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } +gpui.workspace = true +language.workspace = true log.workspace = true -lsp = { path = "../lsp" } -node_runtime = { path = "../node_runtime" } +lsp.workspace = true +node_runtime.workspace = true parking_lot.workspace = true serde.workspace = true serde_derive.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true -theme = { path = "../theme" } -util = { path = "../util" } +theme.workspace = true +util.workspace = true [dev-dependencies] -clock = { path = "../clock" } -collections = { path = "../collections", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } -rpc = { path = "../rpc", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } -util = { path = "../util", features = ["test-support"] } +clock.workspace = true +collections = { workspace = true, features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/copilot_ui/Cargo.toml b/crates/copilot_ui/Cargo.toml index 6d9f87ddcee3f88ff514718715acc7da90d3f4a2..9ce26eef4cd7d70af7f5ebff84c00496dc3d62f7 100644 --- a/crates/copilot_ui/Cargo.toml +++ b/crates/copilot_ui/Cargo.toml @@ -11,19 +11,19 @@ doctest = false [dependencies] anyhow.workspace = true -copilot = { path = "../copilot" } -editor = { path = "../editor" } -fs = { path = "../fs" } +copilot.workspace = true +editor.workspace = true +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } -settings = { path = "../settings" } +gpui.workspace = true +language.workspace = true +settings.workspace = true smol.workspace = true -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } -zed_actions = { path = "../zed_actions" } +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 5a70f2a93736596ea319514547da3b8f080d327c..138eba776af8bc563626060281b9b4d33deeed10 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -15,21 +15,21 @@ test-support = [] [dependencies] anyhow.workspace = true async-trait.workspace = true -collections = { path = "../collections" } -gpui = { path = "../gpui" } +collections.workspace = true +gpui.workspace = true indoc.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true -release_channel = { path = "../release_channel" } +release_channel.workspace = true serde.workspace = true serde_derive.workspace = true smol.workspace = true -sqlez = { path = "../sqlez" } -sqlez_macros = { path = "../sqlez_macros" } -util = { path = "../util" } +sqlez.workspace = true +sqlez_macros.workspace = true +util.workspace = true [dev-dependencies] env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } tempfile.workspace = true diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 79a7a717c7d477f3e28d4c7bd037e0fd67e810ef..8a7980e10c672620e477bad4d2a162565928e8a7 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -11,32 +11,32 @@ doctest = false [dependencies] anyhow.workspace = true -collections = { path = "../collections" } -editor = { path = "../editor" } +collections.workspace = true +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } +gpui.workspace = true +language.workspace = true log.workspace = true -lsp = { path = "../lsp" } +lsp.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } serde_json.workspace = true -theme = { path = "../theme", features = ["test-support"] } +theme = { workspace = true, features = ["test-support"] } unindent.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index be8745e4c225bae875d42999ffe0e7b450ad985a..a838dd65722a1d70284eda87b6f4a02afe8e05a4 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -26,68 +26,68 @@ test-support = [ [dependencies] aho-corasick = "1.1" anyhow.workspace = true -client = { path = "../client" } -clock = { path = "../clock" } -collections = { path = "../collections" } +client.workspace = true +clock.workspace = true +collections.workspace = true convert_case = "0.6.0" -copilot = { path = "../copilot" } -db = { path = "../db" } +copilot.workspace = true +db.workspace = true futures.workspace = true -fuzzy = { path = "../fuzzy" } -git = { path = "../git" } -gpui = { path = "../gpui" } +fuzzy.workspace = true +git.workspace = true +gpui.workspace = true indoc = "1.0.4" itertools = "0.10" -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true linkify = "0.10.0" log.workspace = true -lsp = { path = "../lsp" } -multi_buffer = { path = "../multi_buffer" } +lsp.workspace = true +multi_buffer.workspace = true ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true rand.workspace = true -rich_text = { path = "../rich_text" } -rpc = { path = "../rpc" } +rich_text.workspace = true +rpc.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true smol.workspace = true -snippet = { path = "../snippet" } -sqlez = { path = "../sqlez" } -sum_tree = { path = "../sum_tree" } -text = { path = "../text" } -theme = { path = "../theme" } +snippet.workspace = true +sqlez.workspace = true +sum_tree.workspace = true +text.workspace = true +theme.workspace = true tree-sitter-html = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } -ui = { path = "../ui" } +ui.workspace = true url.workspace = true -util = { path = "../util" } -workspace = { path = "../workspace" } +util.workspace = true +workspace.workspace = true [dev-dependencies] -copilot = { path = "../copilot", features = ["test-support"] } +copilot = { workspace = true, features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } -multi_buffer = { path = "../multi_buffer", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } -release_channel = { path = "../release_channel" } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } +multi_buffer = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +release_channel.workspace = true rand.workspace = true -settings = { path = "../settings", features = ["test-support"] } -text = { path = "../text", features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +text = { workspace = true, features = ["test-support"] } tree-sitter-html.workspace = true tree-sitter-rust.workspace = true tree-sitter-typescript.workspace = true tree-sitter.workspace = true unindent.workspace = true -util = { path = "../util", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/feature_flags/Cargo.toml b/crates/feature_flags/Cargo.toml index 20c106393f05d77c7c0eaddee4936d30f2bbe1fb..78caa1dc72c8d14e2ddaa0140426d8586ee8cec4 100644 --- a/crates/feature_flags/Cargo.toml +++ b/crates/feature_flags/Cargo.toml @@ -10,4 +10,4 @@ path = "src/feature_flags.rs" [dependencies] anyhow.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 59344eebecb716af09f2c8c3103f5a90ab38efa0..30b4773d6f56fd89e94bfbc3cb4208170c17f2a9 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -14,34 +14,34 @@ test-support = [] [dependencies] anyhow.workspace = true bitflags = "2.4.1" -client = { path = "../client" } -db = { path = "../db" } -editor = { path = "../editor" } +client.workspace = true +db.workspace = true +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true human_bytes = "0.4.1" isahc.workspace = true -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true log.workspace = true -menu = { path = "../menu" } +menu.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true regex.workspace = true -release_channel = { path = "../release_channel" } +release_channel.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true smol.workspace = true sysinfo.workspace = true -theme = { path = "../theme" } +theme.workspace = true tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } -ui = { path = "../ui" } +ui.workspace = true urlencoding = "2.1.2" -util = { path = "../util" } -workspace = { path = "../workspace" } +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index e67997f97d961a919679e94e707e36129371692b..25e77d4dabeab7793a9ff9b896e8b95638f4605e 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -11,29 +11,29 @@ doctest = false [dependencies] anyhow.workspace = true -collections = { path = "../collections" } -editor = { path = "../editor" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } +collections.workspace = true +editor.workspace = true +fuzzy.workspace = true +gpui.workspace = true itertools = "0.11" -menu = { path = "../menu" } -picker = { path = "../picker" } +menu.workspace = true +picker.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true serde.workspace = true -settings = { path = "../settings" } -text = { path = "../text" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +settings.workspace = true +text.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] ctor.workspace = true -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } serde_json.workspace = true -theme = { path = "../theme", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +theme = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index debdefe3ce1dbd08c5937a2f183413265875dddf..ea18ec1fb33d19ea1524c2811faccfb27dce75ae 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -9,11 +9,11 @@ license = "GPL-3.0-or-later" path = "src/fs.rs" [dependencies] -collections = { path = "../collections" } -rope = { path = "../rope" } -text = { path = "../text" } -util = { path = "../util" } -sum_tree = { path = "../sum_tree" } +collections.workspace = true +rope.workspace = true +text.workspace = true +util.workspace = true +sum_tree.workspace = true anyhow.workspace = true async-trait.workspace = true @@ -31,16 +31,16 @@ log.workspace = true libc = "0.2" time.workspace = true -gpui = { path = "../gpui", optional = true} +gpui = { workspace = true, optional = true} [target.'cfg(target_os = "macos")'.dependencies] -fsevent = { path = "../fsevent" } +fsevent.workspace = true [target.'cfg(not(target_os = "macos"))'.dependencies] notify = "6.1.1" [dev-dependencies] -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } [features] test-support = ["gpui/test-support"] diff --git a/crates/fuzzy/Cargo.toml b/crates/fuzzy/Cargo.toml index 5fc2e3de63b8d5ee6f4f831965d21dae3a6340e9..3b323afdaac4db10ec4453ebccf543541873887f 100644 --- a/crates/fuzzy/Cargo.toml +++ b/crates/fuzzy/Cargo.toml @@ -10,5 +10,5 @@ path = "src/fuzzy.rs" doctest = false [dependencies] -gpui = { path = "../gpui" } -util = { path = "../util" } +gpui.workspace = true +util.workspace = true diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 2d4b652069688d43210836228ac1b7bed2c3d60b..648ea336c28daa80769de2efd5c6a2d637995f47 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -11,17 +11,17 @@ path = "src/git.rs" [dependencies] anyhow.workspace = true async-trait.workspace = true -clock = { path = "../clock" } -collections = { path = "../collections" } +clock.workspace = true +collections.workspace = true futures.workspace = true git2.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true smol.workspace = true -sum_tree = { path = "../sum_tree" } -text = { path = "../text" } -util = { path = "../util" } +sum_tree.workspace = true +text.workspace = true +util.workspace = true [dev-dependencies] unindent.workspace = true diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 1968e2dadf6bcc24a1c0aaa73f51d628c65844ed..e75ed95ad58eeeecd153bf9957f577089f32f35a 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -10,17 +10,17 @@ path = "src/go_to_line.rs" doctest = false [dependencies] -editor = { path = "../editor" } -gpui = { path = "../gpui" } -menu = { path = "../menu" } +editor.workspace = true +gpui.workspace = true +menu.workspace = true postage.workspace = true serde.workspace = true -settings = { path = "../settings" } -text = { path = "../text" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +settings.workspace = true +text.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 96c7b4b02c4b914d2a9f6638d1f708e335c3514a..9e250acda9358ace5c57c2a2cc76550ec0fe5f5f 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -26,14 +26,14 @@ anyhow.workspace = true async-task = "4.7" backtrace = { version = "0.3", optional = true } bitflags = "2.4.0" -collections = { path = "../collections" } +collections.workspace = true ctor.workspace = true derive_more.workspace = true dhat = { version = "0.3", optional = true } env_logger = { version = "0.9", optional = true } etagere = "0.2" futures.workspace = true -gpui_macros = { path = "../gpui_macros" } +gpui_macros.workspace = true image = "0.23" itertools = "0.10" lazy_static.workspace = true @@ -57,24 +57,24 @@ serde_json.workspace = true slotmap = "1.0.6" smallvec.workspace = true smol.workspace = true -sum_tree = { path = "../sum_tree" } +sum_tree.workspace = true taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" } thiserror.workspace = true time.workspace = true tiny-skia = "0.5" usvg = { version = "0.14", features = [] } -util = { path = "../util" } +util.workspace = true uuid = { version = "1.1.2", features = ["v4"] } waker-fn = "1.1.0" [dev-dependencies] backtrace = "0.3" -collections = { path = "../collections", features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } dhat = "0.3" env_logger.workspace = true png = "0.16" simplelog = "0.9" -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } [build-dependencies] bindgen = "0.65.1" @@ -89,6 +89,6 @@ core-text = "19.2" font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "d97147f" } foreign-types = "0.3" log.workspace = true -media = { path = "../media" } +media.workspace = true metal = "0.21.0" objc = "0.2" diff --git a/crates/install_cli/Cargo.toml b/crates/install_cli/Cargo.toml index d50ab5191a0fc2ef6e57afb82a993594e740f140..73bec50a16d00520a5d4a665c3ca5aa35e6a6853 100644 --- a/crates/install_cli/Cargo.toml +++ b/crates/install_cli/Cargo.toml @@ -13,7 +13,7 @@ test-support = [] [dependencies] anyhow.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true log.workspace = true smol.workspace = true -util = { path = "../util" } +util.workspace = true diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 9330a2efffcc6832e68de1fb50b3ef86a55339fd..1b8fb44bc5009ecb858ec6faa19884b0e03af513 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -13,15 +13,15 @@ doctest = false anyhow.workspace = true chrono = "0.4" dirs = "4.0" -editor = { path = "../editor" } -gpui = { path = "../gpui" } +editor.workspace = true +gpui.workspace = true log.workspace = true schemars.workspace = true serde.workspace = true -settings = { path = "../settings" } +settings.workspace = true shellexpand = "2.1.0" -util = { path = "../util" } -workspace = { path = "../workspace" } +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 826a8287e51c89fc9ac3929c128ab4e22a43cacf..821fa80840b182131d08c6c517e517a5dc0f6f83 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -26,50 +26,50 @@ test-support = [ anyhow.workspace = true async-broadcast = "0.4" async-trait.workspace = true -clock = { path = "../clock" } -collections = { path = "../collections" } +clock.workspace = true +collections.workspace = true futures.workspace = true -fuzzy = { path = "../fuzzy" } -git = { path = "../git" } +fuzzy.workspace = true +git.workspace = true globset.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true lazy_static.workspace = true log.workspace = true -lsp = { path = "../lsp" } +lsp.workspace = true parking_lot.workspace = true postage.workspace = true rand = { workspace = true, optional = true } regex.workspace = true -rpc = { path = "../rpc" } +rpc.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true similar = "1.3" smallvec.workspace = true smol.workspace = true -sum_tree = { path = "../sum_tree" } -text = { path = "../text" } -theme = { path = "../theme" } +sum_tree.workspace = true +text.workspace = true +theme.workspace = true tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } pulldown-cmark.workspace = true tree-sitter.workspace = true unicase = "2.6" -util = { path = "../util" } +util.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -collections = { path = "../collections", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true -lsp = { path = "../lsp", features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } rand.workspace = true -settings = { path = "../settings", features = ["test-support"] } -text = { path = "../text", features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +text = { workspace = true, features = ["test-support"] } tree-sitter-elixir.workspace = true tree-sitter-embedded-template.workspace = true tree-sitter-heex.workspace = true @@ -81,4 +81,4 @@ tree-sitter-ruby.workspace = true tree-sitter-rust.workspace = true tree-sitter-typescript.workspace = true unindent.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/language_selector/Cargo.toml b/crates/language_selector/Cargo.toml index adeb9359d298837340945329fa931c8c0fa5d317..dc310460549ba9a48083b3d466355824fdb4d9c2 100644 --- a/crates/language_selector/Cargo.toml +++ b/crates/language_selector/Cargo.toml @@ -11,17 +11,17 @@ doctest = false [dependencies] anyhow.workspace = true -editor = { path = "../editor" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -language = { path = "../language" } -picker = { path = "../picker" } -project = { path = "../project" } -settings = { path = "../settings" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +editor.workspace = true +fuzzy.workspace = true +gpui.workspace = true +language.workspace = true +picker.workspace = true +project.workspace = true +settings.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index 769509aa6bfc9300d3ba1dbec75575421d26c4a1..06884fe455049564e0c4bc2e00ebd817b0481d04 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -11,27 +11,27 @@ doctest = false [dependencies] anyhow.workspace = true -collections = { path = "../collections" } -editor = { path = "../editor" } +collections.workspace = true +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } -lsp = { path = "../lsp" } -project = { path = "../project" } +gpui.workspace = true +language.workspace = true +lsp.workspace = true +project.workspace = true serde.workspace = true serde_json.workspace = true -settings = { path = "../settings" } -theme = { path = "../theme" } +settings.workspace = true +theme.workspace = true tree-sitter.workspace = true -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -release_channel = { path = "../release_channel" } +client = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +release_channel.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } unindent.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/live_kit_client/Cargo.toml b/crates/live_kit_client/Cargo.toml index 32c180c48c63d3cc54d6a8bd1c7ddd11a3ad20e9..732cbde338535622794bd33c7a73dc7b9558a7fc 100644 --- a/crates/live_kit_client/Cargo.toml +++ b/crates/live_kit_client/Cargo.toml @@ -26,12 +26,12 @@ test-support = [ anyhow.workspace = true async-broadcast = "0.4" async-trait = { workspace = true, optional = true } -collections = { path = "../collections", optional = true } +collections = { workspace = true, optional = true } futures.workspace = true -gpui = { path = "../gpui", optional = true } -live_kit_server = { path = "../live_kit_server", optional = true } +gpui = { workspace = true, optional = true } +live_kit_server = { workspace = true, optional = true } log.workspace = true -media = { path = "../media" } +media.workspace = true nanoid = { version ="0.4", optional = true} parking_lot.workspace = true postage.workspace = true @@ -41,9 +41,9 @@ core-foundation = "0.9.3" [target.'cfg(not(target_os = "macos"))'.dependencies] async-trait = { workspace = true } -collections = { path = "../collections", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -live_kit_server = { path = "../live_kit_server" } +collections = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +live_kit_server.workspace = true [dev-dependencies] anyhow.workspace = true @@ -51,14 +51,14 @@ async-trait.workspace = true block = "0.1" byteorder = "1.4" bytes = "1.2" -collections = { path = "../collections", features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } foreign-types = "0.3" futures.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } hmac = "0.12" jwt = "0.16" -live_kit_server = { path = "../live_kit_server" } -media = { path = "../media" } +live_kit_server.workspace = true +media.workspace = true nanoid = "0.4" parking_lot.workspace = true serde.workspace = true diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index c1fdd7b10765a54aa9f31bc7a6833bfd95f5af63..5f218aea6c75620f3e2e2d702a5c4b782dba7f86 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -15,9 +15,9 @@ test-support = ["async-pipe"] [dependencies] anyhow.workspace = true async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true } -collections = { path = "../collections" } +collections.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true log.workspace = true lsp-types = { git = "https://github.com/zed-industries/lsp-types", branch = "updated-completion-list-item-defaults" } parking_lot.workspace = true @@ -26,13 +26,13 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true smol.workspace = true -util = { path = "../util" } -release_channel = { path = "../release_channel" } +util.workspace = true +release_channel.workspace = true [dev-dependencies] async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } unindent.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/markdown_preview/Cargo.toml b/crates/markdown_preview/Cargo.toml index df41f12d21ac4df570795f52b712ffe5545f3b9a..ba23a8225938166cb05b45caf53365a6328a6d98 100644 --- a/crates/markdown_preview/Cargo.toml +++ b/crates/markdown_preview/Cargo.toml @@ -13,19 +13,19 @@ test-support = [] [dependencies] anyhow.workspace = true -editor = { path = "../editor" } -gpui = { path = "../gpui" } -language = { path = "../language" } +editor.workspace = true +gpui.workspace = true +language.workspace = true lazy_static.workspace = true log.workspace = true -menu = { path = "../menu" } -project = { path = "../project" } +menu.workspace = true +project.workspace = true pulldown-cmark.workspace = true -rich_text = { path = "../rich_text" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +rich_text.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/menu/Cargo.toml b/crates/menu/Cargo.toml index 5492570b6ba8b4702ce4fb58dbd062d8f0736ac7..cf177272427706b56a4d8c15bc7b36b0f63beca8 100644 --- a/crates/menu/Cargo.toml +++ b/crates/menu/Cargo.toml @@ -10,5 +10,5 @@ path = "src/menu.rs" doctest = false [dependencies] -gpui = { path = "../gpui" } +gpui.workspace = true serde = { workspace = true } diff --git a/crates/multi_buffer/Cargo.toml b/crates/multi_buffer/Cargo.toml index 5fc7bdb254b012ec949ba8d924b15781fe6c1334..ed3993400a3665241bd7798d425d9be74985eba2 100644 --- a/crates/multi_buffer/Cargo.toml +++ b/crates/multi_buffer/Cargo.toml @@ -23,54 +23,54 @@ test-support = [ [dependencies] aho-corasick = "1.1" anyhow.workspace = true -client = { path = "../client" } -clock = { path = "../clock" } -collections = { path = "../collections" } +client.workspace = true +clock.workspace = true +collections.workspace = true convert_case = "0.6.0" futures.workspace = true -git = { path = "../git" } -gpui = { path = "../gpui" } +git.workspace = true +gpui.workspace = true indoc = "1.0.4" itertools = "0.10" -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true log.workspace = true -lsp = { path = "../lsp" } +lsp.workspace = true ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true pulldown-cmark.workspace = true rand.workspace = true -rich_text = { path = "../rich_text" } +rich_text.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true smol.workspace = true -snippet = { path = "../snippet" } -sum_tree = { path = "../sum_tree" } -text = { path = "../text" } -theme = { path = "../theme" } +snippet.workspace = true +sum_tree.workspace = true +text.workspace = true +theme.workspace = true tree-sitter-html = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } -util = { path = "../util" } +util.workspace = true [dev-dependencies] -copilot = { path = "../copilot", features = ["test-support"] } +copilot = { workspace = true, features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } rand.workspace = true -settings = { path = "../settings", features = ["test-support"] } -text = { path = "../text", features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +text = { workspace = true, features = ["test-support"] } tree-sitter-html.workspace = true tree-sitter-rust.workspace = true tree-sitter-typescript.workspace = true tree-sitter.workspace = true unindent.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index b111cf575281b2ecbecc0459b1808041ff240f01..891dcd9e48aa21a4c7b0fe106d0b20c2ba513dd7 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -21,4 +21,4 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true smol.workspace = true -util = { path = "../util" } +util.workspace = true diff --git a/crates/notifications/Cargo.toml b/crates/notifications/Cargo.toml index 134bda415bfe5eb50276141fabb1a9fda798ed7c..269766dcce6b5372c0c0ce811fc2c1c5bd31027c 100644 --- a/crates/notifications/Cargo.toml +++ b/crates/notifications/Cargo.toml @@ -19,24 +19,24 @@ test-support = [ [dependencies] anyhow.workspace = true -channel = { path = "../channel" } -client = { path = "../client" } -clock = { path = "../clock" } -collections = { path = "../collections" } -db = { path = "../db" } -feature_flags = { path = "../feature_flags" } -gpui = { path = "../gpui" } -rpc = { path = "../rpc" } -settings = { path = "../settings" } -sum_tree = { path = "../sum_tree" } -text = { path = "../text" } +channel.workspace = true +client.workspace = true +clock.workspace = true +collections.workspace = true +db.workspace = true +feature_flags.workspace = true +gpui.workspace = true +rpc.workspace = true +settings.workspace = true +sum_tree.workspace = true +text.workspace = true time.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -collections = { path = "../collections", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -rpc = { path = "../rpc", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } -util = { path = "../util", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 9dbd540797d441056e01792d90fcf0c59b26812b..a67371847a54640bcee9842b6f212f76b655a55f 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -10,20 +10,20 @@ path = "src/outline.rs" doctest = false [dependencies] -editor = { path = "../editor" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -language = { path = "../language" } +editor.workspace = true +fuzzy.workspace = true +gpui.workspace = true +language.workspace = true ordered-float.workspace = true -picker = { path = "../picker" } +picker.workspace = true postage.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true -text = { path = "../text" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +text.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index 480911fe47cee59a081a7a1e90e8c52a7db0b5b6..63c408fb2f77744e1934ff21c195b6b6513fbd0a 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -10,19 +10,19 @@ path = "src/picker.rs" doctest = false [dependencies] -editor = { path = "../editor" } -gpui = { path = "../gpui" } -menu = { path = "../menu" } +editor.workspace = true +gpui.workspace = true +menu.workspace = true parking_lot.workspace = true -settings = { path = "../settings" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +settings.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] ctor.workspace = true -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } serde_json.workspace = true diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml index c07da5d7c7538a0965deeda43fa47a4515f72500..859ec0467b5c889bc910892930aada558c9412fa 100644 --- a/crates/plugin/Cargo.toml +++ b/crates/plugin/Cargo.toml @@ -7,6 +7,6 @@ license = "GPL-3.0-or-later" [dependencies] bincode = "1.3" -plugin_macros = { path = "../plugin_macros" } +plugin_macros.workspace = true serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 565a43ded7d07cc5238a8a51619c81936c333eb7..451ef78306fcba5e1953d05286b1316545ad7e37 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -14,22 +14,22 @@ test-support = [] [dependencies] anyhow.workspace = true -client = { path = "../client" } -collections = { path = "../collections" } -fs = { path = "../fs" } +client.workspace = true +collections.workspace = true +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } +gpui.workspace = true +language.workspace = true log.workspace = true -lsp = { path = "../lsp" } -node_runtime = { path = "../node_runtime" } +lsp.workspace = true +node_runtime.workspace = true parking_lot.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] -fs = { path = "../fs", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 1854feb8fe4e25cfa76ac22350a0b1accfab0ebb..8f36511c644f57dcb27c1dc724a9967c9f4f2ad0 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -25,62 +25,62 @@ aho-corasick = "1.1" anyhow.workspace = true async-trait.workspace = true backtrace = "0.3" -client = { path = "../client" } -clock = { path = "../clock" } -collections = { path = "../collections" } -copilot = { path = "../copilot" } -db = { path = "../db" } -fs = { path = "../fs" } -fsevent = { path = "../fsevent" } +client.workspace = true +clock.workspace = true +collections.workspace = true +copilot.workspace = true +db.workspace = true +fs.workspace = true +fsevent.workspace = true futures.workspace = true -fuzzy = { path = "../fuzzy" } -git = { path = "../git" } +fuzzy.workspace = true +git.workspace = true globset.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true ignore = "0.4" itertools = "0.10" -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true log.workspace = true -lsp = { path = "../lsp" } -node_runtime = { path = "../node_runtime" } +lsp.workspace = true +node_runtime.workspace = true parking_lot.workspace = true postage.workspace = true -prettier = { path = "../prettier" } +prettier.workspace = true rand.workspace = true regex.workspace = true -rpc = { path = "../rpc" } +rpc.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true sha2 = "0.10" similar = "1.3" smol.workspace = true -sum_tree = { path = "../sum_tree" } -terminal = { path = "../terminal" } -text = { path = "../text" } +sum_tree.workspace = true +terminal.workspace = true +text.workspace = true thiserror.workspace = true toml.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -collections = { path = "../collections", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } ctor.workspace = true -db = { path = "../db", features = ["test-support"] } +db = { workspace = true, features = ["test-support"] } env_logger.workspace = true -fs = { path = "../fs", features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } git2.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -release_channel = { path = "../release_channel" } -lsp = { path = "../lsp", features = ["test-support"] } -prettier = { path = "../prettier", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +release_channel.workspace = true +lsp = { workspace = true, features = ["test-support"] } +prettier = { workspace = true, features = ["test-support"] } pretty_assertions.workspace = true -rpc = { path = "../rpc", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } tempfile.workspace = true unindent.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index a518a2c075f683756ea2b437e96775b29a5768b6..8946ca7d151f96c02a0b47f9150284c077a3300a 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -11,33 +11,33 @@ doctest = false [dependencies] anyhow.workspace = true -collections = { path = "../collections" } -db = { path = "../db" } -editor = { path = "../editor" } +collections.workspace = true +db.workspace = true +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -menu = { path = "../menu" } +gpui.workspace = true +menu.workspace = true postage.workspace = true pretty_assertions.workspace = true -project = { path = "../project" } +project.workspace = true schemars.workspace = true -search = { path = "../search" } +search.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true -theme = { path = "../theme" } -ui = { path = "../ui" } +theme.workspace = true +ui.workspace = true unicase = "2.6" -util = { path = "../util" } -client = { path = "../client" } -workspace = { path = "../workspace", package = "workspace" } +util.workspace = true +client.workspace = true +workspace.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } serde_json.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index 2b3027a08942f80e1dc6b83041a224dee2349442..078d09f0a1199270731ffa46aefd109b2ef34210 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -11,29 +11,29 @@ doctest = false [dependencies] anyhow.workspace = true -editor = { path = "../editor" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } +editor.workspace = true +fuzzy.workspace = true +gpui.workspace = true ordered-float.workspace = true -picker = { path = "../picker" } +picker.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true -text = { path = "../text" } -theme = { path = "../theme" } -util = { path = "../util" } -workspace = { path = "../workspace" } +text.workspace = true +theme.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } futures.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } -release_channel = { path = "../release_channel" } -settings = { path = "../settings", features = ["test-support"] } -theme = { path = "../theme", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +release_channel.workspace = true +settings = { workspace = true, features = ["test-support"] } +theme = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/quick_action_bar/Cargo.toml b/crates/quick_action_bar/Cargo.toml index 07e081acd39a5a711758808287ceeb9b14bc3e1b..e1f8c66d106f50d36ee7f96673958d4a1b596379 100644 --- a/crates/quick_action_bar/Cargo.toml +++ b/crates/quick_action_bar/Cargo.toml @@ -10,15 +10,15 @@ path = "src/quick_action_bar.rs" doctest = false [dependencies] -assistant = { path = "../assistant" } -editor = { path = "../editor" } -gpui = { path = "../gpui" } -search = { path = "../search" } -settings = { path = "../settings" } -ui = { path = "../ui" } -workspace = { path = "../workspace" } +assistant.workspace = true +editor.workspace = true +gpui.workspace = true +search.workspace = true +settings.workspace = true +ui.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 333bb107a4941c6bfa64337665bf8bd463df87a9..48e3cff72d69b1441e4e32d75596c33f313afb38 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -10,21 +10,21 @@ path = "src/recent_projects.rs" doctest = false [dependencies] -editor = { path = "../editor" } +editor.workspace = true futures.workspace = true -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -language = { path = "../language" } +fuzzy.workspace = true +gpui.workspace = true +language.workspace = true ordered-float.workspace = true -picker = { path = "../picker" } +picker.workspace = true postage.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true -text = { path = "../text" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +text.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/release_channel/Cargo.toml b/crates/release_channel/Cargo.toml index 243ea5ace32fe4f243f0a3d1e8d17551a47c1431..b40ce363460038927a8b839f924e17de99422aff 100644 --- a/crates/release_channel/Cargo.toml +++ b/crates/release_channel/Cargo.toml @@ -6,5 +6,5 @@ publish = false license = "GPL-3.0-or-later" [dependencies] -gpui = { path = "../gpui" } +gpui.workspace = true once_cell = "1.19.0" diff --git a/crates/rich_text/Cargo.toml b/crates/rich_text/Cargo.toml index bfd8cf70d11a58dfb4eb5aa8820f9c52fc545df7..b9f765d123f54a071475112bd08fffd06b9f0de8 100644 --- a/crates/rich_text/Cargo.toml +++ b/crates/rich_text/Cargo.toml @@ -17,15 +17,15 @@ test-support = [ [dependencies] anyhow.workspace = true -collections = { path = "../collections" } +collections.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } +gpui.workspace = true +language.workspace = true lazy_static.workspace = true pulldown-cmark.workspace = true smallvec.workspace = true smol.workspace = true -sum_tree = { path = "../sum_tree" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } +sum_tree.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index 60d6e8f40b68b7451a9eb2cf61337f5909093e57..9190341f5397a0434c7a3a5123f610163228be24 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -13,10 +13,10 @@ arrayvec = "0.7.1" bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" } log.workspace = true smallvec.workspace = true -sum_tree = { path = "../sum_tree" } -util = { path = "../util" } +sum_tree.workspace = true +util.workspace = true [dev-dependencies] -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } rand.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index fe213be68c587fb36897877f209d485dd91d20ec..0f2772b3d7fb3064218941e4b519ec3e0998e0be 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -18,10 +18,10 @@ anyhow.workspace = true async-lock = "2.4" async-tungstenite = "0.16" base64 = "0.13" -clock = { path = "../clock" } -collections = { path = "../collections" } +clock.workspace = true +collections.workspace = true futures.workspace = true -gpui = { path = "../gpui", optional = true } +gpui = { workspace = true, optional = true } parking_lot.workspace = true prost.workspace = true rand.workspace = true @@ -32,16 +32,16 @@ serde_json.workspace = true smol-timeout = "0.6" strum.workspace = true tracing = { version = "0.1.34", features = ["log"] } -util = { path = "../util" } +util.workspace = true zstd = "0.11" [build-dependencies] prost-build = "0.9" [dev-dependencies] -collections = { path = "../collections", features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } smol.workspace = true tempfile.workspace = true diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 0266770d101a62b319fe5bbae86cc211d6596d94..d4e2c70a2311418ca0975e99b8340e8698ad843a 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -12,30 +12,30 @@ doctest = false [dependencies] anyhow.workspace = true bitflags = "1" -collections = { path = "../collections" } -editor = { path = "../editor" } +collections.workspace = true +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } +gpui.workspace = true +language.workspace = true log.workspace = true -menu = { path = "../menu" } +menu.workspace = true postage.workspace = true -project = { path = "../project" } -semantic_index = { path = "../semantic_index" } +project.workspace = true +semantic_index.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true smol.workspace = true -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } unindent.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index 7e8717a78ef7b5640037c0aa3aebd05570e290d8..4ee5baa662f3155020671452b625fc508d9b445c 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -10,51 +10,51 @@ path = "src/semantic_index.rs" doctest = false [dependencies] -ai = { path = "../ai" } +ai.workspace = true anyhow.workspace = true async-trait.workspace = true -collections = { path = "../collections" } +collections.workspace = true futures.workspace = true globset.workspace = true -gpui = { path = "../gpui" } -language = { path = "../language" } +gpui.workspace = true +language.workspace = true lazy_static.workspace = true log.workspace = true ndarray = { version = "0.15.0" } ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true rand.workspace = true -release_channel = { path = "../release_channel" } -rpc = { path = "../rpc" } +release_channel.workspace = true +rpc.workspace = true rusqlite.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true sha1 = "0.10.5" smol.workspace = true tiktoken-rs.workspace = true tree-sitter.workspace = true -util = { path = "../util" } -workspace = { path = "../workspace" } +util.workspace = true +workspace.workspace = true [dev-dependencies] -ai = { path = "../ai", features = ["test-support"] } -client = { path = "../client" } -collections = { path = "../collections", features = ["test-support"] } +ai = { workspace = true, features = ["test-support"] } +client.workspace = true +collections = { workspace = true, features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -node_runtime = { path = "../node_runtime" } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +node_runtime.workspace = true pretty_assertions.workspace = true -project = { path = "../project", features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } rand.workspace = true -rpc = { path = "../rpc", features = ["test-support"] } +rpc = { workspace = true, features = ["test-support"] } rust-embed.workspace = true -settings = { path = "../settings", features = ["test-support"]} +settings = { workspace = true, features = ["test-support"]} tempfile.workspace = true tree-sitter-cpp.workspace = true tree-sitter-elixir.workspace = true @@ -66,4 +66,4 @@ tree-sitter-rust.workspace = true tree-sitter-toml.workspace = true tree-sitter-typescript.workspace = true unindent.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 2d0669cadabb20338d79347426d1d1210cd97d4f..459bcf6703a75db4ab315948d71a35dd91e77453 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -14,14 +14,14 @@ test-support = ["gpui/test-support", "fs/test-support"] [dependencies] anyhow.workspace = true -collections = { path = "../collections" } -feature_flags = { path = "../feature_flags" } -fs = { path = "../fs" } +collections.workspace = true +feature_flags.workspace = true +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true lazy_static.workspace = true postage.workspace = true -release_channel = { path = "../release_channel" } +release_channel.workspace = true rust-embed.workspace = true schemars.workspace = true serde.workspace = true @@ -32,11 +32,11 @@ smallvec.workspace = true toml.workspace = true tree-sitter-json = "*" tree-sitter.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] -fs = { path = "../fs", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true pretty_assertions.workspace = true unindent.workspace = true diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 0d5f9dba541a0931cd503324c3214142ba4c6c55..2ee5e6cbf39468cea60fcd108daae7b95fa093da 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -14,5 +14,5 @@ libsqlite3-sys = { version = "0.26", features = ["bundled"] } parking_lot.workspace = true smol.workspace = true thread_local = "1.1.4" -util = { path = "../util" } +util.workspace = true uuid.workspace = true diff --git a/crates/sqlez_macros/Cargo.toml b/crates/sqlez_macros/Cargo.toml index a6e3881b7f3d87f5972f27460c72445acd878807..8b9b29dd570d9af857ed35264dbfe55d6421bdbe 100644 --- a/crates/sqlez_macros/Cargo.toml +++ b/crates/sqlez_macros/Cargo.toml @@ -14,6 +14,6 @@ doctest = false lazy_static.workspace = true proc-macro2 = "1.0" quote = "1.0" -sqlez = { path = "../sqlez" } +sqlez.workspace = true sqlformat = "0.2" syn = "1.0" diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index 9b17a04096d00d9a5c2451788a0e8dd5db49e7d8..fc1754c253c6f523241ec98906e6ad0785d15fae 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -6,6 +6,6 @@ publish = false license = "GPL-3.0-or-later" [dependencies] -gpui = { path = "../gpui" } +gpui.workspace = true itertools = { package = "itertools", version = "0.10" } smallvec.workspace = true diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 6177609d947ecdb538e77b54660d8a6cffbdf4b4..c95ca3c2e9e30284b3c2a7e44338aec3fb22000a 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -15,28 +15,28 @@ anyhow.workspace = true backtrace-on-stack-overflow = "0.3.0" chrono = "0.4" clap = { version = "4.4", features = ["derive", "string"] } -collab_ui = { path = "../collab_ui", features = ["stories"] } +collab_ui = { workspace = true, features = ["stories"] } ctrlc = "3.4" dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } -editor = { path = "../editor" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } +editor.workspace = true +fuzzy.workspace = true +gpui.workspace = true indoc.workspace = true itertools = "0.11.0" -language = { path = "../language" } +language.workspace = true log.workspace = true -menu = { path = "../menu" } -picker = { path = "../picker" } +menu.workspace = true +picker.workspace = true rust-embed.workspace = true serde.workspace = true -settings = { path = "../settings" } +settings.workspace = true simplelog = "0.9" smallvec.workspace = true -story = { path = "../story" } +story.workspace = true strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -ui = { path = "../ui", features = ["stories"] } -util = { path = "../util" } +theme.workspace = true +ui = { workspace = true, features = ["stories"] } +util.workspace = true [dev-dependencies] -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 670510356d577c386da3aabb281dded17a010e33..d4eefd861f3b067c6ca7cb41861d63734ed7aa01 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -14,10 +14,10 @@ doctest = false # needed for "a few weeks" until alacritty 0.13.2 is out alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "2d2b894c3b869fadc78fce9d72cb5c8d2b764cac" } anyhow.workspace = true -db = { path = "../db" } +db.workspace = true dirs = "4.0.0" futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true itertools = "0.10" lazy_static.workspace = true libc = "0.2" @@ -28,13 +28,13 @@ schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true shellexpand = "2.1.0" smallvec.workspace = true smol.workspace = true -theme = { path = "../theme" } +theme.workspace = true thiserror.workspace = true -util = { path = "../util" } +util.workspace = true [dev-dependencies] rand.workspace = true diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 134f9f08dd8a6d1706b2dddb3d8b6d54bc8cbe04..55eaa705892e8b894e18c21196cdb7c3da294a78 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -11,39 +11,39 @@ doctest = false [dependencies] anyhow.workspace = true -db = { path = "../db" } -collections = { path = "../collections" } +db.workspace = true +collections.workspace = true dirs = "4.0.0" -editor = { path = "../editor" } +editor.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true itertools = "0.10" -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true libc = "0.2" mio-extras = "2.0.6" ordered-float.workspace = true procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } -project = { path = "../project" } -search = { path = "../search" } +project.workspace = true +search.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true shellexpand = "2.1.0" smallvec.workspace = true smol.workspace = true -terminal = { path = "../terminal" } -theme = { path = "../theme" } +terminal.workspace = true +theme.workspace = true thiserror.workspace = true -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -client = { path = "../client", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } rand.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml index e3cf4b023867c5d24a26ce02aa51b1fb18f27078..1a7f8177a9da9238d9a6ab8df4e9caa0912ea713 100644 --- a/crates/text/Cargo.toml +++ b/crates/text/Cargo.toml @@ -14,8 +14,8 @@ test-support = ["rand"] [dependencies] anyhow.workspace = true -clock = { path = "../clock" } -collections = { path = "../collections" } +clock.workspace = true +collections.workspace = true digest = { version = "0.9", features = ["std"] } lazy_static.workspace = true log.workspace = true @@ -23,15 +23,15 @@ parking_lot.workspace = true postage.workspace = true rand = { workspace = true, optional = true } regex.workspace = true -rope = { path = "../rope" } +rope.workspace = true smallvec.workspace = true -sum_tree = { path = "../sum_tree" } -util = { path = "../util" } +sum_tree.workspace = true +util.workspace = true [dev-dependencies] -collections = { path = "../collections", features = ["test-support"] } +collections = { workspace = true, features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } rand.workspace = true -util = { path = "../util", features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index b29ac5d2fe534a82e3d1a4f5d74afc1d3307f1ef..8970cfbacf70d4e44e063ef60431b9b0cfff6677 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -20,11 +20,11 @@ doctest = false [dependencies] anyhow.workspace = true -color = { path = "../color" } +color.workspace = true derive_more.workspace = true -fs = { path = "../fs" } +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } +gpui.workspace = true indexmap = { version = "1.6.2", features = ["serde"] } itertools = { version = "0.11.0", optional = true } palette = { version = "0.7.3", default-features = false, features = ["std"] } @@ -36,13 +36,13 @@ serde_derive.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true serde_repr.workspace = true -settings = { path = "../settings" } -story = { path = "../story", optional = true } +settings.workspace = true +story = { workspace = true, optional = true } toml.workspace = true -util = { path = "../util" } +util.workspace = true uuid.workspace = true [dev-dependencies] -fs = { path = "../fs", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } diff --git a/crates/theme_importer/Cargo.toml b/crates/theme_importer/Cargo.toml index ddb9433f16d3e32f24bec3ed3f1d9741b9edf4e5..9e2052e2bbab50318880281cb61edead059cde76 100644 --- a/crates/theme_importer/Cargo.toml +++ b/crates/theme_importer/Cargo.toml @@ -10,7 +10,7 @@ any_ascii = "0.3.2" anyhow.workspace = true clap = { version = "4.4", features = ["derive"] } convert_case = "0.6.0" -gpui = { path = "../gpui" } +gpui.workspace = true indexmap = { version = "1.6.2", features = ["serde"] } indoc.workspace = true log.workspace = true @@ -23,6 +23,6 @@ serde_json.workspace = true serde_json_lenient.workspace = true simplelog = "0.9" strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } +theme.workspace = true uuid.workspace = true vscode_theme = "0.2.0" diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index b3727092ea29417992542cb928ad43a9ca830d3b..869204be0717939b5058df9ae7adda2d419451d9 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -10,22 +10,22 @@ path = "src/theme_selector.rs" doctest = false [dependencies] -client = { path = "../client" } -editor = { path = "../editor" } -feature_flags = { path = "../feature_flags" } -fs = { path = "../fs" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } +client.workspace = true +editor.workspace = true +feature_flags.workspace = true +fs.workspace = true +fuzzy.workspace = true +gpui.workspace = true log.workspace = true parking_lot.workspace = true -picker = { path = "../picker" } +picker.workspace = true postage.workspace = true -settings = { path = "../settings" } +settings.workspace = true smol.workspace = true -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +theme.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index 4b1d2d7c863fce8abf6c7d89a412e61b37506525..26fece87a2033171c25895ff368358ced0da376b 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -12,16 +12,16 @@ path = "src/ui.rs" [dependencies] anyhow.workspace = true chrono = "0.4" -gpui = { path = "../gpui" } +gpui.workspace = true itertools = { version = "0.11.0", optional = true } -menu = { path = "../menu" } +menu.workspace = true rand = "0.8" serde.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true -story = { path = "../story", optional = true } +story = { workspace = true, optional = true } strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } +theme.workspace = true [features] default = [] diff --git a/crates/vcs_menu/Cargo.toml b/crates/vcs_menu/Cargo.toml index 88cbbb0a846002ffb1c64e8b7ef67c96cb6dba18..3258c229dd8eda5d6b01042dd66f91882e038017 100644 --- a/crates/vcs_menu/Cargo.toml +++ b/crates/vcs_menu/Cargo.toml @@ -7,10 +7,10 @@ license = "GPL-3.0-or-later" [dependencies] anyhow.workspace = true -fs = { path = "../fs" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -picker = { path = "../picker" } -ui = { path = "../ui" } -util = { path = "../util" } -workspace = { path = "../workspace" } +fs.workspace = true +fuzzy.workspace = true +gpui.workspace = true +picker.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 8415111849632575e6fca51cc62af40383aa470e..d5cd23420aac33cd661031d7dd2b86f1725cadbc 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -16,40 +16,40 @@ neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"] anyhow.workspace = true async-compat = { version = "0.2.1", "optional" = true } async-trait = { workspace = true, "optional" = true } -collections = { path = "../collections" } -command_palette = { path = "../command_palette" } +collections.workspace = true +command_palette.workspace = true # HACK: We're only depending on `copilot` here for `CommandPaletteFilter`. See the attached comment on that type. -copilot = { path = "../copilot" } -diagnostics = { path = "../diagnostics" } -editor = { path = "../editor" } -gpui = { path = "../gpui" } +copilot.workspace = true +diagnostics.workspace = true +editor.workspace = true +gpui.workspace = true itertools = "0.10" -language = { path = "../language" } +language.workspace = true log.workspace = true nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true } regex.workspace = true -search = { path = "../search" } +search.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } -theme = { path = "../theme" } +settings.workspace = true +theme.workspace = true tokio = { version = "1.15", "optional" = true } -ui = { path = "../ui" } -workspace = { path = "../workspace" } -zed_actions = { path = "../zed_actions" } +ui.workspace = true +workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } futures.workspace = true -gpui = { path = "../gpui", features = ["test-support"] } -release_channel = { path = "../release_channel" } +gpui = { workspace = true, features = ["test-support"] } +release_channel.workspace = true indoc.workspace = true -language = { path = "../language", features = ["test-support"] } -lsp = { path = "../lsp", features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +lsp = { workspace = true, features = ["test-support"] } parking_lot.workspace = true -project = { path = "../project", features = ["test-support"] } -settings = { path = "../settings" } -theme = { path = "../theme", features = ["test-support"] } -util = { path = "../util", features = ["test-support"] } -workspace = { path = "../workspace", features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +settings.workspace = true +theme = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index e915581f646a4000555226eeaba1315862d33794..6b8012e0fe7aadf3212fb63db9bb9a5f532884e2 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -13,25 +13,25 @@ test-support = [] [dependencies] anyhow.workspace = true -client = { path = "../client" } -db = { path = "../db" } -editor = { path = "../editor" } -fs = { path = "../fs" } -fuzzy = { path = "../fuzzy" } -gpui = { path = "../gpui" } -install_cli = { path = "../install_cli" } +client.workspace = true +db.workspace = true +editor.workspace = true +fs.workspace = true +fuzzy.workspace = true +gpui.workspace = true +install_cli.workspace = true log.workspace = true -picker = { path = "../picker" } -project = { path = "../project" } +picker.workspace = true +project.workspace = true schemars.workspace = true serde.workspace = true -settings = { path = "../settings" } -theme = { path = "../theme" } -theme_selector = { path = "../theme_selector" } -ui = { path = "../ui" } -util = { path = "../util" } -vim = { path = "../vim" } -workspace = { path = "../workspace" } +settings.workspace = true +theme.workspace = true +theme_selector.workspace = true +ui.workspace = true +util.workspace = true +vim.workspace = true +workspace.workspace = true [dev-dependencies] -editor = { path = "../editor", features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index bbba42e9af063cc5037382705f4cf0dbce91f751..6e0d4d4c5399887861af7cc32247b0564aea6deb 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -23,43 +23,43 @@ test-support = [ anyhow.workspace = true async-recursion = "1.0.0" bincode = "1.2.1" -call = { path = "../call" } -client = { path = "../client" } -collections = { path = "../collections" } -db = { path = "../db" } +call.workspace = true +client.workspace = true +collections.workspace = true +db.workspace = true derive_more.workspace = true -fs = { path = "../fs" } +fs.workspace = true futures.workspace = true -gpui = { path = "../gpui" } -install_cli = { path = "../install_cli" } +gpui.workspace = true +install_cli.workspace = true itertools = "0.10" -language = { path = "../language" } +language.workspace = true lazy_static.workspace = true log.workspace = true -node_runtime = { path = "../node_runtime" } +node_runtime.workspace = true parking_lot.workspace = true postage.workspace = true -project = { path = "../project" } +project.workspace = true schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true smallvec.workspace = true -sqlez = { path = "../sqlez" } -terminal = { path = "../terminal" } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } +sqlez.workspace = true +terminal.workspace = true +theme.workspace = true +ui.workspace = true +util.workspace = true uuid.workspace = true [dev-dependencies] -call = { path = "../call", features = ["test-support"] } -client = { path = "../client", features = ["test-support"] } -db = { path = "../db", features = ["test-support"] } +call = { workspace = true, features = ["test-support"] } +client = { workspace = true, features = ["test-support"] } +db = { workspace = true, features = ["test-support"] } env_logger.workspace = true -fs = { path = "../fs", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } indoc.workspace = true -project = { path = "../project", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +settings = { workspace = true, features = ["test-support"] } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 46025a59bbf800c9aa06be4ff222d0a4109aea0f..fda5391ac23d27e3ce414c54c500c48b1392a2a7 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -16,94 +16,94 @@ name = "Zed" path = "src/main.rs" [dependencies] -activity_indicator = { path = "../activity_indicator" } -ai = { path = "../ai" } +activity_indicator.workspace = true +ai.workspace = true anyhow.workspace = true -assets = { path = "../assets" } -assistant = { path = "../assistant" } +assets.workspace = true +assistant.workspace = true async-compression.workspace = true async-recursion = "0.3" async-tar = "0.4.2" async-trait.workspace = true -audio = { path = "../audio" } -auto_update = { path = "../auto_update" } +audio.workspace = true +auto_update.workspace = true backtrace = "0.3" -breadcrumbs = { path = "../breadcrumbs" } -call = { path = "../call" } -channel = { path = "../channel" } +breadcrumbs.workspace = true +call.workspace = true +channel.workspace = true chrono = "0.4" -cli = { path = "../cli" } -client = { path = "../client" } -collab_ui = { path = "../collab_ui" } -collections = { path = "../collections" } -command_palette = { path = "../command_palette" } -copilot = { path = "../copilot" } -copilot_ui = { path = "../copilot_ui" } +cli.workspace = true +client.workspace = true +collab_ui.workspace = true +collections.workspace = true +command_palette.workspace = true +copilot.workspace = true +copilot_ui.workspace = true ctor.workspace = true -db = { path = "../db" } -diagnostics = { path = "../diagnostics" } -editor = { path = "../editor" } +db.workspace = true +diagnostics.workspace = true +editor.workspace = true env_logger.workspace = true -feature_flags = { path = "../feature_flags" } -feedback = { path = "../feedback" } -file_finder = { path = "../file_finder" } -fs = { path = "../fs" } -fsevent = { path = "../fsevent" } +feature_flags.workspace = true +feedback.workspace = true +file_finder.workspace = true +fs.workspace = true +fsevent.workspace = true futures.workspace = true -go_to_line = { path = "../go_to_line" } -gpui = { path = "../gpui" } +go_to_line.workspace = true +gpui.workspace = true ignore = "0.4" image = "0.23" indexmap = "1.6.2" -install_cli = { path = "../install_cli" } +install_cli.workspace = true isahc.workspace = true itertools = "0.11" -journal = { path = "../journal" } -language = { path = "../language" } -language_selector = { path = "../language_selector" } -language_tools = { path = "../language_tools" } +journal.workspace = true +language.workspace = true +language_selector.workspace = true +language_tools.workspace = true lazy_static.workspace = true libc = "0.2" log.workspace = true -lsp = { path = "../lsp" } -markdown_preview = { path = "../markdown_preview" } -menu = { path = "../menu" } +lsp.workspace = true +markdown_preview.workspace = true +menu.workspace = true mimalloc = "0.1" -node_runtime = { path = "../node_runtime" } -notifications = { path = "../notifications" } +node_runtime.workspace = true +notifications.workspace = true num_cpus = "1.13.0" -outline = { path = "../outline" } +outline.workspace = true parking_lot.workspace = true postage.workspace = true -project = { path = "../project" } -project_panel = { path = "../project_panel" } -project_symbols = { path = "../project_symbols" } -quick_action_bar = { path = "../quick_action_bar" } +project.workspace = true +project_panel.workspace = true +project_symbols.workspace = true +quick_action_bar.workspace = true rand.workspace = true -recent_projects = { path = "../recent_projects" } +recent_projects.workspace = true regex.workspace = true -release_channel = { path = "../release_channel" } -rope = { path = "../rope" } -rpc = { path = "../rpc" } +release_channel.workspace = true +rope.workspace = true +rpc.workspace = true rsa = "0.4" rust-embed.workspace = true schemars.workspace = true -search = { path = "../search" } -semantic_index = { path = "../semantic_index" } +search.workspace = true +semantic_index.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -settings = { path = "../settings" } +settings.workspace = true shellexpand = "2.1.0" simplelog = "0.9" smallvec.workspace = true smol.workspace = true -sum_tree = { path = "../sum_tree" } +sum_tree.workspace = true tempfile.workspace = true -terminal_view = { path = "../terminal_view" } -text = { path = "../text" } -theme = { path = "../theme" } -theme_selector = { path = "../theme_selector" } +terminal_view.workspace = true +text.workspace = true +theme.workspace = true +theme_selector.workspace = true thiserror.workspace = true tiny_http = "0.8" toml.workspace = true @@ -151,22 +151,22 @@ tree-sitter-zig.workspace = true tree-sitter.workspace = true url.workspace = true urlencoding = "2.1.2" -util = { path = "../util" } +util.workspace = true uuid.workspace = true -vim = { path = "../vim" } -welcome = { path = "../welcome" } -workspace = { path = "../workspace" } -zed_actions = { path = "../zed_actions" } +vim.workspace = true +welcome.workspace = true +workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] -call = { path = "../call", features = ["test-support"] } -editor = { path = "../editor", features = ["test-support"] } -gpui = { path = "../gpui", features = ["test-support"] } -language = { path = "../language", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } -text = { path = "../text", features = ["test-support"] } +call = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +language = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +text = { workspace = true, features = ["test-support"] } unindent.workspace = true -workspace = { path = "../workspace", features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } [package.metadata.bundle-dev] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] diff --git a/crates/zed_actions/Cargo.toml b/crates/zed_actions/Cargo.toml index 5e98793f8b91cb942cff4efa0671bf013b076bdd..19c9415514ee6f2122f5e07fef1c2af03226f6a8 100644 --- a/crates/zed_actions/Cargo.toml +++ b/crates/zed_actions/Cargo.toml @@ -6,5 +6,5 @@ publish = false license = "GPL-3.0-or-later" [dependencies] -gpui = { path = "../gpui" } +gpui.workspace = true serde.workspace = true From 6c4b96ec7609e5431193e86e5959a7985da3ddfc Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Tue, 6 Feb 2024 21:22:54 +0100 Subject: [PATCH 082/101] Add the ability to reply to a message (#7170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature - [x] Allow to click on reply to go to the real message - [x] In chat - [x] Show only a part of the message that you reply to - [x] In chat - [x] In reply preview TODO’s - [x] Fix migration - [x] timestamp(in filename) - [x] remove the reference to the reply_message_id - [x] Fix markdown cache for reply message - [x] Fix spacing when first message is a reply to you and you want to reply to that message. - [x] Fetch message that you replied to - [x] allow fetching messages that are not inside the current view - [x] When message is deleted, we should show a text like `message deleted` or something - [x] Show correct GitHub username + icon after `Replied to: ` - [x] Show correct message(now it's hard-coded) - [x] Add icon to reply + add the onClick logic - [x] Show message that you want to reply to - [x] Allow to click away the message that you want to reply to - [x] Fix hard-coded GitHub user + icon after `Reply tp:` - [x] Add tests Screenshot 2024-02-06 at 20 51 40 Screenshot 2024-02-06 at 20 52 02 Release Notes: - Added the ability to reply to a message. - Added highlight message when you click on mention notifications or a reply message. --------- Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com> Co-authored-by: Conrad Irwin --- crates/channel/src/channel_chat.rs | 171 ++++++--- crates/channel/src/channel_store_tests.rs | 5 + .../20221109000000_test_schema.sql | 3 +- .../20240203113741_add_reply_to_message.sql | 1 + crates/collab/src/db/queries/messages.rs | 3 + .../collab/src/db/tables/channel_message.rs | 1 + crates/collab/src/db/tests/message_tests.rs | 50 ++- crates/collab/src/rpc.rs | 5 + .../collab/src/tests/channel_message_tests.rs | 64 ++++ crates/collab_ui/src/chat_panel.rs | 352 +++++++++++++++--- .../src/chat_panel/message_editor.rs | 22 +- crates/rpc/proto/zed.proto | 2 + 12 files changed, 569 insertions(+), 110 deletions(-) create mode 100644 crates/collab/migrations/20240203113741_add_reply_to_message.sql diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index e9353a14419adf069a6319ed76997754008caa18..ad843470be35cc2f123d9feaace647cd80c57265 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -6,11 +6,12 @@ use client::{ Client, Subscription, TypedEnvelope, UserId, }; use futures::lock::Mutex; -use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task}; +use gpui::{ + AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, +}; use rand::prelude::*; use std::{ collections::HashSet, - mem, ops::{ControlFlow, Range}, sync::Arc, }; @@ -26,6 +27,7 @@ pub struct ChannelChat { loaded_all_messages: bool, last_acknowledged_id: Option, next_pending_message_id: usize, + first_loaded_message_id: Option, user_store: Model, rpc: Arc, outgoing_messages_lock: Arc>, @@ -37,6 +39,7 @@ pub struct ChannelChat { pub struct MessageParams { pub text: String, pub mentions: Vec<(Range, UserId)>, + pub reply_to_message_id: Option, } #[derive(Clone, Debug)] @@ -47,6 +50,7 @@ pub struct ChannelMessage { pub sender: Arc, pub nonce: u128, pub mentions: Vec<(Range, UserId)>, + pub reply_to_message_id: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -55,6 +59,15 @@ pub enum ChannelMessageId { Pending(usize), } +impl Into> for ChannelMessageId { + fn into(self) -> Option { + match self { + ChannelMessageId::Saved(id) => Some(id), + ChannelMessageId::Pending(_) => None, + } + } +} + #[derive(Clone, Debug, Default)] pub struct ChannelMessageSummary { max_id: ChannelMessageId, @@ -96,28 +109,35 @@ impl ChannelChat { let response = client .request(proto::JoinChannelChat { channel_id }) .await?; - let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?; - let loaded_all_messages = response.done; - Ok(cx.new_model(|cx| { + let handle = cx.new_model(|cx| { cx.on_release(Self::release).detach(); - let mut this = Self { + Self { channel_id: channel.id, - user_store, + user_store: user_store.clone(), channel_store, - rpc: client, + rpc: client.clone(), outgoing_messages_lock: Default::default(), messages: Default::default(), acknowledged_message_ids: Default::default(), - loaded_all_messages, + loaded_all_messages: false, next_pending_message_id: 0, last_acknowledged_id: None, rng: StdRng::from_entropy(), + first_loaded_message_id: None, _subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()), - }; - this.insert_messages(messages, cx); - this - })?) + } + })?; + Self::handle_loaded_messages( + handle.downgrade(), + user_store, + client, + response.messages, + response.done, + &mut cx, + ) + .await?; + Ok(handle) } fn release(&mut self, _: &mut AppContext) { @@ -166,6 +186,7 @@ impl ChannelChat { timestamp: OffsetDateTime::now_utc(), mentions: message.mentions.clone(), nonce, + reply_to_message_id: message.reply_to_message_id, }, &(), ), @@ -183,6 +204,7 @@ impl ChannelChat { body: message.text, nonce: Some(nonce.into()), mentions: mentions_to_proto(&message.mentions), + reply_to_message_id: message.reply_to_message_id, }); let response = request.await?; drop(outgoing_message_guard); @@ -227,12 +249,16 @@ impl ChannelChat { before_message_id, }) .await?; - let loaded_all_messages = response.done; - let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?; - this.update(&mut cx, |this, cx| { - this.loaded_all_messages = loaded_all_messages; - this.insert_messages(messages, cx); - })?; + Self::handle_loaded_messages( + this, + user_store, + rpc, + response.messages, + response.done, + &mut cx, + ) + .await?; + anyhow::Ok(()) } .log_err() @@ -240,9 +266,14 @@ impl ChannelChat { } pub fn first_loaded_message_id(&mut self) -> Option { - self.messages.first().and_then(|message| match message.id { - ChannelMessageId::Saved(id) => Some(id), - ChannelMessageId::Pending(_) => None, + self.first_loaded_message_id + } + + /// Load a message by its id, if it's already stored locally. + pub fn find_loaded_message(&self, id: u64) -> Option<&ChannelMessage> { + self.messages.iter().find(|message| match message.id { + ChannelMessageId::Saved(message_id) => message_id == id, + ChannelMessageId::Pending(_) => false, }) } @@ -304,6 +335,66 @@ impl ChannelChat { } } + async fn handle_loaded_messages( + this: WeakModel, + user_store: Model, + rpc: Arc, + proto_messages: Vec, + loaded_all_messages: bool, + cx: &mut AsyncAppContext, + ) -> Result<()> { + let loaded_messages = messages_from_proto(proto_messages, &user_store, cx).await?; + + let first_loaded_message_id = loaded_messages.first().map(|m| m.id); + let loaded_message_ids = this.update(cx, |this, _| { + let mut loaded_message_ids: HashSet = HashSet::default(); + for message in loaded_messages.iter() { + if let Some(saved_message_id) = message.id.into() { + loaded_message_ids.insert(saved_message_id); + } + } + for message in this.messages.iter() { + if let Some(saved_message_id) = message.id.into() { + loaded_message_ids.insert(saved_message_id); + } + } + loaded_message_ids + })?; + + let missing_ancestors = loaded_messages + .iter() + .filter_map(|message| { + if let Some(ancestor_id) = message.reply_to_message_id { + if !loaded_message_ids.contains(&ancestor_id) { + return Some(ancestor_id); + } + } + None + }) + .collect::>(); + + let loaded_ancestors = if missing_ancestors.is_empty() { + None + } else { + let response = rpc + .request(proto::GetChannelMessagesById { + message_ids: missing_ancestors, + }) + .await?; + Some(messages_from_proto(response.messages, &user_store, cx).await?) + }; + this.update(cx, |this, cx| { + this.first_loaded_message_id = first_loaded_message_id.and_then(|msg_id| msg_id.into()); + this.loaded_all_messages = loaded_all_messages; + this.insert_messages(loaded_messages, cx); + if let Some(loaded_ancestors) = loaded_ancestors { + this.insert_messages(loaded_ancestors, cx); + } + })?; + + Ok(()) + } + pub fn rejoin(&mut self, cx: &mut ModelContext) { let user_store = self.user_store.clone(); let rpc = self.rpc.clone(); @@ -311,28 +402,17 @@ impl ChannelChat { cx.spawn(move |this, mut cx| { async move { let response = rpc.request(proto::JoinChannelChat { channel_id }).await?; - let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?; - let loaded_all_messages = response.done; - - let pending_messages = this.update(&mut cx, |this, cx| { - if let Some((first_new_message, last_old_message)) = - messages.first().zip(this.messages.last()) - { - if first_new_message.id > last_old_message.id { - let old_messages = mem::take(&mut this.messages); - cx.emit(ChannelChatEvent::MessagesUpdated { - old_range: 0..old_messages.summary().count, - new_count: 0, - }); - this.loaded_all_messages = loaded_all_messages; - } - } - - this.insert_messages(messages, cx); - if loaded_all_messages { - this.loaded_all_messages = loaded_all_messages; - } - + Self::handle_loaded_messages( + this.clone(), + user_store.clone(), + rpc.clone(), + response.messages, + response.done, + &mut cx, + ) + .await?; + + let pending_messages = this.update(&mut cx, |this, _| { this.pending_messages().cloned().collect::>() })?; @@ -342,6 +422,7 @@ impl ChannelChat { body: pending_message.body, mentions: mentions_to_proto(&pending_message.mentions), nonce: Some(pending_message.nonce.into()), + reply_to_message_id: pending_message.reply_to_message_id, }); let response = request.await?; let message = ChannelMessage::from_proto( @@ -553,6 +634,7 @@ impl ChannelMessage { .nonce .ok_or_else(|| anyhow!("nonce is required"))? .into(), + reply_to_message_id: message.reply_to_message_id, }) } @@ -642,6 +724,7 @@ impl<'a> From<&'a str> for MessageParams { Self { text: value.into(), mentions: Vec::new(), + reply_to_message_id: None, } } } diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 57b9d2071091caa5448ed84e274970d581fc9da3..c668b7202275cdc505551e8fe0441970d946483a 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -184,6 +184,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { sender_id: 5, mentions: vec![], nonce: Some(1.into()), + reply_to_message_id: None, }, proto::ChannelMessage { id: 11, @@ -192,6 +193,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { sender_id: 6, mentions: vec![], nonce: Some(2.into()), + reply_to_message_id: None, }, ], done: false, @@ -239,6 +241,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { sender_id: 7, mentions: vec![], nonce: Some(3.into()), + reply_to_message_id: None, }), }); @@ -292,6 +295,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { sender_id: 5, nonce: Some(4.into()), mentions: vec![], + reply_to_message_id: None, }, proto::ChannelMessage { id: 9, @@ -300,6 +304,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { sender_id: 6, nonce: Some(5.into()), mentions: vec![], + reply_to_message_id: None, }, ], }, diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 04676106086d5c3c92daf8728ef40b98d0555818..7be5725b68f7cb2b9e10bc4bafd9f65d331e65b2 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -217,7 +217,8 @@ CREATE TABLE IF NOT EXISTS "channel_messages" ( "sender_id" INTEGER NOT NULL REFERENCES users (id), "body" TEXT NOT NULL, "sent_at" TIMESTAMP, - "nonce" BLOB NOT NULL + "nonce" BLOB NOT NULL, + "reply_to_message_id" INTEGER DEFAULT NULL ); CREATE INDEX "index_channel_messages_on_channel_id" ON "channel_messages" ("channel_id"); CREATE UNIQUE INDEX "index_channel_messages_on_sender_id_nonce" ON "channel_messages" ("sender_id", "nonce"); diff --git a/crates/collab/migrations/20240203113741_add_reply_to_message.sql b/crates/collab/migrations/20240203113741_add_reply_to_message.sql new file mode 100644 index 0000000000000000000000000000000000000000..6f40b62822bb4936f0f90e3be65f640e323d09d0 --- /dev/null +++ b/crates/collab/migrations/20240203113741_add_reply_to_message.sql @@ -0,0 +1 @@ +ALTER TABLE channel_messages ADD reply_to_message_id INTEGER DEFAULT NULL diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index 9baa7162c50d497fab4cc49864b0c899360cb040..38a828efa4c91e4311405c80ba614835ea6a8c11 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -161,6 +161,7 @@ impl Database { upper_half: nonce.0, lower_half: nonce.1, }), + reply_to_message_id: row.reply_to_message_id.map(|id| id.to_proto()), } }) .collect::>(); @@ -207,6 +208,7 @@ impl Database { mentions: &[proto::ChatMention], timestamp: OffsetDateTime, nonce: u128, + reply_to_message_id: Option, ) -> Result { self.transaction(|tx| async move { let channel = self.get_channel_internal(channel_id, &*tx).await?; @@ -245,6 +247,7 @@ impl Database { sent_at: ActiveValue::Set(timestamp), nonce: ActiveValue::Set(Uuid::from_u128(nonce)), id: ActiveValue::NotSet, + reply_to_message_id: ActiveValue::Set(reply_to_message_id), }) .on_conflict( OnConflict::columns([ diff --git a/crates/collab/src/db/tables/channel_message.rs b/crates/collab/src/db/tables/channel_message.rs index ff49c63ba71d675f20f542d3300d74d322d70722..b2493d2eadd7cce961dcaba5769406025445bb93 100644 --- a/crates/collab/src/db/tables/channel_message.rs +++ b/crates/collab/src/db/tables/channel_message.rs @@ -12,6 +12,7 @@ pub struct Model { pub body: String, pub sent_at: PrimitiveDateTime, pub nonce: Uuid, + pub reply_to_message_id: Option, } impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index e0467b08f3a55dbcce33576a725c45a9356f735a..c785e3cb733066b98899b0fcb3345afbdd69e854 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -32,6 +32,7 @@ async fn test_channel_message_retrieval(db: &Arc) { &[], OffsetDateTime::now_utc(), i, + None, ) .await .unwrap() @@ -106,6 +107,7 @@ async fn test_channel_message_nonces(db: &Arc) { &mentions_to_proto(&[(3..10, user_b.to_proto())]), OffsetDateTime::now_utc(), 100, + None, ) .await .unwrap() @@ -118,6 +120,7 @@ async fn test_channel_message_nonces(db: &Arc) { &mentions_to_proto(&[]), OffsetDateTime::now_utc(), 200, + None, ) .await .unwrap() @@ -130,6 +133,7 @@ async fn test_channel_message_nonces(db: &Arc) { &mentions_to_proto(&[(4..11, user_c.to_proto())]), OffsetDateTime::now_utc(), 100, + None, ) .await .unwrap() @@ -142,6 +146,7 @@ async fn test_channel_message_nonces(db: &Arc) { &mentions_to_proto(&[]), OffsetDateTime::now_utc(), 200, + None, ) .await .unwrap() @@ -157,6 +162,7 @@ async fn test_channel_message_nonces(db: &Arc) { &mentions_to_proto(&[(4..11, user_a.to_proto())]), OffsetDateTime::now_utc(), 100, + None, ) .await .unwrap() @@ -231,17 +237,41 @@ async fn test_unseen_channel_messages(db: &Arc) { .unwrap(); let _ = db - .create_channel_message(channel_1, user, "1_1", &[], OffsetDateTime::now_utc(), 1) + .create_channel_message( + channel_1, + user, + "1_1", + &[], + OffsetDateTime::now_utc(), + 1, + None, + ) .await .unwrap(); let _ = db - .create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2) + .create_channel_message( + channel_1, + user, + "1_2", + &[], + OffsetDateTime::now_utc(), + 2, + None, + ) .await .unwrap(); let third_message = db - .create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3) + .create_channel_message( + channel_1, + user, + "1_3", + &[], + OffsetDateTime::now_utc(), + 3, + None, + ) .await .unwrap() .message_id; @@ -251,7 +281,15 @@ async fn test_unseen_channel_messages(db: &Arc) { .unwrap(); let fourth_message = db - .create_channel_message(channel_2, user, "2_1", &[], OffsetDateTime::now_utc(), 4) + .create_channel_message( + channel_2, + user, + "2_1", + &[], + OffsetDateTime::now_utc(), + 4, + None, + ) .await .unwrap() .message_id; @@ -317,6 +355,7 @@ async fn test_channel_message_mentions(db: &Arc) { &mentions_to_proto(&[(3..10, user_b.to_proto()), (15..22, user_c.to_proto())]), OffsetDateTime::now_utc(), 1, + None, ) .await .unwrap(); @@ -327,6 +366,7 @@ async fn test_channel_message_mentions(db: &Arc) { &mentions_to_proto(&[(4..11, user_c.to_proto())]), OffsetDateTime::now_utc(), 2, + None, ) .await .unwrap(); @@ -337,6 +377,7 @@ async fn test_channel_message_mentions(db: &Arc) { &mentions_to_proto(&[]), OffsetDateTime::now_utc(), 3, + None, ) .await .unwrap(); @@ -347,6 +388,7 @@ async fn test_channel_message_mentions(db: &Arc) { &mentions_to_proto(&[(0..7, user_b.to_proto())]), OffsetDateTime::now_utc(), 4, + None, ) .await .unwrap(); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index c97c283b2fda64dae85961f6b0a3b70cc9664f29..2861449db0bb6d422cc66fa23fcd0de9fb6395a6 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3019,6 +3019,10 @@ async fn send_channel_message( &request.mentions, timestamp, nonce.clone().into(), + match request.reply_to_message_id { + Some(reply_to_message_id) => Some(MessageId::from_proto(reply_to_message_id)), + None => None, + }, ) .await?; let message = proto::ChannelMessage { @@ -3028,6 +3032,7 @@ async fn send_channel_message( mentions: request.mentions, timestamp: timestamp.unix_timestamp() as u64, nonce: Some(nonce), + reply_to_message_id: request.reply_to_message_id, }; broadcast( Some(session.connection_id), diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index 270ba04f7ece0144c82075a1e40a1c087895a460..081ac8d19ad7e1c69e5dced0178488249bfe5fff 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -43,6 +43,7 @@ async fn test_basic_channel_messages( MessageParams { text: "hi @user_c!".into(), mentions: vec![(3..10, client_c.id())], + reply_to_message_id: None, }, cx, ) @@ -402,3 +403,66 @@ async fn test_channel_message_changes( assert!(b_has_messages); } + +#[gpui::test] +async fn test_chat_replies(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + let channel_id = server + .make_channel( + "the-channel", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b)], + ) + .await; + + // Client A sends a message, client B should see that there is a new message. + let channel_chat_a = client_a + .channel_store() + .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) + .await + .unwrap(); + + let channel_chat_b = client_b + .channel_store() + .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx)) + .await + .unwrap(); + + let msg_id = channel_chat_a + .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) + .await + .unwrap(); + + cx_a.run_until_parked(); + + let reply_id = channel_chat_b + .update(cx_b, |c, cx| { + c.send_message( + MessageParams { + text: "reply".into(), + reply_to_message_id: Some(msg_id), + mentions: Vec::new(), + }, + cx, + ) + .unwrap() + }) + .await + .unwrap(); + + cx_a.run_until_parked(); + + channel_chat_a.update(cx_a, |channel_chat, _| { + assert_eq!( + channel_chat + .find_loaded_message(reply_id) + .unwrap() + .reply_to_message_id, + Some(msg_id), + ) + }); +} diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 0ee4805a74b08280cd937eb23a95ecceaac98ab4..9a751335fadc2d177c7600fac64cf013c36558e2 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,16 +1,16 @@ use crate::{collab_panel, ChatPanelSettings}; use anyhow::Result; use call::{room, ActiveCall}; -use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; +use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore}; use client::Client; use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ - actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, DismissEvent, - ElementId, EventEmitter, Fill, FocusHandle, FocusableView, FontWeight, ListOffset, - ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext, - VisualContext, WeakView, + actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, CursorStyle, + DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, + HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, StyledText, + Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; @@ -23,7 +23,7 @@ use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; use ui::{ popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, - TabBar, + TabBar, Tooltip, }; use util::{ResultExt, TryFutureExt}; use workspace::{ @@ -62,6 +62,7 @@ pub struct ChatPanel { markdown_data: HashMap, focus_handle: FocusHandle, open_context_menu: Option<(u64, Subscription)>, + highlighted_message: Option<(u64, Task<()>)>, } #[derive(Serialize, Deserialize)] @@ -124,6 +125,7 @@ impl ChatPanel { markdown_data: Default::default(), focus_handle: cx.focus_handle(), open_context_menu: None, + highlighted_message: None, }; if let Some(channel_id) = ActiveCall::global(cx) @@ -236,6 +238,7 @@ impl ChatPanel { let channel_name = chat.channel(cx).map(|channel| channel.name.clone()); self.message_editor.update(cx, |editor, cx| { editor.set_channel(channel_id, channel_name, cx); + editor.clear_reply_to_message_id(); }); }; let subscription = cx.subscribe(&chat, Self::channel_did_change); @@ -285,6 +288,99 @@ impl ChatPanel { } } + fn render_replied_to_message( + &mut self, + message_id: Option, + reply_to_message: &ChannelMessage, + cx: &mut ViewContext, + ) -> impl IntoElement { + let body_element_id: ElementId = match message_id { + Some(ChannelMessageId::Saved(id)) => ("reply-to-saved-message", id).into(), + Some(ChannelMessageId::Pending(id)) => ("reply-to-pending-message", id).into(), // This should never happen + None => ("composing-reply").into(), + }; + + let message_element_id: ElementId = match message_id { + Some(ChannelMessageId::Saved(id)) => ("reply-to-saved-message-container", id).into(), + Some(ChannelMessageId::Pending(id)) => { + ("reply-to-pending-message-container", id).into() + } // This should never happen + None => ("composing-reply-container").into(), + }; + + let current_channel_id = self.channel_id(cx); + let reply_to_message_id = reply_to_message.id; + + let reply_to_message_body = self + .markdown_data + .entry(reply_to_message.id) + .or_insert_with(|| { + Self::render_markdown_with_mentions( + &self.languages, + self.client.id(), + reply_to_message, + ) + }); + + const REPLY_TO_PREFIX: &str = "Reply to @"; + + div().flex_grow().child( + v_flex() + .id(message_element_id) + .text_ui_xs() + .child( + h_flex() + .gap_x_1() + .items_center() + .justify_start() + .overflow_x_hidden() + .whitespace_nowrap() + .child( + StyledText::new(format!( + "{}{}", + REPLY_TO_PREFIX, + reply_to_message.sender.github_login.clone() + )) + .with_highlights( + &cx.text_style(), + vec![( + (REPLY_TO_PREFIX.len() - 1) + ..(reply_to_message.sender.github_login.len() + + REPLY_TO_PREFIX.len()), + HighlightStyle { + font_weight: Some(FontWeight::BOLD), + ..Default::default() + }, + )], + ), + ), + ) + .child( + div() + .border_l_2() + .border_color(cx.theme().colors().border) + .px_1() + .py_0p5() + .mb_1() + .overflow_hidden() + .child( + div() + .max_h_12() + .child(reply_to_message_body.element(body_element_id, cx)), + ), + ) + .cursor(CursorStyle::PointingHand) + .tooltip(|cx| Tooltip::text("Go to message", cx)) + .on_click(cx.listener(move |chat_panel, _, cx| { + if let Some(channel_id) = current_channel_id { + chat_panel + .select_channel(channel_id, reply_to_message_id.into(), cx) + .detach_and_log_err(cx) + } + })), + ) + } + fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> impl IntoElement { let active_chat = &self.active_chat.as_ref().unwrap().0; let (message, is_continuation_from_previous, is_admin) = @@ -317,18 +413,9 @@ impl ChatPanel { }); let _is_pending = message.is_pending(); - let text = self.markdown_data.entry(message.id).or_insert_with(|| { - Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message) - }); let belongs_to_user = Some(message.sender.id) == self.client.user_id(); - let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) = - (message.id, belongs_to_user || is_admin) - { - Some(id) - } else { - None - }; + let can_delete_message = belongs_to_user || is_admin; let element_id: ElementId = match message.id { ChannelMessageId::Saved(id) => ("saved-message", id).into(), @@ -341,19 +428,41 @@ impl ChatPanel { .iter() .any(|m| Some(m.1) == self.client.user_id()); + let message_id = match message.id { + ChannelMessageId::Saved(id) => Some(id), + ChannelMessageId::Pending(_) => None, + }; + + let reply_to_message = message + .reply_to_message_id + .map(|id| active_chat.read(cx).find_loaded_message(id)) + .flatten() + .cloned(); + + let replied_to_you = + reply_to_message.as_ref().map(|m| m.sender.id) == self.client.user_id(); + + let is_highlighted_message = self + .highlighted_message + .as_ref() + .is_some_and(|(id, _)| Some(id) == message_id.as_ref()); + let background = if is_highlighted_message { + cx.theme().status().info_background + } else if mentioning_you || replied_to_you { + cx.theme().colors().background + } else { + cx.theme().colors().panel_background + }; + v_flex().w_full().relative().child( div() - .bg(if mentioning_you { - Fill::from(cx.theme().colors().background) - } else { - Fill::default() - }) + .bg(background) .rounded_md() .overflow_hidden() .px_1() .py_0p5() .when(!is_continuation_from_previous, |this| { - this.mt_1().child( + this.mt_2().child( h_flex() .text_ui_sm() .child(div().absolute().child( @@ -377,36 +486,86 @@ impl ChatPanel { ), ) }) - .when(mentioning_you, |this| this.mt_1()) - .child( - v_flex() - .w_full() - .text_ui_sm() - .id(element_id) - .group("") - .child(text.element("body".into(), cx)) - .child( + .when( + message.reply_to_message_id.is_some() && reply_to_message.is_none(), + |this| { + const MESSAGE_DELETED: &str = "Message has been deleted"; + + let body_text = StyledText::new(MESSAGE_DELETED).with_highlights( + &cx.text_style(), + vec![( + 0..MESSAGE_DELETED.len(), + HighlightStyle { + font_style: Some(FontStyle::Italic), + ..Default::default() + }, + )], + ); + + this.child( div() - .absolute() - .z_index(1) - .right_0() - .w_6() - .bg(cx.theme().colors().panel_background) - .when(!self.has_open_menu(message_id_to_remove), |el| { - el.visible_on_hover("") - }) - .children(message_id_to_remove.map(|message_id| { - popover_menu(("menu", message_id)) - .trigger(IconButton::new( - ("trigger", message_id), - IconName::Ellipsis, - )) - .menu(move |cx| { - Some(Self::render_message_menu(&this, message_id, cx)) - }) - })), - ), - ), + .border_l_2() + .text_ui_xs() + .border_color(cx.theme().colors().border) + .px_1() + .py_0p5() + .child(body_text), + ) + }, + ) + .when_some(reply_to_message, |el, reply_to_message| { + el.child(self.render_replied_to_message( + Some(message.id), + &reply_to_message, + cx, + )) + }) + .when(mentioning_you || replied_to_you, |this| this.my_0p5()) + .map(|el| { + let text = self.markdown_data.entry(message.id).or_insert_with(|| { + Self::render_markdown_with_mentions( + &self.languages, + self.client.id(), + &message, + ) + }); + el.child( + v_flex() + .w_full() + .text_ui_sm() + .id(element_id) + .group("") + .child(text.element("body".into(), cx)) + .child( + div() + .absolute() + .z_index(1) + .right_0() + .w_6() + .bg(background) + .when(!self.has_open_menu(message_id), |el| { + el.visible_on_hover("") + }) + .when_some(message_id, |el, message_id| { + el.child( + popover_menu(("menu", message_id)) + .trigger(IconButton::new( + ("trigger", message_id), + IconName::Ellipsis, + )) + .menu(move |cx| { + Some(Self::render_message_menu( + &this, + message_id, + can_delete_message, + cx, + )) + }), + ) + }), + ), + ) + }), ) } @@ -420,13 +579,27 @@ impl ChatPanel { fn render_message_menu( this: &View, message_id: u64, + can_delete_message: bool, cx: &mut WindowContext, ) -> View { let menu = { - let this = this.clone(); - ContextMenu::build(cx, move |menu, _| { - menu.entry("Delete message", None, move |cx| { - this.update(cx, |this, cx| this.remove_message(message_id, cx)) + ContextMenu::build(cx, move |menu, cx| { + menu.entry( + "Reply to message", + None, + cx.handler_for(&this, move |this, cx| { + this.message_editor.update(cx, |editor, cx| { + editor.set_reply_to_message_id(message_id); + editor.focus_handle(cx).focus(cx); + }) + }), + ) + .when(can_delete_message, move |menu| { + menu.entry( + "Delete message", + None, + cx.handler_for(&this, move |this, cx| this.remove_message(message_id, cx)), + ) }) }) }; @@ -517,7 +690,21 @@ impl ChatPanel { ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone()) .await { + let task = cx.spawn({ + let this = this.clone(); + + |mut cx| async move { + cx.background_executor().timer(Duration::from_secs(2)).await; + this.update(&mut cx, |this, cx| { + this.highlighted_message.take(); + cx.notify(); + }) + .ok(); + } + }); + this.update(&mut cx, |this, cx| { + this.highlighted_message = Some((message_id, task)); if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) { this.message_list.scroll_to(ListOffset { item_ix, @@ -536,6 +723,8 @@ impl ChatPanel { impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let reply_to_message_id = self.message_editor.read(cx).reply_to_message_id(); + v_flex() .track_focus(&self.focus_handle) .full() @@ -558,7 +747,7 @@ impl Render for ChatPanel { ), ), ) - .child(div().flex_grow().px_2().pt_1().map(|this| { + .child(div().flex_grow().px_2().map(|this| { if self.active_chat.is_some() { this.child(list(self.message_list.clone()).full()) } else { @@ -589,14 +778,56 @@ impl Render for ChatPanel { ) } })) + .when_some(reply_to_message_id, |el, reply_to_message_id| { + let reply_message = self + .active_chat() + .map(|active_chat| { + active_chat.read(cx).messages().iter().find_map(|m| { + if m.id == ChannelMessageId::Saved(reply_to_message_id) { + Some(m) + } else { + None + } + }) + }) + .flatten() + .cloned(); + + el.when_some(reply_message, |el, reply_message| { + el.child( + div() + .when(!self.is_scrolled_to_bottom, |el| { + el.border_t_1().border_color(cx.theme().colors().border) + }) + .flex() + .w_full() + .items_start() + .overflow_hidden() + .py_1() + .px_2() + .bg(cx.theme().colors().background) + .child(self.render_replied_to_message(None, &reply_message, cx)) + .child( + IconButton::new("close-reply-preview", IconName::Close) + .shape(ui::IconButtonShape::Square) + .on_click(cx.listener(move |this, _, cx| { + this.message_editor.update(cx, |editor, _| { + editor.clear_reply_to_message_id() + }); + })), + ), + ) + }) + }) .children( Some( h_flex() - .when(!self.is_scrolled_to_bottom, |el| { - el.border_t_1().border_color(cx.theme().colors().border) - }) + .when( + !self.is_scrolled_to_bottom && reply_to_message_id.is_none(), + |el| el.border_t_1().border_color(cx.theme().colors().border), + ) .p_2() - .child(self.message_editor.clone()), + .map(|el| el.child(self.message_editor.clone())), ) .filter(|_| self.active_chat.is_some()), ) @@ -738,6 +969,7 @@ mod tests { }), nonce: 5, mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)], + reply_to_message_id: None, }; let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message); diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 06501fe3fc22d626151602452ee0423c5520202c..d29929e1a21beab665f60b92dcbc86af896b14cb 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -34,6 +34,7 @@ pub struct MessageEditor { mentions: Vec, mentions_task: Option>, channel_id: Option, + reply_to_message_id: Option, } struct MessageEditorCompletionProvider(WeakView); @@ -112,9 +113,22 @@ impl MessageEditor { channel_id: None, mentions: Vec::new(), mentions_task: None, + reply_to_message_id: None, } } + pub fn reply_to_message_id(&self) -> Option { + self.reply_to_message_id + } + + pub fn set_reply_to_message_id(&mut self, reply_to_message_id: u64) { + self.reply_to_message_id = Some(reply_to_message_id); + } + + pub fn clear_reply_to_message_id(&mut self) { + self.reply_to_message_id = None; + } + pub fn set_channel( &mut self, channel_id: u64, @@ -172,8 +186,13 @@ impl MessageEditor { editor.clear(cx); self.mentions.clear(); + let reply_to_message_id = std::mem::take(&mut self.reply_to_message_id); - MessageParams { text, mentions } + MessageParams { + text, + mentions, + reply_to_message_id, + } }) } @@ -424,6 +443,7 @@ mod tests { MessageParams { text, mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)], + reply_to_message_id: None } ); }); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3a513902e5a5cd16b736440546ddc4b67bba46f6..1823b9b6c58d067a4dbb39f39af07f7ee34b5f90 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1122,6 +1122,7 @@ message SendChannelMessage { string body = 2; Nonce nonce = 3; repeated ChatMention mentions = 4; + optional uint64 reply_to_message_id = 5; } message RemoveChannelMessage { @@ -1173,6 +1174,7 @@ message ChannelMessage { uint64 sender_id = 4; Nonce nonce = 5; repeated ChatMention mentions = 6; + optional uint64 reply_to_message_id = 7; } message ChatMention { From 4e519e3af7e7cfa5db68dceb5938025dbca3b61b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 6 Feb 2024 12:38:54 -0800 Subject: [PATCH 083/101] Make diagnostics with empty messages take up one line (#7456) When a supporting diagnostic had an empty message, we were accidentally giving the corresponding block a height of zero lines. Release Notes: - Fixed an issue where an editors' lines were not laid out correctly when showing certain diagnostics. Co-authored-by: Marshall --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad4ebd2655e63ffb27312fad02eccac87ac697c1..4cbd8965df7bccaeeab654ac797f844ef214c0c3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7907,7 +7907,7 @@ impl Editor { .insert_blocks( diagnostic_group.iter().map(|entry| { let diagnostic = entry.diagnostic.clone(); - let message_height = diagnostic.message.lines().count() as u8; + let message_height = diagnostic.message.matches('\n').count() as u8 + 1; BlockProperties { style: BlockStyle::Fixed, position: buffer.anchor_after(entry.range.start), From 1264e36429fcc715d1778341c775388dcab7e8a0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 6 Feb 2024 12:45:15 -0800 Subject: [PATCH 084/101] Remove Default impl for ConnectionId (#7452) We noticed the following message in my logs when trying to debug some lag when collaborating: ``` 2024-02-06T09:42:09-08:00 [ERROR] error handling message. client_id:3, sender_id:Some(PeerId { owner_id: 327, id: 1123430 }), type:GetCompletions, error:no such connection: 0/0 ``` That `0/0` looks like a bogus connection id, constructed via a derived `Default`. We didn't ever find a code path that would *use* a default `ConnectionId` and lead to this error, but it did seem like an improvement to not have a `Default` for that type. Release Notes: - N/A Co-authored-by: Marshall --- crates/collab/src/db.rs | 2 +- crates/collab/src/db/queries/projects.rs | 2 +- crates/collab/src/db/queries/rooms.rs | 4 ++-- crates/collab/src/rpc.rs | 2 +- crates/rpc/src/peer.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 2e8540641bc4b5a5f5a3a08c1c7cbd6b7efe57b8..6f6a95ebc2596b99041a6638e2027a7ec2cb5c13 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -692,7 +692,7 @@ impl ProjectCollaborator { pub struct LeftProject { pub id: ProjectId, pub host_user_id: UserId, - pub host_connection_id: ConnectionId, + pub host_connection_id: Option, pub connection_ids: Vec, } diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index f81403a796b0e2402eef5c46124c8b51bc68d2b5..3fdb94b34324d9d4ea5a03c8561bb22b34da0e5d 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -778,7 +778,7 @@ impl Database { let left_project = LeftProject { id: project_id, host_user_id: project.host_user_id, - host_connection_id: project.host_connection()?, + host_connection_id: Some(project.host_connection()?), connection_ids, }; Ok((room, left_project)) diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index c6aa5da125d5c74c0a86ea6b4d7cbed8fc1c07a0..f8afbeab38130cf75eca4c856d120cbf61be460c 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -862,7 +862,7 @@ impl Database { id: collaborator.project_id, host_user_id: Default::default(), connection_ids: Default::default(), - host_connection_id: Default::default(), + host_connection_id: None, }); let collaborator_connection_id = collaborator.connection(); @@ -872,7 +872,7 @@ impl Database { if collaborator.is_host { left_project.host_user_id = collaborator.user_id; - left_project.host_connection_id = collaborator_connection_id; + left_project.host_connection_id = Some(collaborator_connection_id); } } drop(collaborators); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 2861449db0bb6d422cc66fa23fcd0de9fb6395a6..e37681cd324c5b49324d1087e486035e594d7570 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1691,7 +1691,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result tracing::info!( %project_id, host_user_id = %project.host_user_id, - host_connection_id = %project.host_connection_id, + host_connection_id = ?project.host_connection_id, "leave project" ); diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 9d789bd3d01aef7a08dd9cabe7b29e9d86fa1be8..f3f74899b9d767eaa76ba283e7d7f7c9b2199681 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -25,7 +25,7 @@ use std::{ }; use tracing::instrument; -#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)] pub struct ConnectionId { pub owner_id: u32, pub id: u32, From 90cd3b5e874907782756b7f12b30ae0f7e1697de Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 6 Feb 2024 20:25:02 -0700 Subject: [PATCH 085/101] Prevent terminal being a single column wide (#7471) Fixes: #2750 Fixes: #7457 Release Notes: - Fixed a hang/panic that could happen rendering a double-width character in a single-width terminal ([#2750](https://github.com/zed-industries/zed/issues/2750), [#7457](https://github.com/zed-industries/zed/issues/7457)). --- crates/terminal_view/src/terminal_element.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 66ef42bc46ebb09244ff4ae45c7cb26bbd6b3ead..7ddb5ad988c6847dc91d0f75bfff29e692ab6033 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -450,6 +450,13 @@ impl TerminalElement { let mut size = bounds.size.clone(); size.width -= gutter; + // https://github.com/zed-industries/zed/issues/2750 + // if the terminal is one column wide, rendering 🦀 + // causes alacritty to misbehave. + if size.width < cell_width * 2.0 { + size.width = cell_width * 2.0; + } + TerminalSize::new(line_height, cell_width, size) }; From 3aa4e0c90b82db0a9cf542e56c2efae22500d8fc Mon Sep 17 00:00:00 2001 From: Andrew Marek Date: Tue, 6 Feb 2024 19:25:56 -0800 Subject: [PATCH 086/101] Fix Vim 'e' Behavior When Boundary Is Last Point on Line (#7424) This was originally just to fix https://github.com/zed-industries/zed/issues/4354, which I did by just returning the previous offset in `find_boundary`.. but `find_boundary` is used in the "insert mode" / normal editor too, so returning the previous boundary breaks existing functionality in that case. I was considering a new `find_boundary` function just for some of the vim motions like this, but I thought that this is straightforward enough and future Vim functions might need similar logic too. Release Notes: - Fixed https://github.com/zed-industries/zed/issues/4354 --- crates/editor/src/movement.rs | 35 ++++++++++++++++--- crates/vim/src/motion.rs | 30 ++++++++-------- .../test_next_word_end_newline_last_char.json | 3 ++ 3 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 crates/vim/test_data/test_next_word_end_newline_last_char.json diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 4aacc7e4e7e3500926e73172022aff8fa116cc6d..81c6c8c21fc844fd5382222479a0ff22d667f858 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -395,14 +395,17 @@ pub fn find_preceding_boundary( /// Scans for a boundary following the given start point until a boundary is found, indicated by the /// given predicate returning true. The predicate is called with the character to the left and right /// of the candidate boundary location, and will be called with `\n` characters indicating the start -/// or end of a line. -pub fn find_boundary( +/// or end of a line. The function supports optionally returning the point just before the boundary +/// is found via return_point_before_boundary. +pub fn find_boundary_point( map: &DisplaySnapshot, from: DisplayPoint, find_range: FindRange, mut is_boundary: impl FnMut(char, char) -> bool, + return_point_before_boundary: bool, ) -> DisplayPoint { let mut offset = from.to_offset(&map, Bias::Right); + let mut prev_offset = offset; let mut prev_ch = None; for ch in map.buffer_snapshot.chars_at(offset) { @@ -411,16 +414,38 @@ pub fn find_boundary( } if let Some(prev_ch) = prev_ch { if is_boundary(prev_ch, ch) { - break; + if return_point_before_boundary { + return map.clip_point(prev_offset.to_display_point(map), Bias::Right); + } else { + break; + } } } - + prev_offset = offset; offset += ch.len_utf8(); prev_ch = Some(ch); } map.clip_point(offset.to_display_point(map), Bias::Right) } +pub fn find_boundary( + map: &DisplaySnapshot, + from: DisplayPoint, + find_range: FindRange, + is_boundary: impl FnMut(char, char) -> bool, +) -> DisplayPoint { + return find_boundary_point(map, from, find_range, is_boundary, false); +} + +pub fn find_boundary_exclusive( + map: &DisplaySnapshot, + from: DisplayPoint, + find_range: FindRange, + is_boundary: impl FnMut(char, char) -> bool, +) -> DisplayPoint { + return find_boundary_point(map, from, find_range, is_boundary, true); +} + /// Returns an iterator over the characters following a given offset in the [`DisplaySnapshot`]. /// The returned value also contains a range of the start/end of a returned character in /// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer. @@ -763,7 +788,7 @@ mod tests { &snapshot, display_points[0], FindRange::MultiLine, - is_boundary + is_boundary, ), display_points[1] ); diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 67abd5836c9ac35c9dc52c787f4fbfc11bb318e2..99472dd1c85a3bfef17c1422deff3f8416911b2f 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -798,23 +798,14 @@ fn next_word_end( *point.row_mut() += 1; *point.column_mut() = 0; } - point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| { - let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); - let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); - left_kind != right_kind && left_kind != CharKind::Whitespace - }); + point = + movement::find_boundary_exclusive(map, point, FindRange::MultiLine, |left, right| { + let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); + let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); - // find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know - // we have backtracked already - if !map - .chars_at(point) - .nth(1) - .map(|(c, _)| c == '\n') - .unwrap_or(true) - { - *point.column_mut() = point.column().saturating_sub(1); - } + left_kind != right_kind && left_kind != CharKind::Whitespace + }); point = map.clip_point(point, Bias::Left); } point @@ -1285,6 +1276,15 @@ mod test { cx.assert_shared_state("one two thˇree four").await; } + #[gpui::test] + async fn test_next_word_end_newline_last_char(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + let initial_state = indoc! {r"something(ˇfoo)"}; + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes(["}"]).await; + cx.assert_shared_state(indoc! {r"something(fooˇ)"}).await; + } + #[gpui::test] async fn test_next_line_start(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/test_data/test_next_word_end_newline_last_char.json b/crates/vim/test_data/test_next_word_end_newline_last_char.json new file mode 100644 index 0000000000000000000000000000000000000000..9dac2979f5d00b096d5cedd479590589be883db9 --- /dev/null +++ b/crates/vim/test_data/test_next_word_end_newline_last_char.json @@ -0,0 +1,3 @@ +{"Put":{"state":"something(ˇfoo)"}} +{"Key":"}"} +{"Get":{"state":"something(fooˇ)","mode":"Normal"}} From 9fd221271a1f72a7dd6b37a6fae29ba7b035276f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 6 Feb 2024 20:58:38 -0700 Subject: [PATCH 087/101] Go back to an alacritty release (#7474) Release Notes: - N/A --- Cargo.lock | 5 +++-- crates/terminal/Cargo.toml | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef68faac45b47479046f1894e365670a0e4fcb2b..78a47cc60464dc3d83e1dd9c185b751cd3c1223f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,8 +103,9 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "0.20.1-dev" -source = "git+https://github.com/alacritty/alacritty?rev=2d2b894c3b869fadc78fce9d72cb5c8d2b764cac#2d2b894c3b869fadc78fce9d72cb5c8d2b764cac" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ceabf6fc76511f616ca216b51398a2511f19ba9f71bcbd977999edff1b0d1" dependencies = [ "base64 0.21.4", "bitflags 2.4.1", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index d4eefd861f3b067c6ca7cb41861d63734ed7aa01..7f12fc6eb75f25c490e392ff43818ec9f213dd22 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -11,8 +11,7 @@ doctest = false [dependencies] -# needed for "a few weeks" until alacritty 0.13.2 is out -alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "2d2b894c3b869fadc78fce9d72cb5c8d2b764cac" } +alacritty_terminal = "0.22.0" anyhow.workspace = true db.workspace = true dirs = "4.0.0" From 7939673a7de935a3a58de1ef85dc9dd26a30d1aa Mon Sep 17 00:00:00 2001 From: James Gee <1285296+geemanjs@users.noreply.github.com> Date: Wed, 7 Feb 2024 07:12:34 +0000 Subject: [PATCH 088/101] Jetbrains keymap - Movement between panes (#7464) Release Notes: - Improved Jetbrains keybindings to include cmd+alt+left/right to go back and forwards between panes rather than the default previous / next pane Signed-off-by: James Gee <1285296+geemanjs@users.noreply.github.com> --- assets/keymaps/jetbrains.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index d2dcbddbc498ed857b3d409051000db2bdd89bf6..5fb9bf7e3228c265e1de17cb247c17e07eb5a3b1 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -81,6 +81,13 @@ "cmd-6": "diagnostics::Deploy" } }, + { + "context": "Pane", + "bindings": { + "cmd-alt-left": "pane::GoBack", + "cmd-alt-right": "pane::GoForward" + } + }, { "context": "ProjectPanel", "bindings": { From eb236302c2cd4d50b7e54fdbbb00a1298f8056ca Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 7 Feb 2024 09:45:37 +0200 Subject: [PATCH 089/101] Use Fx* variants of HashMap and HashSet everywhere in Zed (#7481) Release Notes: - N/A --- Cargo.lock | 4 ++++ crates/channel/src/channel_chat.rs | 2 +- crates/client/src/client.rs | 2 +- crates/gpui/src/app.rs | 26 ++++++++++----------- crates/gpui/src/key_dispatch.rs | 14 +++++------ crates/gpui/src/keymap.rs | 7 ++---- crates/gpui/src/platform/mac/metal_atlas.rs | 4 ++-- crates/gpui/src/scene.rs | 4 ++-- crates/gpui/src/taffy.rs | 22 ++++++++--------- crates/gpui/src/text_system.rs | 12 +++++----- crates/gpui/src/text_system/line_layout.rs | 12 +++++----- crates/gpui/src/window.rs | 6 ++--- crates/gpui/src/window/element_cx.rs | 22 ++++++++--------- crates/project_panel/src/project_panel.rs | 18 ++++---------- crates/search/src/project_search.rs | 4 ++-- crates/semantic_index/src/parsing.rs | 4 ++-- crates/sqlez/Cargo.toml | 1 + crates/sqlez/src/thread_safe_connection.rs | 3 ++- crates/terminal/Cargo.toml | 1 + crates/terminal/src/terminal.rs | 2 +- crates/terminal/src/terminal_settings.rs | 3 ++- crates/theme/Cargo.toml | 7 ++---- crates/theme/src/registry.rs | 4 ++-- crates/util/Cargo.toml | 1 + crates/util/src/test/marked_text.rs | 3 ++- crates/zed/src/open_listener.rs | 4 ++-- crates/zed/src/zed.rs | 6 ++--- 27 files changed, 96 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78a47cc60464dc3d83e1dd9c185b751cd3c1223f..8e72a590decd0a8a7708e1952b23de1069fde1d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7530,6 +7530,7 @@ name = "sqlez" version = "0.1.0" dependencies = [ "anyhow", + "collections", "futures 0.3.28", "indoc", "lazy_static", @@ -8105,6 +8106,7 @@ version = "0.1.0" dependencies = [ "alacritty_terminal", "anyhow", + "collections", "db", "dirs 4.0.0", "futures 0.3.28", @@ -8200,6 +8202,7 @@ name = "theme" version = "0.1.0" dependencies = [ "anyhow", + "collections", "color", "derive_more", "fs", @@ -9343,6 +9346,7 @@ version = "0.1.0" dependencies = [ "anyhow", "backtrace", + "collections", "dirs 3.0.2", "futures 0.3.28", "git2", diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index ad843470be35cc2f123d9feaace647cd80c57265..e6ed013adee691627826d6d27c6f944609c33f51 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -5,13 +5,13 @@ use client::{ user::{User, UserStore}, Client, Subscription, TypedEnvelope, UserId, }; +use collections::HashSet; use futures::lock::Mutex; use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use rand::prelude::*; use std::{ - collections::HashSet, ops::{ControlFlow, Range}, sync::Arc, }; diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index ff8adc96607b854b2345a5b2b9fa25f8088d6bec..e454d3ddaf32302413a7fcbedece657a7fd5de42 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -10,6 +10,7 @@ use async_tungstenite::tungstenite::{ error::Error as WebsocketError, http::{Request, StatusCode}, }; +use collections::HashMap; use futures::{ channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt, @@ -29,7 +30,6 @@ use serde_json; use settings::{Settings, SettingsStore}; use std::{ any::TypeId, - collections::HashMap, convert::TryFrom, fmt::Write as _, future::Future, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 45c26b0ba81029b2490274cc4cd491ad522b946f..ec4bf83d54e3ef5b548c6a223fb2818368f93b18 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -23,7 +23,7 @@ use crate::{ TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; -use collections::{FxHashMap, FxHashSet, VecDeque}; +use collections::{HashMap, HashSet, VecDeque}; use futures::{channel::oneshot, future::LocalBoxFuture, Future}; use slotmap::SlotMap; @@ -212,24 +212,24 @@ pub struct AppContext { pending_updates: usize, pub(crate) actions: Rc, pub(crate) active_drag: Option, - pub(crate) next_frame_callbacks: FxHashMap>, - pub(crate) frame_consumers: FxHashMap>, + pub(crate) next_frame_callbacks: HashMap>, + pub(crate) frame_consumers: HashMap>, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, pub(crate) svg_renderer: SvgRenderer, asset_source: Arc, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, - pub(crate) globals_by_type: FxHashMap>, + pub(crate) globals_by_type: HashMap>, pub(crate) entities: EntityMap, pub(crate) new_view_observers: SubscriberSet, pub(crate) windows: SlotMap>, pub(crate) keymap: Rc>, pub(crate) global_action_listeners: - FxHashMap>>, + HashMap>>, pending_effects: VecDeque, - pub(crate) pending_notifications: FxHashSet, - pub(crate) pending_global_notifications: FxHashSet, + pub(crate) pending_notifications: HashSet, + pub(crate) pending_global_notifications: HashSet, pub(crate) observers: SubscriberSet, // TypeId is the type of the event that the listener callback expects pub(crate) event_listeners: SubscriberSet, @@ -274,23 +274,23 @@ impl AppContext { flushing_effects: false, pending_updates: 0, active_drag: None, - next_frame_callbacks: FxHashMap::default(), - frame_consumers: FxHashMap::default(), + next_frame_callbacks: HashMap::default(), + frame_consumers: HashMap::default(), background_executor: executor, foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), asset_source, image_cache: ImageCache::new(http_client), text_style_stack: Vec::new(), - globals_by_type: FxHashMap::default(), + globals_by_type: HashMap::default(), entities, new_view_observers: SubscriberSet::new(), windows: SlotMap::with_key(), keymap: Rc::new(RefCell::new(Keymap::default())), - global_action_listeners: FxHashMap::default(), + global_action_listeners: HashMap::default(), pending_effects: VecDeque::new(), - pending_notifications: FxHashSet::default(), - pending_global_notifications: FxHashSet::default(), + pending_notifications: HashSet::default(), + pending_global_notifications: HashSet::default(), observers: SubscriberSet::new(), event_listeners: SubscriberSet::new(), release_listeners: SubscriberSet::new(), diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 8f9eb16a62584d31465256b5e397f29a90079e8d..894f3b7d84cc948b4853ddff982baf0b26e857d6 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -53,7 +53,7 @@ use crate::{ Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding, KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext, }; -use collections::FxHashMap; +use collections::HashMap; use smallvec::{smallvec, SmallVec}; use std::{ any::{Any, TypeId}, @@ -69,9 +69,9 @@ pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, nodes: Vec, - focusable_node_ids: FxHashMap, - view_node_ids: FxHashMap, - keystroke_matchers: FxHashMap, KeystrokeMatcher>, + focusable_node_ids: HashMap, + view_node_ids: HashMap, + keystroke_matchers: HashMap, KeystrokeMatcher>, keymap: Rc>, action_registry: Rc, } @@ -100,9 +100,9 @@ impl DispatchTree { node_stack: Vec::new(), context_stack: Vec::new(), nodes: Vec::new(), - focusable_node_ids: FxHashMap::default(), - view_node_ids: FxHashMap::default(), - keystroke_matchers: FxHashMap::default(), + focusable_node_ids: HashMap::default(), + view_node_ids: HashMap::default(), + keystroke_matchers: HashMap::default(), keymap, action_registry, } diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 45e0ebbe951fe4a260cf869787e24322a47b1bd0..e2a6bdfc4b59f428e88855a830e1405d3668767e 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -7,12 +7,9 @@ pub use context::*; pub(crate) use matcher::*; use crate::{Action, Keystroke, NoAction}; -use collections::HashSet; +use collections::{HashMap, HashSet}; use smallvec::SmallVec; -use std::{ - any::{Any, TypeId}, - collections::HashMap, -}; +use std::any::{Any, TypeId}; /// An opaque identifier of which version of the keymap is currently active. /// The keymap's version is changed whenever bindings are added or removed. diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 95f78a446539de117938113f010201afd62cbb71..c029ec9be75e156efb0cd99c6e6d6c89d03bb422 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -3,7 +3,7 @@ use crate::{ Point, Size, }; use anyhow::Result; -use collections::FxHashMap; +use collections::HashMap; use derive_more::{Deref, DerefMut}; use etagere::BucketedAtlasAllocator; use metal::Device; @@ -53,7 +53,7 @@ struct MetalAtlasState { monochrome_textures: Vec, polychrome_textures: Vec, path_textures: Vec, - tiles_by_key: FxHashMap, + tiles_by_key: HashMap, } impl PlatformAtlas for MetalAtlas { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index d5d717e278f3a9d0eeb01488091b491e9efe1bca..dfd51c7bc74f86279cda6c69a1bf13cb5bf55f49 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -2,7 +2,7 @@ use crate::{ point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, Point, ScaledPixels, StackingOrder, }; -use collections::{BTreeMap, FxHashSet}; +use collections::{BTreeMap, HashSet}; use std::{fmt::Debug, iter::Peekable, slice}; // Exported to metal @@ -159,7 +159,7 @@ impl Scene { layer_id } - pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { + pub fn reuse_views(&mut self, views: &HashSet, prev_scene: &mut Self) { for shadow in prev_scene.shadows.drain(..) { if views.contains(&shadow.view_id.into()) { let order = &prev_scene.orders_by_layer[&shadow.layer_id]; diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 0797c8f3b497b561897ee68fd3216499824b59db..3536d3fa59693568aa62af9dd9084b02d2585af4 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -2,7 +2,7 @@ use crate::{ AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, WindowContext, }; -use collections::{FxHashMap, FxHashSet}; +use collections::{HashMap, HashSet}; use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ @@ -17,11 +17,11 @@ type NodeMeasureFn = pub struct TaffyLayoutEngine { taffy: Taffy, - styles: FxHashMap, - children_to_parents: FxHashMap, - absolute_layout_bounds: FxHashMap>, - computed_layouts: FxHashSet, - nodes_to_measure: FxHashMap, + styles: HashMap, + children_to_parents: HashMap, + absolute_layout_bounds: HashMap>, + computed_layouts: HashSet, + nodes_to_measure: HashMap, } static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible"; @@ -30,11 +30,11 @@ impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { taffy: Taffy::new(), - styles: FxHashMap::default(), - children_to_parents: FxHashMap::default(), - absolute_layout_bounds: FxHashMap::default(), - computed_layouts: FxHashSet::default(), - nodes_to_measure: FxHashMap::default(), + styles: HashMap::default(), + children_to_parents: HashMap::default(), + absolute_layout_bounds: HashMap::default(), + computed_layouts: HashSet::default(), + nodes_to_measure: HashMap::default(), } } diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 12242e26c245be91167c3f34242e9e7a8b3d01e5..75d98a830513ea000cfa61e7ed7a73f0477f4c45 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -13,7 +13,7 @@ use crate::{ SharedString, Size, UnderlineStyle, }; use anyhow::anyhow; -use collections::{BTreeSet, FxHashMap, FxHashSet}; +use collections::{BTreeSet, HashMap, HashSet}; use core::fmt; use derive_more::Deref; use itertools::Itertools; @@ -42,10 +42,10 @@ pub(crate) const SUBPIXEL_VARIANTS: u8 = 4; /// The GPUI text rendering sub system. pub struct TextSystem { platform_text_system: Arc, - font_ids_by_font: RwLock>>, - font_metrics: RwLock>, - raster_bounds: RwLock>>, - wrapper_pool: Mutex>>, + font_ids_by_font: RwLock>>, + font_metrics: RwLock>, + raster_bounds: RwLock>>, + wrapper_pool: Mutex>>, font_runs_pool: Mutex>>, fallback_font_stack: SmallVec<[Font; 2]>, } @@ -447,7 +447,7 @@ impl WindowTextSystem { Ok(lines) } - pub(crate) fn finish_frame(&self, reused_views: &FxHashSet) { + pub(crate) fn finish_frame(&self, reused_views: &HashSet) { self.line_layout_cache.finish_frame(reused_views) } diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 014c2bb1ec26c141e4c05beeb5cb61005c2ecbd7..144f855c56f56a75feb10e4dda1aac62e287bcaa 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -1,5 +1,5 @@ use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size}; -use collections::{FxHashMap, FxHashSet}; +use collections::{HashMap, HashSet}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -277,10 +277,10 @@ impl WrappedLineLayout { pub(crate) struct LineLayoutCache { view_stack: Mutex>, - previous_frame: Mutex>>, - current_frame: RwLock>>, - previous_frame_wrapped: Mutex>>, - current_frame_wrapped: RwLock>>, + previous_frame: Mutex>>, + current_frame: RwLock>>, + previous_frame_wrapped: Mutex>>, + current_frame_wrapped: RwLock>>, platform_text_system: Arc, } @@ -296,7 +296,7 @@ impl LineLayoutCache { } } - pub fn finish_frame(&self, reused_views: &FxHashSet) { + pub fn finish_frame(&self, reused_views: &HashSet) { debug_assert_eq!(self.view_stack.lock().len(), 0); let mut prev_frame = self.previous_frame.lock(); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 4b7c43e113672b9bb2d9ff431042ac0d7ea88776..429ea0dbe2391741c0053c307b76c8e0e7c6c8f5 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -10,7 +10,7 @@ use crate::{ WindowOptions, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; -use collections::FxHashSet; +use collections::HashSet; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -259,7 +259,7 @@ pub struct Window { pub(crate) element_id_stack: GlobalElementId, pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, - pub(crate) dirty_views: FxHashSet, + pub(crate) dirty_views: HashSet, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, focus_lost_listeners: SubscriberSet<(), AnyObserver>, @@ -428,7 +428,7 @@ impl Window { element_id_stack: GlobalElementId::default(), rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), - dirty_views: FxHashSet::default(), + dirty_views: HashSet::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), focus_lost_listeners: SubscriberSet::new(), diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 8ba3fc5c4fc94c27629d18efee3c45e023b4a8a1..a1e96af5fff759a53ea745a6d58ac42f3438f38d 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -21,7 +21,7 @@ use std::{ }; use anyhow::Result; -use collections::{FxHashMap, FxHashSet}; +use collections::{HashMap, HashSet}; use derive_more::{Deref, DerefMut}; use media::core_video::CVImageBuffer; use smallvec::SmallVec; @@ -53,8 +53,8 @@ pub(crate) struct TooltipRequest { pub(crate) struct Frame { pub(crate) focus: Option, pub(crate) window_active: bool, - pub(crate) element_states: FxHashMap, - pub(crate) mouse_listeners: FxHashMap>, + pub(crate) element_states: HashMap, + pub(crate) mouse_listeners: HashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene: Scene, pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, @@ -65,13 +65,13 @@ pub(crate) struct Frame { pub(crate) element_offset_stack: Vec>, pub(crate) requested_input_handler: Option, pub(crate) tooltip_request: Option, - pub(crate) cursor_styles: FxHashMap, + pub(crate) cursor_styles: HashMap, pub(crate) requested_cursor_style: Option, pub(crate) view_stack: Vec, - pub(crate) reused_views: FxHashSet, + pub(crate) reused_views: HashSet, #[cfg(any(test, feature = "test-support"))] - pub(crate) debug_bounds: collections::FxHashMap>, + pub(crate) debug_bounds: collections::HashMap>, } impl Frame { @@ -79,8 +79,8 @@ impl Frame { Frame { focus: None, window_active: false, - element_states: FxHashMap::default(), - mouse_listeners: FxHashMap::default(), + element_states: HashMap::default(), + mouse_listeners: HashMap::default(), dispatch_tree, scene: Scene::default(), depth_map: Vec::new(), @@ -91,13 +91,13 @@ impl Frame { element_offset_stack: Vec::new(), requested_input_handler: None, tooltip_request: None, - cursor_styles: FxHashMap::default(), + cursor_styles: HashMap::default(), requested_cursor_style: None, view_stack: Vec::new(), - reused_views: FxHashSet::default(), + reused_views: HashSet::default(), #[cfg(any(test, feature = "test-support"))] - debug_bounds: FxHashMap::default(), + debug_bounds: HashMap::default(), } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b43b5d84131e5d1f110224d50ed62b8df4db6969..3744a36c00f61e0c14442a0075c4784c64dff820 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -8,6 +8,7 @@ use editor::{actions::Cancel, scroll::Autoscroll, Editor}; use file_associations::FileAssociations; use anyhow::{anyhow, Result}; +use collections::{hash_map, HashMap}; use gpui::{ actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement, @@ -22,14 +23,7 @@ use project::{ }; use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings}; use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - collections::{hash_map, HashMap}, - ffi::OsStr, - ops::Range, - path::Path, - sync::Arc, -}; +use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc}; use theme::ThemeSettings; use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; @@ -1699,15 +1693,13 @@ impl ClipboardEntry { #[cfg(test)] mod tests { use super::*; + use collections::HashSet; use gpui::{TestAppContext, View, VisualTestContext, WindowHandle}; use pretty_assertions::assert_eq; use project::{project_settings::ProjectSettings, FakeFs}; use serde_json::json; use settings::SettingsStore; - use std::{ - collections::HashSet, - path::{Path, PathBuf}, - }; + use std::path::{Path, PathBuf}; use workspace::AppState; #[gpui::test] @@ -3509,7 +3501,7 @@ mod tests { cx: &mut VisualTestContext, ) -> Vec { let mut result = Vec::new(); - let mut project_entries = HashSet::new(); + let mut project_entries = HashSet::default(); let mut has_editor = false; panel.update(cx, |panel, cx| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 89f94ad6c431598f2e3b3fdeb8b6a044b839529c..c4502939451bd35dcacbe6ccd0bc6e5c4001b237 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -25,11 +25,11 @@ use project::{ }; use semantic_index::{SemanticIndex, SemanticIndexStatus}; +use collections::HashSet; use settings::Settings; use smol::stream::StreamExt; use std::{ any::{Any, TypeId}, - collections::HashSet, mem, ops::{Not, Range}, path::PathBuf, @@ -955,7 +955,7 @@ impl ProjectSearchView { semantic_state: None, semantic_permissioned: None, search_options: options, - panels_with_errors: HashSet::new(), + panels_with_errors: HashSet::default(), active_match_index: None, query_editor_was_focused: false, included_files_editor, diff --git a/crates/semantic_index/src/parsing.rs b/crates/semantic_index/src/parsing.rs index 9f2db711ae0e97d5f7f21af7afd12f09de147a18..e6f4a37d10daab0d420eabc1f234ea1fa986ac74 100644 --- a/crates/semantic_index/src/parsing.rs +++ b/crates/semantic_index/src/parsing.rs @@ -3,6 +3,7 @@ use ai::{ models::TruncationDirection, }; use anyhow::{anyhow, Result}; +use collections::HashSet; use language::{Grammar, Language}; use rusqlite::{ types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef}, @@ -12,7 +13,6 @@ use sha1::{Digest, Sha1}; use std::{ borrow::Cow, cmp::{self, Reverse}, - collections::HashSet, ops::Range, path::Path, sync::Arc, @@ -267,7 +267,7 @@ impl CodeContextRetriever { let mut spans = Vec::new(); let mut collapsed_ranges_within = Vec::new(); - let mut parsed_name_ranges = HashSet::new(); + let mut parsed_name_ranges = HashSet::default(); for (i, context_match) in matches.iter().enumerate() { // Items which are collapsible but not embeddable have no item range let item_range = if let Some(item_range) = context_match.item_range.clone() { diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 2ee5e6cbf39468cea60fcd108daae7b95fa093da..71c67af0950e8ef940ce55e3410493a7bb1ec311 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -7,6 +7,7 @@ license = "GPL-3.0-or-later" [dependencies] anyhow.workspace = true +collections.workspace = true futures.workspace = true indoc.workspace = true lazy_static.workspace = true diff --git a/crates/sqlez/src/thread_safe_connection.rs b/crates/sqlez/src/thread_safe_connection.rs index 98402df108e35d90b67d9aba4ac7574fd6c5b9d8..cd4664c9aeca98df8f41123f06746b4f7c72bc24 100644 --- a/crates/sqlez/src/thread_safe_connection.rs +++ b/crates/sqlez/src/thread_safe_connection.rs @@ -1,8 +1,9 @@ use anyhow::Context; +use collections::HashMap; use futures::{channel::oneshot, Future, FutureExt}; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; -use std::{collections::HashMap, marker::PhantomData, ops::Deref, sync::Arc, thread}; +use std::{marker::PhantomData, ops::Deref, sync::Arc, thread}; use thread_local::ThreadLocal; use crate::{connection::Connection, domain::Migrator, util::UnboundedSyncSender}; diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 7f12fc6eb75f25c490e392ff43818ec9f213dd22..a52a7c6f939d88f8479d0be09f166067c71d5af7 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -13,6 +13,7 @@ doctest = false [dependencies] alacritty_terminal = "0.22.0" anyhow.workspace = true +collections.workspace = true db.workspace = true dirs = "4.0.0" futures.workspace = true diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ad9587a3267732d66295b667f4a63e4437fc9915..46c7cb0c191cb5e18aaa5bf736c675e251fe8288 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -30,6 +30,7 @@ use mappings::mouse::{ scroll_report, }; +use collections::{HashMap, VecDeque}; use procinfo::LocalProcessInfo; use serde::{Deserialize, Serialize}; use settings::Settings; @@ -39,7 +40,6 @@ use util::truncate_and_trailoff; use std::{ cmp::{self, min}, - collections::{HashMap, VecDeque}, fmt::Display, ops::{Deref, Index, RangeInclusive}, os::unix::prelude::AsRawFd, diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 7b3d97145c0c58b2f4eceaa64f87b9f6010d9f8c..1a072ca8bce9ab9ed839d7ea5c361722d588730a 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -1,3 +1,4 @@ +use collections::HashMap; use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels}; use schemars::{ gen::SchemaGenerator, @@ -7,7 +8,7 @@ use schemars::{ use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use settings::SettingsJsonSchemaParams; -use std::{collections::HashMap, path::PathBuf}; +use std::path::PathBuf; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 8970cfbacf70d4e44e063ef60431b9b0cfff6677..c060627ad61a382aca3807c7fa47f4ad59e1132a 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -8,11 +8,7 @@ license = "GPL-3.0-or-later" [features] default = [] stories = ["dep:itertools", "dep:story"] -test-support = [ - "gpui/test-support", - "fs/test-support", - "settings/test-support" -] +test-support = ["gpui/test-support", "fs/test-support", "settings/test-support"] [lib] path = "src/theme.rs" @@ -20,6 +16,7 @@ doctest = false [dependencies] anyhow.workspace = true +collections.workspace = true color.workspace = true derive_more.workspace = true fs.workspace = true diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index fac119ff1d0b61a6b70a652c0ca0c0c8b00790aa..2cc4d902a2d010777edad12e281f46332a9fcfcb 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; use std::path::Path; use std::sync::Arc; use anyhow::{anyhow, Context, Result}; +use collections::HashMap; use derive_more::{Deref, DerefMut}; use fs::Fs; use futures::StreamExt; @@ -64,7 +64,7 @@ impl ThemeRegistry { pub fn new(assets: Box) -> Self { let registry = Self { state: RwLock::new(ThemeRegistryState { - themes: HashMap::new(), + themes: HashMap::default(), }), assets, }; diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index c510d6f557779f1dde4ceaeaa6a26b177665abfe..fc06ebeb4ae4729f6e6446b92047344bc7f23d1c 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -15,6 +15,7 @@ test-support = ["tempfile", "git2"] [dependencies] anyhow.workspace = true backtrace = "0.3" +collections.workspace = true dirs = "3.0" futures.workspace = true git2 = { workspace = true, optional = true } diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index dadf78622deab3d13b6ca20d8ea16bac17356578..cf85a806e4db085f45f9855c2d8ca682bfaf8ce9 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -1,4 +1,5 @@ -use std::{cmp::Ordering, collections::HashMap, ops::Range}; +use collections::HashMap; +use std::{cmp::Ordering, ops::Range}; /// Construct a string and a list of offsets within that string using a single /// string containing embedded position markers. diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index 012b5a2413e874397a24dcda1e6181a1b3757fcc..d3ccf753d1af55269aad1286dbf0af382e255966 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use collections::HashMap; use editor::scroll::Autoscroll; use editor::Editor; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -10,7 +11,6 @@ use gpui::{AppContext, AsyncAppContext, Global}; use itertools::Itertools; use language::{Bias, Point}; use release_channel::parse_zed_link; -use std::collections::HashMap; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use std::path::Path; @@ -176,7 +176,7 @@ pub async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::new(); + let mut caret_positions = HashMap::default(); let paths = if paths.is_empty() { workspace::last_opened_workspace_paths() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 443adac50d254a4014561ae666b701d8d7faf286..b4ec47111368a820b3e76cc700c484c8ebc129b5 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -733,6 +733,7 @@ fn open_settings_file( mod tests { use super::*; use assets::Assets; + use collections::HashSet; use editor::{scroll::Autoscroll, DisplayPoint, Editor}; use gpui::{ actions, Action, AnyWindowHandle, AppContext, AssetSource, Entity, TestAppContext, @@ -742,10 +743,7 @@ mod tests { use project::{project_settings::ProjectSettings, Project, ProjectPath}; use serde_json::json; use settings::{handle_settings_file_changes, watch_config_file, SettingsStore}; - use std::{ - collections::HashSet, - path::{Path, PathBuf}, - }; + use std::path::{Path, PathBuf}; use theme::{ThemeRegistry, ThemeSettings}; use workspace::{ item::{Item, ItemHandle}, From 2aa8ccd6b16a902ac94f8c1b34fe0fe1c8ac102d Mon Sep 17 00:00:00 2001 From: Todsaporn Banjerdkit Date: Wed, 7 Feb 2024 16:13:52 +0700 Subject: [PATCH 090/101] fix: use OPEN_AI_API_URL (#7484) Release Notes: - N/A --- crates/ai/src/providers/open_ai/embedding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ai/src/providers/open_ai/embedding.rs b/crates/ai/src/providers/open_ai/embedding.rs index 4a8b051df3fd7fc4ae678d9e641e90eb2395fa62..29ee8fac9ba12e1affc21f597159730c40b5c5d4 100644 --- a/crates/ai/src/providers/open_ai/embedding.rs +++ b/crates/ai/src/providers/open_ai/embedding.rs @@ -134,7 +134,7 @@ impl OpenAiEmbeddingProvider { spans: Vec<&str>, request_timeout: u64, ) -> Result> { - let request = Request::post("https://api.openai.com/v1/embeddings") + let request = Request::post(format!("{OPEN_AI_API_URL}/embeddings")) .redirect_policy(isahc::config::RedirectPolicy::Follow) .timeout(Duration::from_secs(request_timeout)) .header("Content-Type", "application/json") From db39b9dadc4cb60bf579cd0be2ab8dabc4da7994 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 7 Feb 2024 12:50:22 +0100 Subject: [PATCH 091/101] Add ability to bind to pane::RevealInProjectPanel (#7487) Previously it wasn't possible to create a keybinding for this action because it required an argument. Now the action takes the active item of the pane and if it's a multi-buffer the first one. This also adds a default keybinding for Vim mode: `-` will reveal the file in the project panel. Fixes #7485. Release Notes: - Added `pane::RevealInProjectPanel` as an action in the command palette. ([#7485](https://github.com/zed-industries/zed/issues/7485)). Co-authored-by: Antonio --- assets/keymaps/vim.json | 3 ++- crates/editor/src/actions.rs | 2 +- crates/workspace/src/pane.rs | 22 ++++++++++++++-------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index bf68ee10e4d19a3dbe048dd8eb0f8792c35bef27..9c95b352a50c51adf89584a6f9b551828334ec53 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -284,7 +284,8 @@ "ctrl-w o": "workspace::CloseInactiveTabsAndPanes", "ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes", "ctrl-w n": ["workspace::NewFileInDirection", "Up"], - "ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"] + "ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"], + "-": "pane::RevealInProjectPanel" } }, { diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 049c152e2cf90a3e1aa3b0a4411a45a774135cd7..ec8113a985a1575fd6954958918e4c8521481b84 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -112,7 +112,7 @@ impl_actions!( MoveUpByLines, MoveDownByLines, SelectUpByLines, - SelectDownByLines, + SelectDownByLines ] ); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 55ae875fefcfd2e4a0746fd81cfba27f7106834d..55389cf8d0e2c44284cf24ec50f83520a7393292 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -72,10 +72,10 @@ pub struct CloseAllItems { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct RevealInProjectPanel { - pub entry_id: u64, + pub entry_id: Option, } impl_actions!( @@ -1442,7 +1442,9 @@ impl Pane { let entry_id = entry.to_proto(); menu = menu.separator().entry( "Reveal In Project Panel", - Some(Box::new(RevealInProjectPanel { entry_id })), + Some(Box::new(RevealInProjectPanel { + entry_id: Some(entry_id), + })), cx.handler_for(&pane, move |pane, cx| { pane.project.update(cx, |_, cx| { cx.emit(project::Event::RevealInProjectPanel( @@ -1807,11 +1809,15 @@ impl Render for Pane { ) .on_action( cx.listener(|pane: &mut Self, action: &RevealInProjectPanel, cx| { - pane.project.update(cx, |_, cx| { - cx.emit(project::Event::RevealInProjectPanel( - ProjectEntryId::from_proto(action.entry_id), - )) - }) + let entry_id = action + .entry_id + .map(ProjectEntryId::from_proto) + .or_else(|| pane.active_item()?.project_entry_ids(cx).first().copied()); + if let Some(entry_id) = entry_id { + pane.project.update(cx, |_, cx| { + cx.emit(project::Event::RevealInProjectPanel(entry_id)) + }); + } }), ) .when(self.active_item().is_some(), |pane| { From 5c8073d3444eb17254e59f6740e48256079dbf38 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 7 Feb 2024 12:52:49 +0100 Subject: [PATCH 092/101] Underline text if in dead key state (#7488) This highlights dead keys. Example: when in Brazilian keyboard layout and typing `"` it's now underlined. https://github.com/zed-industries/zed/assets/1185253/a6b65f7b-1007-473d-ab0f-5d658faa191b Release Notes: - Fixed dead keys not being underlined. Co-authored-by: Antonio --- crates/editor/src/editor.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4cbd8965df7bccaeeab654ac797f844ef214c0c3..d37ba5f3c72b662f7d0d294d97b3fdc971f26db2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -61,8 +61,8 @@ use gpui::{ DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle, - UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakView, - WhiteSpace, WindowContext, + UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, + WeakView, WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -9714,7 +9714,14 @@ impl ViewInputHandler for Editor { } else { this.highlight_text::( marked_ranges.clone(), - HighlightStyle::default(), // todo!() this.style(cx).composition_mark, + HighlightStyle { + underline: Some(UnderlineStyle { + thickness: px(1.), + color: None, + wavy: false, + }), + ..Default::default() + }, cx, ); } From 55129d4d6c1b9750bf46dcbac82d3d4083a392f0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 7 Feb 2024 13:16:22 +0100 Subject: [PATCH 093/101] Revert "Use Fx* variants of HashMap and HashSet everywhere in Zed" (#7492) Reverts zed-industries/zed#7481 This would regress performance because we'd be using the standard library's hash maps everywhere, so reverting for now. --- Cargo.lock | 4 ---- crates/channel/src/channel_chat.rs | 2 +- crates/client/src/client.rs | 2 +- crates/gpui/src/app.rs | 26 ++++++++++----------- crates/gpui/src/key_dispatch.rs | 14 +++++------ crates/gpui/src/keymap.rs | 7 ++++-- crates/gpui/src/platform/mac/metal_atlas.rs | 4 ++-- crates/gpui/src/scene.rs | 4 ++-- crates/gpui/src/taffy.rs | 22 ++++++++--------- crates/gpui/src/text_system.rs | 12 +++++----- crates/gpui/src/text_system/line_layout.rs | 12 +++++----- crates/gpui/src/window.rs | 6 ++--- crates/gpui/src/window/element_cx.rs | 22 ++++++++--------- crates/project_panel/src/project_panel.rs | 18 ++++++++++---- crates/search/src/project_search.rs | 4 ++-- crates/semantic_index/src/parsing.rs | 4 ++-- crates/sqlez/Cargo.toml | 1 - crates/sqlez/src/thread_safe_connection.rs | 3 +-- crates/terminal/Cargo.toml | 1 - crates/terminal/src/terminal.rs | 2 +- crates/terminal/src/terminal_settings.rs | 3 +-- crates/theme/Cargo.toml | 7 ++++-- crates/theme/src/registry.rs | 4 ++-- crates/util/Cargo.toml | 1 - crates/util/src/test/marked_text.rs | 3 +-- crates/zed/src/open_listener.rs | 4 ++-- crates/zed/src/zed.rs | 6 +++-- 27 files changed, 102 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e72a590decd0a8a7708e1952b23de1069fde1d2..78a47cc60464dc3d83e1dd9c185b751cd3c1223f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7530,7 +7530,6 @@ name = "sqlez" version = "0.1.0" dependencies = [ "anyhow", - "collections", "futures 0.3.28", "indoc", "lazy_static", @@ -8106,7 +8105,6 @@ version = "0.1.0" dependencies = [ "alacritty_terminal", "anyhow", - "collections", "db", "dirs 4.0.0", "futures 0.3.28", @@ -8202,7 +8200,6 @@ name = "theme" version = "0.1.0" dependencies = [ "anyhow", - "collections", "color", "derive_more", "fs", @@ -9346,7 +9343,6 @@ version = "0.1.0" dependencies = [ "anyhow", "backtrace", - "collections", "dirs 3.0.2", "futures 0.3.28", "git2", diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index e6ed013adee691627826d6d27c6f944609c33f51..ad843470be35cc2f123d9feaace647cd80c57265 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -5,13 +5,13 @@ use client::{ user::{User, UserStore}, Client, Subscription, TypedEnvelope, UserId, }; -use collections::HashSet; use futures::lock::Mutex; use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use rand::prelude::*; use std::{ + collections::HashSet, ops::{ControlFlow, Range}, sync::Arc, }; diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index e454d3ddaf32302413a7fcbedece657a7fd5de42..ff8adc96607b854b2345a5b2b9fa25f8088d6bec 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -10,7 +10,6 @@ use async_tungstenite::tungstenite::{ error::Error as WebsocketError, http::{Request, StatusCode}, }; -use collections::HashMap; use futures::{ channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt, @@ -30,6 +29,7 @@ use serde_json; use settings::{Settings, SettingsStore}; use std::{ any::TypeId, + collections::HashMap, convert::TryFrom, fmt::Write as _, future::Future, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ec4bf83d54e3ef5b548c6a223fb2818368f93b18..45c26b0ba81029b2490274cc4cd491ad522b946f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -23,7 +23,7 @@ use crate::{ TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; -use collections::{HashMap, HashSet, VecDeque}; +use collections::{FxHashMap, FxHashSet, VecDeque}; use futures::{channel::oneshot, future::LocalBoxFuture, Future}; use slotmap::SlotMap; @@ -212,24 +212,24 @@ pub struct AppContext { pending_updates: usize, pub(crate) actions: Rc, pub(crate) active_drag: Option, - pub(crate) next_frame_callbacks: HashMap>, - pub(crate) frame_consumers: HashMap>, + pub(crate) next_frame_callbacks: FxHashMap>, + pub(crate) frame_consumers: FxHashMap>, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, pub(crate) svg_renderer: SvgRenderer, asset_source: Arc, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, - pub(crate) globals_by_type: HashMap>, + pub(crate) globals_by_type: FxHashMap>, pub(crate) entities: EntityMap, pub(crate) new_view_observers: SubscriberSet, pub(crate) windows: SlotMap>, pub(crate) keymap: Rc>, pub(crate) global_action_listeners: - HashMap>>, + FxHashMap>>, pending_effects: VecDeque, - pub(crate) pending_notifications: HashSet, - pub(crate) pending_global_notifications: HashSet, + pub(crate) pending_notifications: FxHashSet, + pub(crate) pending_global_notifications: FxHashSet, pub(crate) observers: SubscriberSet, // TypeId is the type of the event that the listener callback expects pub(crate) event_listeners: SubscriberSet, @@ -274,23 +274,23 @@ impl AppContext { flushing_effects: false, pending_updates: 0, active_drag: None, - next_frame_callbacks: HashMap::default(), - frame_consumers: HashMap::default(), + next_frame_callbacks: FxHashMap::default(), + frame_consumers: FxHashMap::default(), background_executor: executor, foreground_executor, svg_renderer: SvgRenderer::new(asset_source.clone()), asset_source, image_cache: ImageCache::new(http_client), text_style_stack: Vec::new(), - globals_by_type: HashMap::default(), + globals_by_type: FxHashMap::default(), entities, new_view_observers: SubscriberSet::new(), windows: SlotMap::with_key(), keymap: Rc::new(RefCell::new(Keymap::default())), - global_action_listeners: HashMap::default(), + global_action_listeners: FxHashMap::default(), pending_effects: VecDeque::new(), - pending_notifications: HashSet::default(), - pending_global_notifications: HashSet::default(), + pending_notifications: FxHashSet::default(), + pending_global_notifications: FxHashSet::default(), observers: SubscriberSet::new(), event_listeners: SubscriberSet::new(), release_listeners: SubscriberSet::new(), diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 894f3b7d84cc948b4853ddff982baf0b26e857d6..8f9eb16a62584d31465256b5e397f29a90079e8d 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -53,7 +53,7 @@ use crate::{ Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding, KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext, }; -use collections::HashMap; +use collections::FxHashMap; use smallvec::{smallvec, SmallVec}; use std::{ any::{Any, TypeId}, @@ -69,9 +69,9 @@ pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, nodes: Vec, - focusable_node_ids: HashMap, - view_node_ids: HashMap, - keystroke_matchers: HashMap, KeystrokeMatcher>, + focusable_node_ids: FxHashMap, + view_node_ids: FxHashMap, + keystroke_matchers: FxHashMap, KeystrokeMatcher>, keymap: Rc>, action_registry: Rc, } @@ -100,9 +100,9 @@ impl DispatchTree { node_stack: Vec::new(), context_stack: Vec::new(), nodes: Vec::new(), - focusable_node_ids: HashMap::default(), - view_node_ids: HashMap::default(), - keystroke_matchers: HashMap::default(), + focusable_node_ids: FxHashMap::default(), + view_node_ids: FxHashMap::default(), + keystroke_matchers: FxHashMap::default(), keymap, action_registry, } diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index e2a6bdfc4b59f428e88855a830e1405d3668767e..45e0ebbe951fe4a260cf869787e24322a47b1bd0 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -7,9 +7,12 @@ pub use context::*; pub(crate) use matcher::*; use crate::{Action, Keystroke, NoAction}; -use collections::{HashMap, HashSet}; +use collections::HashSet; use smallvec::SmallVec; -use std::any::{Any, TypeId}; +use std::{ + any::{Any, TypeId}, + collections::HashMap, +}; /// An opaque identifier of which version of the keymap is currently active. /// The keymap's version is changed whenever bindings are added or removed. diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index c029ec9be75e156efb0cd99c6e6d6c89d03bb422..95f78a446539de117938113f010201afd62cbb71 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -3,7 +3,7 @@ use crate::{ Point, Size, }; use anyhow::Result; -use collections::HashMap; +use collections::FxHashMap; use derive_more::{Deref, DerefMut}; use etagere::BucketedAtlasAllocator; use metal::Device; @@ -53,7 +53,7 @@ struct MetalAtlasState { monochrome_textures: Vec, polychrome_textures: Vec, path_textures: Vec, - tiles_by_key: HashMap, + tiles_by_key: FxHashMap, } impl PlatformAtlas for MetalAtlas { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index dfd51c7bc74f86279cda6c69a1bf13cb5bf55f49..d5d717e278f3a9d0eeb01488091b491e9efe1bca 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -2,7 +2,7 @@ use crate::{ point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, Point, ScaledPixels, StackingOrder, }; -use collections::{BTreeMap, HashSet}; +use collections::{BTreeMap, FxHashSet}; use std::{fmt::Debug, iter::Peekable, slice}; // Exported to metal @@ -159,7 +159,7 @@ impl Scene { layer_id } - pub fn reuse_views(&mut self, views: &HashSet, prev_scene: &mut Self) { + pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { for shadow in prev_scene.shadows.drain(..) { if views.contains(&shadow.view_id.into()) { let order = &prev_scene.orders_by_layer[&shadow.layer_id]; diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 3536d3fa59693568aa62af9dd9084b02d2585af4..0797c8f3b497b561897ee68fd3216499824b59db 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -2,7 +2,7 @@ use crate::{ AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, WindowContext, }; -use collections::{HashMap, HashSet}; +use collections::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ @@ -17,11 +17,11 @@ type NodeMeasureFn = pub struct TaffyLayoutEngine { taffy: Taffy, - styles: HashMap, - children_to_parents: HashMap, - absolute_layout_bounds: HashMap>, - computed_layouts: HashSet, - nodes_to_measure: HashMap, + styles: FxHashMap, + children_to_parents: FxHashMap, + absolute_layout_bounds: FxHashMap>, + computed_layouts: FxHashSet, + nodes_to_measure: FxHashMap, } static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible"; @@ -30,11 +30,11 @@ impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { taffy: Taffy::new(), - styles: HashMap::default(), - children_to_parents: HashMap::default(), - absolute_layout_bounds: HashMap::default(), - computed_layouts: HashSet::default(), - nodes_to_measure: HashMap::default(), + styles: FxHashMap::default(), + children_to_parents: FxHashMap::default(), + absolute_layout_bounds: FxHashMap::default(), + computed_layouts: FxHashSet::default(), + nodes_to_measure: FxHashMap::default(), } } diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 75d98a830513ea000cfa61e7ed7a73f0477f4c45..12242e26c245be91167c3f34242e9e7a8b3d01e5 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -13,7 +13,7 @@ use crate::{ SharedString, Size, UnderlineStyle, }; use anyhow::anyhow; -use collections::{BTreeSet, HashMap, HashSet}; +use collections::{BTreeSet, FxHashMap, FxHashSet}; use core::fmt; use derive_more::Deref; use itertools::Itertools; @@ -42,10 +42,10 @@ pub(crate) const SUBPIXEL_VARIANTS: u8 = 4; /// The GPUI text rendering sub system. pub struct TextSystem { platform_text_system: Arc, - font_ids_by_font: RwLock>>, - font_metrics: RwLock>, - raster_bounds: RwLock>>, - wrapper_pool: Mutex>>, + font_ids_by_font: RwLock>>, + font_metrics: RwLock>, + raster_bounds: RwLock>>, + wrapper_pool: Mutex>>, font_runs_pool: Mutex>>, fallback_font_stack: SmallVec<[Font; 2]>, } @@ -447,7 +447,7 @@ impl WindowTextSystem { Ok(lines) } - pub(crate) fn finish_frame(&self, reused_views: &HashSet) { + pub(crate) fn finish_frame(&self, reused_views: &FxHashSet) { self.line_layout_cache.finish_frame(reused_views) } diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 144f855c56f56a75feb10e4dda1aac62e287bcaa..014c2bb1ec26c141e4c05beeb5cb61005c2ecbd7 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -1,5 +1,5 @@ use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size}; -use collections::{HashMap, HashSet}; +use collections::{FxHashMap, FxHashSet}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -277,10 +277,10 @@ impl WrappedLineLayout { pub(crate) struct LineLayoutCache { view_stack: Mutex>, - previous_frame: Mutex>>, - current_frame: RwLock>>, - previous_frame_wrapped: Mutex>>, - current_frame_wrapped: RwLock>>, + previous_frame: Mutex>>, + current_frame: RwLock>>, + previous_frame_wrapped: Mutex>>, + current_frame_wrapped: RwLock>>, platform_text_system: Arc, } @@ -296,7 +296,7 @@ impl LineLayoutCache { } } - pub fn finish_frame(&self, reused_views: &HashSet) { + pub fn finish_frame(&self, reused_views: &FxHashSet) { debug_assert_eq!(self.view_stack.lock().len(), 0); let mut prev_frame = self.previous_frame.lock(); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 429ea0dbe2391741c0053c307b76c8e0e7c6c8f5..4b7c43e113672b9bb2d9ff431042ac0d7ea88776 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -10,7 +10,7 @@ use crate::{ WindowOptions, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; -use collections::HashSet; +use collections::FxHashSet; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -259,7 +259,7 @@ pub struct Window { pub(crate) element_id_stack: GlobalElementId, pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, - pub(crate) dirty_views: HashSet, + pub(crate) dirty_views: FxHashSet, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, focus_lost_listeners: SubscriberSet<(), AnyObserver>, @@ -428,7 +428,7 @@ impl Window { element_id_stack: GlobalElementId::default(), rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), - dirty_views: HashSet::default(), + dirty_views: FxHashSet::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), focus_lost_listeners: SubscriberSet::new(), diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index a1e96af5fff759a53ea745a6d58ac42f3438f38d..8ba3fc5c4fc94c27629d18efee3c45e023b4a8a1 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -21,7 +21,7 @@ use std::{ }; use anyhow::Result; -use collections::{HashMap, HashSet}; +use collections::{FxHashMap, FxHashSet}; use derive_more::{Deref, DerefMut}; use media::core_video::CVImageBuffer; use smallvec::SmallVec; @@ -53,8 +53,8 @@ pub(crate) struct TooltipRequest { pub(crate) struct Frame { pub(crate) focus: Option, pub(crate) window_active: bool, - pub(crate) element_states: HashMap, - pub(crate) mouse_listeners: HashMap>, + pub(crate) element_states: FxHashMap, + pub(crate) mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene: Scene, pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, @@ -65,13 +65,13 @@ pub(crate) struct Frame { pub(crate) element_offset_stack: Vec>, pub(crate) requested_input_handler: Option, pub(crate) tooltip_request: Option, - pub(crate) cursor_styles: HashMap, + pub(crate) cursor_styles: FxHashMap, pub(crate) requested_cursor_style: Option, pub(crate) view_stack: Vec, - pub(crate) reused_views: HashSet, + pub(crate) reused_views: FxHashSet, #[cfg(any(test, feature = "test-support"))] - pub(crate) debug_bounds: collections::HashMap>, + pub(crate) debug_bounds: collections::FxHashMap>, } impl Frame { @@ -79,8 +79,8 @@ impl Frame { Frame { focus: None, window_active: false, - element_states: HashMap::default(), - mouse_listeners: HashMap::default(), + element_states: FxHashMap::default(), + mouse_listeners: FxHashMap::default(), dispatch_tree, scene: Scene::default(), depth_map: Vec::new(), @@ -91,13 +91,13 @@ impl Frame { element_offset_stack: Vec::new(), requested_input_handler: None, tooltip_request: None, - cursor_styles: HashMap::default(), + cursor_styles: FxHashMap::default(), requested_cursor_style: None, view_stack: Vec::new(), - reused_views: HashSet::default(), + reused_views: FxHashSet::default(), #[cfg(any(test, feature = "test-support"))] - debug_bounds: HashMap::default(), + debug_bounds: FxHashMap::default(), } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 3744a36c00f61e0c14442a0075c4784c64dff820..b43b5d84131e5d1f110224d50ed62b8df4db6969 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -8,7 +8,6 @@ use editor::{actions::Cancel, scroll::Autoscroll, Editor}; use file_associations::FileAssociations; use anyhow::{anyhow, Result}; -use collections::{hash_map, HashMap}; use gpui::{ actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement, @@ -23,7 +22,14 @@ use project::{ }; use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings}; use serde::{Deserialize, Serialize}; -use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc}; +use std::{ + cmp::Ordering, + collections::{hash_map, HashMap}, + ffi::OsStr, + ops::Range, + path::Path, + sync::Arc, +}; use theme::ThemeSettings; use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; @@ -1693,13 +1699,15 @@ impl ClipboardEntry { #[cfg(test)] mod tests { use super::*; - use collections::HashSet; use gpui::{TestAppContext, View, VisualTestContext, WindowHandle}; use pretty_assertions::assert_eq; use project::{project_settings::ProjectSettings, FakeFs}; use serde_json::json; use settings::SettingsStore; - use std::path::{Path, PathBuf}; + use std::{ + collections::HashSet, + path::{Path, PathBuf}, + }; use workspace::AppState; #[gpui::test] @@ -3501,7 +3509,7 @@ mod tests { cx: &mut VisualTestContext, ) -> Vec { let mut result = Vec::new(); - let mut project_entries = HashSet::default(); + let mut project_entries = HashSet::new(); let mut has_editor = false; panel.update(cx, |panel, cx| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c4502939451bd35dcacbe6ccd0bc6e5c4001b237..89f94ad6c431598f2e3b3fdeb8b6a044b839529c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -25,11 +25,11 @@ use project::{ }; use semantic_index::{SemanticIndex, SemanticIndexStatus}; -use collections::HashSet; use settings::Settings; use smol::stream::StreamExt; use std::{ any::{Any, TypeId}, + collections::HashSet, mem, ops::{Not, Range}, path::PathBuf, @@ -955,7 +955,7 @@ impl ProjectSearchView { semantic_state: None, semantic_permissioned: None, search_options: options, - panels_with_errors: HashSet::default(), + panels_with_errors: HashSet::new(), active_match_index: None, query_editor_was_focused: false, included_files_editor, diff --git a/crates/semantic_index/src/parsing.rs b/crates/semantic_index/src/parsing.rs index e6f4a37d10daab0d420eabc1f234ea1fa986ac74..9f2db711ae0e97d5f7f21af7afd12f09de147a18 100644 --- a/crates/semantic_index/src/parsing.rs +++ b/crates/semantic_index/src/parsing.rs @@ -3,7 +3,6 @@ use ai::{ models::TruncationDirection, }; use anyhow::{anyhow, Result}; -use collections::HashSet; use language::{Grammar, Language}; use rusqlite::{ types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef}, @@ -13,6 +12,7 @@ use sha1::{Digest, Sha1}; use std::{ borrow::Cow, cmp::{self, Reverse}, + collections::HashSet, ops::Range, path::Path, sync::Arc, @@ -267,7 +267,7 @@ impl CodeContextRetriever { let mut spans = Vec::new(); let mut collapsed_ranges_within = Vec::new(); - let mut parsed_name_ranges = HashSet::default(); + let mut parsed_name_ranges = HashSet::new(); for (i, context_match) in matches.iter().enumerate() { // Items which are collapsible but not embeddable have no item range let item_range = if let Some(item_range) = context_match.item_range.clone() { diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 71c67af0950e8ef940ce55e3410493a7bb1ec311..2ee5e6cbf39468cea60fcd108daae7b95fa093da 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -7,7 +7,6 @@ license = "GPL-3.0-or-later" [dependencies] anyhow.workspace = true -collections.workspace = true futures.workspace = true indoc.workspace = true lazy_static.workspace = true diff --git a/crates/sqlez/src/thread_safe_connection.rs b/crates/sqlez/src/thread_safe_connection.rs index cd4664c9aeca98df8f41123f06746b4f7c72bc24..98402df108e35d90b67d9aba4ac7574fd6c5b9d8 100644 --- a/crates/sqlez/src/thread_safe_connection.rs +++ b/crates/sqlez/src/thread_safe_connection.rs @@ -1,9 +1,8 @@ use anyhow::Context; -use collections::HashMap; use futures::{channel::oneshot, Future, FutureExt}; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; -use std::{marker::PhantomData, ops::Deref, sync::Arc, thread}; +use std::{collections::HashMap, marker::PhantomData, ops::Deref, sync::Arc, thread}; use thread_local::ThreadLocal; use crate::{connection::Connection, domain::Migrator, util::UnboundedSyncSender}; diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index a52a7c6f939d88f8479d0be09f166067c71d5af7..7f12fc6eb75f25c490e392ff43818ec9f213dd22 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -13,7 +13,6 @@ doctest = false [dependencies] alacritty_terminal = "0.22.0" anyhow.workspace = true -collections.workspace = true db.workspace = true dirs = "4.0.0" futures.workspace = true diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 46c7cb0c191cb5e18aaa5bf736c675e251fe8288..ad9587a3267732d66295b667f4a63e4437fc9915 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -30,7 +30,6 @@ use mappings::mouse::{ scroll_report, }; -use collections::{HashMap, VecDeque}; use procinfo::LocalProcessInfo; use serde::{Deserialize, Serialize}; use settings::Settings; @@ -40,6 +39,7 @@ use util::truncate_and_trailoff; use std::{ cmp::{self, min}, + collections::{HashMap, VecDeque}, fmt::Display, ops::{Deref, Index, RangeInclusive}, os::unix::prelude::AsRawFd, diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 1a072ca8bce9ab9ed839d7ea5c361722d588730a..7b3d97145c0c58b2f4eceaa64f87b9f6010d9f8c 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -1,4 +1,3 @@ -use collections::HashMap; use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels}; use schemars::{ gen::SchemaGenerator, @@ -8,7 +7,7 @@ use schemars::{ use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use settings::SettingsJsonSchemaParams; -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index c060627ad61a382aca3807c7fa47f4ad59e1132a..8970cfbacf70d4e44e063ef60431b9b0cfff6677 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -8,7 +8,11 @@ license = "GPL-3.0-or-later" [features] default = [] stories = ["dep:itertools", "dep:story"] -test-support = ["gpui/test-support", "fs/test-support", "settings/test-support"] +test-support = [ + "gpui/test-support", + "fs/test-support", + "settings/test-support" +] [lib] path = "src/theme.rs" @@ -16,7 +20,6 @@ doctest = false [dependencies] anyhow.workspace = true -collections.workspace = true color.workspace = true derive_more.workspace = true fs.workspace = true diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 2cc4d902a2d010777edad12e281f46332a9fcfcb..fac119ff1d0b61a6b70a652c0ca0c0c8b00790aa 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -1,8 +1,8 @@ +use std::collections::HashMap; use std::path::Path; use std::sync::Arc; use anyhow::{anyhow, Context, Result}; -use collections::HashMap; use derive_more::{Deref, DerefMut}; use fs::Fs; use futures::StreamExt; @@ -64,7 +64,7 @@ impl ThemeRegistry { pub fn new(assets: Box) -> Self { let registry = Self { state: RwLock::new(ThemeRegistryState { - themes: HashMap::default(), + themes: HashMap::new(), }), assets, }; diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index fc06ebeb4ae4729f6e6446b92047344bc7f23d1c..c510d6f557779f1dde4ceaeaa6a26b177665abfe 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -15,7 +15,6 @@ test-support = ["tempfile", "git2"] [dependencies] anyhow.workspace = true backtrace = "0.3" -collections.workspace = true dirs = "3.0" futures.workspace = true git2 = { workspace = true, optional = true } diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index cf85a806e4db085f45f9855c2d8ca682bfaf8ce9..dadf78622deab3d13b6ca20d8ea16bac17356578 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -1,5 +1,4 @@ -use collections::HashMap; -use std::{cmp::Ordering, ops::Range}; +use std::{cmp::Ordering, collections::HashMap, ops::Range}; /// Construct a string and a list of offsets within that string using a single /// string containing embedded position markers. diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index d3ccf753d1af55269aad1286dbf0af382e255966..012b5a2413e874397a24dcda1e6181a1b3757fcc 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,7 +1,6 @@ use anyhow::{anyhow, Context, Result}; use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; -use collections::HashMap; use editor::scroll::Autoscroll; use editor::Editor; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -11,6 +10,7 @@ use gpui::{AppContext, AsyncAppContext, Global}; use itertools::Itertools; use language::{Bias, Point}; use release_channel::parse_zed_link; +use std::collections::HashMap; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use std::path::Path; @@ -176,7 +176,7 @@ pub async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::default(); + let mut caret_positions = HashMap::new(); let paths = if paths.is_empty() { workspace::last_opened_workspace_paths() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b4ec47111368a820b3e76cc700c484c8ebc129b5..443adac50d254a4014561ae666b701d8d7faf286 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -733,7 +733,6 @@ fn open_settings_file( mod tests { use super::*; use assets::Assets; - use collections::HashSet; use editor::{scroll::Autoscroll, DisplayPoint, Editor}; use gpui::{ actions, Action, AnyWindowHandle, AppContext, AssetSource, Entity, TestAppContext, @@ -743,7 +742,10 @@ mod tests { use project::{project_settings::ProjectSettings, Project, ProjectPath}; use serde_json::json; use settings::{handle_settings_file_changes, watch_config_file, SettingsStore}; - use std::path::{Path, PathBuf}; + use std::{ + collections::HashSet, + path::{Path, PathBuf}, + }; use theme::{ThemeRegistry, ThemeSettings}; use workspace::{ item::{Item, ItemHandle}, From ad3940c66f49ab6d6f62500b11a7751097006dbf Mon Sep 17 00:00:00 2001 From: Kieran Gill Date: Wed, 7 Feb 2024 09:51:27 -0500 Subject: [PATCH 094/101] text rendering: support strikethroughs (#7363) image Release Notes: - Added support for styling text with strikethrough. Related: - https://github.com/zed-industries/zed/issues/5364 - https://github.com/zed-industries/zed/pull/7345 --- crates/assistant/src/assistant_panel.rs | 2 + .../src/chat_panel/message_editor.rs | 1 + crates/collab_ui/src/collab_panel.rs | 1 + crates/editor/src/editor.rs | 2 + crates/editor/src/element.rs | 8 +++ crates/gpui/src/style.rs | 28 ++++++++++ crates/gpui/src/text_system.rs | 9 +++- crates/gpui/src/text_system/line.rs | 53 ++++++++++++++++++- crates/gpui/src/text_system/line_wrapper.rs | 2 + crates/gpui/src/window/element_cx.rs | 36 ++++++++++++- crates/outline/src/outline.rs | 1 + crates/search/src/buffer_search.rs | 1 + crates/search/src/project_search.rs | 1 + crates/terminal_view/src/terminal_element.rs | 4 ++ 14 files changed, 145 insertions(+), 4 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 42e4180adde2cb255199d634f4d9a21e196ddf36..3bd928961daa1530671b53291f53def9a70c06ce 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -962,6 +962,7 @@ impl AssistantPanel { line_height: relative(1.3).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; EditorElement::new( @@ -3166,6 +3167,7 @@ impl InlineAssistant { line_height: relative(1.3).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; EditorElement::new( diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index d29929e1a21beab665f60b92dcbc86af896b14cb..ed47c7a54a702a7a120600a815bb37ec0a7c4b12 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -360,6 +360,7 @@ impl Render for MessageEditor { line_height: relative(1.3).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 9460be3e954aa6fef86b5ca9bea6b1164d92ec68..a010abdbcb08cafdd8d44ec73b4a91022bf5e391 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2068,6 +2068,7 @@ impl CollabPanel { line_height: relative(1.3).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d37ba5f3c72b662f7d0d294d97b3fdc971f26db2..b30c9dd8489e40666471aa810e905d4a88608e17 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9495,6 +9495,7 @@ impl Render for Editor { line_height: relative(settings.buffer_line_height.value()), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }, @@ -9508,6 +9509,7 @@ impl Render for Editor { line_height: relative(settings.buffer_line_height.value()), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }, }; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 46c85ddd104b70d850ade75ea4a222b0f492ebcc..24b0a06bcb53cd850dbb0decf45b06f914eb0ecb 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1073,6 +1073,7 @@ impl EditorElement { font: self.style.text.font(), color: self.style.background, background_color: None, + strikethrough: None, underline: None, }], ) @@ -1713,6 +1714,7 @@ impl EditorElement { color: Hsla::default(), background_color: None, underline: None, + strikethrough: None, }], ) .unwrap(); @@ -1849,6 +1851,7 @@ impl EditorElement { color, background_color: None, underline: None, + strikethrough: None, }; let shaped_line = cx .text_system() @@ -1906,6 +1909,7 @@ impl EditorElement { color: placeholder_color, background_color: None, underline: Default::default(), + strikethrough: None, }; cx.text_system() .shape_line(line.to_string().into(), font_size, &[run]) @@ -2321,6 +2325,7 @@ impl EditorElement { color: cx.theme().colors().editor_invisible, background_color: None, underline: None, + strikethrough: None, }], ) .unwrap(); @@ -2335,6 +2340,7 @@ impl EditorElement { color: cx.theme().colors().editor_invisible, background_color: None, underline: None, + strikethrough: None, }], ) .unwrap(); @@ -2868,6 +2874,7 @@ impl LineWithInvisibles { color: text_style.color, background_color: text_style.background_color, underline: text_style.underline, + strikethrough: text_style.strikethrough, }); if editor_mode == EditorMode::Full { @@ -3281,6 +3288,7 @@ fn layout_line( color: Hsla::default(), background_color: None, underline: None, + strikethrough: None, }], ) } diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 6118a0ae963621d22944f54e59b2b1b4ce361194..c7054b98c5553eaee4cb8f1a3689061cdb6174d0 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -197,6 +197,9 @@ pub struct TextStyle { /// The underline style of the text pub underline: Option, + /// The strikethrough style of the text + pub strikethrough: Option, + /// How to handle whitespace in the text pub white_space: WhiteSpace, } @@ -214,6 +217,7 @@ impl Default for TextStyle { font_style: FontStyle::default(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, } } @@ -246,6 +250,10 @@ impl TextStyle { self.underline = Some(underline); } + if let Some(strikethrough) = style.strikethrough { + self.strikethrough = Some(strikethrough); + } + self } @@ -277,6 +285,7 @@ impl TextStyle { color: self.color, background_color: self.background_color, underline: self.underline, + strikethrough: self.strikethrough, } } } @@ -300,6 +309,9 @@ pub struct HighlightStyle { /// The underline style of the text pub underline: Option, + /// The underline style of the text + pub strikethrough: Option, + /// Similar to the CSS `opacity` property, this will cause the text to be less vibrant. pub fade_out: Option, } @@ -553,6 +565,17 @@ pub struct UnderlineStyle { pub wavy: bool, } +/// The properties that can be applied to a strikethrough. +#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)] +#[refineable(Debug)] +pub struct StrikethroughStyle { + /// The thickness of the strikethrough. + pub thickness: Pixels, + + /// The color of the strikethrough. + pub color: Option, +} + /// The kinds of fill that can be applied to a shape. #[derive(Clone, Debug)] pub enum Fill { @@ -601,6 +624,7 @@ impl From<&TextStyle> for HighlightStyle { font_style: Some(other.font_style), background_color: other.background_color, underline: other.underline, + strikethrough: other.strikethrough, fade_out: None, } } @@ -636,6 +660,10 @@ impl HighlightStyle { self.underline = other.underline; } + if other.strikethrough.is_some() { + self.strikethrough = other.strikethrough; + } + match (other.fade_out, self.fade_out) { (Some(source_fade), None) => self.fade_out = Some(source_fade), (Some(source_fade), Some(dest_fade)) => { diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 12242e26c245be91167c3f34242e9e7a8b3d01e5..43d7b2bb1bfe78f96c4d81e96ab6a53c30f464d9 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -10,7 +10,7 @@ pub use line_wrapper::*; use crate::{ px, Bounds, DevicePixels, EntityId, Hsla, Pixels, PlatformTextSystem, Point, Result, - SharedString, Size, UnderlineStyle, + SharedString, Size, StrikethroughStyle, UnderlineStyle, }; use anyhow::anyhow; use collections::{BTreeSet, FxHashMap, FxHashSet}; @@ -317,6 +317,7 @@ impl WindowTextSystem { if let Some(last_run) = decoration_runs.last_mut() { if last_run.color == run.color && last_run.underline == run.underline + && last_run.strikethrough == run.strikethrough && last_run.background_color == run.background_color { last_run.len += run.len as u32; @@ -328,6 +329,7 @@ impl WindowTextSystem { color: run.color, background_color: run.background_color, underline: run.underline, + strikethrough: run.strikethrough, }); } @@ -382,6 +384,7 @@ impl WindowTextSystem { if decoration_runs.last().map_or(false, |last_run| { last_run.color == run.color && last_run.underline == run.underline + && last_run.strikethrough == run.strikethrough && last_run.background_color == run.background_color }) { decoration_runs.last_mut().unwrap().len += run_len_within_line as u32; @@ -391,6 +394,7 @@ impl WindowTextSystem { color: run.color, background_color: run.background_color, underline: run.underline, + strikethrough: run.strikethrough, }); } @@ -406,6 +410,7 @@ impl WindowTextSystem { let layout = self .line_layout_cache .layout_wrapped_line(&line_text, font_size, &font_runs, wrap_width); + lines.push(WrappedLine { layout, decoration_runs, @@ -599,6 +604,8 @@ pub struct TextRun { pub background_color: Option, /// The underline style (if any) pub underline: Option, + /// The strikethrough style (if any) + pub strikethrough: Option, } /// An identifier for a specific glyph, as returned by [`TextSystem::layout_line`]. diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index e4fe5aad043344da965d26414fada05c571dd4a0..fbf34d39b2413e535252771a7f5c0f82d01691ed 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -1,6 +1,6 @@ use crate::{ black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result, - SharedString, UnderlineStyle, WrapBoundary, WrappedLineLayout, + SharedString, StrikethroughStyle, UnderlineStyle, WrapBoundary, WrappedLineLayout, }; use derive_more::{Deref, DerefMut}; use smallvec::SmallVec; @@ -20,6 +20,9 @@ pub struct DecorationRun { /// The underline style for this run pub underline: Option, + + /// The strikethrough style for this run + pub strikethrough: Option, } /// A line of text that has been shaped and decorated. @@ -113,6 +116,7 @@ fn paint_line( let mut run_end = 0; let mut color = black(); let mut current_underline: Option<(Point, UnderlineStyle)> = None; + let mut current_strikethrough: Option<(Point, StrikethroughStyle)> = None; let mut current_background: Option<(Point, Hsla)> = None; let text_system = cx.text_system().clone(); let mut glyph_origin = origin; @@ -145,6 +149,17 @@ fn paint_line( underline_origin.x = origin.x; underline_origin.y += line_height; } + if let Some((strikethrough_origin, strikethrough_style)) = + current_strikethrough.as_mut() + { + cx.paint_strikethrough( + *strikethrough_origin, + glyph_origin.x - strikethrough_origin.x, + strikethrough_style, + ); + strikethrough_origin.x = origin.x; + strikethrough_origin.y += line_height; + } glyph_origin.x = origin.x; glyph_origin.y += line_height; @@ -153,6 +168,7 @@ fn paint_line( let mut finished_background: Option<(Point, Hsla)> = None; let mut finished_underline: Option<(Point, UnderlineStyle)> = None; + let mut finished_strikethrough: Option<(Point, StrikethroughStyle)> = None; if glyph.index >= run_end { if let Some(style_run) = decoration_runs.next() { if let Some((_, background_color)) = &mut current_background { @@ -183,6 +199,24 @@ fn paint_line( }, )); } + if let Some((_, strikethrough_style)) = &mut current_strikethrough { + if style_run.strikethrough.as_ref() != Some(strikethrough_style) { + finished_strikethrough = current_strikethrough.take(); + } + } + if let Some(run_strikethrough) = style_run.strikethrough.as_ref() { + current_strikethrough.get_or_insert(( + point( + glyph_origin.x, + glyph_origin.y + + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5), + ), + StrikethroughStyle { + color: Some(run_strikethrough.color.unwrap_or(style_run.color)), + thickness: run_strikethrough.thickness, + }, + )); + } run_end += style_run.len as usize; color = style_run.color; @@ -190,6 +224,7 @@ fn paint_line( run_end = layout.len; finished_background = current_background.take(); finished_underline = current_underline.take(); + finished_strikethrough = current_strikethrough.take(); } } @@ -211,6 +246,14 @@ fn paint_line( ); } + if let Some((strikethrough_origin, strikethrough_style)) = finished_strikethrough { + cx.paint_strikethrough( + strikethrough_origin, + glyph_origin.x - strikethrough_origin.x, + &strikethrough_style, + ); + } + let max_glyph_bounds = Bounds { origin: glyph_origin, size: max_glyph_size, @@ -263,5 +306,13 @@ fn paint_line( ); } + if let Some((strikethrough_start, strikethrough_style)) = current_strikethrough.take() { + cx.paint_strikethrough( + strikethrough_start, + last_line_end_x - strikethrough_start.x, + &strikethrough_style, + ); + } + Ok(()) } diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 0abe31352d58753b09030303f6a0d9de94ffab0b..56f18ea48e21e908652d9fd0cf66ea11fda59bd8 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -225,6 +225,7 @@ mod tests { font: font("Helvetica"), color: Default::default(), underline: Default::default(), + strikethrough: None, background_color: None, }; let bold = TextRun { @@ -232,6 +233,7 @@ mod tests { font: font("Helvetica").bold(), color: Default::default(), underline: Default::default(), + strikethrough: None, background_color: None, }; diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 8ba3fc5c4fc94c27629d18efee3c45e023b4a8a1..2f05e716731c029781e980c97658e31f3790c83f 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -34,8 +34,8 @@ use crate::{ InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext, - StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, Window, - WindowContext, SUBPIXEL_VARIANTS, + StackingOrder, StrikethroughStyle, Style, Surface, TextStyleRefinement, Underline, + UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS, }; type AnyMouseListener = Box; @@ -758,6 +758,38 @@ impl<'a> ElementContext<'a> { ); } + /// Paint a strikethrough into the scene for the next frame at the current z-index. + pub fn paint_strikethrough( + &mut self, + origin: Point, + width: Pixels, + style: &StrikethroughStyle, + ) { + let scale_factor = self.scale_factor(); + let height = style.thickness; + let bounds = Bounds { + origin, + size: size(width, height), + }; + let content_mask = self.content_mask(); + let view_id = self.parent_view_id(); + + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + Underline { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds: bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + thickness: style.thickness.scale(scale_factor), + color: style.color.unwrap_or_default(), + wavy: false, + }, + ); + } + /// Paints a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index. /// /// The y component of the origin is the baseline of the glyph. diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 53f78b8fbd45bbf208aa7cc537b4d5d3638ded40..e670aa550ac503b8115679b6daafd808ee4d7017 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -282,6 +282,7 @@ impl PickerDelegate for OutlineViewDelegate { line_height: relative(1.).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index bf01347ce80c588f0e137984f46704ef06662ab5..e09080e6b2ebcef0d8a0dbf79d1974094e2e9c4f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -88,6 +88,7 @@ impl BufferSearchBar { line_height: relative(1.3).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 89f94ad6c431598f2e3b3fdeb8b6a044b839529c..f79e2556a38edcb6132fcef3b4f27c7f564c27cc 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1632,6 +1632,7 @@ impl ProjectSearchBar { line_height: relative(1.3).into(), background_color: None, underline: None, + strikethrough: None, white_space: WhiteSpace::Normal, }; diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 7ddb5ad988c6847dc91d0f75bfff29e692ab6033..eaac6069c21a7f8592669e91ba4fb7e9a4248dc9 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -362,6 +362,7 @@ impl TerminalElement { ..text_style.font() }, underline, + strikethrough: None, }; if let Some((style, range)) = hyperlink { @@ -414,6 +415,7 @@ impl TerminalElement { color: Some(theme.colors().link_text_hover), wavy: false, }), + strikethrough: None, fade_out: None, }; @@ -427,6 +429,7 @@ impl TerminalElement { white_space: WhiteSpace::Normal, // These are going to be overridden per-cell underline: None, + strikethrough: None, color: theme.colors().text, font_weight: FontWeight::NORMAL, }; @@ -545,6 +548,7 @@ impl TerminalElement { color: theme.colors().terminal_background, background_color: None, underline: Default::default(), + strikethrough: None, }], ) .unwrap() From c7b022144f8e10ec34388c963d1764e3d500c5cd Mon Sep 17 00:00:00 2001 From: d1y Date: Wed, 7 Feb 2024 23:13:46 +0800 Subject: [PATCH 095/101] theme_importer: Read theme name from VS Code theme (#7489) apply theme_name(fallback use "") Release Notes: - N/A --- crates/theme_importer/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index c689d7f7f9914105dd592cf05ff3cb0b5a6b90f8..be470e6833afdab37a51127914a2030f799dfc64 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -135,7 +135,7 @@ fn main() -> Result<()> { .context(format!("failed to parse theme {theme_file_path:?}"))?; let theme_metadata = ThemeMetadata { - name: "".to_string(), + name: vscode_theme.name.clone().unwrap_or("".to_string()), appearance: ThemeAppearanceJson::Dark, file_name: "".to_string(), }; From 42a5081aff91e07fdea5a19cc3d92da750f5704a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=B1=B1=E9=A2=A8=E9=9C=B2?= Date: Thu, 8 Feb 2024 01:27:26 +0900 Subject: [PATCH 096/101] Use try_from_bytes for windows build (#7500) Reduce windows build error. Release Notes: - N/A --- crates/fs/src/repository.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 4bd666381ce9051a3e2c8b59c9beef08d7a51c62..66dd0503cf03a2d71c35476a6e8b494475f51c86 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -5,14 +5,12 @@ use parking_lot::Mutex; use serde_derive::{Deserialize, Serialize}; use std::{ cmp::Ordering, - ffi::OsStr, - os::unix::prelude::OsStrExt, path::{Component, Path, PathBuf}, sync::Arc, time::SystemTime, }; use sum_tree::{MapSeekTarget, TreeMap}; -use util::ResultExt; +use util::{paths::PathExt, ResultExt}; pub use git2::Repository as LibGitRepository; @@ -119,7 +117,7 @@ impl GitRepository for LibGitRepository { if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses.iter() { - let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); + let path = RepoPath(PathBuf::try_from_bytes(status.path_bytes()).unwrap()); let status = status.status(); if !status.contains(git2::Status::IGNORED) { if let Some(status) = read_status(status) { From b59e110c5996cc503a1b1cc63a3dfcd99b527709 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 7 Feb 2024 09:45:49 -0700 Subject: [PATCH 097/101] Attempt to fix random lag (#7506) Co-Authored-By: Antonio Co-Authored-By: Thorsten Co-Authored-By: Mikayla Release Notes: - N/A **or** - N/A Co-authored-by: Antonio Co-authored-by: Thorsten Co-authored-by: Mikayla --- crates/gpui/build.rs | 1 + crates/gpui/src/platform/mac/dispatcher.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 4c86fe9a622bebef795a0590d8fd1acf84b6f762..41505ec6783ebb417f062ff8ea0b5d78c115855d 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -22,6 +22,7 @@ fn generate_dispatch_bindings() { .header("src/platform/mac/dispatch.h") .allowlist_var("_dispatch_main_q") .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT") + .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH") .allowlist_var("DISPATCH_TIME_NOW") .allowlist_function("dispatch_get_global_queue") .allowlist_function("dispatch_async_f") diff --git a/crates/gpui/src/platform/mac/dispatcher.rs b/crates/gpui/src/platform/mac/dispatcher.rs index 46afc14ba836ca78c66e3c6d35957064344a4dfb..58375a7e48823679a3ca67f535c20a5d737f474d 100644 --- a/crates/gpui/src/platform/mac/dispatcher.rs +++ b/crates/gpui/src/platform/mac/dispatcher.rs @@ -56,7 +56,7 @@ impl PlatformDispatcher for MacDispatcher { fn dispatch(&self, runnable: Runnable, _: Option) { unsafe { dispatch_async_f( - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH.try_into().unwrap(), 0), runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline), ); From c322179bb99fe98b2b5be281cfd23d0cbef9a3c6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 7 Feb 2024 11:51:18 -0500 Subject: [PATCH 098/101] Initialize the `SystemAppearance` using the app's global window appearance (#7508) This PR changes our approach to initializing the `SystemAppearance` so that we can do it earlier in the startup process. Previously we were using the appearance from the window, meaning that we couldn't initialize the value until we first opened the window. Now we read the `window_appearance` from the `AppContext`. On macOS this is backed by the [`effectiveAppearance`](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance) on the `NSApplication`. We currently still watch for changes to the appearance at the window level, as the only hook I could find in the documentation is [`viewDidChangeEffectiveAppearance`](https://developer.apple.com/documentation/appkit/nsview/2977088-viewdidchangeeffectiveappearance), which is at the `NSView` level. In my testing this makes it so Zed appropriately chooses the correct light/dark theme on startup. Release Notes: - N/A --- .../incoming_call_notification.rs | 4 +-- .../project_shared_notification.rs | 4 +-- crates/gpui/src/app.rs | 6 +++++ crates/gpui/src/platform.rs | 26 +++++++++++-------- crates/gpui/src/platform/mac/platform.rs | 11 +++++++- crates/gpui/src/platform/test/platform.rs | 6 ++++- crates/theme/src/settings.rs | 14 +++++----- crates/workspace/src/workspace.rs | 2 -- crates/zed/src/main.rs | 1 + 9 files changed, 46 insertions(+), 28 deletions(-) diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 12662fe6cbc8ae6c4cc596c742e78a44eaa770c5..f66194c52a0006c46c37cba2514e505c4ee5ec9b 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -5,7 +5,7 @@ use futures::StreamExt; use gpui::{prelude::*, AppContext, WindowHandle}; use settings::Settings; use std::sync::{Arc, Weak}; -use theme::{SystemAppearance, ThemeSettings}; +use theme::ThemeSettings; use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -35,8 +35,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { let options = notification_window_options(screen, window_size); let window = cx .open_window(options, |cx| { - SystemAppearance::init_for_window(cx); - cx.new_view(|_| { IncomingCallNotification::new( incoming_call.clone(), diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index bb70fc9571684cc9ec441855449c835401eec8e7..b8ceefcd765f4e1797bcf1584ac93594a1fffdaa 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -6,7 +6,7 @@ use collections::HashMap; use gpui::{AppContext, Size}; use settings::Settings; use std::sync::{Arc, Weak}; -use theme::{SystemAppearance, ThemeSettings}; +use theme::ThemeSettings; use ui::{prelude::*, Button, Label}; use workspace::AppState; @@ -28,8 +28,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for screen in cx.displays() { let options = notification_window_options(screen, window_size); let window = cx.open_window(options, |cx| { - SystemAppearance::init_for_window(cx); - cx.new_view(|_| { ProjectSharedNotification::new( owner.clone(), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 45c26b0ba81029b2490274cc4cd491ad522b946f..85d76bb3faa624309c9daaad210b97b834dda792 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -14,6 +14,7 @@ use smol::future::FutureExt; pub use test_context::*; use time::UtcOffset; +use crate::WindowAppearance; use crate::{ current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, @@ -522,6 +523,11 @@ impl AppContext { self.platform.displays() } + /// Returns the appearance of the application's windows. + pub fn window_appearance(&self) -> WindowAppearance { + self.platform.window_appearance() + } + /// Writes data to the platform clipboard. pub fn write_to_clipboard(&self, item: ClipboardItem) { self.platform.write_to_clipboard(item) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 62ac543319417207a454f2fa31f987c2289f4ad3..612a2711111b3f72e803ca4ccd4c151e507a6eaf 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -67,6 +67,9 @@ pub(crate) trait Platform: 'static { options: WindowOptions, ) -> Box; + /// Returns the appearance of the application's windows. + fn window_appearance(&self) -> WindowAppearance; + fn set_display_link_output_callback( &self, display_id: DisplayId, @@ -559,29 +562,30 @@ pub enum WindowBounds { Fixed(Bounds), } -/// The appearance of the window, as defined by the operating system -/// On macOS, this corresponds to named [NSAppearance](https://developer.apple.com/documentation/appkit/nsappearance) -/// values +/// The appearance of the window, as defined by the operating system. +/// +/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance) +/// values. #[derive(Copy, Clone, Debug)] pub enum WindowAppearance { - /// A light appearance + /// A light appearance. /// - /// on macOS, this corresponds to the `aqua` appearance + /// On macOS, this corresponds to the `aqua` appearance. Light, - /// A light appearance with vibrant colors + /// A light appearance with vibrant colors. /// - /// on macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance + /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance. VibrantLight, - /// A dark appearance + /// A dark appearance. /// - /// on macOS, this corresponds to the `darkAqua` appearance + /// On macOS, this corresponds to the `darkAqua` appearance. Dark, - /// A dark appearance with vibrant colors + /// A dark appearance with vibrant colors. /// - /// on macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance + /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance. VibrantDark, } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 0e3864065f1675cb9da15f145410010859405738..d2cbd52574605bea47d058cecd15253be012c9dc 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -3,7 +3,8 @@ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, - PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, + WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -505,6 +506,14 @@ impl Platform for MacPlatform { )) } + fn window_appearance(&self) -> WindowAppearance { + unsafe { + let app = NSApplication::sharedApplication(nil); + let appearance: id = msg_send![app, effectiveAppearance]; + WindowAppearance::from_native(appearance) + } + } + fn set_display_link_output_callback( &self, display_id: DisplayId, diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 4d954b32669f653e70e14d60279532e8cdfc0d1c..b7e1c99f30becbef0cd42b1bd5d644bac3a1907b 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,7 +1,7 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, - WindowOptions, + WindowAppearance, WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -178,6 +178,10 @@ impl Platform for TestPlatform { Box::new(window) } + fn window_appearance(&self) -> WindowAppearance { + WindowAppearance::Light + } + fn set_display_link_output_callback( &self, _display_id: DisplayId, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 17404d7c67c6dcdd25a8fdfabf2ba9560a10724f..dd1623970841988b6207390e51c2546fd37ae3f6 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -4,7 +4,7 @@ use anyhow::Result; use derive_more::{Deref, DerefMut}; use gpui::{ px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription, - ViewContext, WindowContext, + ViewContext, }; use refineable::Refineable; use schemars::{ @@ -49,6 +49,12 @@ struct GlobalSystemAppearance(SystemAppearance); impl Global for GlobalSystemAppearance {} impl SystemAppearance { + /// Initializes the [`SystemAppearance`] for the application. + pub fn init(cx: &mut AppContext) { + *cx.default_global::() = + GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into())); + } + /// Returns the global [`SystemAppearance`]. /// /// Inserts a default [`SystemAppearance`] if one does not yet exist. @@ -56,12 +62,6 @@ impl SystemAppearance { cx.default_global::().0 } - /// Initializes the [`SystemAppearance`] for the current window. - pub fn init_for_window(cx: &mut WindowContext) { - *cx.default_global::() = - GlobalSystemAppearance(SystemAppearance(cx.appearance().into())); - } - /// Returns the global [`SystemAppearance`]. pub fn global(cx: &AppContext) -> Self { cx.global::().0 diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 181f14386de4786aac1ed5a88ed641f7e6d46b55..77a2a47400335afc4758a7b21ec23473f3c7e2a9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -855,8 +855,6 @@ impl Workspace { let workspace_id = workspace_id.clone(); let project_handle = project_handle.clone(); move |cx| { - SystemAppearance::init_for_window(cx); - cx.new_view(|cx| { Workspace::new(workspace_id, project_handle, app_state, cx) }) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 33fcfc040f531f6adc36f5c3601cef1cbe312ba6..f010468d26a508cb7ee17a5db1c88b1f7d4da96d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -126,6 +126,7 @@ fn main() { AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx); } + SystemAppearance::init(cx); OpenListener::set_global(listener.clone(), cx); load_embedded_fonts(cx); From 83cffdde1fad8bf0797b8dc4dc6a52772d26d200 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 7 Feb 2024 19:06:03 +0200 Subject: [PATCH 099/101] Use collections::{HashMap, HashSet} instead of its std:: counterpart (#7502) --- Cargo.lock | 4 ++++ crates/channel/src/channel_chat.rs | 2 +- crates/client/src/client.rs | 2 +- crates/gpui/src/keymap.rs | 7 ++----- crates/gpui/src/window/element_cx.rs | 2 +- crates/project_panel/src/project_panel.rs | 18 +++++------------- crates/search/src/project_search.rs | 4 ++-- crates/semantic_index/src/parsing.rs | 4 ++-- crates/sqlez/Cargo.toml | 1 + crates/sqlez/src/thread_safe_connection.rs | 3 ++- crates/terminal/Cargo.toml | 1 + crates/terminal/src/terminal.rs | 2 +- crates/terminal/src/terminal_settings.rs | 3 ++- crates/theme/Cargo.toml | 7 ++----- crates/theme/src/registry.rs | 4 ++-- crates/util/Cargo.toml | 1 + crates/util/src/test/marked_text.rs | 3 ++- crates/zed/src/open_listener.rs | 4 ++-- crates/zed/src/zed.rs | 6 ++---- 19 files changed, 36 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78a47cc60464dc3d83e1dd9c185b751cd3c1223f..8e72a590decd0a8a7708e1952b23de1069fde1d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7530,6 +7530,7 @@ name = "sqlez" version = "0.1.0" dependencies = [ "anyhow", + "collections", "futures 0.3.28", "indoc", "lazy_static", @@ -8105,6 +8106,7 @@ version = "0.1.0" dependencies = [ "alacritty_terminal", "anyhow", + "collections", "db", "dirs 4.0.0", "futures 0.3.28", @@ -8200,6 +8202,7 @@ name = "theme" version = "0.1.0" dependencies = [ "anyhow", + "collections", "color", "derive_more", "fs", @@ -9343,6 +9346,7 @@ version = "0.1.0" dependencies = [ "anyhow", "backtrace", + "collections", "dirs 3.0.2", "futures 0.3.28", "git2", diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index ad843470be35cc2f123d9feaace647cd80c57265..e6ed013adee691627826d6d27c6f944609c33f51 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -5,13 +5,13 @@ use client::{ user::{User, UserStore}, Client, Subscription, TypedEnvelope, UserId, }; +use collections::HashSet; use futures::lock::Mutex; use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use rand::prelude::*; use std::{ - collections::HashSet, ops::{ControlFlow, Range}, sync::Arc, }; diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index ff8adc96607b854b2345a5b2b9fa25f8088d6bec..e454d3ddaf32302413a7fcbedece657a7fd5de42 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -10,6 +10,7 @@ use async_tungstenite::tungstenite::{ error::Error as WebsocketError, http::{Request, StatusCode}, }; +use collections::HashMap; use futures::{ channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt, @@ -29,7 +30,6 @@ use serde_json; use settings::{Settings, SettingsStore}; use std::{ any::TypeId, - collections::HashMap, convert::TryFrom, fmt::Write as _, future::Future, diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 45e0ebbe951fe4a260cf869787e24322a47b1bd0..e2a6bdfc4b59f428e88855a830e1405d3668767e 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -7,12 +7,9 @@ pub use context::*; pub(crate) use matcher::*; use crate::{Action, Keystroke, NoAction}; -use collections::HashSet; +use collections::{HashMap, HashSet}; use smallvec::SmallVec; -use std::{ - any::{Any, TypeId}, - collections::HashMap, -}; +use std::any::{Any, TypeId}; /// An opaque identifier of which version of the keymap is currently active. /// The keymap's version is changed whenever bindings are added or removed. diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 2f05e716731c029781e980c97658e31f3790c83f..e3d95de58d6b9e1237423ebf4032f63bf515e71a 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -71,7 +71,7 @@ pub(crate) struct Frame { pub(crate) reused_views: FxHashSet, #[cfg(any(test, feature = "test-support"))] - pub(crate) debug_bounds: collections::FxHashMap>, + pub(crate) debug_bounds: FxHashMap>, } impl Frame { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b43b5d84131e5d1f110224d50ed62b8df4db6969..3744a36c00f61e0c14442a0075c4784c64dff820 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -8,6 +8,7 @@ use editor::{actions::Cancel, scroll::Autoscroll, Editor}; use file_associations::FileAssociations; use anyhow::{anyhow, Result}; +use collections::{hash_map, HashMap}; use gpui::{ actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement, @@ -22,14 +23,7 @@ use project::{ }; use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings}; use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - collections::{hash_map, HashMap}, - ffi::OsStr, - ops::Range, - path::Path, - sync::Arc, -}; +use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc}; use theme::ThemeSettings; use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; @@ -1699,15 +1693,13 @@ impl ClipboardEntry { #[cfg(test)] mod tests { use super::*; + use collections::HashSet; use gpui::{TestAppContext, View, VisualTestContext, WindowHandle}; use pretty_assertions::assert_eq; use project::{project_settings::ProjectSettings, FakeFs}; use serde_json::json; use settings::SettingsStore; - use std::{ - collections::HashSet, - path::{Path, PathBuf}, - }; + use std::path::{Path, PathBuf}; use workspace::AppState; #[gpui::test] @@ -3509,7 +3501,7 @@ mod tests { cx: &mut VisualTestContext, ) -> Vec { let mut result = Vec::new(); - let mut project_entries = HashSet::new(); + let mut project_entries = HashSet::default(); let mut has_editor = false; panel.update(cx, |panel, cx| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index f79e2556a38edcb6132fcef3b4f27c7f564c27cc..ad2e89452737069b3d1bcc9fcd35c6db4d504f24 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -25,11 +25,11 @@ use project::{ }; use semantic_index::{SemanticIndex, SemanticIndexStatus}; +use collections::HashSet; use settings::Settings; use smol::stream::StreamExt; use std::{ any::{Any, TypeId}, - collections::HashSet, mem, ops::{Not, Range}, path::PathBuf, @@ -955,7 +955,7 @@ impl ProjectSearchView { semantic_state: None, semantic_permissioned: None, search_options: options, - panels_with_errors: HashSet::new(), + panels_with_errors: HashSet::default(), active_match_index: None, query_editor_was_focused: false, included_files_editor, diff --git a/crates/semantic_index/src/parsing.rs b/crates/semantic_index/src/parsing.rs index 9f2db711ae0e97d5f7f21af7afd12f09de147a18..e6f4a37d10daab0d420eabc1f234ea1fa986ac74 100644 --- a/crates/semantic_index/src/parsing.rs +++ b/crates/semantic_index/src/parsing.rs @@ -3,6 +3,7 @@ use ai::{ models::TruncationDirection, }; use anyhow::{anyhow, Result}; +use collections::HashSet; use language::{Grammar, Language}; use rusqlite::{ types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef}, @@ -12,7 +13,6 @@ use sha1::{Digest, Sha1}; use std::{ borrow::Cow, cmp::{self, Reverse}, - collections::HashSet, ops::Range, path::Path, sync::Arc, @@ -267,7 +267,7 @@ impl CodeContextRetriever { let mut spans = Vec::new(); let mut collapsed_ranges_within = Vec::new(); - let mut parsed_name_ranges = HashSet::new(); + let mut parsed_name_ranges = HashSet::default(); for (i, context_match) in matches.iter().enumerate() { // Items which are collapsible but not embeddable have no item range let item_range = if let Some(item_range) = context_match.item_range.clone() { diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 2ee5e6cbf39468cea60fcd108daae7b95fa093da..71c67af0950e8ef940ce55e3410493a7bb1ec311 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -7,6 +7,7 @@ license = "GPL-3.0-or-later" [dependencies] anyhow.workspace = true +collections.workspace = true futures.workspace = true indoc.workspace = true lazy_static.workspace = true diff --git a/crates/sqlez/src/thread_safe_connection.rs b/crates/sqlez/src/thread_safe_connection.rs index 98402df108e35d90b67d9aba4ac7574fd6c5b9d8..cd4664c9aeca98df8f41123f06746b4f7c72bc24 100644 --- a/crates/sqlez/src/thread_safe_connection.rs +++ b/crates/sqlez/src/thread_safe_connection.rs @@ -1,8 +1,9 @@ use anyhow::Context; +use collections::HashMap; use futures::{channel::oneshot, Future, FutureExt}; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; -use std::{collections::HashMap, marker::PhantomData, ops::Deref, sync::Arc, thread}; +use std::{marker::PhantomData, ops::Deref, sync::Arc, thread}; use thread_local::ThreadLocal; use crate::{connection::Connection, domain::Migrator, util::UnboundedSyncSender}; diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 7f12fc6eb75f25c490e392ff43818ec9f213dd22..a52a7c6f939d88f8479d0be09f166067c71d5af7 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -13,6 +13,7 @@ doctest = false [dependencies] alacritty_terminal = "0.22.0" anyhow.workspace = true +collections.workspace = true db.workspace = true dirs = "4.0.0" futures.workspace = true diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ad9587a3267732d66295b667f4a63e4437fc9915..46c7cb0c191cb5e18aaa5bf736c675e251fe8288 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -30,6 +30,7 @@ use mappings::mouse::{ scroll_report, }; +use collections::{HashMap, VecDeque}; use procinfo::LocalProcessInfo; use serde::{Deserialize, Serialize}; use settings::Settings; @@ -39,7 +40,6 @@ use util::truncate_and_trailoff; use std::{ cmp::{self, min}, - collections::{HashMap, VecDeque}, fmt::Display, ops::{Deref, Index, RangeInclusive}, os::unix::prelude::AsRawFd, diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 7b3d97145c0c58b2f4eceaa64f87b9f6010d9f8c..1a072ca8bce9ab9ed839d7ea5c361722d588730a 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -1,3 +1,4 @@ +use collections::HashMap; use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels}; use schemars::{ gen::SchemaGenerator, @@ -7,7 +8,7 @@ use schemars::{ use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use settings::SettingsJsonSchemaParams; -use std::{collections::HashMap, path::PathBuf}; +use std::path::PathBuf; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 8970cfbacf70d4e44e063ef60431b9b0cfff6677..c060627ad61a382aca3807c7fa47f4ad59e1132a 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -8,11 +8,7 @@ license = "GPL-3.0-or-later" [features] default = [] stories = ["dep:itertools", "dep:story"] -test-support = [ - "gpui/test-support", - "fs/test-support", - "settings/test-support" -] +test-support = ["gpui/test-support", "fs/test-support", "settings/test-support"] [lib] path = "src/theme.rs" @@ -20,6 +16,7 @@ doctest = false [dependencies] anyhow.workspace = true +collections.workspace = true color.workspace = true derive_more.workspace = true fs.workspace = true diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index fac119ff1d0b61a6b70a652c0ca0c0c8b00790aa..2cc4d902a2d010777edad12e281f46332a9fcfcb 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; use std::path::Path; use std::sync::Arc; use anyhow::{anyhow, Context, Result}; +use collections::HashMap; use derive_more::{Deref, DerefMut}; use fs::Fs; use futures::StreamExt; @@ -64,7 +64,7 @@ impl ThemeRegistry { pub fn new(assets: Box) -> Self { let registry = Self { state: RwLock::new(ThemeRegistryState { - themes: HashMap::new(), + themes: HashMap::default(), }), assets, }; diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index c510d6f557779f1dde4ceaeaa6a26b177665abfe..fc06ebeb4ae4729f6e6446b92047344bc7f23d1c 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -15,6 +15,7 @@ test-support = ["tempfile", "git2"] [dependencies] anyhow.workspace = true backtrace = "0.3" +collections.workspace = true dirs = "3.0" futures.workspace = true git2 = { workspace = true, optional = true } diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index dadf78622deab3d13b6ca20d8ea16bac17356578..cf85a806e4db085f45f9855c2d8ca682bfaf8ce9 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -1,4 +1,5 @@ -use std::{cmp::Ordering, collections::HashMap, ops::Range}; +use collections::HashMap; +use std::{cmp::Ordering, ops::Range}; /// Construct a string and a list of offsets within that string using a single /// string containing embedded position markers. diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index 012b5a2413e874397a24dcda1e6181a1b3757fcc..d3ccf753d1af55269aad1286dbf0af382e255966 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use collections::HashMap; use editor::scroll::Autoscroll; use editor::Editor; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -10,7 +11,6 @@ use gpui::{AppContext, AsyncAppContext, Global}; use itertools::Itertools; use language::{Bias, Point}; use release_channel::parse_zed_link; -use std::collections::HashMap; use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use std::path::Path; @@ -176,7 +176,7 @@ pub async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { - let mut caret_positions = HashMap::new(); + let mut caret_positions = HashMap::default(); let paths = if paths.is_empty() { workspace::last_opened_workspace_paths() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 443adac50d254a4014561ae666b701d8d7faf286..b4ec47111368a820b3e76cc700c484c8ebc129b5 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -733,6 +733,7 @@ fn open_settings_file( mod tests { use super::*; use assets::Assets; + use collections::HashSet; use editor::{scroll::Autoscroll, DisplayPoint, Editor}; use gpui::{ actions, Action, AnyWindowHandle, AppContext, AssetSource, Entity, TestAppContext, @@ -742,10 +743,7 @@ mod tests { use project::{project_settings::ProjectSettings, Project, ProjectPath}; use serde_json::json; use settings::{handle_settings_file_changes, watch_config_file, SettingsStore}; - use std::{ - collections::HashSet, - path::{Path, PathBuf}, - }; + use std::path::{Path, PathBuf}; use theme::{ThemeRegistry, ThemeSettings}; use workspace::{ item::{Item, ItemHandle}, From 114889b8bbf27930e1626a66405ce8a3f13bda71 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 7 Feb 2024 12:19:41 -0500 Subject: [PATCH 100/101] v0.123.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e72a590decd0a8a7708e1952b23de1069fde1d2..04f18936cd774580903214dc2ed7578a6f21f111 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10297,7 +10297,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.122.0" +version = "0.123.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index fda5391ac23d27e3ce414c54c500c48b1392a2a7..c023faf6f7fb8795952984668787c179d24209af 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -2,7 +2,7 @@ description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.122.0" +version = "0.123.0" publish = false license = "GPL-3.0-or-later" From 07fce7aae1ebe03a344160e3083837044d7de850 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 7 Feb 2024 19:45:30 +0100 Subject: [PATCH 101/101] Stop display link when window is occluded (#7511) Release Notes: - Fixed a bug that caused the window to become unresponsive after foregrounding. --------- Co-authored-by: Conrad --- crates/gpui/src/platform/mac/window.rs | 27 ++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index b15e8c8de5c47c25ffff0f744597bc2f6b6b2b89..b6d0e3b886dd1954112fcaccfaae7e80e88d4cc8 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -12,7 +12,7 @@ use cocoa::{ CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags, NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, - NSWindowStyleMask, NSWindowTitleVisibility, + NSWindowOcclusionState, NSWindowStyleMask, NSWindowTitleVisibility, }, base::{id, nil}, foundation::{ @@ -249,6 +249,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowDidResize:), window_did_resize as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowDidChangeOcclusionState:), + window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowWillEnterFullScreen:), window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id), @@ -530,14 +534,12 @@ impl MacWindow { let native_view = NSView::init(native_view); assert!(!native_view.is_null()); - let display_link = start_display_link(native_window.screen(), native_view); - let window = Self(Arc::new(Mutex::new(MacWindowState { handle, executor, native_window, native_view: NonNull::new_unchecked(native_view), - display_link, + display_link: nil, renderer: MetalRenderer::new(instance_buffer_pool), kind: options.kind, request_frame_callback: None, @@ -1332,6 +1334,23 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { } } +extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut lock = window_state.lock(); + unsafe { + if lock + .native_window + .occlusionState() + .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible) + { + lock.display_link = + start_display_link(lock.native_window.screen(), lock.native_view.as_ptr()); + } else { + lock.display_link = nil; + } + } +} + extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; window_state.as_ref().lock().move_traffic_light();