From 759ea0ec48fbaff66725817fb6da4f7445c051c7 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Tue, 11 Feb 2025 12:47:41 -0500 Subject: [PATCH 1/6] Touch up stale hunks fix (#24669) Release Notes: - N/A Co-authored-by: Max --- crates/buffer_diff/src/buffer_diff.rs | 14 ++++++++------ crates/project/src/buffer_store.rs | 18 +++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index fb244abbb7ec70f7daa18ab18969a366d9427840..772835f9a93f382934b91e016ba4c8c2cb91fef7 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -587,16 +587,18 @@ impl BufferDiff { range: Range, buffer: &text::BufferSnapshot, cx: &App, - ) -> Range { + ) -> Option> { let start = self .hunks_intersecting_range(range.clone(), &buffer, cx) - .next() - .map_or(Anchor::MIN, |hunk| hunk.buffer_range.start); + .next()? + .buffer_range + .start; let end = self .hunks_intersecting_range_rev(range.clone(), &buffer) - .next() - .map_or(Anchor::MAX, |hunk| hunk.buffer_range.end); - start..end + .next()? + .buffer_range + .end; + Some(start..end) } #[allow(clippy::too_many_arguments)] diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 0955f8e869b59c438270fa0548b49cdabd4df081..08d11de899ae8a068934873053819f53fc40496a 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -260,16 +260,20 @@ impl BufferDiffState { let changed_range = match (unstaged_changed_range, uncommitted_changed_range) { (None, None) => None, (Some(unstaged_range), None) => { - Some(uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)) + uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx) } (None, Some(uncommitted_range)) => Some(uncommitted_range), - (Some(unstaged_range), Some(uncommitted_range)) => maybe!({ - let expanded_range = - uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx); - let start = expanded_range.start.min(&uncommitted_range.start, &buffer); - let end = expanded_range.end.max(&uncommitted_range.end, &buffer); + (Some(unstaged_range), Some(uncommitted_range)) => { + let mut start = uncommitted_range.start; + let mut end = uncommitted_range.end; + if let Some(unstaged_range) = + uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx) + { + start = unstaged_range.start.min(&uncommitted_range.start, &buffer); + end = unstaged_range.end.max(&uncommitted_range.end, &buffer); + } Some(start..end) - }), + } }; cx.emit(BufferDiffEvent::DiffChanged { changed_range }); })?; From 7378ab9ba55190554a36841ce45d765876561423 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 11 Feb 2025 13:06:45 -0500 Subject: [PATCH 2/6] Correctly handle `[[` autoclosing in Markdown (#24662) --- crates/languages/src/markdown/config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/languages/src/markdown/config.toml b/crates/languages/src/markdown/config.toml index af20aa49ba01828a7f954633a17373525b5dc878..f6e15118d89d960d64fd4c4fdadf577022b29e4e 100644 --- a/crates/languages/src/markdown/config.toml +++ b/crates/languages/src/markdown/config.toml @@ -3,6 +3,7 @@ grammar = "markdown" path_suffixes = ["md", "mdx", "mdwn", "markdown", "MD"] word_characters = ["-"] block_comment = [""] +autoclose_before = "}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, From 0a146793ea48a62cf9b104c6640095307d41bdb7 Mon Sep 17 00:00:00 2001 From: 5brian Date: Tue, 11 Feb 2025 13:35:59 -0500 Subject: [PATCH 3/6] vim: Prevent around word operations from selecting indentation (#24635) Closes https://github.com/zed-industries/zed/issues/15323 Changes: Added check for first word on line Tested `v/c/d/y aw`. Matches standard neovim. |initial|old|new| |---|---|---| |![image](https://github.com/user-attachments/assets/725b74e6-3aa0-40dc-9fd2-4d2b593e9926)|![image](https://github.com/user-attachments/assets/eeebd267-b4c6-4ea6-bb9a-fb913614754c)|![image](https://github.com/user-attachments/assets/fb695e54-b4c2-44a6-a588-909c1fd415e0) Release Notes: - vim: Prevent around word operations from selecting indentation --- crates/vim/src/object.rs | 53 ++++++++++++++++++- .../test_around_containing_word_indent.json | 23 ++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 crates/vim/test_data/test_around_containing_word_indent.json diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 7b920c252f3b76e58c200bc93bbd742dbef2cdcf..06b40587c4fa06a1edfad9dff5c8b0953446ffdc 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -727,8 +727,25 @@ fn around_containing_word( relative_to: DisplayPoint, ignore_punctuation: bool, ) -> Option> { - in_word(map, relative_to, ignore_punctuation) - .map(|range| expand_to_include_whitespace(map, range, true)) + in_word(map, relative_to, ignore_punctuation).map(|range| { + let line_start = DisplayPoint::new(range.start.row(), 0); + let is_first_word = map + .buffer_chars_at(line_start.to_offset(map, Bias::Left)) + .take_while(|(ch, offset)| { + offset < &range.start.to_offset(map, Bias::Left) && ch.is_whitespace() + }) + .count() + > 0; + + if is_first_word { + // For first word on line, trim indentation + let mut expanded = expand_to_include_whitespace(map, range.clone(), true); + expanded.start = range.start; + expanded + } else { + expand_to_include_whitespace(map, range, true) + } + }) } fn around_next_word( @@ -2455,4 +2472,36 @@ mod test { Mode::Visual, ); } + #[gpui::test] + async fn test_around_containing_word_indent(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(" ˇconst f = (x: unknown) => {") + .await; + cx.simulate_shared_keystrokes("v a w").await; + cx.shared_state() + .await + .assert_eq(" «const ˇ»f = (x: unknown) => {"); + + cx.set_shared_state(" ˇconst f = (x: unknown) => {") + .await; + cx.simulate_shared_keystrokes("y a w").await; + cx.shared_clipboard().await.assert_eq("const "); + + cx.set_shared_state(" ˇconst f = (x: unknown) => {") + .await; + cx.simulate_shared_keystrokes("d a w").await; + cx.shared_state() + .await + .assert_eq(" ˇf = (x: unknown) => {"); + cx.shared_clipboard().await.assert_eq("const "); + + cx.set_shared_state(" ˇconst f = (x: unknown) => {") + .await; + cx.simulate_shared_keystrokes("c a w").await; + cx.shared_state() + .await + .assert_eq(" ˇf = (x: unknown) => {"); + cx.shared_clipboard().await.assert_eq("const "); + } } diff --git a/crates/vim/test_data/test_around_containing_word_indent.json b/crates/vim/test_data/test_around_containing_word_indent.json new file mode 100644 index 0000000000000000000000000000000000000000..6707ff6804de91701422ed929ff00eadafd42c34 --- /dev/null +++ b/crates/vim/test_data/test_around_containing_word_indent.json @@ -0,0 +1,23 @@ +{"Put":{"state":" ˇconst f = (x: unknown) => {"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"w"} +{"Get":{"state":" «const ˇ»f = (x: unknown) => {","mode":"Visual"}} +{"Put":{"state":" ˇconst f = (x: unknown) => {"}} +{"Key":"y"} +{"Key":"a"} +{"Key":"w"} +{"Get":{"state":" ˇconst f = (x: unknown) => {","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"const "}} +{"Put":{"state":" ˇconst f = (x: unknown) => {"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"w"} +{"Get":{"state":" ˇf = (x: unknown) => {","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"const "}} +{"Put":{"state":" ˇconst f = (x: unknown) => {"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"w"} +{"Get":{"state":" ˇf = (x: unknown) => {","mode":"Insert"}} +{"ReadRegister":{"name":"\"","value":"const "}} From 477cec0ef1398915711b0aec9c23dd5bb1cddd9d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 11 Feb 2025 11:18:54 -0800 Subject: [PATCH 4/6] Add more view tracking (#24683) This should fix a panic in `Window::current_view()` Release Notes: - N/A --- crates/gpui/src/view.rs | 185 +++++++++++++++++++------------------- crates/gpui/src/window.rs | 15 +++- 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index c6483ba6ae6f0938ff6aed9b20018b13f92a32a1..933a04b5f306325c14c826399e4c9e803c146b35 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -40,7 +40,9 @@ impl Element for Entity { cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let mut element = self.update(cx, |view, cx| view.render(window, cx).into_any_element()); - let layout_id = element.request_layout(window, cx); + let layout_id = window.with_rendered_view(self.entity_id(), |window| { + element.request_layout(window, cx) + }); (layout_id, element) } @@ -53,7 +55,7 @@ impl Element for Entity { cx: &mut App, ) { window.set_view_id(self.entity_id()); - element.prepaint(window, cx); + window.with_rendered_view(self.entity_id(), |window| element.prepaint(window, cx)); } fn paint( @@ -65,7 +67,7 @@ impl Element for Entity { window: &mut Window, cx: &mut App, ) { - element.paint(window, cx); + window.with_rendered_view(self.entity_id(), |window| element.paint(window, cx)); } } @@ -150,18 +152,18 @@ impl Element for AnyView { window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { - if let Some(style) = self.cached_style.as_ref() { - let mut root_style = Style::default(); - root_style.refine(style); - let layout_id = window.request_layout(root_style, None, cx); - (layout_id, None) - } else { - window.with_rendered_view(self.entity_id(), |window| { + window.with_rendered_view(self.entity_id(), |window| { + if let Some(style) = self.cached_style.as_ref() { + let mut root_style = Style::default(); + root_style.refine(style); + let layout_id = window.request_layout(root_style, None, cx); + (layout_id, None) + } else { let mut element = (self.render)(self, window, cx); let layout_id = element.request_layout(window, cx); (layout_id, Some(element)) - }) - } + } + }) } fn prepaint( @@ -173,69 +175,66 @@ impl Element for AnyView { cx: &mut App, ) -> Option { window.set_view_id(self.entity_id()); - if self.cached_style.is_some() { - window.with_element_state::( - global_id.unwrap(), - |element_state, window| { - let content_mask = window.content_mask(); - let text_style = window.text_style(); - - if let Some(mut element_state) = element_state { - if element_state.cache_key.bounds == bounds - && element_state.cache_key.content_mask == content_mask - && element_state.cache_key.text_style == text_style - && !window.dirty_views.contains(&self.entity_id()) - && !window.refreshing - { - let prepaint_start = window.prepaint_index(); - window.reuse_prepaint(element_state.prepaint_range.clone()); - cx.entities - .extend_accessed(&element_state.accessed_entities); - let prepaint_end = window.prepaint_index(); - element_state.prepaint_range = prepaint_start..prepaint_end; - - return (None, element_state); + window.with_rendered_view(self.entity_id(), |window| { + if self.cached_style.is_some() { + window.with_element_state::( + global_id.unwrap(), + |element_state, window| { + let content_mask = window.content_mask(); + let text_style = window.text_style(); + + if let Some(mut element_state) = element_state { + if element_state.cache_key.bounds == bounds + && element_state.cache_key.content_mask == content_mask + && element_state.cache_key.text_style == text_style + && !window.dirty_views.contains(&self.entity_id()) + && !window.refreshing + { + let prepaint_start = window.prepaint_index(); + window.reuse_prepaint(element_state.prepaint_range.clone()); + cx.entities + .extend_accessed(&element_state.accessed_entities); + let prepaint_end = window.prepaint_index(); + element_state.prepaint_range = prepaint_start..prepaint_end; + + return (None, element_state); + } } - } - - let refreshing = mem::replace(&mut window.refreshing, true); - let prepaint_start = window.prepaint_index(); - let (mut element, accessed_entities) = - window.with_rendered_view(self.entity_id(), |window| { - cx.detect_accessed_entities(|cx| { - let mut element = (self.render)(self, window, cx); - element.layout_as_root(bounds.size.into(), window, cx); - element.prepaint_at(bounds.origin, window, cx); - element - }) + + let refreshing = mem::replace(&mut window.refreshing, true); + let prepaint_start = window.prepaint_index(); + let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| { + let mut element = (self.render)(self, window, cx); + element.layout_as_root(bounds.size.into(), window, cx); + element.prepaint_at(bounds.origin, window, cx); + element }); - let prepaint_end = window.prepaint_index(); - window.refreshing = refreshing; - - ( - Some(element), - AnyViewState { - accessed_entities, - prepaint_range: prepaint_start..prepaint_end, - paint_range: PaintIndex::default()..PaintIndex::default(), - cache_key: ViewCacheKey { - bounds, - content_mask, - text_style, + let prepaint_end = window.prepaint_index(); + window.refreshing = refreshing; + + ( + Some(element), + AnyViewState { + accessed_entities, + prepaint_range: prepaint_start..prepaint_end, + paint_range: PaintIndex::default()..PaintIndex::default(), + cache_key: ViewCacheKey { + bounds, + content_mask, + text_style, + }, }, - }, - ) - }, - ) - } else { - let mut element = element.take().unwrap(); - window.with_rendered_view(self.entity_id(), |window| { + ) + }, + ) + } else { + let mut element = element.take().unwrap(); element.prepaint(window, cx); - }); - Some(element) - } + Some(element) + } + }) } fn paint( @@ -247,33 +246,33 @@ impl Element for AnyView { window: &mut Window, cx: &mut App, ) { - if self.cached_style.is_some() { - window.with_element_state::( - global_id.unwrap(), - |element_state, window| { - let mut element_state = element_state.unwrap(); + window.with_rendered_view(self.entity_id(), |window| { + if self.cached_style.is_some() { + window.with_element_state::( + global_id.unwrap(), + |element_state, window| { + let mut element_state = element_state.unwrap(); - let paint_start = window.paint_index(); + let paint_start = window.paint_index(); - if let Some(element) = element { - let refreshing = mem::replace(&mut window.refreshing, true); - window.with_rendered_view(self.entity_id(), |window| { + if let Some(element) = element { + let refreshing = mem::replace(&mut window.refreshing, true); element.paint(window, cx); - }); - window.refreshing = refreshing; - } else { - window.reuse_paint(element_state.paint_range.clone()); - } - - let paint_end = window.paint_index(); - element_state.paint_range = paint_start..paint_end; - - ((), element_state) - }, - ) - } else { - element.as_mut().unwrap().paint(window, cx); - } + window.refreshing = refreshing; + } else { + window.reuse_paint(element_state.paint_range.clone()); + } + + let paint_end = window.paint_index(); + element_state.paint_range = paint_start..paint_end; + + ((), element_state) + }, + ) + } else { + element.as_mut().unwrap().paint(window, cx); + } + }); } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index dbbe42f9846a4ad503aff7610578c91df5c504b7..b3afcdb63d30822901f88d5b4dff2e21f9f721cc 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -477,6 +477,7 @@ pub(crate) struct TooltipRequest { } pub(crate) struct DeferredDraw { + current_view: EntityId, priority: usize, parent_node: DispatchNodeId, element_id_stack: SmallVec<[ElementId; 32]>, @@ -1750,9 +1751,11 @@ impl Window { let prepaint_start = self.prepaint_index(); if let Some(element) = deferred_draw.element.as_mut() { - self.with_absolute_element_offset(deferred_draw.absolute_offset, |window| { - element.prepaint(window, cx) - }); + self.with_rendered_view(deferred_draw.current_view, |window| { + window.with_absolute_element_offset(deferred_draw.absolute_offset, |window| { + element.prepaint(window, cx) + }); + }) } else { self.reuse_prepaint(deferred_draw.prepaint_range.clone()); } @@ -1783,7 +1786,9 @@ impl Window { let paint_start = self.paint_index(); if let Some(element) = deferred_draw.element.as_mut() { - element.paint(self, cx); + self.with_rendered_view(deferred_draw.current_view, |window| { + element.paint(window, cx); + }) } else { self.reuse_paint(deferred_draw.paint_range.clone()); } @@ -1841,6 +1846,7 @@ impl Window { [range.start.deferred_draws_index..range.end.deferred_draws_index] .iter() .map(|deferred_draw| DeferredDraw { + current_view: deferred_draw.current_view, parent_node: reused_subtree.refresh_node_id(deferred_draw.parent_node), element_id_stack: deferred_draw.element_id_stack.clone(), text_style_stack: deferred_draw.text_style_stack.clone(), @@ -2247,6 +2253,7 @@ impl Window { self.invalidator.debug_assert_prepaint(); let parent_node = self.next_frame.dispatch_tree.active_node_id().unwrap(); self.next_frame.deferred_draws.push(DeferredDraw { + current_view: self.current_view(), parent_node, element_id_stack: self.element_id_stack.clone(), text_style_stack: self.text_style_stack.clone(), From 12163c9b45f551540b4687a2cbdd563cf9b68711 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 11 Feb 2025 12:42:43 -0700 Subject: [PATCH 5/6] Add `Editor &&` to accept edit contexts in vim keymap (#24684) Without this, these default vim bindings were taking precedence over user keybindings Release Notes: - N/A --- assets/keymaps/vim.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 111d4c8181f111dca294f2d7dc1eeccab9f1373e..450e435bb37d3cd0e20ecee1077e2c5e436c2d31 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -696,7 +696,7 @@ } }, { - "context": "edit_prediction && !edit_prediction_requires_modifier", + "context": "Editor && edit_prediction && !edit_prediction_requires_modifier", "bindings": { // This is identical to the binding in the base keymap, but the vim bindings above to // "vim::Tab" shadow it, so it needs to be bound again. @@ -704,7 +704,7 @@ } }, { - "context": "os != macos && edit_prediction", + "context": "os != macos && Editor && edit_prediction", "bindings": { // alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This // is because alt-tab may not be available, as it is often used for window switching on Linux From aab3e0495d8b639f2247fc5f64e7757622444184 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 11 Feb 2025 15:02:52 -0500 Subject: [PATCH 6/6] inline_completion_button: Add menu option to toggle "Eager Preview"s for edit predictions (#24685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a menu option to the edit prediction menu to toggle the "Eager Preview" behavior: Screenshot 2025-02-11 at 2 44 52 PM Release Notes: - N/A --- .../src/inline_completion_button.rs | 47 ++++++++++++++++--- crates/language/src/language_settings.rs | 4 +- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 8961512bd28718b6576346e85a03f7ecad4a48dc..b6a0e6841f3a4a921cd441642afe63846f1f8b36 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -438,13 +438,10 @@ impl InlineCompletionButton { let settings = AllLanguageSettings::get_global(cx); let globally_enabled = settings.show_inline_completions(None, cx); - menu = menu.toggleable_entry( - "All Files", - globally_enabled, - IconPosition::Start, - None, - move |_, cx| toggle_inline_completions_globally(fs.clone(), cx), - ); + menu = menu.toggleable_entry("All Files", globally_enabled, IconPosition::Start, None, { + let fs = fs.clone(); + move |_, cx| toggle_inline_completions_globally(fs.clone(), cx) + }); menu = menu.separator().header("Privacy Settings"); if let Some(provider) = &self.edit_prediction_provider { @@ -554,6 +551,42 @@ impl InlineCompletionButton { ); } + let is_eager_preview_enabled = match settings.inline_completions_preview_mode() { + language::InlineCompletionPreviewMode::Auto => true, + language::InlineCompletionPreviewMode::WhenHoldingModifier => false, + }; + menu = menu.separator().toggleable_entry( + "Eager Preview", + is_eager_preview_enabled, + IconPosition::Start, + None, + { + let fs = fs.clone(); + move |_window, cx| { + update_settings_file::( + fs.clone(), + cx, + move |settings, _cx| { + let inline_preview = match is_eager_preview_enabled { + true => language::InlineCompletionPreviewMode::WhenHoldingModifier, + false => language::InlineCompletionPreviewMode::Auto, + }; + + if let Some(edit_predictions) = settings.edit_predictions.as_mut() { + edit_predictions.inline_preview = inline_preview; + } else { + settings.edit_predictions = + Some(language_settings::EditPredictionSettingsContent { + inline_preview, + ..Default::default() + }); + } + }, + ); + } + }, + ); + if let Some(editor_focus_handle) = self.editor_focus_handle.clone() { menu = menu .separator() diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index b9c0821721bc15fbf5ba8b0fa286f9f25695731d..a882677a8505d7e6a51a6f3ad86db200e6abfb62 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -250,7 +250,7 @@ pub struct AllLanguageSettingsContent { pub features: Option, /// The edit prediction settings. #[serde(default)] - pub edit_predictions: Option, + pub edit_predictions: Option, /// The default language settings. #[serde(flatten)] pub defaults: LanguageSettingsContent, @@ -428,7 +428,7 @@ pub struct LanguageSettingsContent { /// The contents of the edit prediction settings. #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct InlineCompletionSettingsContent { +pub struct EditPredictionSettingsContent { /// A list of globs representing files that edit predictions should be disabled for. /// This list adds to a pre-existing, sensible default set of globs. /// Any additional ones you add are combined with them.