From ec47566cb0af0fab62e00a65fd1f5aff7a867e79 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Thu, 26 Jun 2025 17:14:10 +0200 Subject: [PATCH] radicle-term: Remove custom pager I learned that there is an implementation of a pager in `crates/radicle-term/src/pager.rs`. It does not make sense to me to maintain a pager in `heartwood`, this is too far from the core of what we are trying to achieve and adds unnecessary maintenance burden. We can allow ourselves to defer to the paging done by `git` or just call the user's `$PAGER`. Even though falling back to unpaged output might not be the nicest thing, I still prefer that over maintaining the pager. It also was frustrating to me that the pager does not support things like search (like `less` does). This also significantly decreases our dependence on `termion`. The pager was originally implemented by cloudhead in 40f4383bbc94acbcadd06fce41e2960a6ffb9c33 and integrated into `rad diff` in 6dd52c94fb1472e251110e439fe656aaed74019f This commit restores the behaviour introduced in 84a95848b02de5599f5a2dc905fa2ba4c185b7ab (before there was a custom pager implementation). --- crates/radicle-cli/src/commands/diff.rs | 2 +- crates/radicle-term/src/lib.rs | 1 - crates/radicle-term/src/pager.rs | 186 ------------------------ 3 files changed, 1 insertion(+), 188 deletions(-) delete mode 100644 crates/radicle-term/src/pager.rs diff --git a/crates/radicle-cli/src/commands/diff.rs b/crates/radicle-cli/src/commands/diff.rs index 78f2ee58..fbf28b10 100644 --- a/crates/radicle-cli/src/commands/diff.rs +++ b/crates/radicle-cli/src/commands/diff.rs @@ -145,7 +145,7 @@ pub fn run(options: Options, _ctx: impl term::Context) -> anyhow::Result<()> { let mut hi = Highlighter::default(); let pretty = diff.pretty(&mut hi, &(), &repo); - term::pager::page(pretty)?; + crate::pager::run(pretty)?; Ok(()) } diff --git a/crates/radicle-term/src/lib.rs b/crates/radicle-term/src/lib.rs index b7fd4f05..7102da48 100644 --- a/crates/radicle-term/src/lib.rs +++ b/crates/radicle-term/src/lib.rs @@ -8,7 +8,6 @@ pub mod format; pub mod hstack; pub mod io; pub mod label; -pub mod pager; pub mod spinner; pub mod table; pub mod textarea; diff --git a/crates/radicle-term/src/pager.rs b/crates/radicle-term/src/pager.rs deleted file mode 100644 index a783e501..00000000 --- a/crates/radicle-term/src/pager.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::io::{IsTerminal, Write}; -use std::{io, thread}; - -use crate::element::Size; -use crate::{Constraint, Element, Line, Paint}; - -use crossbeam_channel as chan; -use radicle_signals as signals; -use termion::event::{Event, Key, MouseButton, MouseEvent}; -use termion::{input::TermRead, raw::IntoRawMode, screen::IntoAlternateScreen}; - -/// How many lines to scroll when the mouse wheel is used. -const MOUSE_SCROLL_LINES: usize = 3; - -/// Pager error. -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error(transparent)] - Io(#[from] io::Error), - #[error(transparent)] - Channel(#[from] chan::RecvError), -} - -/// A pager for the given element. Re-renders the element when the terminal is resized so that -/// it doesn't wrap. If the output device is not a TTY, just prints the element via -/// [`Element::print`]. -/// -/// # Signal Handling -/// -/// This will install handlers for the pager until finished by the user, with there -/// being only one element handling signals at a time. If the pager cannot install -/// handlers, then it will return with an error. -pub fn page(element: E) -> Result<(), Error> { - let (events_tx, events_rx) = chan::unbounded(); - let (signals_tx, signals_rx) = chan::unbounded(); - - signals::install(signals_tx)?; - - thread::spawn(move || { - for e in io::stdin().events() { - events_tx.send(e).ok(); - } - }); - let result = thread::spawn(move || main(element, signals_rx, events_rx)) - .join() - .unwrap(); - - signals::uninstall()?; - - result -} - -fn main( - element: E, - signals_rx: chan::Receiver, - events_rx: chan::Receiver>, -) -> Result<(), Error> { - let stdout = io::stdout(); - if !stdout.is_terminal() { - element.print(); - return Ok(()); - } - let raw = stdout.into_raw_mode()?; - let mut stdout = termion::input::MouseTerminal::from(raw).into_alternate_screen()?; - let (mut width, mut height) = termion::terminal_size()?; - let mut lines = element.render(Constraint::max(Size::new(width as usize, height as usize))); - let mut line = 0; - - render(&mut stdout, lines.as_slice(), line, (width, height))?; - - loop { - chan::select! { - recv(signals_rx) -> signal => { - match signal? { - signals::Signal::WindowChanged => { - let (w, h) = termion::terminal_size()?; - - lines = element.render(Constraint::max(Size::new(w as usize, h as usize))); - width = w; - height = h; - } - signals::Signal::Interrupt | signals::Signal::Terminate => { - break; - } - _ => continue, - } - } - recv(events_rx) -> event => { - let event = event??; - let page = height as usize - 1; // Don't count the status bar. - let end = if page > lines.len() { 0 } else { lines.len() - page }; - let prev = line; - - match event { - Event::Key(key) => match key { - Key::Up | Key::Char('k') => { - line = line.saturating_sub(1); - } - Key::Home => { - line = 0; - } - Key::End | Key::Char('G') => { - line = end; - } - Key::PageUp | Key::Char('b') => { - line = line.saturating_sub(page); - } - Key::PageDown | Key::Char(' ') => { - line = (line + page).min(end); - } - Key::Down | Key::Char('j') => { - if line < end { - line += 1; - } - } - Key::Char('q') => break, - - _ => continue, - } - Event::Mouse(MouseEvent::Press(MouseButton::WheelDown, _, _)) => { - if line < end { - line += MOUSE_SCROLL_LINES; - } - } - Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, _, _)) => { - line = line.saturating_sub(MOUSE_SCROLL_LINES); - } - _ => continue, - } - // Don't re-render if there's no change in line. - if line == prev { - continue; - } - } - } - render(&mut stdout, &lines, line, (width, height))?; - } - Ok(()) -} - -fn render( - out: &mut W, - lines: &[Line], - start_line: usize, - (width, height): (u16, u16), -) -> io::Result<()> { - write!( - out, - "{}{}", - termion::clear::All, - termion::cursor::Goto(1, 1) - )?; - - let content_length = lines.len(); - let window_size = height as usize - 1; - let end_line = if start_line + window_size > content_length { - content_length - } else { - start_line + window_size - }; - // Render content. - for (ix, line) in lines[start_line..end_line].iter().enumerate() { - write!(out, "{}{}", termion::cursor::Goto(1, ix as u16 + 1), line)?; - } - // Render progress meter. - write!( - out, - "{}{}", - termion::cursor::Goto(width - 3, height), - Paint::new(format!( - "{:.0}%", - end_line as f64 / lines.len() as f64 * 100. - )) - .dim() - )?; - // Render cursor input area. - write!( - out, - "{}{}", - termion::cursor::Goto(1, height), - Paint::new(":").dim() - )?; - out.flush()?; - - Ok(()) -}