use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use crate::exhaustive_match;
use crossterm::event::{KeyEvent, MouseEvent};
use derive_more::{Debug, Eq, PartialEq};
type BoxError = Box<dyn std::error::Error + Sync + Send>;
type BoxFuture<'a> = Pin<Box<dyn Future<Output = Result<Vec<Event>, BoxError>> + Send + 'a>>;
trait AsyncCallbackFn: Send {
fn call<'a>(&'a self, app: &'a mut crate::tui::App) -> BoxFuture<'a>;
}
struct AsyncFnWrapper<F>(F);
impl<F, Fut> AsyncCallbackFn for AsyncFnWrapper<F>
where
F: for<'a> Fn(&'a mut crate::tui::App) -> Fut + Send,
Fut: Future<Output = Result<Vec<Event>, BoxError>> + Send + 'static,
{
fn call<'a>(&'a self, app: &'a mut crate::tui::App) -> BoxFuture<'a> {
Box::pin((self.0)(app))
}
}
struct SyncFnWrapper<F>(F);
impl<F> AsyncCallbackFn for SyncFnWrapper<F>
where
F: Fn(&mut crate::tui::App) -> Result<Vec<Event>, BoxError> + Send,
{
fn call<'a>(&'a self, app: &'a mut crate::tui::App) -> BoxFuture<'a> {
Box::pin(std::future::ready((self.0)(app)))
}
}
#[derive(Clone)]
pub struct ActionCallback(Arc<Mutex<dyn AsyncCallbackFn>>);
impl std::fmt::Debug for ActionCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ActionCallback").finish()
}
}
impl ActionCallback {
pub fn new<F, Fut>(f: F) -> Self
where
F: for<'a> Fn(&'a mut crate::tui::App) -> Fut + Send + 'static,
Fut: Future<Output = Result<Vec<Event>, BoxError>> + Send + 'static,
{
Self(Arc::new(Mutex::new(AsyncFnWrapper(f))))
}
pub fn new_sync<F>(f: F) -> Self
where
F: Fn(&mut crate::tui::App) -> Result<Vec<Event>, BoxError> + Send + 'static,
{
Self(Arc::new(Mutex::new(SyncFnWrapper(f))))
}
pub(crate) fn call(&self, app: &mut crate::tui::App) -> Result<Vec<Event>, BoxError> {
let callback = self.0.lock().unwrap();
let fut = callback.call(app);
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
}
}
#[derive(Clone, Debug)]
pub enum Event {
Quit,
Error(String),
Close,
Tick,
Render,
Key(KeyEvent),
Paste(String),
Mouse(MouseEvent),
PreviewReady,
InvalidInput,
Action(Action),
AppendItems(Vec<Arc<dyn crate::SkimItem>>),
ClearItems,
Clear,
Heartbeat,
RunPreview,
Redraw,
Reload(String),
Resize(u16, u16),
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Action {
Abort,
Accept(Option<String>),
AddChar(char),
AppendAndSelect,
BackwardChar,
BackwardDeleteChar,
BackwardDeleteCharEof,
BackwardKillWord,
BackwardWord,
BeginningOfLine,
Cancel,
ClearScreen,
DeleteChar,
DeleteCharEof,
DeselectAll,
Down(u16),
EndOfLine,
Execute(String),
ExecuteSilent(String),
First,
ForwardChar,
ForwardWord,
IfQueryEmpty(String, Option<String>),
IfQueryNotEmpty(String, Option<String>),
IfNonMatched(String, Option<String>),
Ignore,
KillLine,
KillWord,
Last,
NextHistory,
HalfPageDown(i32),
HalfPageUp(i32),
PageDown(i32),
PageUp(i32),
PreviewUp(i32),
PreviewDown(i32),
PreviewLeft(i32),
PreviewRight(i32),
PreviewPageUp(i32),
PreviewPageDown(i32),
PreviousHistory,
Redraw,
RefreshCmd,
RefreshPreview,
RestartMatcher,
Reload(Option<String>),
RotateMode,
ScrollLeft(i32),
ScrollRight(i32),
SelectAll,
SelectRow(usize),
Select,
SetHeader(Option<String>),
SetPreviewCmd(String),
SetQuery(String),
Toggle,
ToggleAll,
ToggleIn,
ToggleInteractive,
ToggleOut,
TogglePreview,
TogglePreviewWrap,
ToggleSort,
Top,
UnixLineDiscard,
UnixWordRubout,
Up(u16),
Yank,
#[debug("custom")]
#[eq(skip)]
#[partial_eq(skip)]
#[serde(skip)]
Custom(ActionCallback),
}
pub fn parse_action(raw_action: &str) -> Option<Action> {
let parts = raw_action.split_once([':', '(', ')']);
let action;
let mut arg = None;
match parts {
None => action = raw_action,
Some((act, "")) => action = act,
Some((act, a)) => {
action = act;
arg = Some(a.trim_end_matches(")").to_string())
}
}
debug!("parse_action: action={action}, arg={arg:?}");
if action.starts_with("if-") {
let then_arg;
let mut otherwise_arg = None;
let if_arg = arg.unwrap_or_else(|| panic!("no arg specified for event {action}"));
if if_arg.contains("+") {
let split = if_arg.split_once("+");
match split {
Some((a, "")) => {
then_arg = a.to_string();
}
Some((a, b)) => {
then_arg = a.to_string();
otherwise_arg = Some(b.to_string());
}
None => unreachable!(),
}
} else {
then_arg = if_arg.to_string();
}
match action {
"if-non-matched" => Some(Action::IfNonMatched(then_arg, otherwise_arg)),
"if-query-empty" => Some(Action::IfQueryEmpty(then_arg, otherwise_arg)),
"if-query-not-empty" => Some(Action::IfQueryNotEmpty(then_arg, otherwise_arg)),
_ => None,
}
} else {
exhaustive_match! {
action => Option<Action>;
{
"abort" => Some(Abort),
"accept" => Some(Accept(arg)),
"add-char" => Some(AddChar(
arg.unwrap_or_default()
.chars()
.next()
.expect("add-char should have an argument"),
)),
"append-and-select" => Some(AppendAndSelect),
"backward-char" => Some(BackwardChar),
"backward-delete-char" => Some(BackwardDeleteChar),
"backward-delete-char/eof" => Some(BackwardDeleteCharEof),
"backward-kill-word" => Some(BackwardKillWord),
"backward-word" => Some(BackwardWord),
"beginning-of-line" => Some(BeginningOfLine),
"cancel" => Some(Cancel),
"clear-screen" => Some(ClearScreen),
"delete-char" => Some(DeleteChar),
"delete-char/eof" => Some(DeleteCharEof),
"deselect-all" => Some(DeselectAll),
"down" => Some(Down(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"end-of-line" => Some(EndOfLine),
"execute" => Some(Execute(arg.expect("execute event should have argument"))),
"execute-silent" => Some(ExecuteSilent(arg.expect("execute-silent event should have argument"))),
"first" => Some(First),
"forward-char" => Some(ForwardChar),
"forward-word" => Some(ForwardWord),
"ignore" => Some(Ignore),
"kill-line" => Some(KillLine),
"kill-word" => Some(KillWord),
"last" => Some(Last),
"next-history" => Some(NextHistory),
"half-page-down" => Some(HalfPageDown(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"half-page-up" => Some(HalfPageUp(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"page-down" => Some(PageDown(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"page-up" => Some(PageUp(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"preview-up" => Some(PreviewUp(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"preview-down" => Some(PreviewDown(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"preview-left" => Some(PreviewLeft(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"preview-right" => Some(PreviewRight(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"preview-page-up" => Some(PreviewPageUp(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"preview-page-down" => Some(PreviewPageDown(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"previous-history" => Some(PreviousHistory),
"redraw" => Some(Redraw),
"refresh-cmd" => Some(RefreshCmd),
"refresh-preview" => Some(RefreshPreview),
"restart-matcher" => Some(RestartMatcher),
"reload" => Some(Reload(arg.clone())),
"rotate-mode" => Some(RotateMode),
"scroll-left" => Some(ScrollLeft(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"scroll-right" => Some(ScrollRight(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"select" => Some(Select),
"select-all" => Some(SelectAll),
"select-row" => Some(SelectRow(arg.and_then(|s| s.parse().ok()).unwrap_or_default())),
"set-header" => Some(SetHeader(arg)),
"set-preview-cmd" => Some(SetPreviewCmd(arg.expect("set-preview-cmd action needs a value"))),
"set-query" => Some(SetQuery(arg.expect("set-query action needs a value"))),
"toggle" => Some(Toggle),
"toggle-all" => Some(ToggleAll),
"toggle-in" => Some(ToggleIn),
"toggle-interactive" => Some(ToggleInteractive),
"toggle-out" => Some(ToggleOut),
"toggle-preview" => Some(TogglePreview),
"toggle-preview-wrap" => Some(TogglePreviewWrap),
"toggle-sort" => Some(ToggleSort),
"top" => Some(Top),
"unix-line-discard" => Some(UnixLineDiscard),
"unix-word-rubout" => Some(UnixWordRubout),
"up" => Some(Up(arg.and_then(|s| s.parse().ok()).unwrap_or(1))),
"yank" => Some(Yank),
"unreachable-if-non-matched" => Some(IfNonMatched(Default::default(), None)),
"unreachable-if-query-empty" => Some(IfQueryEmpty(Default::default(), None)),
"unreachable-if-query-not-empty" => Some(IfQueryNotEmpty(Default::default(), None)),
"custom-do-not-use-from-cli" => Some(Custom(ActionCallback::new_sync(|_: &mut crate::tui::App| { Ok(Vec::new()) }))),
}
default _ => None
}
}
}