use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Action {
CursorLeft,
CursorRight,
CursorWordLeft,
CursorWordRight,
CursorWordEnd,
CursorStart,
CursorEnd,
DeleteCharBefore,
DeleteCharAfter,
DeleteWordBefore,
DeleteWordAfter,
DeleteToWordBoundary,
ClearLine,
ClearToStart,
ClearToEnd,
SelectNext,
SelectPrevious,
ScrollHalfPageUp,
ScrollHalfPageDown,
ScrollPageUp,
ScrollPageDown,
ScrollToTop,
ScrollToBottom,
ScrollToScreenTop,
ScrollToScreenMiddle,
ScrollToScreenBottom,
Accept,
AcceptNth(u8),
ReturnSelection,
ReturnSelectionNth(u8),
Copy,
Delete,
DeleteAll,
ReturnOriginal,
ReturnQuery,
Exit,
Redraw,
CycleFilterMode,
CycleSearchMode,
SwitchContext,
ClearContext,
ToggleTab,
VimEnterNormal,
VimEnterInsert,
VimEnterInsertAfter,
VimEnterInsertAtStart,
VimEnterInsertAtEnd,
VimSearchInsert,
VimChangeToEnd,
EnterPrefixMode,
InspectPrevious,
InspectNext,
Noop,
}
impl Action {
pub fn from_str(s: &str) -> Result<Self, String> {
if let Some(rest) = s.strip_prefix("accept-")
&& let Ok(n) = rest.parse::<u8>()
&& (1..=9).contains(&n)
{
return Ok(Action::AcceptNth(n));
}
if let Some(rest) = s.strip_prefix("return-selection-")
&& let Ok(n) = rest.parse::<u8>()
&& (1..=9).contains(&n)
{
return Ok(Action::ReturnSelectionNth(n));
}
match s {
"cursor-left" => Ok(Action::CursorLeft),
"cursor-right" => Ok(Action::CursorRight),
"cursor-word-left" => Ok(Action::CursorWordLeft),
"cursor-word-right" => Ok(Action::CursorWordRight),
"cursor-word-end" => Ok(Action::CursorWordEnd),
"cursor-start" => Ok(Action::CursorStart),
"cursor-end" => Ok(Action::CursorEnd),
"delete-char-before" => Ok(Action::DeleteCharBefore),
"delete-char-after" => Ok(Action::DeleteCharAfter),
"delete-word-before" => Ok(Action::DeleteWordBefore),
"delete-word-after" => Ok(Action::DeleteWordAfter),
"delete-to-word-boundary" => Ok(Action::DeleteToWordBoundary),
"clear-line" => Ok(Action::ClearLine),
"clear-to-start" => Ok(Action::ClearToStart),
"clear-to-end" => Ok(Action::ClearToEnd),
"select-next" => Ok(Action::SelectNext),
"select-previous" => Ok(Action::SelectPrevious),
"scroll-half-page-up" => Ok(Action::ScrollHalfPageUp),
"scroll-half-page-down" => Ok(Action::ScrollHalfPageDown),
"scroll-page-up" => Ok(Action::ScrollPageUp),
"scroll-page-down" => Ok(Action::ScrollPageDown),
"scroll-to-top" => Ok(Action::ScrollToTop),
"scroll-to-bottom" => Ok(Action::ScrollToBottom),
"scroll-to-screen-top" => Ok(Action::ScrollToScreenTop),
"scroll-to-screen-middle" => Ok(Action::ScrollToScreenMiddle),
"scroll-to-screen-bottom" => Ok(Action::ScrollToScreenBottom),
"accept" => Ok(Action::Accept),
"return-selection" => Ok(Action::ReturnSelection),
"copy" => Ok(Action::Copy),
"delete" => Ok(Action::Delete),
"delete-all" => Ok(Action::DeleteAll),
"return-original" => Ok(Action::ReturnOriginal),
"return-query" => Ok(Action::ReturnQuery),
"exit" => Ok(Action::Exit),
"redraw" => Ok(Action::Redraw),
"cycle-filter-mode" => Ok(Action::CycleFilterMode),
"cycle-search-mode" => Ok(Action::CycleSearchMode),
"switch-context" => Ok(Action::SwitchContext),
"clear-context" => Ok(Action::ClearContext),
"toggle-tab" => Ok(Action::ToggleTab),
"vim-enter-normal" => Ok(Action::VimEnterNormal),
"vim-enter-insert" => Ok(Action::VimEnterInsert),
"vim-enter-insert-after" => Ok(Action::VimEnterInsertAfter),
"vim-enter-insert-at-start" => Ok(Action::VimEnterInsertAtStart),
"vim-enter-insert-at-end" => Ok(Action::VimEnterInsertAtEnd),
"vim-search-insert" => Ok(Action::VimSearchInsert),
"vim-change-to-end" => Ok(Action::VimChangeToEnd),
"enter-prefix-mode" => Ok(Action::EnterPrefixMode),
"inspect-previous" => Ok(Action::InspectPrevious),
"inspect-next" => Ok(Action::InspectNext),
"noop" => Ok(Action::Noop),
_ => Err(format!("unknown action: {s}")),
}
}
pub fn as_str(&self) -> String {
match self {
Action::CursorLeft => "cursor-left".to_string(),
Action::CursorRight => "cursor-right".to_string(),
Action::CursorWordLeft => "cursor-word-left".to_string(),
Action::CursorWordRight => "cursor-word-right".to_string(),
Action::CursorWordEnd => "cursor-word-end".to_string(),
Action::CursorStart => "cursor-start".to_string(),
Action::CursorEnd => "cursor-end".to_string(),
Action::DeleteCharBefore => "delete-char-before".to_string(),
Action::DeleteCharAfter => "delete-char-after".to_string(),
Action::DeleteWordBefore => "delete-word-before".to_string(),
Action::DeleteWordAfter => "delete-word-after".to_string(),
Action::DeleteToWordBoundary => "delete-to-word-boundary".to_string(),
Action::ClearLine => "clear-line".to_string(),
Action::ClearToStart => "clear-to-start".to_string(),
Action::ClearToEnd => "clear-to-end".to_string(),
Action::SelectNext => "select-next".to_string(),
Action::SelectPrevious => "select-previous".to_string(),
Action::ScrollHalfPageUp => "scroll-half-page-up".to_string(),
Action::ScrollHalfPageDown => "scroll-half-page-down".to_string(),
Action::ScrollPageUp => "scroll-page-up".to_string(),
Action::ScrollPageDown => "scroll-page-down".to_string(),
Action::ScrollToTop => "scroll-to-top".to_string(),
Action::ScrollToBottom => "scroll-to-bottom".to_string(),
Action::ScrollToScreenTop => "scroll-to-screen-top".to_string(),
Action::ScrollToScreenMiddle => "scroll-to-screen-middle".to_string(),
Action::ScrollToScreenBottom => "scroll-to-screen-bottom".to_string(),
Action::Accept => "accept".to_string(),
Action::AcceptNth(n) => format!("accept-{n}"),
Action::ReturnSelection => "return-selection".to_string(),
Action::ReturnSelectionNth(n) => format!("return-selection-{n}"),
Action::Copy => "copy".to_string(),
Action::Delete => "delete".to_string(),
Action::DeleteAll => "delete-all".to_string(),
Action::ReturnOriginal => "return-original".to_string(),
Action::ReturnQuery => "return-query".to_string(),
Action::Exit => "exit".to_string(),
Action::Redraw => "redraw".to_string(),
Action::CycleFilterMode => "cycle-filter-mode".to_string(),
Action::CycleSearchMode => "cycle-search-mode".to_string(),
Action::SwitchContext => "switch-context".to_string(),
Action::ClearContext => "clear-context".to_string(),
Action::ToggleTab => "toggle-tab".to_string(),
Action::VimEnterNormal => "vim-enter-normal".to_string(),
Action::VimEnterInsert => "vim-enter-insert".to_string(),
Action::VimEnterInsertAfter => "vim-enter-insert-after".to_string(),
Action::VimEnterInsertAtStart => "vim-enter-insert-at-start".to_string(),
Action::VimEnterInsertAtEnd => "vim-enter-insert-at-end".to_string(),
Action::VimSearchInsert => "vim-search-insert".to_string(),
Action::VimChangeToEnd => "vim-change-to-end".to_string(),
Action::EnterPrefixMode => "enter-prefix-mode".to_string(),
Action::InspectPrevious => "inspect-previous".to_string(),
Action::InspectNext => "inspect-next".to_string(),
Action::Noop => "noop".to_string(),
}
}
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Serialize for Action {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.as_str())
}
}
impl<'de> Deserialize<'de> for Action {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Action::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_basic_actions() {
assert_eq!(Action::from_str("cursor-left").unwrap(), Action::CursorLeft);
assert_eq!(Action::from_str("accept").unwrap(), Action::Accept);
assert_eq!(Action::from_str("exit").unwrap(), Action::Exit);
assert_eq!(Action::from_str("noop").unwrap(), Action::Noop);
assert_eq!(
Action::from_str("vim-enter-normal").unwrap(),
Action::VimEnterNormal
);
}
#[test]
fn parse_accept_nth() {
assert_eq!(Action::from_str("accept-1").unwrap(), Action::AcceptNth(1));
assert_eq!(Action::from_str("accept-9").unwrap(), Action::AcceptNth(9));
}
#[test]
fn parse_return_selection() {
assert_eq!(
Action::from_str("return-selection").unwrap(),
Action::ReturnSelection
);
assert_eq!(
Action::from_str("return-selection-1").unwrap(),
Action::ReturnSelectionNth(1)
);
assert_eq!(
Action::from_str("return-selection-9").unwrap(),
Action::ReturnSelectionNth(9)
);
}
#[test]
fn parse_unknown_action() {
assert!(Action::from_str("unknown-action").is_err());
assert!(Action::from_str("accept-0").is_err());
assert!(Action::from_str("accept-10").is_err());
assert!(Action::from_str("return-selection-0").is_err());
assert!(Action::from_str("return-selection-10").is_err());
}
#[test]
fn round_trip() {
let actions = vec![
Action::CursorLeft,
Action::Accept,
Action::AcceptNth(5),
Action::ReturnSelection,
Action::ReturnSelectionNth(3),
Action::VimSearchInsert,
Action::ScrollToScreenMiddle,
];
for action in actions {
let s = action.as_str();
let parsed = Action::from_str(&s).unwrap();
assert_eq!(action, parsed);
}
}
#[test]
fn serde_round_trip() {
let action = Action::CursorLeft;
let json = serde_json::to_string(&action).unwrap();
assert_eq!(json, "\"cursor-left\"");
let parsed: Action = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, Action::CursorLeft);
let action = Action::AcceptNth(3);
let json = serde_json::to_string(&action).unwrap();
assert_eq!(json, "\"accept-3\"");
let parsed: Action = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, Action::AcceptNth(3));
}
}