Skip to content
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ regex = { version = "^1", optional = true }
crossbeam-channel = "^0.5"
parking_lot = "0.12.1"
arboard = { version = "3", optional = true, default-features = false, features = ["wayland-data-control"] }
rustyline = { version = "18.0.0", default-features = false, features = ["custom-bindings"] }

[features]
search = [ "regex" ]
Expand Down
36 changes: 28 additions & 8 deletions src/core/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::fmt::Debug;
use crate::{
ExitStrategy, LineNumbers,
hooks::{Hook, HookCallback},
input::{InputClassifier, InputEvent},
input::{self, InputEvent, hashed_event_register::EventWrapper},
minus_core::utils::display::AppendStyle,
};

Expand All @@ -29,6 +29,19 @@ pub enum IoCommand {
FetchSearchQuery,
}

#[derive(Debug, PartialEq, Eq)]
pub enum InputType {
KeyMouse(Vec<EventWrapper>),
AllKeys,
AllMouses,
Resize,
Wild,
}

#[cfg(feature = "search")]
pub type IncrementalSearchCondition =
Box<dyn Fn(&SearchOpts, &str) -> bool + Send + Sync + 'static>;

/// Different events that can be encountered while the pager is running
#[non_exhaustive]
#[allow(private_interfaces)]
Expand All @@ -52,14 +65,17 @@ pub enum Command {

// Configuration options
SetExitStrategy(ExitStrategy),
SetInputClassifier(Box<dyn InputClassifier + Send + Sync + 'static>),
AddExitCallback(Box<dyn FnMut() + Send + Sync + 'static>),
AddHook(Hook, u64, HookCallback),
RemoveHook(Hook, u64),
#[cfg(feature = "static_output")]
SetRunNoOverflow(bool),
#[cfg(feature = "search")]
IncrementalSearchCondition(Box<dyn Fn(&SearchOpts) -> bool + Send + Sync + 'static>),
SetIncrementalSearchCondition(IncrementalSearchCondition),

// Input
AddInputBinding(InputType, input::InputEventBoxed),
RemoveInputBinding(InputType),

Io(IoCommand),
}
Expand All @@ -77,12 +93,15 @@ impl PartialEq for Command {
(Self::SetExitStrategy(d1), Self::SetExitStrategy(d2)) => d1 == d2,
#[cfg(feature = "static_output")]
(Self::SetRunNoOverflow(d1), Self::SetRunNoOverflow(d2)) => d1 == d2,
(Self::SetInputClassifier(_), Self::SetInputClassifier(_))
| (Self::AddExitCallback(_), Self::AddExitCallback(_))
(Self::AddExitCallback(_), Self::AddExitCallback(_))
| (Self::AddHook(..), Self::AddHook(..)) => true,
(Self::RemoveHook(h1, id1), Self::RemoveHook(h2, id2)) => h1 == h2 && id1 == id2,
#[cfg(feature = "search")]
(Self::IncrementalSearchCondition(_), Self::IncrementalSearchCondition(_)) => true,
(Self::SetIncrementalSearchCondition(_), Self::SetIncrementalSearchCondition(_)) => {
true
}
(Self::AddInputBinding(et_a, _), Self::AddInputBinding(et_b, _)) => et_a == et_b,
(Self::RemoveInputBinding(et_a), Self::RemoveInputBinding(et_b)) => et_a == et_b,
(Self::Io(a), Self::Io(b)) => a == b,
_ => false,
}
Expand All @@ -99,16 +118,17 @@ impl Debug for Command {
Self::SetLineNumbers(ln) => write!(f, "SetLineNumbers({ln:?})"),
Self::LineWrapping(lw) => write!(f, "LineWrapping({lw:?})"),
Self::SetExitStrategy(es) => write!(f, "SetExitStrategy({es:?})"),
Self::SetInputClassifier(_) => write!(f, "SetInputClassifier"),
Self::ShowPrompt(show) => write!(f, "ShowPrompt({show:?})"),
#[cfg(feature = "search")]
Self::IncrementalSearchCondition(_) => write!(f, "IncrementalSearchCondition"),
Self::SetIncrementalSearchCondition(_) => write!(f, "IncrementalSearchCondition"),
Self::AddExitCallback(_) => write!(f, "AddExitCallback"),
Self::AddHook(h, id, _) => write!(f, "AddHook({h:?}, {id})"),
Self::RemoveHook(h, id) => write!(f, "RemoveHook({h:?}, {id})"),
#[cfg(feature = "static_output")]
Self::SetRunNoOverflow(val) => write!(f, "SetRunNoOverflow({val:?})"),
Self::UserInput(input) => write!(f, "UserInput({input:?})"),
Self::AddInputBinding(et, _) => write!(f, "AddInputBinding({et:?})"),
Self::RemoveInputBinding(et) => write!(f, "RemoveInputBinding({et:?})"),
Self::FollowOutput(follow_output) => write!(f, "FollowOutput({follow_output:?})"),
Self::Io(c) => write!(f, "Io({c:?})"),
}
Expand Down
111 changes: 24 additions & 87 deletions src/core/ev_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use parking_lot::{Condvar, Mutex};
use super::CommandQueue;
use super::commands::{Command, IoCommand};
use super::utils::display::{self, AppendStyle};
use crate::ExitStrategy;
#[cfg(feature = "search")]
use crate::search;
use crate::{ExitStrategy, minus_core::commands::InputType};
use crate::{PagerState, error::MinusError, hooks::Hook, input::InputEvent};

/// Respond based on the type of command
Expand All @@ -23,8 +23,6 @@ use crate::{PagerState, error::MinusError, hooks::Hook, input::InputEvent};
/// - Call search related functions
#[cfg_attr(not(feature = "search"), allow(unused_mut))]
#[allow(clippy::too_many_lines)]
// TODO: Remove it in next major release
#[allow(deprecated)]
pub fn handle_event(
ev: Command,
p: &mut PagerState,
Expand Down Expand Up @@ -171,69 +169,18 @@ pub fn handle_event(
command_queue.push_back(Command::Io(IoCommand::FetchSearchQuery));
}
#[cfg(feature = "search")]
Command::UserInput(InputEvent::NextMatch | InputEvent::MoveToNextMatch(1))
if p.search_state.search_term.is_some() =>
{
Command::UserInput(InputEvent::GoToMatch(n)) if p.search_state.search_term.is_some() => {
// Move to next search match after the current upper_mark
let position_of_next_match =
search::next_nth_match(&p.search_state.search_idx, p.upper_mark, 1);
if let Some(pnm) = position_of_next_match {
p.search_state.search_mark = pnm;
let upper_mark = *p
.search_state
.search_idx
.iter()
.nth(p.search_state.search_mark)
.unwrap();
command_queue.push_back(Command::Io(IoCommand::SetUpperMark(upper_mark)));
p.format_prompt();
command_queue.push_back(Command::Io(IoCommand::RedrawPrompt));
}
}
#[cfg(feature = "search")]
Command::UserInput(InputEvent::PrevMatch | InputEvent::MoveToPrevMatch(1))
if p.search_state.search_term.is_some() =>
{
// If no matches, return immediately
if p.search_state.search_idx.is_empty() {
return;
}
// Decrement the s_mark and get the preceding index
p.search_state.search_mark = p.search_state.search_mark.saturating_sub(1);
if let Some(y) = p
.search_state
.search_idx
.iter()
.nth(p.search_state.search_mark)
{
// If the index is less than or equal to the upper_mark, then set y to the new upper_mark
if *y < p.upper_mark {
command_queue.push_back(Command::UserInput(InputEvent::UpdateUpperMark(*y)));
p.format_prompt();
command_queue.push_back(Command::Io(IoCommand::RedrawPrompt));
}
}
}
#[cfg(feature = "search")]
Command::UserInput(InputEvent::MoveToNextMatch(n))
if p.search_state.search_term.is_some() =>
{
// Move to next nth search match after the current upper_mark
let position_of_next_match =
search::next_nth_match(&p.search_state.search_idx, p.upper_mark, n);
if let Some(pnm) = position_of_next_match {
p.search_state.search_mark = pnm;
let match_pos =
search::nth_match(&p.search_state.search_idx, p.upper_mark, n, p.search_mode);
if let Some(pm) = match_pos {
p.search_state.search_mark = pm;
let mut upper_mark = *p
.search_state
.search_idx
.iter()
.nth(p.search_state.search_mark)
.unwrap();

// Ensure there is enough text available after location corresponding to
// position_of_next_match so that we can display a pagefull of data. If not,
// reduce it so that a pagefull of text can be accommodated.
// NOTE: Add 1 to total number of lines to avoid off-by-one errors
while p.upper_mark.saturating_add(p.rows)
> p.screen.formatted_lines_count().saturating_add(1)
{
Expand All @@ -245,36 +192,12 @@ pub fn handle_event(
.nth(p.search_state.search_mark)
.unwrap();
}
command_queue
.push_back(Command::UserInput(InputEvent::UpdateUpperMark(upper_mark)));

command_queue.push_back(Command::Io(IoCommand::SetUpperMark(upper_mark)));
p.format_prompt();
command_queue.push_back(Command::Io(IoCommand::RedrawPrompt));
}
}
#[cfg(feature = "search")]
Command::UserInput(InputEvent::MoveToPrevMatch(n))
if p.search_state.search_term.is_some() =>
{
// If no matches, return immediately
if p.search_state.search_idx.is_empty() {
return;
}
// Decrement the s_mark and get the preceding index
p.search_state.search_mark = p.search_state.search_mark.saturating_sub(n);
if let Some(y) = p
.search_state
.search_idx
.iter()
.nth(p.search_state.search_mark)
{
// If the index is less than or equal to the upper_mark, then set y to the new upper_mark
if *y < p.upper_mark {
command_queue.push_back(Command::Io(IoCommand::SetUpperMark(*y)));
p.format_prompt();
command_queue.push_back(Command::Io(IoCommand::RedrawPrompt));
}
}
}

Command::UserInput(InputEvent::HorizontalScroll(val)) => {
p.screen.line_wrapping = val;
Expand Down Expand Up @@ -341,8 +264,9 @@ pub fn handle_event(
#[cfg(feature = "static_output")]
Command::SetRunNoOverflow(val) => p.run_no_overflow = val,
#[cfg(feature = "search")]
Command::IncrementalSearchCondition(cb) => p.search_state.incremental_search_condition = cb,
Command::SetInputClassifier(clf) => p.input_classifier = clf,
Command::SetIncrementalSearchCondition(cb) => {
p.search_state.incremental_search_condition = cb;
}
Command::AddExitCallback(cb) => p.exit_callbacks.push(cb),
Command::AddHook(hook, id, cb) => p.hooks.add_callback(hook, id, cb),
Command::RemoveHook(hook, id) => {
Expand All @@ -358,6 +282,19 @@ pub fn handle_event(
p.format_prompt();
command_queue.push_back(Command::Io(IoCommand::RedrawPrompt));
}
Command::AddInputBinding(et, cb) => match et {
InputType::KeyMouse(desc) => p.input_register.map_km_parsed(desc, cb),
InputType::Resize => p.input_register.map_resize(cb),
InputType::Wild => p.input_register.map_wild_event(cb),
InputType::AllKeys | InputType::AllMouses => unreachable!(),
},
Command::RemoveInputBinding(et) => match et {
InputType::KeyMouse(desc) => p.input_register.clear_km_parsed(&desc),
InputType::AllKeys => p.input_register.clear_all_keys(),
InputType::AllMouses => p.input_register.clear_all_mouse(),
InputType::Resize => p.input_register.clear_resize(),
InputType::Wild => p.input_register.clear_wild_event(),
},
Command::UserInput(_) => {}
Command::Io(_) => unreachable!(),
}
Expand Down
8 changes: 4 additions & 4 deletions src/core/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ use super::{CommandQueue, RUNMODE, utils::display::draw_for_change};
/// [`event reader`]: event_reader
#[allow(clippy::module_name_repetitions)]
#[allow(clippy::too_many_lines)]
pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusError> {
pub fn init_core(pager: Pager, rm: RunMode) -> std::result::Result<(), MinusError> {
#[cfg(not(test))]
let mut out = stdout();
#[cfg(test)]
Expand Down Expand Up @@ -144,8 +144,8 @@ pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusErr

let ps_mutex = Arc::new(Mutex::new(ps));

let evtx = pager.tx.clone();
let rx = pager.rx.clone();
let evtx = pager.tx;
let rx = pager.rx;

let p1 = ps_mutex.clone();

Expand Down Expand Up @@ -344,7 +344,7 @@ fn event_reader(
let ev = event::read().map_err(|e| MinusError::HandleEvent(e.into()))?;
let mut guard = ps.lock();
// Get the events
let input = guard.input_classifier.classify_input(ev, &guard);
let input = guard.input_register.classify_input(ev, &guard);
if let Some(iev) = input {
if !matches!(iev, InputEvent::Number(_)) {
guard.prefix_num.clear();
Expand Down
3 changes: 1 addition & 2 deletions src/dynamic_pager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::minus_core::init;
/// # Errors
/// The function will return with an error if it encounters a error during paging.
#[cfg_attr(docsrs, doc(cfg(feature = "dynamic_output")))]
#[allow(clippy::needless_pass_by_value)]
pub fn dynamic_paging(pager: Pager) -> Result<(), MinusError> {
init::init_core(&pager, crate::RunMode::Dynamic)
init::init_core(pager, crate::RunMode::Dynamic)
}
20 changes: 12 additions & 8 deletions src/input/definitions/keydefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ impl Default for KeySeq {
}
}

/// Parse a key input description
///
/// # Panics
/// This function will panic if the description is not valid. See the [`input`](crate::input) module
/// docs on how to write descriptions.
#[must_use]
pub fn parse_key_event(text: &str) -> KeyEvent {
let token_list = super::parse_tokens(text);

Expand All @@ -73,8 +79,7 @@ impl KeySeq {
token_iter.next();
assert!(
!(token_iter.peek() == Some(&&Token::Separator)),
"'{}': Multiple separators found consecutively",
text
"'{text}': Multiple separators found consecutively",
);
}
Token::SingleChar(c) => {
Expand All @@ -83,30 +88,29 @@ impl KeySeq {
if token_iter.next() == Some(&Token::Separator) {
assert!(
!ks.modifiers.contains(*m),
"'{}': Multiple instances of same modifier given",
text
"'{text}': Multiple instances of same modifier given",
);
ks.modifiers.insert(*m);
} else if ks.code.is_none() {
ks.code = Some(KeyCode::Char(*c));
} else {
panic!("'{}' Invalid key input sequence given", text);
panic!("'{text}' Invalid key input sequence given");
}
} else if ks.code.is_none() {
ks.code = Some(KeyCode::Char(*c));
} else {
panic!("'{}': Invalid key input sequence given", text);
panic!("'{text}': Invalid key input sequence given");
}
}
Token::MultipleChar(c) => {
let c = c.to_ascii_lowercase().clone();
SPECIAL_KEYS.get(c.as_str()).map_or_else(
|| panic!("'{}': Invalid key input sequence given", text),
|| panic!("'{text}': Invalid key input sequence given"),
|key| {
if ks.code.is_none() {
ks.code = Some(*key);
} else {
panic!("'{}': Invalid key input sequence given", text);
panic!("'{text}': Invalid key input sequence given");
}
},
);
Expand Down
8 changes: 2 additions & 6 deletions src/input/definitions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![allow(clippy::uninlined_format_args)]

pub mod keydefs;
pub mod mousedefs;

Expand All @@ -9,14 +7,12 @@ use std::{collections::HashMap, sync::LazyLock};
fn parse_tokens(mut text: &str) -> Vec<Token> {
assert!(
text.is_ascii(),
"'{}': Non ascii sequence found in input sequence",
text
"'{text}': Non ascii sequence found in input sequence",
);
text = text.trim();
assert!(
text.chars().any(|c| !c.is_whitespace()),
"'{}': Whitespace character found in input sequence",
text
"'{text}': Whitespace character found in input sequence",
);

let mut token_list = Vec::with_capacity(text.len());
Expand Down
Loading
Loading