use std::io::{self, Write};
use crossterm::{
cursor::MoveTo,
event::{self, Event, KeyCode},
execute,
terminal::{
Clear, ClearType, DisableLineWrap, EnableLineWrap, EnterAlternateScreen,
LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewCopyAction {
Exit,
ToggleLineNumbers,
None,
}
#[derive(Debug, Clone)]
pub struct ViewCopyConfig {
pub header: Option<String>,
pub show_hints: bool,
pub exit_keys: Vec<KeyCode>,
pub toggle_key: KeyCode,
}
impl Default for ViewCopyConfig {
fn default() -> Self {
Self {
header: None,
show_hints: true,
exit_keys: vec![KeyCode::Char('c'), KeyCode::Char('q'), KeyCode::Esc],
toggle_key: KeyCode::Char('n'),
}
}
}
impl ViewCopyConfig {
pub fn with_header(mut self, header: impl Into<String>) -> Self {
self.header = Some(header.into());
self
}
pub fn show_hints(mut self, show: bool) -> Self {
self.show_hints = show;
self
}
pub fn exit_keys(mut self, keys: Vec<KeyCode>) -> Self {
self.exit_keys = keys;
self
}
pub fn toggle_key(mut self, key: KeyCode) -> Self {
self.toggle_key = key;
self
}
}
pub struct ViewCopyMode {
config: ViewCopyConfig,
}
impl ViewCopyMode {
pub fn enter<W: Write>(stdout: &mut W) -> io::Result<Self> {
Self::enter_with_config(stdout, ViewCopyConfig::default())
}
pub fn enter_with_config<W: Write>(stdout: &mut W, config: ViewCopyConfig) -> io::Result<Self> {
use crossterm::event::DisableMouseCapture;
execute!(stdout, LeaveAlternateScreen, DisableMouseCapture)?;
disable_raw_mode()?;
execute!(
stdout,
Clear(ClearType::Purge),
Clear(ClearType::All),
MoveTo(0, 0),
DisableLineWrap
)?;
stdout.flush()?;
Ok(Self { config })
}
pub fn clear(&self) -> io::Result<()> {
let mut stdout = io::stdout();
execute!(
stdout,
Clear(ClearType::Purge),
Clear(ClearType::All),
MoveTo(0, 0)
)?;
stdout.flush()?;
Ok(())
}
pub fn print_lines(&self, lines: &[String]) -> io::Result<()> {
if self.config.show_hints {
if let Some(header) = &self.config.header {
println!("=== {} ===", header);
} else {
println!("=== View/Copy Mode ===");
}
println!("Press 'c', 'q', or Esc to exit | 'n' to toggle line numbers");
println!("{}", "─".repeat(60));
println!();
}
for line in lines {
println!("{}", line);
}
if self.config.show_hints {
println!();
println!("{}", "─".repeat(60));
println!("Press 'c', 'q', or Esc to exit | 'n' to toggle line numbers");
}
io::stdout().flush()?;
Ok(())
}
pub fn print_raw(&self, lines: &[String]) -> io::Result<()> {
for line in lines {
println!("{}", line);
}
io::stdout().flush()?;
Ok(())
}
pub fn wait_for_input(&self) -> io::Result<ViewCopyAction> {
enable_raw_mode()?;
let action = loop {
if event::poll(std::time::Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if self.config.exit_keys.contains(&key.code) {
break ViewCopyAction::Exit;
} else if key.code == self.config.toggle_key {
break ViewCopyAction::ToggleLineNumbers;
}
}
}
};
disable_raw_mode()?;
Ok(action)
}
pub fn exit<B>(self, terminal: &mut ratatui::Terminal<B>) -> io::Result<()>
where
B: ratatui::backend::Backend,
io::Error: From<B::Error>,
{
use crossterm::event::EnableMouseCapture;
let mut stdout = io::stdout();
enable_raw_mode()?;
execute!(
stdout,
EnableLineWrap,
EnterAlternateScreen,
EnableMouseCapture
)?;
terminal.clear()?;
Ok(())
}
}
pub fn clear_main_screen() -> io::Result<()> {
let mut stdout = io::stdout();
execute!(
stdout,
Clear(ClearType::Purge),
Clear(ClearType::All),
MoveTo(0, 0)
)?;
stdout.flush()?;
Ok(())
}
#[derive(Debug, Clone)]
pub enum ExitStrategy {
RestoreConsole,
PrintContent(Vec<String>),
}
impl ExitStrategy {
pub fn execute(&self) -> io::Result<()> {
match self {
ExitStrategy::RestoreConsole => {
Ok(())
}
ExitStrategy::PrintContent(lines) => {
let mut stdout = io::stdout();
execute!(
stdout,
Clear(ClearType::Purge),
Clear(ClearType::All),
MoveTo(0, 0)
)?;
for line in lines {
println!("{}", line);
}
stdout.flush()?;
Ok(())
}
}
}
pub fn print_content(lines: &[String]) -> Self {
ExitStrategy::PrintContent(lines.to_vec())
}
pub fn print_content_iter<I, S>(lines: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
ExitStrategy::PrintContent(lines.into_iter().map(|s| s.into()).collect())
}
}