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(()) -}