use std::ffi::OsStr;
use std::io::Read;
use std::sync::Arc;
use termwiz::caps::ColorLevel;
use termwiz::caps::{Capabilities, ProbeHints};
use termwiz::terminal::{SystemTerminal, Terminal};
use vec_map::VecMap;
use crate::action::ActionSender;
use crate::bindings::Keymap;
use crate::config::{Config, InterfaceMode, KeymapConfig, WrappingMode};
use crate::control::Controller;
use crate::error::{Error, Result};
use crate::event::EventStream;
use crate::file::{ControlledFile, File, FileIndex, FileInfo, LoadedFile};
use crate::progress::Progress;
pub struct Pager {
term: SystemTerminal,
caps: Capabilities,
events: EventStream,
files: Vec<File>,
error_files: VecMap<File>,
progress: Option<Progress>,
config: Config,
}
fn termcaps() -> Result<Capabilities> {
let hints = ProbeHints::new_from_env()
.color_level(Some(ColorLevel::TrueColor))
.mouse_reporting(Some(false));
let caps = Capabilities::new_with_hints(hints).map_err(Error::Termwiz)?;
if cfg!(unix) && caps.terminfo_db().is_none() {
Err(Error::TerminfoDatabaseMissing)
} else {
Ok(caps)
}
}
impl Pager {
pub fn new_using_system_terminal() -> Result<Self> {
Self::new_using_system_terminal_with_config(Config::from_user_config())
}
pub fn new_using_system_terminal_with_config(config: Config) -> Result<Self> {
Self::new_with_terminal_func_with_config(
move |caps| SystemTerminal::new(caps).map_err(Error::Termwiz),
config,
)
}
pub fn new_using_stdio() -> Result<Self> {
Self::new_using_stdio_with_config(Config::from_user_config())
}
pub fn new_using_stdio_with_config(config: Config) -> Result<Self> {
Self::new_with_terminal_func_with_config(
move |caps| SystemTerminal::new_from_stdio(caps).map_err(Error::Termwiz),
config,
)
}
#[cfg(unix)]
pub fn new_with_input_output(
input: &impl std::os::unix::io::AsRawFd,
output: &impl std::os::unix::io::AsRawFd,
) -> Result<Self> {
Self::new_with_input_output_with_config(input, output, Config::from_user_config())
}
#[cfg(unix)]
pub fn new_with_input_output_with_config(
input: &impl std::os::unix::io::AsRawFd,
output: &impl std::os::unix::io::AsRawFd,
config: Config,
) -> Result<Self> {
Self::new_with_terminal_func_with_config(
move |caps| SystemTerminal::new_with(caps, input, output).map_err(Error::Termwiz),
config,
)
}
#[cfg(windows)]
pub fn new_with_input_output(
input: impl std::io::Read + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
output: impl std::io::Write + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
) -> Result<Self> {
Self::new_with_input_output_with_config(input, output, Config::from_user_config())
}
#[cfg(windows)]
pub fn new_with_input_output_with_config(
input: impl std::io::Read + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
output: impl std::io::Write + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
config: Config,
) -> Result<Self> {
Self::new_with_terminal_func_with_config(
move |caps| SystemTerminal::new_with(caps, input, output).map_err(Error::Termwiz),
config,
)
}
fn new_with_terminal_func_with_config(
create_term: impl FnOnce(Capabilities) -> Result<SystemTerminal>,
config: Config,
) -> Result<Self> {
let caps = termcaps()?;
let term = create_term(caps.clone())?;
let events = EventStream::new(term.waker());
let files = Vec::new();
let error_files = VecMap::new();
let progress = None;
Ok(Self {
term,
caps,
events,
files,
error_files,
progress,
config,
})
}
pub fn add_stream(
&mut self,
stream: impl Read + Send + 'static,
title: &str,
) -> Result<FileIndex> {
let index = self.files.len();
let event_sender = self.events.sender();
let file = LoadedFile::new_streamed(index, stream, title, event_sender);
self.files.push(file.into());
Ok(index)
}
pub fn add_error_stream(
&mut self,
stream: impl Read + Send + 'static,
title: &str,
) -> Result<FileIndex> {
let index = self.files.len();
let event_sender = self.events.sender();
let file = LoadedFile::new_streamed(index, stream, title, event_sender);
if let Some(out_file) = self.files.last() {
self.error_files
.insert(out_file.index(), file.clone().into());
}
self.files.push(file.into());
Ok(index)
}
#[cfg(feature = "load_file")]
pub fn add_file(&mut self, filename: &OsStr) -> Result<FileIndex> {
let index = self.files.len();
let event_sender = self.events.sender();
let file = LoadedFile::new_file(index, filename, event_sender)?;
self.files.push(file.into());
Ok(index)
}
pub fn add_controlled_file(&mut self, controller: &Controller) -> Result<FileIndex> {
let index = self.files.len();
let event_sender = self.events.sender();
let file = ControlledFile::new(controller, index, event_sender);
self.files.push(file.into());
Ok(index)
}
pub fn add_subprocess<I, S>(
&mut self,
command: &OsStr,
args: I,
title: &str,
) -> Result<(FileIndex, FileIndex)>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let index = self.files.len();
let event_sender = self.events.sender();
let (out_file, err_file) =
LoadedFile::new_command(index, command, args, title, event_sender)?;
self.error_files.insert(index, err_file.clone().into());
self.files.push(out_file.into());
self.files.push(err_file.into());
Ok((index, index + 1))
}
pub fn set_progress_stream(&mut self, stream: impl Read + Send + 'static) {
let event_sender = self.events.sender();
self.progress = Some(Progress::new(stream, event_sender));
}
pub fn set_interface_mode(&mut self, value: impl Into<InterfaceMode>) {
self.config.interface_mode = value.into();
}
pub fn set_scroll_past_eof(&mut self, value: bool) {
self.config.scroll_past_eof = value;
}
pub fn set_read_ahead_lines(&mut self, lines: usize) {
self.config.read_ahead_lines = lines;
}
pub fn set_startup_poll_input(&mut self, poll_input: bool) {
self.config.startup_poll_input = poll_input;
}
pub fn set_show_ruler(&mut self, show_ruler: bool) {
self.config.show_ruler = show_ruler;
}
pub fn set_wrapping_mode(&mut self, value: impl Into<WrappingMode>) {
self.config.wrapping_mode = value.into();
}
pub fn set_keymap_name(&mut self, keymap: impl Into<String>) {
self.config.keymap = KeymapConfig::Name(keymap.into());
}
pub fn set_keymap(&mut self, keymap: Keymap) {
self.config.keymap = KeymapConfig::Keymap(Arc::new(keymap));
}
pub fn action_sender(&self) -> ActionSender {
self.events.action_sender()
}
pub fn run(self) -> Result<()> {
crate::display::start(
self.term,
self.caps,
self.events,
self.files,
self.error_files,
self.progress,
self.config,
)
}
}