use crossterm::event::{Event, KeyEvent, KeyEventKind};
use serde::{Deserialize, Serialize};
use strum::{EnumDiscriminants, EnumIter, EnumString, VariantArray};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MouseButton {
#[default]
Left,
Right,
Middle,
}
impl From<crossterm::event::MouseButton> for MouseButton {
fn from(button: crossterm::event::MouseButton) -> Self {
match button {
crossterm::event::MouseButton::Left => Self::Left,
crossterm::event::MouseButton::Right => Self::Right,
crossterm::event::MouseButton::Middle => Self::Middle,
}
}
}
#[non_exhaustive]
#[derive(Debug)]
pub enum Signal {
Success(String),
CtrlC, CtrlD,
HostCommand(String),
ExternalBreak(String),
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum TextObjectScope {
Inner,
Around,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum TextObjectType {
Word,
BigWord,
Brackets,
Quote,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct TextObject {
pub scope: TextObjectScope,
pub object_type: TextObjectType,
}
impl Default for TextObject {
fn default() -> Self {
Self {
scope: TextObjectScope::Inner,
object_type: TextObjectType::Word,
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Direction {
Forward,
Backward,
}
impl Direction {
pub(crate) fn reversed(self) -> Self {
match self {
Direction::Forward => Direction::Backward,
Direction::Backward => Direction::Forward,
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum WordKind {
Word,
LongWord,
Unicode,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum WordEdge {
Start,
End,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum FindStop {
On,
Before,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Granularity {
#[default]
CharWise,
LineWise,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum MotionTarget {
Grapheme(Direction),
Word {
kind: WordKind,
edge: WordEdge,
direction: Direction,
},
LineEdge(Direction),
BufferEdge(Direction),
Line(Direction),
Find {
ch: char,
direction: Direction,
stop: FindStop,
},
Offset(usize),
}
impl MotionTarget {
pub(crate) fn reversed(self) -> Self {
match self {
MotionTarget::Find {
ch,
direction,
stop,
} => MotionTarget::Find {
ch,
direction: direction.reversed(),
stop,
},
other => other,
}
}
}
#[non_exhaustive]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, EnumDiscriminants)]
#[strum_discriminants(doc = "This is the auto generated discriminant type for [`EditCommand`]")]
#[strum_discriminants(derive(EnumIter, EnumString, VariantArray))]
#[strum_discriminants(strum(ascii_case_insensitive))]
pub enum EditCommand {
MoveToStart {
select: bool,
},
MoveToLineStart {
select: bool,
},
MoveToLineNonBlankStart {
select: bool,
},
MoveToEnd {
select: bool,
},
MoveToLineEnd {
select: bool,
},
MoveLineUp {
select: bool,
},
MoveLineDown {
select: bool,
},
MoveLeft {
select: bool,
},
MoveRight {
select: bool,
},
MoveWordLeft {
select: bool,
},
MoveBigWordLeft {
select: bool,
},
MoveWordRight {
select: bool,
},
MoveWordRightStart {
select: bool,
},
MoveBigWordRightStart {
select: bool,
},
MoveWordRightEnd {
select: bool,
},
MoveBigWordRightEnd {
select: bool,
},
MoveToPosition {
position: usize,
select: bool,
},
Move(MotionTarget),
Extend(MotionTarget),
Cut {
target: MotionTarget,
granularity: Granularity,
},
Copy {
target: MotionTarget,
granularity: Granularity,
},
Change {
target: MotionTarget,
granularity: Granularity,
},
Erase(MotionTarget),
InsertChar(char),
InsertString(String),
InsertNewline,
InsertNewlineAbove,
InsertNewlineBelow,
ReplaceChar(char),
ReplaceChars(usize, String),
Backspace,
Delete,
CutChar,
BackspaceWord,
DeleteWord,
Clear,
ClearToLineEnd,
Complete,
CutCurrentLine,
CutFromStart,
CutFromStartLinewise {
leave_blank_line: bool,
},
CutFromLineStart,
CutFromLineNonBlankStart,
CutToEnd,
CutToEndLinewise {
leave_blank_line: bool,
},
CutToLineEnd,
KillLine,
CutWordLeft,
CutBigWordLeft,
CutWordRight,
CutBigWordRight,
CutWordRightToNext,
CutBigWordRightToNext,
PasteCutBufferBefore,
PasteCutBufferAfter,
UppercaseWord,
LowercaseWord,
CapitalizeChar,
SwitchcaseChar,
SwapWords,
SwapGraphemes,
Undo,
Redo,
CutRightUntil(char),
CutRightBefore(char),
MoveRightUntil {
c: char,
select: bool,
},
MoveRightBefore {
c: char,
select: bool,
},
CutLeftUntil(char),
CutLeftBefore(char),
MoveLeftUntil {
c: char,
select: bool,
},
MoveLeftBefore {
c: char,
select: bool,
},
SelectAll,
CutSelection,
CopySelection,
Paste,
CopyFromStart,
CopyFromStartLinewise,
CopyFromLineStart,
CopyFromLineNonBlankStart,
CopyToEnd,
CopyToEndLinewise,
CopyToLineEnd,
CopyCurrentLine,
CopyWordLeft,
CopyBigWordLeft,
CopyWordRight,
CopyBigWordRight,
CopyWordRightToNext,
CopyBigWordRightToNext,
CopyLeft,
CopyRight,
CopyRightUntil(char),
CopyRightBefore(char),
CopyLeftUntil(char),
CopyLeftBefore(char),
SwapCursorAndAnchor,
#[cfg(feature = "system_clipboard")]
CutSelectionSystem,
#[cfg(feature = "system_clipboard")]
CopySelectionSystem,
#[cfg(feature = "system_clipboard")]
PasteSystem,
CutInsidePair {
left: char,
right: char,
},
CopyInsidePair {
left: char,
right: char,
},
CutAroundPair {
left: char,
right: char,
},
CopyAroundPair {
left: char,
right: char,
},
CutTextObject {
text_object: TextObject,
},
CopyTextObject {
text_object: TextObject,
},
}
impl EditCommand {
pub fn edit_type(&self) -> EditType {
match self {
EditCommand::MoveToStart { select, .. }
| EditCommand::MoveToEnd { select, .. }
| EditCommand::MoveToLineStart { select, .. }
| EditCommand::MoveToLineEnd { select, .. }
| EditCommand::MoveToLineNonBlankStart { select, .. }
| EditCommand::MoveToPosition { select, .. }
| EditCommand::MoveLineUp { select, .. }
| EditCommand::MoveLineDown { select, .. }
| EditCommand::MoveLeft { select, .. }
| EditCommand::MoveRight { select, .. }
| EditCommand::MoveWordLeft { select, .. }
| EditCommand::MoveBigWordLeft { select, .. }
| EditCommand::MoveWordRight { select, .. }
| EditCommand::MoveWordRightStart { select, .. }
| EditCommand::MoveBigWordRightStart { select, .. }
| EditCommand::MoveWordRightEnd { select, .. }
| EditCommand::MoveBigWordRightEnd { select, .. }
| EditCommand::MoveRightUntil { select, .. }
| EditCommand::MoveRightBefore { select, .. }
| EditCommand::MoveLeftUntil { select, .. }
| EditCommand::MoveLeftBefore { select, .. } => {
EditType::MoveCursor { select: *select }
}
EditCommand::SwapCursorAndAnchor => EditType::MoveCursor { select: true },
EditCommand::SelectAll => EditType::MoveCursor { select: true },
EditCommand::InsertChar(_)
| EditCommand::Backspace
| EditCommand::Delete
| EditCommand::CutChar
| EditCommand::InsertString(_)
| EditCommand::InsertNewline
| EditCommand::InsertNewlineAbove
| EditCommand::InsertNewlineBelow
| EditCommand::ReplaceChar(_)
| EditCommand::ReplaceChars(_, _)
| EditCommand::BackspaceWord
| EditCommand::DeleteWord
| EditCommand::Clear
| EditCommand::ClearToLineEnd
| EditCommand::Complete
| EditCommand::CutCurrentLine
| EditCommand::CutFromStart
| EditCommand::CutFromStartLinewise { .. }
| EditCommand::CutFromLineStart
| EditCommand::CutFromLineNonBlankStart
| EditCommand::CutToLineEnd
| EditCommand::KillLine
| EditCommand::CutToEnd
| EditCommand::CutToEndLinewise { .. }
| EditCommand::CutWordLeft
| EditCommand::CutBigWordLeft
| EditCommand::CutWordRight
| EditCommand::CutBigWordRight
| EditCommand::CutWordRightToNext
| EditCommand::CutBigWordRightToNext
| EditCommand::PasteCutBufferBefore
| EditCommand::PasteCutBufferAfter
| EditCommand::UppercaseWord
| EditCommand::LowercaseWord
| EditCommand::SwitchcaseChar
| EditCommand::CapitalizeChar
| EditCommand::SwapWords
| EditCommand::SwapGraphemes
| EditCommand::CutRightUntil(_)
| EditCommand::CutRightBefore(_)
| EditCommand::CutLeftUntil(_)
| EditCommand::CutLeftBefore(_)
| EditCommand::CutSelection
| EditCommand::Paste
| EditCommand::CutInsidePair { .. }
| EditCommand::CutAroundPair { .. }
| EditCommand::CutTextObject { .. } => EditType::EditText,
#[cfg(feature = "system_clipboard")] EditCommand::CutSelectionSystem | EditCommand::PasteSystem => EditType::EditText,
EditCommand::Undo | EditCommand::Redo => EditType::UndoRedo,
EditCommand::CopySelection => EditType::NoOp,
#[cfg(feature = "system_clipboard")]
EditCommand::CopySelectionSystem => EditType::NoOp,
EditCommand::CopyFromStart
| EditCommand::CopyFromStartLinewise
| EditCommand::CopyFromLineStart
| EditCommand::CopyFromLineNonBlankStart
| EditCommand::CopyToEnd
| EditCommand::CopyToEndLinewise
| EditCommand::CopyToLineEnd
| EditCommand::CopyCurrentLine
| EditCommand::CopyWordLeft
| EditCommand::CopyBigWordLeft
| EditCommand::CopyWordRight
| EditCommand::CopyBigWordRight
| EditCommand::CopyWordRightToNext
| EditCommand::CopyBigWordRightToNext
| EditCommand::CopyLeft
| EditCommand::CopyRight
| EditCommand::CopyRightUntil(_)
| EditCommand::CopyRightBefore(_)
| EditCommand::CopyLeftUntil(_)
| EditCommand::CopyLeftBefore(_)
| EditCommand::CopyInsidePair { .. }
| EditCommand::CopyAroundPair { .. }
| EditCommand::CopyTextObject { .. } => EditType::NoOp,
EditCommand::Move(_) => EditType::MoveCursor { select: false },
EditCommand::Extend(_) => EditType::MoveCursor { select: true },
EditCommand::Cut { .. } => EditType::EditText,
EditCommand::Copy { .. } => EditType::NoOp,
EditCommand::Change { .. } => EditType::EditText,
EditCommand::Erase(_) => EditType::EditText,
}
}
}
#[derive(PartialEq, Eq)]
pub enum EditType {
MoveCursor { select: bool },
UndoRedo,
EditText,
NoOp,
}
#[derive(Debug)]
pub enum UndoBehavior {
InsertCharacter(char),
Backspace(Option<char>),
Delete(Option<char>),
MoveCursor,
HistoryNavigation,
CreateUndoPoint,
NoOp,
}
impl UndoBehavior {
pub fn create_undo_point_after(&self, previous: &UndoBehavior) -> bool {
use UndoBehavior as UB;
match (previous, self) {
(_, UB::MoveCursor) => false,
(UB::HistoryNavigation, UB::HistoryNavigation) => false,
(UB::InsertCharacter(c_prev), UB::InsertCharacter(c_new)) => {
(*c_prev == '\n' || *c_prev == '\r')
|| (!c_prev.is_whitespace() && c_new.is_whitespace())
}
(UB::Backspace(Some(c_prev)), UB::Backspace(Some(c_new))) => {
(*c_new == '\n' || *c_new == '\r')
|| (c_prev.is_whitespace() && !c_new.is_whitespace())
}
(UB::Backspace(_), UB::Backspace(_)) => false,
(UB::Delete(Some(c_prev)), UB::Delete(Some(c_new))) => {
(*c_new == '\n' || *c_new == '\r')
|| (c_prev.is_whitespace() && !c_new.is_whitespace())
}
(UB::Delete(_), UB::Delete(_)) => false,
(_, _) => true,
}
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, EnumDiscriminants)]
#[strum_discriminants(doc = "This is the auto generated discriminant type for [`ReedlineEvent`]")]
#[strum_discriminants(derive(EnumIter, EnumString, VariantArray))]
#[strum_discriminants(strum(ascii_case_insensitive))]
pub enum ReedlineEvent {
None,
HistoryHintComplete,
HistoryHintWordComplete,
CtrlD,
CtrlC,
ClearScreen,
ClearScrollback,
Enter,
Submit,
SubmitOrNewline,
#[strum_discriminants(strum(serialize = "Esc", serialize = "Escape"))]
Esc,
Mouse {
column: u16,
row: u16,
button: MouseButton,
},
Resize(u16, u16),
Edit(Vec<EditCommand>),
Repaint,
PreviousHistory,
Up,
Down,
Right,
Left,
ToStart,
ToEnd,
NextHistory,
SearchHistory,
Multiple(Vec<ReedlineEvent>),
UntilFound(Vec<ReedlineEvent>),
Menu(String),
MenuNext,
MenuPrevious,
MenuUp,
MenuDown,
MenuLeft,
MenuRight,
MenuPageNext,
MenuPagePrevious,
ExecuteHostCommand(String),
OpenEditor,
ViChangeMode(String),
}
pub enum EventStatus {
Handled,
Inapplicable,
Exits(Signal),
}
pub struct ReedlineRawEvent(Event);
impl TryFrom<Event> for ReedlineRawEvent {
type Error = ();
fn try_from(event: Event) -> Result<Self, Self::Error> {
match event {
Event::Key(KeyEvent {
kind: KeyEventKind::Release,
..
}) => Err(()),
Event::Key(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Repeat,
state,
}) => Ok(Self(Event::Key(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state,
}))),
other => Ok(Self(other)),
}
}
}
impl From<ReedlineRawEvent> for Event {
fn from(event: ReedlineRawEvent) -> Self {
event.0
}
}
#[cfg(feature = "helix")]
impl TryFrom<ReedlineRawEvent> for KeyEvent {
type Error = ReedlineRawEvent;
fn try_from(event: ReedlineRawEvent) -> Result<Self, Self::Error> {
match event.0 {
Event::Key(key_event) => Ok(key_event),
other => Err(ReedlineRawEvent(other)),
}
}
}