Skip to main content

shrub_core/interfaces/
keybindings.rs

1use std::collections::HashMap;
2use std::fmt::{self, Display, Formatter};
3
4/// Represents an action that can be taken in response to a key sequence.
5#[derive(Clone, Debug, Eq, Hash, PartialEq)]
6pub enum KeyAction {
7    /// Execute a shell command.
8    ShellCommand(String),
9    /// Execute an input "function".
10    DoInputFunction(InputFunction),
11    /// Execute a sequence of actions (in order).
12    Sequence(Vec<Self>),
13}
14
15impl Display for KeyAction {
16    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::ShellCommand(command) => write!(f, "shell command: {command}"),
19            Self::DoInputFunction(function) => function.fmt(f),
20            Self::Sequence(actions) => {
21                write!(f, "sequence[")?;
22                for (i, action) in actions.iter().enumerate() {
23                    if i > 0 {
24                        write!(f, ", ")?;
25                    }
26                    action.fmt(f)?;
27                }
28                write!(f, "]")
29            }
30        }
31    }
32}
33
34/// Defines all input functions. Based on standard `readline` functions,
35/// augmented with some `shrub`-specific extensions.
36#[derive(
37    Clone,
38    Debug,
39    Eq,
40    Hash,
41    PartialEq,
42    strum_macros::EnumString,
43    strum_macros::Display,
44    strum_macros::EnumIter,
45    strum_macros::IntoStaticStr,
46)]
47#[strum(serialize_all = "kebab-case")]
48#[expect(missing_docs)]
49pub enum InputFunction {
50    Abort,
51    AcceptLine,
52    AliasExpandLine,
53    ArrowKeyPrefix,
54    BackwardByte,
55    BackwardChar,
56    BackwardDeleteChar,
57    BackwardKillLine,
58    BackwardKillWord,
59    BackwardWord,
60    BashViComplete,
61    BeginningOfHistory,
62    BeginningOfLine,
63    BracketedPasteBegin,
64    ShrubAcceptHint,
65    ShrubAcceptHintWord,
66    CallLastKbdMacro,
67    CapitalizeWord,
68    CharacterSearch,
69    CharacterSearchBackward,
70    ClearDisplay,
71    ClearScreen,
72    Complete,
73    CompleteCommand,
74    CompleteFilename,
75    CompleteHostname,
76    CompleteIntoBraces,
77    CompleteUsername,
78    CompleteVariable,
79    CopyBackwardWord,
80    CopyForwardWord,
81    CopyRegionAsKill,
82    DabbrevExpand,
83    DeleteChar,
84    DeleteCharOrList,
85    DeleteHorizontalSpace,
86    DigitArgument,
87    DisplayShellVersion,
88    DoLowercaseVersion,
89    DowncaseWord,
90    DumpFunctions,
91    DumpMacros,
92    DumpVariables,
93    DynamicCompleteHistory,
94    EditAndExecuteCommand,
95    EmacsEditingMode,
96    EndKbdMacro,
97    EndOfHistory,
98    EndOfLine,
99    ExchangePointAndMark,
100    ExecuteNamedCommand,
101    ExportCompletions,
102    FetchHistory,
103    ForwardBackwardDeleteChar,
104    ForwardByte,
105    ForwardChar,
106    ForwardSearchHistory,
107    ForwardWord,
108    GlobCompleteWord,
109    GlobExpandWord,
110    GlobListExpansions,
111    HistoryAndAliasExpandLine,
112    HistoryExpandLine,
113    HistorySearchBackward,
114    HistorySearchForward,
115    HistorySubstringSearchBackward,
116    HistorySubstringSearchForward,
117    InsertComment,
118    InsertCompletions,
119    InsertLastArgument,
120    KillLine,
121    KillRegion,
122    KillWholeLine,
123    KillWord,
124    MagicSpace,
125    MenuComplete,
126    MenuCompleteBackward,
127    NextHistory,
128    NextScreenLine,
129    NonIncrementalForwardSearchHistory,
130    NonIncrementalForwardSearchHistoryAgain,
131    NonIncrementalReverseSearchHistory,
132    NonIncrementalReverseSearchHistoryAgain,
133    OldMenuComplete,
134    OperateAndGetNext,
135    OverwriteMode,
136    PossibleCommandCompletions,
137    PossibleCompletions,
138    PossibleFilenameCompletions,
139    PossibleHostnameCompletions,
140    PossibleUsernameCompletions,
141    PossibleVariableCompletions,
142    PreviousHistory,
143    PreviousScreenLine,
144    PrintLastKbdMacro,
145    QuotedInsert,
146    ReReadInitFile,
147    RedrawCurrentLine,
148    ReverseSearchHistory,
149    RevertLine,
150    SelfInsert,
151    SetMark,
152    ShellBackwardKillWord,
153    ShellBackwardWord,
154    ShellExpandLine,
155    ShellForwardWord,
156    ShellKillWord,
157    ShellTransposeWords,
158    SkipCsiSequence,
159    SpellCorrectWord,
160    StartKbdMacro,
161    TabInsert,
162    TildeExpand,
163    TransposeChars,
164    TransposeWords,
165    TtyStatus,
166    Undo,
167    UniversalArgument,
168    UnixFilenameRubout,
169    UnixLineDiscard,
170    UnixWordRubout,
171    UpcaseWord,
172    ViAppendEol,
173    ViAppendMode,
174    ViArgDigit,
175    #[strum(serialize = "vi-bWord")]
176    ViBWord,
177    ViBackToIndent,
178    ViBackwardBigword,
179    ViBackwardWord,
180    ViBword,
181    ViChangeCase,
182    ViChangeChar,
183    ViChangeTo,
184    ViCharSearch,
185    ViColumn,
186    ViComplete,
187    ViDelete,
188    ViDeleteTo,
189    #[strum(serialize = "vi-eWord")]
190    ViEWord,
191    ViEditAndExecuteCommand,
192    ViEditingMode,
193    ViEndBigword,
194    ViEndWord,
195    ViEofMaybe,
196    ViEword,
197    #[strum(serialize = "vi-fWord")]
198    ViFWord,
199    ViFetchHistory,
200    ViFirstPrint,
201    ViForwardBigword,
202    ViForwardWord,
203    ViFword,
204    ViGotoMark,
205    ViInsertBeg,
206    ViInsertionMode,
207    ViMatch,
208    ViMovementMode,
209    ViNextWord,
210    ViOverstrike,
211    ViOverstrikeDelete,
212    ViPrevWord,
213    ViPut,
214    ViRedo,
215    ViReplace,
216    ViRubout,
217    ViSearch,
218    ViSearchAgain,
219    ViSetMark,
220    ViSubst,
221    ViTildeExpand,
222    ViUndo,
223    ViUnixWordRubout,
224    ViYankArg,
225    ViYankPop,
226    ViYankTo,
227    Yank,
228    YankLastArg,
229    YankNthArg,
230    YankPop,
231}
232
233/// Represents a sequence of keys.
234#[derive(Clone, Debug, Eq, Hash, PartialEq)]
235pub enum KeySequence {
236    /// Strokes that make up the sequence.
237    Strokes(Vec<KeyStroke>),
238    /// Raw bytes that were used to generate this sequence.
239    Bytes(Vec<Vec<u8>>),
240}
241
242impl Display for KeySequence {
243    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
244        match self {
245            Self::Strokes(strokes) => {
246                for stroke in strokes {
247                    stroke.fmt(f)?;
248                }
249            }
250            Self::Bytes(bytes) => {
251                for byte in bytes.iter().flatten() {
252                    if !byte.is_ascii_control() {
253                        write!(f, "{}", *byte as char)?;
254                    } else if *byte == b'\x1b' {
255                        write!(f, r"\e")?;
256                    } else if *byte >= 0x01 && *byte <= 0x1a {
257                        // Control characters: display as \C-<letter>
258                        let letter = (b'a' + (*byte - 1)) as char;
259                        write!(f, r"\C-{letter}")?;
260                    } else {
261                        write!(f, r"\x{byte:02x}")?;
262                    }
263                }
264            }
265        }
266
267        Ok(())
268    }
269}
270
271impl From<KeyStroke> for KeySequence {
272    /// Creates a new key sequence with a single stroke.
273    fn from(value: KeyStroke) -> Self {
274        Self::Strokes(vec![value])
275    }
276}
277
278#[derive(Clone, Debug, Eq, Hash, PartialEq)]
279/// Represents a single key press.
280pub struct KeyStroke {
281    /// Alt key was pressed.
282    pub alt: bool,
283    /// Control key was pressed.
284    pub control: bool,
285    /// Shift key was pressed.
286    pub shift: bool,
287    /// Primary key pressed.
288    pub key: Key,
289}
290
291impl Display for KeyStroke {
292    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
293        if self.alt {
294            write!(f, "\\e")?;
295        }
296        if self.control {
297            write!(f, "\\C-")?;
298        }
299        if self.shift {
300            // TODO(input): Figure out what to do here or if the key encodes the
301            // shift in it.
302        }
303        self.key.fmt(f)
304    }
305}
306
307impl From<Key> for KeyStroke {
308    /// Creates a new key stroke with a single key.
309    fn from(value: Key) -> Self {
310        Self {
311            alt: false,
312            control: false,
313            shift: false,
314            key: value,
315        }
316    }
317}
318
319#[derive(Clone, Debug, Eq, Hash, PartialEq)]
320/// Represents a single key.
321pub enum Key {
322    /// A simple character key.
323    Character(char),
324    /// Backspace key.
325    Backspace,
326    /// Enter key.
327    Enter,
328    /// Left arrow key.
329    Left,
330    /// Right arrow key.
331    Right,
332    /// Up arrow key.
333    Up,
334    /// Down arrow key.
335    Down,
336    /// Home key.
337    Home,
338    /// End key.
339    End,
340    /// Page up key.
341    PageUp,
342    /// Page down key.
343    PageDown,
344    /// Tab key.
345    Tab,
346    /// Shift + Tab key.
347    BackTab,
348    /// Delete key.
349    Delete,
350    /// Insert key.
351    Insert,
352    /// F key.
353    F(u8),
354    /// Escape key.
355    Escape,
356}
357
358impl Display for Key {
359    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
360        match self {
361            Self::Character(c @ ('\\' | '\"' | '\'')) => write!(f, "\\{c}")?,
362            Self::Character(c) => write!(f, "{c}")?,
363            Self::Backspace => write!(f, "Backspace")?,
364            Self::Enter => write!(f, "Enter")?,
365            Self::Left => write!(f, "Left")?,
366            Self::Right => write!(f, "Right")?,
367            Self::Up => write!(f, "Up")?,
368            Self::Down => write!(f, "Down")?,
369            Self::Home => write!(f, "Home")?,
370            Self::End => write!(f, "End")?,
371            Self::PageUp => write!(f, "PageUp")?,
372            Self::PageDown => write!(f, "PageDown")?,
373            Self::Tab => write!(f, "Tab")?,
374            Self::BackTab => write!(f, "BackTab")?,
375            Self::Delete => write!(f, "Delete")?,
376            Self::Insert => write!(f, "Insert")?,
377            Self::F(n) => write!(f, "F{n}")?,
378            Self::Escape => write!(f, "Esc")?,
379        }
380
381        Ok(())
382    }
383}
384
385/// Encapsulates the shell's interaction with key bindings for input.
386pub trait KeyBindings: Send {
387    /// Retrieves current bindings.
388    fn get_current(&self) -> HashMap<KeySequence, KeyAction>;
389
390    /// Tries to find a binding for an untranslated byte sequence.
391    fn get_untranslated(&self, bytes: &[u8]) -> Option<&KeyAction>;
392
393    /// Sets or updates a binding.
394    ///
395    /// # Arguments
396    ///
397    /// * `seq` - The key sequence to bind.
398    /// * `action` - The action to bind to the sequence.
399    ///
400    /// # Errors
401    ///
402    /// Returns an I/O error if the implementation cannot record or apply the
403    /// binding, such as when the underlying input backend rejects it.
404    fn bind(&mut self, seq: KeySequence, action: KeyAction) -> Result<(), std::io::Error>;
405
406    /// Unbinds a key sequence. Returns true if a binding was removed.
407    ///
408    /// # Arguments
409    ///
410    /// * `seq` - The key sequence to unbind.
411    fn try_unbind(&mut self, seq: KeySequence) -> bool;
412
413    /// Defines a macro that remaps a key sequence to another key sequence.
414    ///
415    /// # Arguments
416    ///
417    /// * `seq` - The key sequence to bind the macro to.
418    /// * `target` - The sequence that makes up the macro.
419    ///
420    /// # Errors
421    ///
422    /// Returns an I/O error if the implementation cannot record or apply the
423    /// macro.
424    fn define_macro(&mut self, seq: KeySequence, target: KeySequence)
425    -> Result<(), std::io::Error>;
426
427    /// Retrieves all defined macros.
428    fn get_macros(&self) -> HashMap<KeySequence, KeySequence>;
429}