#![allow(clippy::pattern_type_mismatch)] use {
crate::{
io::{Dimensions, File, Input, UserAction},
orient,
},
core::fmt::{self, Debug},
crossterm::event::KeyCode,
enum_map::{enum_map, Enum, EnumMap},
lsp_types::{MessageType, ShowMessageRequestParams},
parse_display::Display as ParseDisplay,
};
#[derive(Debug, PartialEq)]
pub(crate) enum Operation {
Resize {
dimensions: Dimensions,
},
Reset,
Confirm(ConfirmAction),
Quit,
StartCommand(Command),
Collect(char),
Execute,
CreateDoc(File),
Scroll(orient::ScreenDirection),
}
#[derive(Debug, PartialEq)]
pub(crate) enum ConfirmAction {
Quit,
}
impl fmt::Display for ConfirmAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "You have input that you want to quit the application.\nPlease confirm this action by pressing `y`. To cancel this action, press any other key.")
}
}
impl From<ConfirmAction> for ShowMessageRequestParams {
#[inline]
#[must_use]
fn from(value: ConfirmAction) -> Self {
Self {
typ: MessageType::Info,
message: value.to_string(),
actions: None,
}
}
}
#[derive(Debug, ParseDisplay, PartialEq)]
pub(crate) enum Command {
#[display("Open <file>")]
Open,
}
#[derive(Debug, PartialEq)]
pub(crate) enum DocOp {
Save,
}
impl fmt::Display for DocOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Save => "save",
}
)
}
}
#[derive(Debug)]
pub(crate) struct Interpreter {
mode: Mode,
map: EnumMap<Mode, &'static dyn ModeInterpreter>,
}
impl Interpreter {
pub(crate) fn translate(&mut self, input: Input) -> Option<Operation> {
let mut output = Output::new();
match input {
Input::File(file) => {
output.add_op(Operation::CreateDoc(file));
}
Input::Lsp(_statement) => {}
Input::User(user_input) => {
#[allow(clippy::indexing_slicing)] let mode_interpreter = self.map[self.mode];
output = mode_interpreter.decode(user_input);
}
}
if let Some(mode) = output.new_mode {
self.mode = mode;
}
output.operation
}
}
impl Default for Interpreter {
fn default() -> Self {
static VIEW_INTERPRETER: ViewInterpreter = ViewInterpreter::new();
static CONFIRM_INTERPRETER: ConfirmInterpreter = ConfirmInterpreter::new();
static COLLECT_INTERPRETER: CollectInterpreter = CollectInterpreter::new();
let view_interpreter: &dyn ModeInterpreter = &VIEW_INTERPRETER;
Self {
map: enum_map! {
Mode::View => view_interpreter,
Mode::Confirm => &CONFIRM_INTERPRETER,
Mode::Collect => &COLLECT_INTERPRETER,
},
mode: Mode::default(),
}
}
}
#[derive(Copy, Clone, Debug, Enum, Eq, ParseDisplay, PartialEq, Hash)]
#[display(style = "CamelCase")]
enum Mode {
View,
Confirm,
Collect,
}
impl Default for Mode {
#[inline]
fn default() -> Self {
Self::View
}
}
#[derive(Debug, Default, PartialEq)]
struct Output {
operation: Option<Operation>,
new_mode: Option<Mode>,
}
impl Output {
fn new() -> Self {
Self::default()
}
fn add_op(&mut self, operation: Operation) {
let _ = self.operation.replace(operation);
}
fn set_mode(&mut self, mode: Mode) {
self.new_mode = Some(mode);
}
fn reset(&mut self) {
self.add_op(Operation::Reset);
self.set_mode(Mode::View);
}
}
trait ModeInterpreter: Debug {
fn decode(&self, input: UserAction) -> Output;
}
#[derive(Clone, Debug)]
struct ViewInterpreter {}
impl ViewInterpreter {
const fn new() -> Self {
Self {}
}
fn decode_key(key: KeyCode, output: &mut Output) {
match key {
KeyCode::Esc => {
output.add_op(Operation::Reset);
}
KeyCode::Char('w') => {
output.add_op(Operation::Confirm(ConfirmAction::Quit));
output.set_mode(Mode::Confirm);
}
KeyCode::Char('o') => {
output.add_op(Operation::StartCommand(Command::Open));
output.set_mode(Mode::Collect);
}
KeyCode::Char('j') => {
output.add_op(Operation::Scroll(orient::ScreenDirection::Down));
}
KeyCode::Char('k') => {
output.add_op(Operation::Scroll(orient::ScreenDirection::Up));
}
KeyCode::Backspace
| KeyCode::Enter
| KeyCode::Left
| KeyCode::Right
| KeyCode::Up
| KeyCode::Down
| KeyCode::Home
| KeyCode::End
| KeyCode::PageUp
| KeyCode::PageDown
| KeyCode::Tab
| KeyCode::BackTab
| KeyCode::Delete
| KeyCode::Insert
| KeyCode::F(..)
| KeyCode::Null
| KeyCode::Char(..) => {}
}
}
}
impl ModeInterpreter for ViewInterpreter {
fn decode(&self, input: UserAction) -> Output {
let mut output = Output::new();
match input {
UserAction::Key { code, .. } => {
Self::decode_key(code, &mut output);
}
UserAction::Resize { dimensions } => {
output.add_op(Operation::Resize { dimensions });
}
UserAction::Mouse => {}
}
output
}
}
#[derive(Clone, Debug)]
struct ConfirmInterpreter {}
impl ConfirmInterpreter {
const fn new() -> Self {
Self {}
}
}
impl ModeInterpreter for ConfirmInterpreter {
fn decode(&self, input: UserAction) -> Output {
let mut output = Output::new();
match input {
UserAction::Key {
code: KeyCode::Char('y'),
..
} => {
output.add_op(Operation::Quit);
}
UserAction::Key { .. } | UserAction::Mouse | UserAction::Resize { .. } => {
output.reset();
}
}
output
}
}
#[derive(Clone, Debug)]
struct CollectInterpreter {}
impl CollectInterpreter {
const fn new() -> Self {
Self {}
}
}
impl ModeInterpreter for CollectInterpreter {
fn decode(&self, input: UserAction) -> Output {
let mut output = Output::new();
match input {
UserAction::Key {
code: KeyCode::Esc, ..
} => {
output.reset();
}
UserAction::Key {
code: KeyCode::Enter,
..
} => {
output.add_op(Operation::Execute);
output.set_mode(Mode::View);
}
UserAction::Key {
code: KeyCode::Char(c),
..
} => {
output.add_op(Operation::Collect(c));
}
UserAction::Key { .. } | UserAction::Mouse | UserAction::Resize { .. } => {}
}
output
}
}
#[cfg(test)]
mod test {
use {super::*, crossterm::event::KeyModifiers};
mod view {
use super::*;
fn view_mode() -> Interpreter {
Interpreter::default()
}
#[test]
fn quit() {
let mut int = view_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('w'),
modifiers: KeyModifiers::CONTROL,
})),
Some(Operation::Confirm(ConfirmAction::Quit))
);
assert_eq!(int.mode, Mode::Confirm);
}
#[test]
fn open() {
let mut int = view_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::CONTROL,
})),
Some(Operation::StartCommand(Command::Open))
);
assert_eq!(int.mode, Mode::Collect);
}
}
mod confirm {
use super::*;
fn confirm_mode() -> Interpreter {
let mut int = Interpreter::default();
int.mode = Mode::Confirm;
int
}
#[test]
fn confirm() {
let mut int = confirm_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('y'),
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Quit)
);
}
#[test]
fn cancel() {
let mut int = confirm_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('n'),
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Reset)
);
assert_eq!(int.mode, Mode::View);
int = confirm_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('1'),
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Reset)
);
assert_eq!(int.mode, Mode::View);
}
}
#[cfg(test)]
mod collect {
use super::*;
fn collect_mode() -> Interpreter {
let mut int = Interpreter::default();
int.mode = Mode::Collect;
int
}
#[test]
fn reset() {
let mut int = collect_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Esc,
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Reset)
);
assert_eq!(int.mode, Mode::View);
}
#[test]
fn collect() {
let mut int = collect_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('a'),
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Collect('a'))
);
assert_eq!(int.mode, Mode::Collect);
int = collect_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('.'),
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Collect('.'))
);
assert_eq!(int.mode, Mode::Collect);
int = collect_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Char('1'),
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Collect('1'))
);
assert_eq!(int.mode, Mode::Collect);
}
#[test]
fn execute() {
let mut int = collect_mode();
assert_eq!(
int.translate(Input::User(UserAction::Key {
code: KeyCode::Enter,
modifiers: KeyModifiers::empty(),
})),
Some(Operation::Execute)
);
assert_eq!(int.mode, Mode::View);
}
}
}