fix: avoid redraw backlogs during rapid mouse scrolling

This commit is contained in:
2026-10-10 01:42:50 +02:00
parent d8e2425c65
commit 6152ae6d36
3 changed files with 102 additions and 6 deletions
+98 -5
View File
@@ -217,6 +217,27 @@ impl BufferViewports {
}
true
}
fn scroll_ready(
&mut self,
session: &mut EditorSession,
mut mouse: MouseEvent,
mut next: impl FnMut() -> io::Result<Option<Event>>,
) -> io::Result<(bool, Option<Event>)> {
let mut redraw = false;
// Bound input work so drawing and background replies cannot starve.
for index in 0..64 {
redraw |= self.scroll(session, mouse);
if index == 63 {
break;
}
match next()? {
Some(Event::Mouse(report)) => mouse = report,
pending => return Ok((redraw, pending)),
}
}
Ok((redraw, None))
}
}
fn draw_frame<W: io::Write>(
@@ -240,6 +261,7 @@ pub fn run(session: &mut EditorSession) -> io::Result<()> {
Terminal::new(CrosstermBackend::new(io::stdout()))?;
let mut viewports = BufferViewports::default();
let mut redraw = true;
let mut pending = None;
while !session.editor().should_quit() {
redraw |= session.poll();
if redraw {
@@ -248,10 +270,15 @@ pub fn run(session: &mut EditorSession) -> io::Result<()> {
})?;
redraw = false;
}
if !event::poll(Duration::from_millis(25))? {
continue;
}
match event::read()? {
let input = if let Some(input) = pending.take() {
input
} else {
if !event::poll(Duration::from_millis(25))? {
continue;
}
event::read()?
};
match input {
Event::Key(key) if key.kind != KeyEventKind::Release => {
if let Some(input) = translate(key) {
viewports.panels.follow_files();
@@ -264,7 +291,17 @@ pub fn run(session: &mut EditorSession) -> io::Result<()> {
session.handle(Input::Paste(text));
redraw = true;
}
Event::Mouse(mouse) => redraw |= viewports.scroll(session, mouse),
Event::Mouse(mouse) => {
let (changed, following) = viewports.scroll_ready(session, mouse, || {
if event::poll(Duration::ZERO)? {
event::read().map(Some)
} else {
Ok(None)
}
})?;
redraw |= changed;
pending = following;
}
Event::Resize(_, _) => redraw = true,
_ => {}
}
@@ -4182,6 +4219,62 @@ end
assert!(terminal.backend().cursor_visible());
}
#[test]
fn queued_wheel_input_renders_latest_view_without_crossing_key_or_resize_barriers() {
let directory = Directory::new();
let text = (0..24)
.map(|row| format!("row-{row:03}\n"))
.collect::<String>();
let path = directory.file("rows.txt", &text);
let mut session = EditorSession::open(Some(path), vec![], None).unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(20, 6)).unwrap();
let mut viewports = BufferViewports::default();
let down = Event::Mouse(document_wheel(MouseEventKind::ScrollDown, 1, 1));
let up = Event::Mouse(document_wheel(MouseEventKind::ScrollUp, 1, 1));
for barrier in [
Event::Key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE)),
Event::Resize(40, 12),
] {
terminal
.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
let mut queued = [
down.clone(),
up.clone(),
down.clone(),
barrier.clone(),
down.clone(),
]
.into_iter();
let (redraw, pending) = viewports
.scroll_ready(
&mut session,
document_wheel(MouseEventKind::ScrollDown, 1, 1),
|| Ok(queued.next()),
)
.unwrap();
assert!(redraw);
terminal
.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
assert!(
screen_text(&terminal)
.lines()
.next()
.unwrap()
.contains("row-006")
);
assert_eq!(pending, Some(barrier));
assert_eq!(queued.next(), Some(down.clone()));
assert_eq!(session.editor().cursor(), Cursor::default());
assert_eq!(session.editor().mode(), Mode::Normal);
assert!(!terminal.backend().cursor_visible());
session.handle(Input::Up);
}
assert_eq!(session.editor().lines().join("\n"), text);
}
#[test]
fn no_op_navigation_restores_caret_following_through_core_dispatch() {
let directory = Directory::new();