Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/core/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use std::fmt::Debug;

use crate::{
ExitStrategy, LineNumbers,
ExitStrategy, LineNumbers, OutputSink,
hooks::{Hook, HookCallback},
input::{InputClassifier, InputEvent},
minus_core::utils::display::AppendStyle,
Expand Down Expand Up @@ -49,6 +49,7 @@ pub enum Command {
LineWrapping(bool),
SetLineNumbers(LineNumbers),
FollowOutput(bool),
SetOutputSink(Box<dyn OutputSink>),

// Configuration options
SetExitStrategy(ExitStrategy),
Expand Down Expand Up @@ -79,7 +80,8 @@ impl PartialEq for Command {
(Self::SetRunNoOverflow(d1), Self::SetRunNoOverflow(d2)) => d1 == d2,
(Self::SetInputClassifier(_), Self::SetInputClassifier(_))
| (Self::AddExitCallback(_), Self::AddExitCallback(_))
| (Self::AddHook(..), Self::AddHook(..)) => true,
| (Self::AddHook(..), Self::AddHook(..))
| (Self::SetOutputSink(_), Self::SetOutputSink(_)) => true,
(Self::RemoveHook(h1, id1), Self::RemoveHook(h2, id2)) => h1 == h2 && id1 == id2,
#[cfg(feature = "search")]
(Self::IncrementalSearchCondition(_), Self::IncrementalSearchCondition(_)) => true,
Expand Down Expand Up @@ -110,6 +112,7 @@ impl Debug for Command {
Self::SetRunNoOverflow(val) => write!(f, "SetRunNoOverflow({val:?})"),
Self::UserInput(input) => write!(f, "UserInput({input:?})"),
Self::FollowOutput(follow_output) => write!(f, "FollowOutput({follow_output:?})"),
Self::SetOutputSink(_) => write!(f, "SetOutputSink"),
Self::Io(c) => write!(f, "Io({c:?})"),
}
}
Expand Down
91 changes: 91 additions & 0 deletions src/core/ev_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ pub fn handle_event(
) {
match ev {
Command::SetData(text) => {
if let Some(ref mut hs) = p.help_state {
hs.screen.orig_text = text;
hs.screen.line_count = hs.screen.orig_text.lines().count();
return;
}
p.screen.orig_text = text;
p.screen.line_count = p.screen.orig_text.lines().count();
p.reformat_display();
command_queue.push_back(Command::Io(IoCommand::RedrawDisplay));
}
Command::UserInput(InputEvent::Exit) => {
if p.help_state.is_some() {
p.exit_help();
command_queue.push_back(Command::Io(IoCommand::RedrawDisplay));
return;
}
p.run_hooks(Hook::PrePagerExit);
p.exit();
is_exited.store(true, std::sync::atomic::Ordering::SeqCst);
Expand Down Expand Up @@ -141,11 +151,24 @@ pub fn handle_event(
}
}
Command::UserInput(InputEvent::RestorePrompt) => {
if p.help_state.is_some() {
p.exit_help();
command_queue.push_back(Command::Io(IoCommand::RedrawDisplay));
return;
}
// Set the message to None and new messages to false as all messages have been shown
p.message = None;
p.format_prompt();
command_queue.push_back(Command::Io(IoCommand::RedrawPrompt));
}
Command::UserInput(InputEvent::ShowHelp) => {
if p.help_state.is_some() {
p.exit_help();
} else {
p.show_help();
}
command_queue.push_back(Command::Io(IoCommand::RedrawDisplay));
}
Command::UserInput(InputEvent::UpdateTermArea(c, r)) => {
p.rows = r;
p.cols = c;
Expand Down Expand Up @@ -283,6 +306,11 @@ pub fn handle_event(
}

Command::AppendData(text) => {
if let Some(ref mut hs) = p.help_state {
hs.screen.orig_text.push_str(&text);
hs.screen.line_count = hs.screen.orig_text.lines().count();
return;
}
let prev_unterminated = p.screen.unterminated;
let prev_fmt_lines_count = p.screen.formatted_lines_count();
let append_style = p.append_str(text.as_str());
Expand Down Expand Up @@ -349,6 +377,17 @@ pub fn handle_event(
p.hooks.remove_callback(hook, id);
}
Command::ShowPrompt(show) => p.show_prompt = show,
Command::SetOutputSink(sink) => {
#[cfg(not(test))]
if sink.is_tty()
&& let Ok(size) = crossterm::terminal::size()
{
p.cols = size.0 as usize;
p.rows = size.1 as usize;
p.reformat_display();
}
*p.output_sink.lock() = sink;
}
Command::FollowOutput(follow_output)
| Command::UserInput(InputEvent::FollowOutput(follow_output)) => {
p.follow_output = follow_output;
Expand Down Expand Up @@ -537,6 +576,58 @@ mod tests {
assert_eq!(ps.message.unwrap(), TEST_STR.to_string());
}

#[test]
fn show_help() {
let mut ps = PagerState::new().unwrap();
ps.screen.orig_text = "original text\n".to_string();
ps.reformat_display();
ps.upper_mark = 0;

let ev = Command::UserInput(InputEvent::ShowHelp);
let mut command_queue = CommandQueue::new_zero();

// Showing help sets the screen to the formatted help table
handle_event(
ev,
&mut ps,
&mut command_queue,
&Arc::new(AtomicBool::new(false)),
);
assert!(ps.help_state.is_some());
assert!(ps.screen.orig_text.contains("COMMAND SUMMARY"));
assert!(ps.prompt.contains("HELP"));

// Pressing help again toggles it off and restores original text
let ev2 = Command::UserInput(InputEvent::ShowHelp);
handle_event(
ev2,
&mut ps,
&mut command_queue,
&Arc::new(AtomicBool::new(false)),
);
assert!(ps.help_state.is_none());
assert_eq!(ps.screen.orig_text, "original text\n");

// Showing help then exiting with Exit returns to pager
handle_event(
Command::UserInput(InputEvent::ShowHelp),
&mut ps,
&mut command_queue,
&Arc::new(AtomicBool::new(false)),
);
assert!(ps.help_state.is_some());
let is_exited = Arc::new(AtomicBool::new(false));
handle_event(
Command::UserInput(InputEvent::Exit),
&mut ps,
&mut command_queue,
&is_exited,
);
assert!(ps.help_state.is_none());
assert_eq!(is_exited.load(std::sync::atomic::Ordering::SeqCst), false);
assert_eq!(ps.screen.orig_text, "original text\n");
}

#[test]
#[cfg(feature = "static_output")]
fn set_run_no_overflow() {
Expand Down
76 changes: 34 additions & 42 deletions src/core/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! the [`Receiver`] held inside the [`Pager`] for events. Whenever a event is
//! detected, it reacts to it accordingly.
use crate::{
Pager, PagerState,
OutputSink, Pager, PagerState,
error::MinusError,
hooks::Hook,
input::InputEvent,
Expand All @@ -24,17 +24,13 @@ use crate::{
use crossbeam_channel::{Receiver, Sender, TrySendError};
use crossterm::event;
use std::{
io::Write,
panic,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};

#[cfg(not(test))]
use std::io::stdout;

#[cfg(feature = "search")]
use parking_lot::Condvar;
use parking_lot::Mutex;
Expand All @@ -50,11 +46,11 @@ use super::{CommandQueue, RUNMODE, utils::display::draw_for_change};
/// and creates the initial state that to be stored inside the [`PagerState`]
///
/// Then it checks if the minus is running in static mode and does some checks:-
/// * If standard output is not a terminal screen, that is if it is a file or block
/// device, minus will write all the data at once to the stdout and quit
/// * If output sink is not a terminal screen, that is if it is a file or block
/// device, minus will write all the data at once to the output sink and quit
///
/// * If the size of the data is less than the available number of rows in the terminal
/// then it displays everything on the main stdout screen at once and quits. This
/// then it displays everything on the main screen at once and quits. This
/// behaviour can be turned off if [`Pager::set_run_no_overflow`] is called
/// by the main application
// Sorry... this behaviour would have been cool to have in async mode, just think about it!!! Many
Expand All @@ -76,11 +72,6 @@ use super::{CommandQueue, RUNMODE, utils::display::draw_for_change};
#[allow(clippy::module_name_repetitions)]
#[allow(clippy::too_many_lines)]
pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusError> {
#[cfg(not(test))]
let mut out = stdout();
#[cfg(test)]
let mut out = Vec::new();

// Is the event reader running
#[cfg(feature = "search")]
let input_thread_running = Arc::new((Mutex::new(true), Condvar::new()));
Expand All @@ -96,20 +87,25 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr
*super::RUNMODE.lock() = rm;
ps.run_hooks(Hook::PrePagerStart);

let output_sink = ps.output_sink.clone();

// Static mode checks
#[cfg(all(feature = "static_output", not(test)))]
if *RUNMODE.lock() == RunMode::Static {
use {super::utils::display::write_raw_lines, crossterm::tty::IsTty};
// If stdout is not a tty, write everything and quit
use super::utils::display::write_raw_lines;
let mut out = output_sink.lock();
// If output sink is not a tty, write everything and quit
if !out.is_tty() {
write_raw_lines(&mut out, &[ps.screen.orig_text], None)?;
write_raw_lines(&mut *out, &[ps.screen.orig_text], None)?;
drop(out);
*RUNMODE.lock() = RunMode::Uninitialized;
return Ok(());
}
// If number of lines of text is less than available rows, write everything and quit
// unless run_no_overflow is set to true
if ps.screen.formatted_lines_count() <= ps.rows && !ps.run_no_overflow {
write_raw_lines(&mut out, &ps.screen.formatted_lines, Some("\r"))?;
write_raw_lines(&mut *out, &ps.screen.formatted_lines, Some("\r"))?;
drop(out);
ps.exit();
*RUNMODE.lock() = RunMode::Uninitialized;
return Ok(());
Expand All @@ -118,26 +114,22 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr

// Setup terminal, adjust line wraps and get rows
#[cfg(not(test))]
term::setup(&mut out)?;
term::setup(&mut *output_sink.lock())?;

// Has the user quit
let is_exited = Arc::new(AtomicBool::new(false));
let is_exited2 = is_exited.clone();

{
let panic_hook = panic::take_hook();
let panic_sink = output_sink.clone();
panic::set_hook(Box::new(move |pinfo| {
is_exited2.store(true, std::sync::atomic::Ordering::SeqCst);
// HACK: In test we don't care about the cleanup code so just use a separate buffer
// for panic handler.
#[cfg(test)]
let mut out2 = Vec::new();
#[cfg(not(test))]
let mut out2 = stdout();

// While silently ignoring error is considered a bad practice, we are forced to do it here
// as we cannot use the ? and panicking here will (probably?) cause an immediate abort
drop(term::cleanup(&mut out2, true));
let mut out2 = panic_sink.lock();
drop(term::cleanup(&mut *out2, true));
drop(out2);
panic_hook(pinfo);
}));
}
Expand All @@ -155,11 +147,9 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr
std::thread::scope(|s| -> crate::Result {
let is_exited3 = is_exited.clone();
let is_exited4 = is_exited.clone();

#[cfg(test)]
let mut out2 = Vec::new();
#[cfg(not(test))]
let mut out2 = stdout();
let output_sink2 = output_sink.clone();
let ps_mutex2 = ps_mutex.clone();
let rx2 = rx.clone();

let t1 = s.spawn(move || {
let res = event_reader(
Expand All @@ -177,9 +167,9 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr
});
let t2 = s.spawn(move || {
let res = start_reactor(
&rx,
&ps_mutex,
&mut out2,
&rx2,
&ps_mutex2,
&output_sink2,
#[cfg(feature = "search")]
&input_thread_running,
&is_exited4,
Expand All @@ -196,7 +186,8 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr

if r1.is_err() || r2.is_err() {
*RUNMODE.lock() = RunMode::Uninitialized;
term::cleanup(&mut out, true)?;
#[cfg(not(test))]
term::cleanup(&mut *output_sink.lock(), true)?;
}

r1?;
Expand All @@ -221,20 +212,21 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr
fn start_reactor(
rx: &Receiver<Command>,
ps: &Arc<Mutex<PagerState>>,
mut out_lock: impl Write,
output_sink: &Arc<Mutex<Box<dyn OutputSink>>>,
#[cfg(feature = "search")] input_thread_running: &Arc<(Mutex<bool>, Condvar)>,
is_exited: &Arc<AtomicBool>,
) -> Result<(), MinusError> {
let mut command_queue = CommandQueue::new();

{
let mut p = ps.lock();
let mut out_lock = output_sink.lock();

draw_full(&mut out_lock, &mut p)?;
draw_full(&mut *out_lock, &mut p)?;
p.run_hooks(Hook::PostPagerStart);

if p.follow_output {
draw_for_change(&mut out_lock, &mut p, &mut (usize::MAX - 1))?;
draw_for_change(&mut *out_lock, &mut p, &mut (usize::MAX - 1))?;
}
}

Expand All @@ -243,7 +235,7 @@ fn start_reactor(
#[cfg(feature = "dynamic_output")]
RunMode::Dynamic => loop {
if is_exited.load(Ordering::SeqCst) {
term::cleanup(&mut out_lock, true)?;
term::cleanup(&mut *output_sink.lock(), true)?;
ps.lock().run_hooks(Hook::PostPagerExit);
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
Expand All @@ -263,7 +255,7 @@ fn start_reactor(

handle_io_command(
ic,
&mut out_lock,
&mut *output_sink.lock(),
&mut p,
&mut command_queue,
#[cfg(feature = "search")]
Expand All @@ -280,7 +272,7 @@ fn start_reactor(
// Cleanup the screen
//
// This is not needed in dynamic paging because this is already handled by handle_event
term::cleanup(&mut out_lock, true)?;
term::cleanup(&mut *output_sink.lock(), true)?;
ps.lock().run_hooks(Hook::PostPagerExit);

let mut rm = RUNMODE.lock();
Expand All @@ -302,7 +294,7 @@ fn start_reactor(

handle_io_command(
ic,
&mut out_lock,
&mut *output_sink.lock(),
&mut p,
&mut command_queue,
#[cfg(feature = "search")]
Expand Down
Loading