use std::marker::PhantomData;
use crate::{
actions::{Action, InsertTextAction, PromptAction},
key::TerminalKey,
keybindings::{
EdgeEvent,
InputKey,
InputKeyState,
InputState,
Mode,
ModeKeys,
ModeSequence,
SequenceStatus,
},
prelude::*,
};
use crate::editing::{
application::{ApplicationInfo, EmptyInfo},
context::{EditContext, EditContextBuilder},
};
use crate::util::{keycode_to_num, option_muladd_u32, option_muladd_usize};
use super::{CharacterContext, CommonKeyClass};
pub mod keybindings;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum EmacsMode {
#[default]
Insert,
Command,
Search,
}
impl<I: ApplicationInfo> Mode<Action<I>, EmacsState<I>> for EmacsMode {
fn enter(&self, _: Self, ctx: &mut EmacsState<I>) -> Vec<Action<I>> {
match self {
EmacsMode::Insert => {
return vec![];
},
EmacsMode::Command | EmacsMode::Search => {
ctx.persist.shape = None;
return vec![];
},
}
}
fn show(&self, ctx: &EmacsState<I>) -> Option<String> {
match self {
EmacsMode::Insert => {
match ctx.persist.insert {
InsertStyle::Insert => {
return None;
},
InsertStyle::Replace => {
return Some("Overwrite mode enabled".into());
},
}
},
EmacsMode::Command | EmacsMode::Search => {
return None;
},
}
}
}
impl<I: ApplicationInfo> ModeSequence<RepeatType, Action<I>, EmacsState<I>> for EmacsMode {
fn sequences(
&self,
action: &Action<I>,
ctx: &EditContext,
) -> Vec<(RepeatType, SequenceStatus)> {
match self {
EmacsMode::Insert | EmacsMode::Command => {
vec![
(RepeatType::EditSequence, action.is_edit_sequence(SequenceStatus::Break, ctx)),
(RepeatType::LastAction, action.is_last_action(ctx)),
(RepeatType::LastSelection, action.is_last_selection(ctx)),
]
},
EmacsMode::Search => {
vec![]
},
}
}
}
impl<I: ApplicationInfo> ModeKeys<TerminalKey, Action<I>, EmacsState<I>> for EmacsMode {
fn unmapped(
&self,
ke: &TerminalKey,
ctx: &mut EmacsState<I>,
) -> (Vec<Action<I>>, Option<Self>) {
ctx.persist.repeating = false;
match self {
EmacsMode::Insert => {
if let Some(c) = ke.get_char() {
let ch = Char::Single(c).into();
let it = InsertTextAction::Type(ch, MoveDir1D::Previous, Count::Contextual);
(vec![it.into()], None)
} else {
(vec![], None)
}
},
EmacsMode::Command => {
if let Some(c) = ke.get_char() {
let it = InsertTextAction::Type(
Char::Single(c).into(),
MoveDir1D::Previous,
Count::Contextual,
);
(vec![it.into()], None)
} else {
(vec![], None)
}
},
EmacsMode::Search => {
if let Some(c) = ke.get_char() {
let ch = Char::Single(c).into();
let it = InsertTextAction::Type(ch, MoveDir1D::Previous, Count::Contextual);
(vec![it.into()], None)
} else {
(vec![PromptAction::Abort(false).into()], Some(EmacsMode::Insert))
}
},
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct ActionContext {
pub(crate) count: Option<usize>,
pub(crate) counting: Option<usize>,
pub(crate) register: Option<Register>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PersistentContext {
regexsearch_dir: MoveDir1D,
regexsearch_inc: bool,
repeating: bool,
insert: InsertStyle,
shape: Option<TargetShape>,
shift: bool,
}
impl Default for PersistentContext {
fn default() -> Self {
Self {
regexsearch_dir: MoveDir1D::Next,
regexsearch_inc: true,
repeating: false,
insert: InsertStyle::Insert,
shape: None,
shift: false,
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct EmacsState<I: ApplicationInfo = EmptyInfo> {
pub(crate) action: ActionContext,
pub(crate) persist: PersistentContext,
pub(self) ch: CharacterContext,
_p: PhantomData<I>,
}
impl<I: ApplicationInfo> Clone for EmacsState<I> {
fn clone(&self) -> Self {
Self {
action: self.action.clone(),
persist: self.persist.clone(),
ch: self.ch.clone(),
_p: PhantomData,
}
}
}
impl<I: ApplicationInfo> InputState for EmacsState<I> {
type Output = EditContext;
fn merge(original: EditContext, overrides: &EditContext) -> EditContext {
let mut builder = EditContextBuilder::from(original);
if let n @ Some(_) = overrides.get_count() {
builder = builder.count(n);
}
builder.build()
}
fn reset(&mut self) {
self.action = ActionContext::default();
}
fn take(&mut self) -> Self::Output {
let state = Self {
persist: self.persist.clone(),
action: std::mem::take(&mut self.action),
ch: std::mem::take(&mut self.ch),
_p: PhantomData,
};
EditContext::from(state)
}
}
impl<I: ApplicationInfo> From<EmacsState<I>> for EditContext {
fn from(ctx: EmacsState<I>) -> Self {
EditContextBuilder::default()
.count(ctx.action.count)
.typed_char(ctx.ch.get_typed())
.search_regex_dir(ctx.persist.regexsearch_dir)
.target_shape(ctx.persist.shape)
.insert_style(ctx.persist.insert.into())
.last_column(true)
.register(ctx.action.register.clone())
.register_append(false)
.search_incremental(ctx.persist.regexsearch_inc)
.build()
}
}
impl<I: ApplicationInfo> Default for EmacsState<I> {
fn default() -> Self {
Self {
action: ActionContext::default(),
persist: PersistentContext::default(),
ch: CharacterContext::default(),
_p: PhantomData,
}
}
}
impl<I: ApplicationInfo> InputKeyState<TerminalKey, CommonKeyClass> for EmacsState<I> {
fn event(&mut self, ev: &EdgeEvent<TerminalKey, CommonKeyClass>, ke: &TerminalKey) {
match ev {
EdgeEvent::Key(_) | EdgeEvent::Fallthrough => {
},
EdgeEvent::Class(CommonKeyClass::Count) => {
if let Some(n) = keycode_to_num(ke, 10) {
let new = option_muladd_usize(&self.action.counting, 10, n as usize);
self.action.counting = Some(new);
}
},
EdgeEvent::Any => {
self.ch.any = Some(*ke);
},
EdgeEvent::Class(CommonKeyClass::Octal) => {
if let Some(n) = keycode_to_num(ke, 8) {
let new = option_muladd_u32(&self.ch.oct, 8, n);
self.ch.oct = Some(new);
}
},
EdgeEvent::Class(CommonKeyClass::Decimal) => {
if let Some(n) = keycode_to_num(ke, 10) {
let new = option_muladd_u32(&self.ch.dec, 10, n);
self.ch.dec = Some(new);
}
},
EdgeEvent::Class(CommonKeyClass::Hexadecimal) => {
if let Some(n) = keycode_to_num(ke, 16) {
let new = option_muladd_u32(&self.ch.hex, 16, n);
self.ch.hex = Some(new);
}
},
EdgeEvent::Class(CommonKeyClass::Digraph1) => {
if let Some(c) = ke.get_char() {
self.ch.digraph1 = Some(c);
}
},
EdgeEvent::Class(CommonKeyClass::Digraph2) => {
if let Some(c) = ke.get_char() {
self.ch.digraph2 = Some(c);
}
},
EdgeEvent::Class(_) => {
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mode_show() {
let mut ctx: EmacsState = EmacsState::default();
assert_eq!(EmacsMode::Search.show(&ctx), None);
ctx.persist.insert = InsertStyle::Insert;
assert_eq!(EmacsMode::Insert.show(&ctx), None);
ctx.persist.insert = InsertStyle::Replace;
assert_eq!(EmacsMode::Insert.show(&ctx), Some("Overwrite mode enabled".into()));
}
}