use crossterm::{
event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind},
terminal,
};
#[cfg(feature = "search")]
use crate::search::SearchMode;
use crate::LineNumbers;
#[derive(Debug, Copy, Clone, PartialEq)]
#[allow(clippy::module_name_repetitions)]
pub enum InputEvent {
Exit,
UpdateTermArea(usize, usize),
UpdateUpperMark(usize),
UpdateLineNumber(LineNumbers),
#[cfg(feature = "search")]
Search(SearchMode),
#[cfg(feature = "search")]
NextMatch,
#[cfg(feature = "search")]
PrevMatch,
}
#[cfg_attr(feature = "search", doc = "use minus::SearchMode;")]
#[cfg_attr(feature = "search", doc = " search_mode: SearchMode,")]
#[allow(clippy::module_name_repetitions)]
pub trait InputHandler {
fn handle_input(
&self,
ev: Event,
upper_mark: usize,
#[cfg(feature = "search")] search_mode: SearchMode,
ln: LineNumbers,
rows: usize,
) -> Option<InputEvent>;
}
pub struct DefaultInputHandler;
impl InputHandler for DefaultInputHandler {
#[allow(clippy::too_many_lines)]
fn handle_input(
&self,
ev: Event,
upper_mark: usize,
#[cfg(feature = "search")] search_mode: SearchMode,
ln: LineNumbers,
rows: usize,
) -> Option<InputEvent> {
#[allow(clippy::unnested_or_patterns)]
match ev {
Event::Key(KeyEvent {
code,
modifiers: KeyModifiers::NONE,
}) if code == KeyCode::Up || code == KeyCode::Char('k') => {
Some(InputEvent::UpdateUpperMark(upper_mark.saturating_sub(1)))
}
Event::Key(KeyEvent {
code,
modifiers: KeyModifiers::NONE,
}) if code == KeyCode::Down || code == KeyCode::Char('j') => {
Some(InputEvent::UpdateUpperMark(upper_mark.saturating_add(1)))
}
Event::Key(KeyEvent {
code: KeyCode::Char('u'),
modifiers,
}) if modifiers == KeyModifiers::CONTROL || modifiers == KeyModifiers::NONE => {
let half_screen = (terminal::size().ok()?.1 / 2) as usize;
Some(InputEvent::UpdateUpperMark(
upper_mark.saturating_sub(half_screen),
))
}
Event::Key(KeyEvent {
code: KeyCode::Char('d'),
modifiers,
}) if modifiers == KeyModifiers::CONTROL || modifiers == KeyModifiers::NONE => {
let half_screen = (terminal::size().ok()?.1 / 2) as usize;
Some(InputEvent::UpdateUpperMark(
upper_mark.saturating_add(half_screen),
))
}
Event::Mouse(MouseEvent {
kind: MouseEventKind::ScrollUp,
..
}) => Some(InputEvent::UpdateUpperMark(upper_mark.saturating_sub(5))),
Event::Mouse(MouseEvent {
kind: MouseEventKind::ScrollDown,
..
}) => Some(InputEvent::UpdateUpperMark(upper_mark.saturating_add(5))),
Event::Key(KeyEvent {
code: KeyCode::Char('g'),
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(0)),
Event::Key(KeyEvent {
code: KeyCode::Char('g'),
modifiers: KeyModifiers::SHIFT,
})
| Event::Key(KeyEvent {
code: KeyCode::Char('G'),
modifiers: KeyModifiers::SHIFT,
})
| Event::Key(KeyEvent {
code: KeyCode::Char('G'),
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(usize::MAX)),
Event::Key(KeyEvent {
code: KeyCode::PageUp,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(
upper_mark.saturating_sub(rows - 1),
)),
Event::Key(KeyEvent {
code: c,
modifiers: KeyModifiers::NONE,
}) if c == KeyCode::Down || c == KeyCode::Char(' ') => Some(
InputEvent::UpdateUpperMark(upper_mark.saturating_add(rows - 1)),
),
Event::Resize(cols, rows) => {
Some(InputEvent::UpdateTermArea(cols as usize, rows as usize))
}
Event::Key(KeyEvent {
code: KeyCode::Char('l'),
modifiers: KeyModifiers::CONTROL,
}) => Some(InputEvent::UpdateLineNumber(!ln)),
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
})
| Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}) => Some(InputEvent::Exit),
#[cfg(feature = "search")]
Event::Key(KeyEvent {
code: KeyCode::Char('/'),
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::Search(SearchMode::Forward)),
#[cfg(feature = "search")]
Event::Key(KeyEvent {
code: KeyCode::Char('?'),
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::Search(SearchMode::Reverse)),
#[cfg(feature = "search")]
Event::Key(KeyEvent {
code: KeyCode::Char('n'),
modifiers: KeyModifiers::NONE,
}) => {
if search_mode == SearchMode::Reverse {
Some(InputEvent::PrevMatch)
} else {
Some(InputEvent::NextMatch)
}
}
#[cfg(feature = "search")]
Event::Key(KeyEvent {
code: KeyCode::Char('p'),
modifiers: KeyModifiers::NONE,
}) => {
if search_mode == SearchMode::Reverse {
Some(InputEvent::NextMatch)
} else {
Some(InputEvent::PrevMatch)
}
}
_ => None,
}
}
}
#[cfg(tests)]
mod tests;