366 lines
11 KiB
Rust
366 lines
11 KiB
Rust
use crate::{state::BufferId, terminal::TerminalEvent};
|
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Dir {
|
|
Left,
|
|
Right,
|
|
Up,
|
|
Down,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Action {
|
|
Char(char), // Insert a character
|
|
Indent(bool), // Indent (indent vs deindent)
|
|
Move(Dir, bool, bool, bool), // Move the cursor (dir, page, retain_base, word)
|
|
PaneMove(Dir), // Move panes
|
|
PaneOpen(Dir), // Create a new pane
|
|
PaneClose, // Close the current pane
|
|
Cancel, // Cancels the current action
|
|
Continue, // Continue past an info-only element (like a help screen)
|
|
Go, // Search, accept, or select the current option
|
|
Yes, // A binary confirmation is answered 'yes'
|
|
No, // A binary confirmation is answered 'no'
|
|
Quit, // Quit the application
|
|
OpenPrompt, // Open the command prompt
|
|
Show(Option<String>, String), // Display an optionally titled informational text box to the user
|
|
OpenSwitcher, // Open the buffer switcher
|
|
OpenOpener(PathBuf), // Open the file opener
|
|
OpenFinder(Option<String>), // Open the finder, with the given default query
|
|
SwitchBuffer(BufferId), // Switch the current pane to the given buffer
|
|
OpenFile(PathBuf), // Open the file and switch the current pane to it
|
|
CommandStart(&'static str), // Start a new command
|
|
GotoLine(isize), // Go to the specified file line
|
|
SelectToken, // Fully select the token under the cursor
|
|
Save, // Save the current buffer
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Event {
|
|
// The incoming event is an action generated by some other internal component.
|
|
Action(Action),
|
|
// The incoming event is a raw user input.
|
|
Raw(RawEvent),
|
|
}
|
|
|
|
impl From<Action> for Event {
|
|
fn from(action: Action) -> Self {
|
|
Self::Action(action)
|
|
}
|
|
}
|
|
|
|
impl Event {
|
|
pub fn from_raw(e: TerminalEvent) -> Self {
|
|
Self::Raw(RawEvent(e))
|
|
}
|
|
|
|
/// Turn the event into an action (if possible).
|
|
///
|
|
/// The translation function allows elements to translate raw events into their own context-specific actions.
|
|
pub fn to_action(&self, translate: impl FnOnce(&RawEvent) -> Option<Action>) -> Option<Action> {
|
|
match self {
|
|
Self::Action(a) => Some(a.clone()),
|
|
Self::Raw(te) => translate(te),
|
|
}
|
|
}
|
|
}
|
|
|
|
const ALT_SHIFT: KeyModifiers = KeyModifiers::ALT.union(KeyModifiers::SHIFT);
|
|
|
|
#[derive(Debug)]
|
|
pub struct RawEvent(TerminalEvent);
|
|
|
|
impl RawEvent {
|
|
pub fn to_char(&self) -> Option<char> {
|
|
match self.0 {
|
|
TerminalEvent::Key(KeyEvent {
|
|
code,
|
|
modifiers,
|
|
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
|
..
|
|
}) => match code {
|
|
KeyCode::Char(c)
|
|
if matches!(modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT) =>
|
|
{
|
|
Some(c)
|
|
}
|
|
KeyCode::Backspace if modifiers == KeyModifiers::NONE => Some('\x08'),
|
|
KeyCode::Delete if modifiers == KeyModifiers::NONE => Some('\x7F'),
|
|
KeyCode::Enter if modifiers == KeyModifiers::NONE => Some('\n'),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_pane_move(&self) -> Option<Dir> {
|
|
match &self.0 {
|
|
TerminalEvent::Key(KeyEvent {
|
|
code,
|
|
modifiers: KeyModifiers::ALT,
|
|
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
|
..
|
|
}) => match code {
|
|
KeyCode::Char('a') => Some(Dir::Left),
|
|
KeyCode::Char('d') => Some(Dir::Right),
|
|
KeyCode::Char('w') => Some(Dir::Up),
|
|
KeyCode::Char('s') => Some(Dir::Down),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_pane_open(&self) -> Option<Dir> {
|
|
match &self.0 {
|
|
TerminalEvent::Key(KeyEvent {
|
|
code,
|
|
modifiers: ALT_SHIFT,
|
|
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
|
..
|
|
}) => match code {
|
|
KeyCode::Char('A') => Some(Dir::Left),
|
|
KeyCode::Char('D') => Some(Dir::Right),
|
|
KeyCode::Char('W') => Some(Dir::Up),
|
|
KeyCode::Char('S') => Some(Dir::Down),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_pane_close(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char('q'),
|
|
modifiers: KeyModifiers::ALT,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::PaneClose)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_move(&self) -> Option<Action> {
|
|
let TerminalEvent::Key(KeyEvent {
|
|
code,
|
|
modifiers,
|
|
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
|
..
|
|
}) = &self.0
|
|
else {
|
|
return None;
|
|
};
|
|
|
|
let retain_base = modifiers.contains(KeyModifiers::SHIFT);
|
|
let word = modifiers.contains(KeyModifiers::CONTROL);
|
|
|
|
let (dir, page) = match code {
|
|
KeyCode::PageUp => (Dir::Up, true),
|
|
KeyCode::PageDown => (Dir::Down, true),
|
|
KeyCode::Left => (Dir::Left, false),
|
|
KeyCode::Right => (Dir::Right, false),
|
|
KeyCode::Up => (Dir::Up, false),
|
|
KeyCode::Down => (Dir::Down, false),
|
|
_ => return None,
|
|
};
|
|
|
|
Some(Action::Move(dir, page, retain_base, word))
|
|
}
|
|
|
|
pub fn to_select_token(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char(' '),
|
|
modifiers: KeyModifiers::CONTROL,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::SelectToken)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_indent(&self) -> Option<Action> {
|
|
if let TerminalEvent::Key(KeyEvent {
|
|
code: c @ (KeyCode::Tab | KeyCode::BackTab),
|
|
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
}) = &self.0
|
|
{
|
|
Some(Action::Indent(*c == KeyCode::Tab))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_open_prompt(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Enter,
|
|
modifiers: KeyModifiers::ALT,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::OpenPrompt)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_open_switcher(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char('b'),
|
|
modifiers: KeyModifiers::CONTROL,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::OpenSwitcher)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_open_opener(&self, path: PathBuf) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char('o'),
|
|
modifiers: KeyModifiers::CONTROL,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::OpenOpener(path))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_open_finder(&self, selection: Option<String>) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char('f'),
|
|
modifiers: KeyModifiers::CONTROL,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::OpenFinder(selection))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_command_start(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char('l'),
|
|
modifiers: KeyModifiers::CONTROL,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::CommandStart("goto_line"))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_go(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Enter,
|
|
modifiers: KeyModifiers::NONE,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::Go)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_yes(&self) -> Option<Action> {
|
|
if matches!(self.to_char(), Some('y' | 'Y')) {
|
|
Some(Action::Yes)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_cancel(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Esc,
|
|
modifiers: KeyModifiers::NONE,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::Cancel)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_continue(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Esc | KeyCode::Enter | KeyCode::Char(' '),
|
|
modifiers: KeyModifiers::NONE,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::Continue)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_no(&self) -> Option<Action> {
|
|
if matches!(self.to_char(), Some('n' | 'N')) {
|
|
Some(Action::No)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn to_save(&self) -> Option<Action> {
|
|
if matches!(
|
|
&self.0,
|
|
TerminalEvent::Key(KeyEvent {
|
|
code: KeyCode::Char('s'),
|
|
modifiers: KeyModifiers::CONTROL,
|
|
kind: KeyEventKind::Press,
|
|
..
|
|
})
|
|
) {
|
|
Some(Action::Save)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|