diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 9283dcd198..70e18c8edd 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -63,6 +63,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider { false } + fn show_completions_in_normal_mode() -> bool { + false + } + fn is_enabled( &self, buffer: &Model, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 0b429fb8c7..d19bcd75af 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -42,7 +42,7 @@ use fold_map::{FoldMap, FoldSnapshot}; use gpui::{ AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle, }; -pub(crate) use inlay_map::Inlay; +pub use inlay_map::Inlay; use inlay_map::{InlayMap, InlaySnapshot}; pub use inlay_map::{InlayOffset, InlayPoint}; use invisibles::{is_invisible, replacement}; diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e288f5a76c..01dd12cc98 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -33,7 +33,7 @@ enum Transform { } #[derive(Debug, Clone)] -pub(crate) struct Inlay { +pub struct Inlay { pub(crate) id: InlayId, pub position: Anchor, pub text: text::Rope, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b11832e1b1..cff145f25a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -258,7 +258,7 @@ pub fn render_parsed_markdown( } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) enum InlayId { +pub enum InlayId { InlineCompletion(usize), Hint(usize), } @@ -3592,7 +3592,7 @@ impl Editor { } } - fn splice_inlays( + pub fn splice_inlays( &self, to_remove: Vec, to_insert: Vec, @@ -4883,7 +4883,7 @@ impl Editor { } } - fn inline_completion_provider(&self) -> Option> { + pub fn inline_completion_provider(&self) -> Option> { Some(self.inline_completion_provider.as_ref()?.provider.clone()) } diff --git a/crates/editor/src/inline_completion_tests.rs b/crates/editor/src/inline_completion_tests.rs index 58dbefb36f..82eb286e97 100644 --- a/crates/editor/src/inline_completion_tests.rs +++ b/crates/editor/src/inline_completion_tests.rs @@ -325,6 +325,10 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider { false } + fn show_completions_in_normal_mode() -> bool { + false + } + fn is_enabled( &self, _buffer: &gpui::Model, diff --git a/crates/inline_completion/src/inline_completion.rs b/crates/inline_completion/src/inline_completion.rs index 82323b50af..5f87bd576e 100644 --- a/crates/inline_completion/src/inline_completion.rs +++ b/crates/inline_completion/src/inline_completion.rs @@ -21,6 +21,7 @@ pub trait InlineCompletionProvider: 'static + Sized { fn name() -> &'static str; fn display_name() -> &'static str; fn show_completions_in_menu() -> bool; + fn show_completions_in_normal_mode() -> bool; fn is_enabled( &self, buffer: &Model, @@ -61,6 +62,7 @@ pub trait InlineCompletionProviderHandle { cx: &AppContext, ) -> bool; fn show_completions_in_menu(&self) -> bool; + fn show_completions_in_normal_mode(&self) -> bool; fn refresh( &self, buffer: Model, @@ -101,6 +103,10 @@ where T::show_completions_in_menu() } + fn show_completions_in_normal_mode(&self) -> bool { + T::show_completions_in_normal_mode() + } + fn is_enabled( &self, buffer: &Model, diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index 4d8fa0f873..eea12959c3 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -106,6 +106,10 @@ impl InlineCompletionProvider for SupermavenCompletionProvider { false } + fn show_completions_in_normal_mode() -> bool { + false + } + fn is_enabled(&self, buffer: &Model, cursor_position: Anchor, cx: &AppContext) -> bool { if !self.supermaven.read(cx).is_enabled() { return false; diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index e90a2c98c0..acce32b24d 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1206,6 +1206,7 @@ fn up_down_buffer_rows( times: isize, text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { + let bias = if times < 0 { Bias::Left } else { Bias::Right }; let start = map.display_point_to_fold_point(point, Bias::Left); let begin_folded_line = map.fold_point_to_display_point( map.fold_snapshot @@ -1229,14 +1230,14 @@ fn up_down_buffer_rows( let mut begin_folded_line = map.fold_point_to_display_point( map.fold_snapshot - .clip_point(FoldPoint::new(new_row, 0), Bias::Left), + .clip_point(FoldPoint::new(new_row, 0), bias), ); let mut i = 0; while i < goal_wrap && begin_folded_line.row() < map.max_point().row() { let next_folded_line = DisplayPoint::new(begin_folded_line.row().next_row(), 0); if map - .display_point_to_fold_point(next_folded_line, Bias::Right) + .display_point_to_fold_point(next_folded_line, bias) .row() == new_row { @@ -1254,10 +1255,7 @@ fn up_down_buffer_rows( }; ( - map.clip_point( - DisplayPoint::new(begin_folded_line.row(), new_col), - Bias::Left, - ), + map.clip_point(DisplayPoint::new(begin_folded_line.row(), new_col), bias), goal, ) } @@ -2484,7 +2482,11 @@ fn section_motion( #[cfg(test)] mod test { - use crate::test::NeovimBackedTestContext; + use crate::{ + state::Mode, + test::{NeovimBackedTestContext, VimTestContext}, + }; + use editor::display_map::Inlay; use indoc::indoc; #[gpui::test] @@ -3146,4 +3148,35 @@ mod test { }ˇ» "}); } + + #[gpui::test] + async fn test_clipping_with_inlay_hints(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! {" + struct Foo { + ˇ + } + "}, + Mode::Normal, + ); + + cx.update_editor(|editor, cx| { + let range = editor.selections.newest_anchor().range(); + let inlay_text = " field: int,\n field2: string\n field3: float"; + let inlay = Inlay::inline_completion(1, range.start, inlay_text); + editor.splice_inlays(vec![], vec![inlay], cx); + }); + + cx.simulate_keystrokes("j"); + cx.assert_state( + indoc! {" + struct Foo { + + ˇ} + "}, + Mode::Normal, + ); + } } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index e6240e7e2e..5e64d1c93e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -1196,7 +1196,15 @@ impl Vim { editor.set_input_enabled(vim.editor_input_enabled()); editor.set_autoindent(vim.should_autoindent()); editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine); - editor.set_inline_completions_enabled(matches!(vim.mode, Mode::Insert | Mode::Replace)); + + let enable_inline_completions = match vim.mode { + Mode::Insert | Mode::Replace => true, + Mode::Normal => editor + .inline_completion_provider() + .map_or(false, |provider| provider.show_completions_in_normal_mode()), + _ => false, + }; + editor.set_inline_completions_enabled(enable_inline_completions); }); cx.notify() } diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index 13c2d37171..ade0f565ca 100644 --- a/crates/zeta/src/zeta.rs +++ b/crates/zeta/src/zeta.rs @@ -979,6 +979,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide true } + fn show_completions_in_normal_mode() -> bool { + true + } + fn is_enabled( &self, buffer: &Model,