This commit is contained in:
Dino 2025-08-27 00:41:03 +08:00 committed by GitHub
commit 1a6fa47c5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 155 additions and 15 deletions

View file

@ -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": {

View file

@ -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)]

View file

@ -188,22 +188,27 @@ 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);
return true;
};
popover.scroll(amount, window, cx);
true
if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
context_menu.scroll_aside(amount, window, cx);
return true;
}
return false;
}
fn cmd_click_reveal_task(

View file

@ -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),

View file

@ -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);

View file

@ -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;

View file

@ -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(),