Skip to main content

inquire/prompts/
action.rs

1//! Definitions for the broad Action type which encompasses
2//! the directives for prompts.
3
4use std::fmt::Debug;
5
6use crate::ui::{Key, KeyModifiers};
7
8/// Top-level type to describe the directives a prompt
9/// receives.
10///
11/// Each prompt should implement its own custom InnerAction type
12/// which is parsed and stored in the Inner variant, if applicable,
13/// on the normal execution flow of a prompt.
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub enum Action<I>
16where
17    I: Copy + Clone + PartialEq + Eq,
18{
19    /// Submits the current prompt answer, finishing the prompt if valid.
20    Submit,
21    /// Cancels the prompt execution with a graceful shutdown.
22    Cancel,
23    /// Interrupts the prompt execution without a graceful shutdown.
24    Interrupt,
25    /// Specialized actions according to the prompt type.
26    Inner(I),
27}
28
29impl<I> Action<I>
30where
31    I: Copy + Clone + PartialEq + Eq,
32{
33    /// Derives a prompt action from a Key event.
34    pub fn from_key<C>(key: Key, config: &C) -> Option<Action<I>>
35    where
36        I: InnerAction<Config = C>,
37    {
38        match key {
39            Key::Enter
40            | Key::Char('\n', KeyModifiers::NONE)
41            | Key::Char('j', KeyModifiers::CONTROL)
42                if I::ESCAPE_POLICY == EscapePolicy::CancelOnEscape =>
43            {
44                Some(Action::Submit)
45            }
46            Key::Escape if I::ESCAPE_POLICY == EscapePolicy::SubmitOnEscape => Some(Action::Submit),
47            Key::Escape if I::ESCAPE_POLICY == EscapePolicy::CancelOnEscape => Some(Action::Cancel),
48            Key::Char('c', KeyModifiers::CONTROL) => Some(Action::Interrupt),
49            key => I::from_key(key, config).map(Action::Inner),
50        }
51    }
52}
53
54/// Defers and configures the core submit/cancel control flow behavior for prompt inputs.
55///
56/// This policy decouples individual components from the global key mapper. Instead of hardcoding
57/// specific component behaviors (e.g., checking if an action belongs to a multi-select prompt),
58/// components advertise their structural key handling strategy via this enum.
59#[derive(Copy, Clone, Debug, PartialEq, Eq)]
60pub enum EscapePolicy {
61    /// Esc 取消组件
62    CancelOnEscape,
63    /// Esc 作为确认并提交
64    SubmitOnEscape,
65    /// 把 Esc 忽略或交给自定义配置
66    IgnoreEscape,
67}
68
69/// InnerActions are specialized prompt actions.
70///
71/// They must provide an implementation to optionally derive an action
72/// from a key event.
73pub trait InnerAction
74where
75    Self: Sized + Copy + Clone + PartialEq + Eq,
76{
77    /// 默认情况下,cancel 为退出,enter 为 submit
78    const ESCAPE_POLICY: EscapePolicy = EscapePolicy::CancelOnEscape;
79
80    /// Configuration type for the prompt.
81    ///
82    /// This is used to derive the action from a key event.
83    type Config;
84
85    /// Derives a prompt action from a Key event and the prompt configuration.
86    fn from_key(key: Key, config: &Self::Config) -> Option<Self>
87    where
88        Self: Sized;
89}
90
91#[cfg(test)]
92mod test {
93    use crate::{
94        ui::{Key, KeyModifiers},
95        Action, InnerAction,
96    };
97
98    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
99    pub enum MockInnerAction {
100        Action(Key),
101    }
102
103    impl InnerAction for MockInnerAction {
104        type Config = ();
105
106        fn from_key(key: Key, _config: &()) -> Option<Self>
107        where
108            Self: Sized,
109        {
110            Some(Self::Action(key))
111        }
112    }
113
114    #[test]
115    fn standard_keybindings_for_submit() {
116        let key = Key::Enter;
117        assert_eq!(
118            Some(Action::<MockInnerAction>::Submit),
119            Action::from_key(key, &())
120        );
121    }
122
123    #[test]
124    fn standard_keybindings_for_cancel() {
125        let key = Key::Escape;
126        assert_eq!(
127            Some(Action::<MockInnerAction>::Cancel),
128            Action::from_key(key, &())
129        );
130    }
131
132    #[test]
133    fn ctrl_c_results_in_interrupt_action() {
134        let key = Key::Char('c', KeyModifiers::CONTROL);
135        assert_eq!(
136            Some(Action::<MockInnerAction>::Interrupt),
137            Action::from_key(key, &())
138        );
139    }
140
141    #[test]
142    fn generic_keys_are_passed_down_to_inner_action() {
143        assert_eq!(
144            Some(Action::<MockInnerAction>::Inner(MockInnerAction::Action(
145                Key::Char('a', KeyModifiers::NONE)
146            ))),
147            Action::from_key(Key::Char('a', KeyModifiers::NONE), &())
148        );
149        assert_eq!(
150            Some(Action::<MockInnerAction>::Inner(MockInnerAction::Action(
151                Key::Home
152            ))),
153            Action::from_key(Key::Home, &())
154        );
155        assert_eq!(
156            Some(Action::<MockInnerAction>::Inner(MockInnerAction::Action(
157                Key::PageDown(KeyModifiers::NONE)
158            ))),
159            Action::from_key(Key::PageDown(KeyModifiers::NONE), &())
160        );
161    }
162
163    #[test]
164    fn emacs_control_keybindings() {
165        assert_eq!(
166            Some(Action::<MockInnerAction>::Submit),
167            Action::from_key(Key::Char('j', KeyModifiers::CONTROL), &())
168        );
169        assert_eq!(
170            Some(Action::<MockInnerAction>::Cancel),
171            Action::from_key(Key::Char('g', KeyModifiers::CONTROL), &())
172        );
173    }
174}