zte/src/action.rs

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
}
}
}