fix: coalesce rapid viewport scrolling redraws

This commit is contained in:
Valdemar Lorenzen
2026-10-16 10:15:24 +02:00
parent 08fdf2bd0f
commit 5a8c37b2f2
3 changed files with 90 additions and 0 deletions
+16
View File
@@ -4457,6 +4457,10 @@ impl Editor {
| Action::Right
| Action::Down
| Action::Up
| Action::ScrollUp
| Action::ScrollDown
| Action::ScrollHalfUp
| Action::ScrollHalfDown
| Action::LineStart
| Action::LineEnd
| Action::DocumentStart
@@ -4472,6 +4476,18 @@ impl Editor {
)
}
pub(crate) fn viewport_scroll_action(&self, input: &Input) -> Option<&'static str> {
match self.bindings.get(&self.active.view.mode)?.get(input)? {
Binding::Action(
action @ (Action::ScrollUp
| Action::ScrollDown
| Action::ScrollHalfUp
| Action::ScrollHalfDown),
) => Some(action.entry().name),
_ => None,
}
}
pub fn diff_editable(&self) -> bool {
self.diff().is_some_and(|diff| {
self.diff_buffer_focus()
+73
View File
@@ -1433,6 +1433,8 @@ fn keys_ready(
mut next: impl FnMut() -> io::Result<Option<Event>>,
) -> io::Result<Option<Event>> {
let mode = session.editor().mode();
// Render before reversing/clamping a scroll or revealing a moved caret.
let scroll = session.editor().viewport_scroll_action(&input);
for handled in 1..=64 {
session.handle(input);
if handled == 64 || session.editor().mode() != mode || session.editor().should_quit() {
@@ -1442,6 +1444,7 @@ fn keys_ready(
if let Some(Event::Key(key)) = &pending
&& let Some(next) = translate(*key)
&& batch_key(session.editor(), key, &next)
&& session.editor().viewport_scroll_action(&next) == scroll
{
input = next;
} else {
@@ -17998,6 +18001,76 @@ end
assert!(terminal.backend().cursor_visible());
}
#[test]
fn queued_scrolls_coalesce_but_preserve_reversals_and_motion_barriers() {
let directory = Directory::new();
let text = (0..100)
.map(|row| format!("row-{row}\n"))
.collect::<String>();
for (first, repeated, barrier) in [
(
Input::Ctrl('u'),
'u',
KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL),
),
(
Input::Ctrl('d'),
'd',
KeyEvent::new(KeyCode::Home, KeyModifiers::NONE),
),
(
Input::Down,
'j',
KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL),
),
] {
let mut session =
EditorSession::open(Some(directory.file("rows.txt", &text)), vec![], None).unwrap();
let mut views = BufferViewports::default();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(80, 12)).unwrap();
terminal
.draw(|frame| views.draw_session(frame, &mut session))
.unwrap();
let modifiers = if repeated == 'j' {
KeyModifiers::NONE
} else {
KeyModifiers::CONTROL
};
let mut events = std::iter::repeat_n(
Event::Key(KeyEvent::new(KeyCode::Char(repeated), modifiers)),
7,
)
.chain([Event::Key(barrier)]);
assert_eq!(
keys_ready(&mut session, first, || Ok(events.next())).unwrap(),
Some(Event::Key(barrier))
);
terminal
.draw(|frame| views.draw_session(frame, &mut session))
.unwrap();
if repeated == 'u' {
assert_eq!(views.offsets[&session.editor().buffer_id()].0, 0);
session.handle(Input::Ctrl('d'));
terminal
.draw(|frame| views.draw_session(frame, &mut session))
.unwrap();
assert_eq!(
views.offsets[&session.editor().buffer_id()].0,
usize::from(views.document.geometry.unwrap().1.height) / 2
);
} else if repeated == 'd' {
assert_eq!(views.offsets[&session.editor().buffer_id()].0, 40);
session.handle(Input::Home);
terminal
.draw(|frame| views.draw_session(frame, &mut session))
.unwrap();
assert_eq!(views.offsets[&session.editor().buffer_id()].0, 0);
} else {
assert_eq!(session.editor().cursor().row, 8);
}
}
}
#[test]
fn queued_navigation_preserves_counted_motions_visual_ranges_and_remaps() {
let directory = Directory::new();