use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use scopeguard::guard;
use termwiz::caps::Capabilities as TermCapabilities;
use termwiz::cell::CellAttributes;
use termwiz::color::ColorAttribute;
use termwiz::input::InputEvent;
use termwiz::surface::change::Change;
use termwiz::surface::{CursorVisibility, Position};
use termwiz::terminal::Terminal;
use vec_map::VecMap;
use crate::command;
use crate::config::Config;
use crate::direct;
use crate::error::Error;
use crate::event::{Event, EventStream, UniqueInstance};
use crate::file::{File, FileIndex, FileInfo, LoadedFile};
use crate::help::help_text;
use crate::progress::Progress;
use crate::screen::Screen;
use crate::search::SearchKind;
#[derive(Default)]
pub(crate) struct Capabilities {
pub(crate) scroll_up: bool,
pub(crate) scroll_down: bool,
}
impl Capabilities {
fn new(term_caps: TermCapabilities) -> Capabilities {
use terminfo::capability as cap;
let mut caps = Capabilities::default();
if let Some(db) = term_caps.terminfo_db() {
if db.get::<cap::ChangeScrollRegion>().is_some() {
caps.scroll_up = db.get::<cap::ParmIndex>().is_some()
|| (db.get::<cap::CursorAddress>().is_some()
&& db.get::<cap::ScrollForward>().is_some());
caps.scroll_down = db.get::<cap::ParmRindex>().is_some()
|| (db.get::<cap::CursorAddress>().is_some()
&& db.get::<cap::ScrollReverse>().is_some());
}
}
caps
}
}
pub(crate) enum DisplayAction {
None,
Run(Box<dyn FnMut(&mut Screen) -> Result<DisplayAction, Error>>),
Change(Change),
Render,
Refresh,
RefreshPrompt,
NextFile,
PreviousFile,
ShowHelp,
ClearOverlay,
Quit,
}
struct Screens {
screens: Vec<Screen>,
overlay: Option<Screen>,
current_index: FileIndex,
overlay_index: FileIndex,
}
impl Screens {
fn new(
files: Vec<File>,
mut error_files: VecMap<File>,
progress: Option<Progress>,
config: Arc<Config>,
) -> Result<Screens, Error> {
let count = files.len();
let mut screens = Vec::new();
for file in files.into_iter() {
let index = file.index();
let mut screen = Screen::new(file, config.clone())?;
screen.set_progress(progress.clone());
screen.set_error_file(error_files.remove(index));
screens.push(screen);
}
Ok(Screens {
screens,
overlay: None,
current_index: 0,
overlay_index: count,
})
}
fn current(&mut self) -> &mut Screen {
if let Some(ref mut screen) = self.overlay {
screen
} else {
&mut self.screens[self.current_index]
}
}
fn is_current_index(&self, index: FileIndex) -> bool {
match self.overlay {
Some(_) => index == self.overlay_index,
None => index == self.current_index,
}
}
fn get(&mut self, index: usize) -> Option<&mut Screen> {
if index == self.overlay_index {
self.overlay.as_mut()
} else if index < self.screens.len() {
Some(&mut self.screens[index])
} else {
None
}
}
}
pub(crate) fn start(
mut term: impl Terminal,
term_caps: TermCapabilities,
mut events: EventStream,
files: Vec<File>,
error_files: VecMap<File>,
progress: Option<Progress>,
config: Config,
) -> Result<(), Error> {
let outcome = {
let output_files = &files[0..1.min(files.len())];
let error_files = match error_files.iter().next() {
None => Vec::new(),
Some((_i, file)) => vec![file.clone()],
};
direct::direct(
&mut term,
output_files,
&error_files[..],
progress.as_ref(),
&mut events,
config.interface_mode,
config.startup_poll_input,
)?
};
match outcome {
direct::Outcome::RenderComplete | direct::Outcome::Interrupted => return Ok(()),
direct::Outcome::RenderIncomplete(rows) => {
let size = term.get_screen_size().map_err(Error::Termwiz)?;
let scroll_count = size.rows.saturating_sub(rows).saturating_sub(1);
if scroll_count > 0 {
term.render(&[Change::Text("\n".repeat(scroll_count))])
.map_err(Error::Termwiz)?;
}
}
direct::Outcome::RenderNothing => term.enter_alternate_screen().map_err(Error::Termwiz)?,
}
let overlay_height = AtomicUsize::new(0);
let mut term = guard(term, |mut term| {
let size = term.get_screen_size().unwrap();
let overlay_height = overlay_height.load(Ordering::SeqCst);
let scroll_count = 1usize.saturating_sub(overlay_height);
term.render(&[
Change::CursorVisibility(CursorVisibility::Visible),
Change::AllAttributes(CellAttributes::default()),
Change::ScrollRegionUp {
first_row: 0,
region_size: size.rows,
scroll_count,
},
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(size.rows.saturating_sub(overlay_height + scroll_count)),
},
Change::ClearToEndOfScreen(ColorAttribute::default()),
])
.unwrap();
});
let config = Arc::new(config);
let caps = Capabilities::new(term_caps);
let mut screens = Screens::new(files, error_files, progress, config.clone())?;
let event_sender = events.sender();
let render_unique = UniqueInstance::new();
let refresh_unique = UniqueInstance::new();
{
let screen = screens.current();
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
screen.maybe_load_more();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
}
loop {
let timeout = if screens.current().animate() {
Some(Duration::from_millis(100))
} else {
None
};
let event = events.get(&mut *term, timeout)?;
let mut action = {
let screen = screens.current();
screen.maybe_load_more();
match event {
None => screen.dispatch_animation(),
Some(Event::Render) => {
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
DisplayAction::None
}
Some(Event::Input(InputEvent::Resized { .. })) => {
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
DisplayAction::None
}
Some(Event::Refresh) => {
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
screen.refresh();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
DisplayAction::None
}
Some(Event::Progress) => {
screen.refresh_progress();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
DisplayAction::None
}
Some(Event::Action(action)) => screen.dispatch_action(action, &event_sender),
Some(Event::Input(InputEvent::Key(key))) => {
let width = screen.width();
if let Some(prompt) = screen.prompt() {
prompt.dispatch_key(key, width)
} else {
screen.dispatch_key(key, &event_sender)
}
}
Some(Event::Input(InputEvent::Paste(ref text))) => {
let width = screen.width();
screen
.prompt()
.get_or_insert_with(|| {
command::search(SearchKind::First, event_sender.clone())
})
.paste(text, width)
}
Some(Event::Loaded(index)) if screens.is_current_index(index) => {
DisplayAction::Refresh
}
Some(Event::Appending(index)) if screens.is_current_index(index) => {
DisplayAction::Refresh
}
Some(Event::Reloading(index)) => {
if let Some(screen) = screens.get(index) {
screen.flush_line_caches();
}
if screens.is_current_index(index) {
DisplayAction::Refresh
} else {
DisplayAction::None
}
}
Some(Event::SearchFirstMatch(index)) => {
if let Some(screen) = screens.get(index) {
screen.search_first_match()
} else {
DisplayAction::None
}
}
Some(Event::SearchFinished(index)) => {
if let Some(screen) = screens.get(index) {
screen.search_finished()
} else {
DisplayAction::None
}
}
_ => DisplayAction::None,
}
};
loop {
match std::mem::replace(&mut action, DisplayAction::None) {
DisplayAction::None => break,
DisplayAction::Run(mut f) => action = f(screens.current())?,
DisplayAction::Change(c) => {
term.render(&[c]).map_err(Error::Termwiz)?;
}
DisplayAction::Render => event_sender.send_unique(Event::Render, &render_unique)?,
DisplayAction::Refresh => {
event_sender.send_unique(Event::Refresh, &refresh_unique)?
}
DisplayAction::RefreshPrompt => {
screens.current().refresh_prompt();
event_sender.send_unique(Event::Render, &render_unique)?;
}
DisplayAction::NextFile => {
screens.overlay = None;
if screens.current_index < screens.screens.len() - 1 {
screens.current_index += 1;
let screen = screens.current();
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
screen.refresh();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
}
}
DisplayAction::PreviousFile => {
screens.overlay = None;
if screens.current_index > 0 {
screens.current_index -= 1;
let screen = screens.current();
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
screen.refresh();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
}
}
DisplayAction::ShowHelp => {
let overlay_index = screens.overlay_index + 1;
let screen = screens.current();
let mut screen = Screen::new(
LoadedFile::new_static(
overlay_index,
"HELP",
help_text(screen.keymap())?.into_bytes(),
event_sender.clone(),
)
.into(),
config.clone(),
)?;
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
screen.refresh();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
screens.overlay = Some(screen);
screens.overlay_index = overlay_index;
}
DisplayAction::ClearOverlay => {
screens.overlay = None;
let screen = screens.current();
let size = term.get_screen_size().map_err(Error::Termwiz)?;
screen.resize(size.cols, size.rows);
screen.refresh();
term.render(&screen.render(&caps)).map_err(Error::Termwiz)?;
}
DisplayAction::Quit => {
let screen = screens.current();
overlay_height.store(screen.overlay_height(), Ordering::SeqCst);
return Ok(());
}
}
}
}
}