Detailed changes
@@ -324,7 +324,7 @@
}
},
{
- "context": "vim_mode == insert",
+ "context": "vim_mode == insert && !menu",
"bindings": {
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
@@ -354,6 +354,15 @@
"ctrl-s": "editor::ShowSignatureHelp"
}
},
+ {
+ "context": "showing_completions",
+ "bindings": {
+ "ctrl-d": "vim::ScrollDown",
+ "ctrl-u": "vim::ScrollUp",
+ "ctrl-e": "vim::LineDown",
+ "ctrl-y": "vim::LineUp"
+ }
+ },
{
"context": "(vim_mode == normal || vim_mode == helix_normal) && !menu",
"bindings": {
@@ -1,7 +1,9 @@
+use crate::scroll::ScrollAmount;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollStrategy, SharedString,
- Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px, uniform_list,
+ AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollHandle, ScrollStrategy,
+ SharedString, Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px,
+ uniform_list,
};
use itertools::Itertools;
use language::CodeLabel;
@@ -184,6 +186,20 @@ impl CodeContextMenu {
CodeContextMenu::CodeActions(_) => false,
}
}
+
+ pub fn scroll_aside(
+ &mut self,
+ scroll_amount: ScrollAmount,
+ window: &mut Window,
+ cx: &mut Context<Editor>,
+ ) {
+ match self {
+ CodeContextMenu::Completions(completions_menu) => {
+ completions_menu.scroll_aside(scroll_amount, window, cx)
+ }
+ CodeContextMenu::CodeActions(_) => (),
+ }
+ }
}
pub enum ContextMenuOrigin {
@@ -207,6 +223,9 @@ pub struct CompletionsMenu {
filter_task: Task<()>,
cancel_filter: Arc<AtomicBool>,
scroll_handle: UniformListScrollHandle,
+ // The `ScrollHandle` used on the Markdown documentation rendered on the
+ // side of the completions menu.
+ pub scroll_handle_aside: ScrollHandle,
resolve_completions: bool,
show_completion_documentation: bool,
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
@@ -279,6 +298,7 @@ impl CompletionsMenu {
filter_task: Task::ready(()),
cancel_filter: Arc::new(AtomicBool::new(false)),
scroll_handle: UniformListScrollHandle::new(),
+ scroll_handle_aside: ScrollHandle::new(),
resolve_completions: true,
last_rendered_range: RefCell::new(None).into(),
markdown_cache: RefCell::new(VecDeque::new()).into(),
@@ -348,6 +368,7 @@ impl CompletionsMenu {
filter_task: Task::ready(()),
cancel_filter: Arc::new(AtomicBool::new(false)),
scroll_handle: UniformListScrollHandle::new(),
+ scroll_handle_aside: ScrollHandle::new(),
resolve_completions: false,
show_completion_documentation: false,
last_rendered_range: RefCell::new(None).into(),
@@ -911,6 +932,7 @@ impl CompletionsMenu {
.max_w(max_size.width)
.max_h(max_size.height)
.overflow_y_scroll()
+ .track_scroll(&self.scroll_handle_aside)
.occlude(),
)
.into_any_element(),
@@ -1175,6 +1197,23 @@ impl CompletionsMenu {
}
});
}
+
+ pub fn scroll_aside(
+ &mut self,
+ amount: ScrollAmount,
+ window: &mut Window,
+ cx: &mut Context<Editor>,
+ ) {
+ let mut offset = self.scroll_handle_aside.offset();
+
+ offset.y -= amount.pixels(
+ window.line_height(),
+ self.scroll_handle_aside.bounds().size.height - px(16.),
+ ) / 2.0;
+
+ cx.notify();
+ self.scroll_handle_aside.set_offset(offset);
+ }
}
#[derive(Clone)]
@@ -188,22 +188,26 @@ impl Editor {
pub fn scroll_hover(
&mut self,
- amount: &ScrollAmount,
+ amount: ScrollAmount,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
let selection = self.selections.newest_anchor().head();
let snapshot = self.snapshot(window, cx);
- let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {
+ if let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {
popover
.symbol_range
.point_within_range(&TriggerPoint::Text(selection), &snapshot)
- }) else {
- return false;
- };
- popover.scroll(amount, window, cx);
- true
+ }) {
+ popover.scroll(amount, window, cx);
+ true
+ } else if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
+ context_menu.scroll_aside(amount, window, cx);
+ true
+ } else {
+ false
+ }
}
fn cmd_click_reveal_task(
@@ -896,7 +896,7 @@ impl InfoPopover {
.into_any_element()
}
- pub fn scroll(&self, amount: &ScrollAmount, window: &mut Window, cx: &mut Context<Editor>) {
+ pub fn scroll(&self, amount: ScrollAmount, window: &mut Window, cx: &mut Context<Editor>) {
let mut current = self.scroll_handle.offset();
current.y -= amount.pixels(
window.line_height(),
@@ -15,7 +15,7 @@ impl ScrollDirection {
}
}
-#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
pub enum ScrollAmount {
// Scroll N lines (positive is towards the end of the document)
Line(f32),
@@ -98,7 +98,7 @@ impl Vim {
Vim::take_forced_motion(cx);
self.exit_temporary_normal(window, cx);
self.update_editor(cx, |_, editor, cx| {
- scroll_editor(editor, move_cursor, &amount, window, cx)
+ scroll_editor(editor, move_cursor, amount, window, cx)
});
}
}
@@ -106,7 +106,7 @@ impl Vim {
fn scroll_editor(
editor: &mut Editor,
preserve_cursor_position: bool,
- amount: &ScrollAmount,
+ amount: ScrollAmount,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -126,7 +126,7 @@ fn scroll_editor(
ScrollAmount::Line(amount.lines(visible_line_count) - 1.0)
}
}
- _ => amount.clone(),
+ _ => amount,
};
editor.scroll_screen(&amount, window, cx);
@@ -8,13 +8,15 @@ use collections::HashMap;
use command_palette::CommandPalette;
use editor::{
AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, actions::DeleteLine,
- display_map::DisplayRow, test::editor_test_context::EditorTestContext,
+ code_context_menus::CodeContextMenu, display_map::DisplayRow,
+ test::editor_test_context::EditorTestContext,
};
use futures::StreamExt;
-use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
+use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext, px};
use language::Point;
pub use neovim_backed_test_context::*;
use settings::SettingsStore;
+use ui::Pixels;
use util::test::marked_text_ranges;
pub use vim_test_context::*;
@@ -971,6 +973,87 @@ async fn test_comma_w(cx: &mut gpui::TestAppContext) {
.assert_eq("hellΛo hello\nhello hello");
}
+#[gpui::test]
+async fn test_completion_menu_scroll_aside(cx: &mut TestAppContext) {
+ let mut cx = VimTestContext::new_typescript(cx).await;
+
+ cx.lsp
+ .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
+ Ok(Some(lsp::CompletionResponse::Array(vec![
+ lsp::CompletionItem {
+ label: "Test Item".to_string(),
+ documentation: Some(lsp::Documentation::String(
+ "This is some very long documentation content that will be displayed in the aside panel for scrolling.\n".repeat(50)
+ )),
+ ..Default::default()
+ },
+ ])))
+ });
+
+ cx.set_state("variableΛ", Mode::Insert);
+ cx.simulate_keystroke(".");
+ cx.executor().run_until_parked();
+
+ let mut initial_offset: Pixels = px(0.0);
+
+ cx.update_editor(|editor, _, _| {
+ let binding = editor.context_menu().borrow();
+ let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
+ panic!("Should have completions menu open");
+ };
+
+ initial_offset = menu.scroll_handle_aside.offset().y;
+ });
+
+ // The `ctrl-e` shortcut should scroll the completion menu's aside content
+ // down, so the updated offset should be lower than the initial offset.
+ cx.simulate_keystroke("ctrl-e");
+ cx.update_editor(|editor, _, _| {
+ let binding = editor.context_menu().borrow();
+ let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
+ panic!("Should have completions menu open");
+ };
+
+ assert!(menu.scroll_handle_aside.offset().y < initial_offset);
+ });
+
+ // The `ctrl-y` shortcut should do the inverse scrolling as `ctrl-e`, so the
+ // offset should now be the same as the initial offset.
+ cx.simulate_keystroke("ctrl-y");
+ cx.update_editor(|editor, _, _| {
+ let binding = editor.context_menu().borrow();
+ let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
+ panic!("Should have completions menu open");
+ };
+
+ assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
+ });
+
+ // The `ctrl-d` shortcut should scroll the completion menu's aside content
+ // down, so the updated offset should be lower than the initial offset.
+ cx.simulate_keystroke("ctrl-d");
+ cx.update_editor(|editor, _, _| {
+ let binding = editor.context_menu().borrow();
+ let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
+ panic!("Should have completions menu open");
+ };
+
+ assert!(menu.scroll_handle_aside.offset().y < initial_offset);
+ });
+
+ // The `ctrl-u` shortcut should do the inverse scrolling as `ctrl-u`, so the
+ // offset should now be the same as the initial offset.
+ cx.simulate_keystroke("ctrl-u");
+ cx.update_editor(|editor, _, _| {
+ let binding = editor.context_menu().borrow();
+ let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
+ panic!("Should have completions menu open");
+ };
+
+ assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
+ });
+}
+
#[gpui::test]
async fn test_rename(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new_typescript(cx).await;
@@ -49,6 +49,10 @@ impl VimTestContext {
Self::new_with_lsp(
EditorLspTestContext::new_typescript(
lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ trigger_characters: Some(vec![".".to_string()]),
+ ..Default::default()
+ }),
rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: Default::default(),