enable alternate scroll in transcript mode (#2686)

this allows the mouse wheel to scroll the transcript / diff views.
This commit is contained in:
Jeremy Rose
2025-08-26 11:47:00 -07:00
committed by GitHub
parent 274d9b413f
commit db98d2ce25
2 changed files with 65 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
use std::io::Result;
use std::time::Duration;
use crate::insert_history;
use crate::tui;
@@ -227,6 +228,7 @@ impl TranscriptApp {
}
fn handle_key_event(&mut self, tui: &mut tui::Tui, key_event: KeyEvent) {
let mut defer_draw_ms: Option<u64> = None;
match key_event {
// Ctrl+Z is handled at the App level when transcript overlay is active
KeyEvent {
@@ -254,6 +256,7 @@ impl TranscriptApp {
..
} => {
self.scroll_offset = self.scroll_offset.saturating_sub(1);
defer_draw_ms = Some(16);
}
KeyEvent {
code: KeyCode::Down,
@@ -261,6 +264,7 @@ impl TranscriptApp {
..
} => {
self.scroll_offset = self.scroll_offset.saturating_add(1);
defer_draw_ms = Some(16);
}
KeyEvent {
code: KeyCode::PageUp,
@@ -269,6 +273,7 @@ impl TranscriptApp {
} => {
let area = self.scroll_area(tui.terminal.viewport_area);
self.scroll_offset = self.scroll_offset.saturating_sub(area.height as usize);
defer_draw_ms = Some(16);
}
KeyEvent {
code: KeyCode::PageDown | KeyCode::Char(' '),
@@ -277,6 +282,7 @@ impl TranscriptApp {
} => {
let area = self.scroll_area(tui.terminal.viewport_area);
self.scroll_offset = self.scroll_offset.saturating_add(area.height as usize);
defer_draw_ms = Some(16);
}
KeyEvent {
code: KeyCode::Home,
@@ -284,6 +290,7 @@ impl TranscriptApp {
..
} => {
self.scroll_offset = 0;
defer_draw_ms = Some(16);
}
KeyEvent {
code: KeyCode::End,
@@ -291,12 +298,18 @@ impl TranscriptApp {
..
} => {
self.scroll_offset = usize::MAX;
defer_draw_ms = Some(16);
}
_ => {
return;
}
}
tui.frame_requester().schedule_frame();
if let Some(ms) = defer_draw_ms {
tui.frame_requester()
.schedule_frame_in(Duration::from_millis(ms));
} else {
tui.frame_requester().schedule_frame();
}
}
fn scroll_area(&self, area: Rect) -> Rect {

View File

@@ -11,6 +11,7 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use crossterm::Command;
use crossterm::SynchronizedUpdate;
use crossterm::cursor;
use crossterm::cursor::MoveTo;
@@ -65,6 +66,48 @@ pub fn set_modes() -> Result<()> {
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EnableAlternateScroll;
impl Command for EnableAlternateScroll {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
write!(f, "\x1b[?1007h")
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
Err(std::io::Error::other(
"tried to execute EnableAlternateScroll using WinAPI; use ANSI instead",
))
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct DisableAlternateScroll;
impl Command for DisableAlternateScroll {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
write!(f, "\x1b[?1007l")
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
Err(std::io::Error::other(
"tried to execute DisableAlternateScroll using WinAPI; use ANSI instead",
))
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
/// Restore the terminal to its original state.
/// Inverse of `set_modes`.
pub fn restore() -> Result<()> {
@@ -290,6 +333,8 @@ impl Tui {
)
{
if alt_screen_active.load(Ordering::Relaxed) {
// Disable alternate scroll when suspending from alt-screen
let _ = execute!(stdout(), DisableAlternateScroll);
let _ = execute!(stdout(), LeaveAlternateScreen);
resume_pending.store(ResumeAction::RestoreAlt as u8, Ordering::Relaxed);
} else {
@@ -370,6 +415,8 @@ impl Tui {
}
PreparedResumeAction::RestoreAltScreen => {
execute!(self.terminal.backend_mut(), EnterAlternateScreen)?;
// Enable "alternate scroll" so terminals may translate wheel to arrows
execute!(self.terminal.backend_mut(), EnableAlternateScroll)?;
if let Ok(size) = self.terminal.size() {
self.terminal.set_viewport_area(ratatui::layout::Rect::new(
0,
@@ -388,6 +435,8 @@ impl Tui {
/// inline viewport for restoration when leaving.
pub fn enter_alt_screen(&mut self) -> Result<()> {
let _ = execute!(self.terminal.backend_mut(), EnterAlternateScreen);
// Enable "alternate scroll" so terminals may translate wheel to arrows
let _ = execute!(self.terminal.backend_mut(), EnableAlternateScroll);
if let Ok(size) = self.terminal.size() {
self.alt_saved_viewport = Some(self.terminal.viewport_area);
self.terminal.set_viewport_area(ratatui::layout::Rect::new(
@@ -404,6 +453,8 @@ impl Tui {
/// Leave alternate screen and restore the previously saved inline viewport, if any.
pub fn leave_alt_screen(&mut self) -> Result<()> {
// Disable alternate scroll when leaving alt-screen
let _ = execute!(self.terminal.backend_mut(), DisableAlternateScroll);
let _ = execute!(self.terminal.backend_mut(), LeaveAlternateScreen);
if let Some(saved) = self.alt_saved_viewport.take() {
self.terminal.set_viewport_area(saved);