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
16 changes: 14 additions & 2 deletions src/core/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
use std::fmt::Debug;

use crate::{
ExitStrategy, LineNumbers,
ExitStrategy, LineNumbers, OutputSink,
hooks::{Hook, HookCallback},
input::{InputClassifier, InputEvent},
minus_core::utils::display::AppendStyle,
};

#[cfg(feature = "clipboard")]
use crate::state::ClipboardHandler;

#[cfg(feature = "search")]
use crate::search::SearchOpts;

Expand Down Expand Up @@ -49,10 +52,13 @@ pub enum Command {
LineWrapping(bool),
SetLineNumbers(LineNumbers),
FollowOutput(bool),
SetOutputSink(Box<dyn OutputSink>),

// Configuration options
SetExitStrategy(ExitStrategy),
SetInputClassifier(Box<dyn InputClassifier + Send + Sync + 'static>),
#[cfg(feature = "clipboard")]
SetClipboardHandler(ClipboardHandler),
AddExitCallback(Box<dyn FnMut() + Send + Sync + 'static>),
AddHook(Hook, u64, HookCallback),
RemoveHook(Hook, u64),
Expand All @@ -79,7 +85,10 @@ 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,
#[cfg(feature = "clipboard")]
(Self::SetClipboardHandler(_), Self::SetClipboardHandler(_)) => true,
(Self::RemoveHook(h1, id1), Self::RemoveHook(h2, id2)) => h1 == h2 && id1 == id2,
#[cfg(feature = "search")]
(Self::IncrementalSearchCondition(_), Self::IncrementalSearchCondition(_)) => true,
Expand All @@ -100,6 +109,8 @@ impl Debug for Command {
Self::LineWrapping(lw) => write!(f, "LineWrapping({lw:?})"),
Self::SetExitStrategy(es) => write!(f, "SetExitStrategy({es:?})"),
Self::SetInputClassifier(_) => write!(f, "SetInputClassifier"),
#[cfg(feature = "clipboard")]
Self::SetClipboardHandler(_) => write!(f, "SetClipboardHandler"),
Self::ShowPrompt(show) => write!(f, "ShowPrompt({show:?})"),
#[cfg(feature = "search")]
Self::IncrementalSearchCondition(_) => write!(f, "IncrementalSearchCondition"),
Expand All @@ -110,6 +121,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
132 changes: 128 additions & 4 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 @@ -130,22 +140,37 @@ pub fn handle_event(

#[cfg(feature = "clipboard")]
Command::UserInput(InputEvent::CopySelection) => {
if let Some(text) = p.extract_selection()
&& let Ok(mut clipboard) = arboard::Clipboard::new()
{
let _ = clipboard.set_text(text);
if let Some(text) = p.extract_selection() {
if let Some(handler) = p.clipboard_handler.as_ref() {
handler(&text);
} else if let Ok(mut clipboard) = arboard::Clipboard::new() {
let _ = clipboard.set_text(text);
}
}
if p.selection.is_some() || p.selection_anchor.is_some() {
p.clear_selection();
command_queue.push_back(Command::Io(IoCommand::RedrawDisplay));
}
}
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 +308,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 @@ -343,12 +373,25 @@ pub fn handle_event(
#[cfg(feature = "search")]
Command::IncrementalSearchCondition(cb) => p.search_state.incremental_search_condition = cb,
Command::SetInputClassifier(clf) => p.input_classifier = clf,
#[cfg(feature = "clipboard")]
Command::SetClipboardHandler(handler) => p.clipboard_handler = Some(handler),
Command::AddExitCallback(cb) => p.exit_callbacks.push(cb),
Command::AddHook(hook, id, cb) => p.hooks.add_callback(hook, id, cb),
Command::RemoveHook(hook, id) => {
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 +580,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 Expand Up @@ -706,4 +801,33 @@ mod tests {
Some(Command::Io(IoCommand::RedrawDisplay))
);
}

#[test]
#[cfg(feature = "clipboard")]
fn copy_selection_uses_clipboard_handler() {
let mut ps = PagerState::new().unwrap();
ps.screen.line_wrapping = false;
ps.screen.orig_text = "hello world\n".to_string();
ps.reformat_display();
ps.selection_anchor = ps.selection_from_coordinates(0, 0);
ps.selection = ps.selection_from_coordinates(10, 0);

let copied = Arc::new(std::sync::Mutex::new(None::<String>));
let copied_handler = copied.clone();
ps.clipboard_handler = Some(Box::new(move |text| {
*copied_handler.lock().unwrap() = Some(text.to_string());
}));

let mut command_queue = CommandQueue::new_zero();
handle_event(
Command::UserInput(InputEvent::CopySelection),
&mut ps,
&mut command_queue,
&Arc::new(AtomicBool::new(false)),
);

assert_eq!(copied.lock().unwrap().as_deref(), Some("hello world"));
assert_eq!(ps.selection, None);
assert_eq!(ps.selection_anchor, None);
}
}
Loading