rucline/actions.rs
1//! Provides mappings and actions to change the behavior of the [`prompt`] when reading user input.
2//!
3//! There is a built-in set of default [`Action`]s that will be executed upon user interaction.
4//! These are meant to feel natural when coming from the default terminal, while also adding further
5//! functionality and editing commands.
6//!
7//! However, bindings that override the default behavior can be given to the [`prompt`] to cause
8//! a different [`Action`] to be taken.
9//!
10//! # Examples
11//!
12//! Changing the behavior of `TAB` from the default [`Suggest`] action to actually printing `\t`.
13//!
14//! Using [`KeyBindings`]:
15//! ```
16//! use rucline::actions::{Action, Event, KeyBindings, KeyCode};
17//! use rucline::prompt::{Builder, Prompt};
18//!
19//! let mut bindings = KeyBindings::new();
20//! bindings.insert(Event::from(KeyCode::Tab), Action::Write('\t'));
21//!
22//! let prompt = Prompt::new().overrider(bindings);
23//! ```
24//!
25//! Using a closure:
26//! ```
27//! use rucline::actions::{Action, Event, KeyCode};
28//! use rucline::prompt::{Builder, Prompt};
29//!
30//! let prompt = Prompt::new().overrider_fn(|e, _| {
31//! if e == Event::from(KeyCode::Tab) {
32//! Some(Action::Write('\t'))
33//! } else {
34//! None
35//! }
36//! });
37//! ```
38//!
39//! # Overriding a default action
40//!
41//! The [`KeyBindings`] map provides a way to store the association
42//! between [`Event`] and [`Action`] which will override the default behavior.
43//!
44//! ```
45//! use rucline::actions::{Action, Event, KeyBindings, KeyCode};
46//!
47//! let mut bindings = KeyBindings::new();
48//! bindings.insert(Event::from(KeyCode::Tab), Action::Write('\t'));
49//! ```
50//!
51//! # Disabling a default action
52//!
53//! To explicitly remove an [`Action`] from the default behavior, the [`Noop`] action can be
54//! set as the override.
55//!
56//! ```
57//! use rucline::actions::{Action, Event, KeyBindings, KeyCode};
58//!
59//! let mut bindings = KeyBindings::new();
60//! bindings.insert(Event::from(KeyCode::Tab), Action::Noop);
61//! ```
62//!
63//! # Saving key binding configurations
64//!
65//! If the feature `serialize` is enabled, [`KeyBindings`] can be serialized, stored, and loaded
66//! at runtime.
67//!
68//! # Default behavior
69//!
70//! In the absence of [`KeyBindings`] or an entry for a given [`Event`], the default behavior
71//! will be as follows:
72//!
73//! ```no_run
74//! # fn default_action(event: rucline::actions::Event) -> rucline::actions::Action {
75//! # use rucline::actions::{Action::*, Direction::*, KeyCode, Range::*, Scope::* };
76//! # match event.code {
77//! KeyCode::Enter => Accept,
78//! KeyCode::Esc => Cancel,
79//! KeyCode::Tab => Suggest(Forward),
80//! KeyCode::BackTab => Suggest(Backward),
81//! KeyCode::Backspace => Delete(Relative(Single, Backward)),
82//! KeyCode::Delete => Delete(Relative(Single, Forward)),
83//! KeyCode::Right => Move(Single, Forward),
84//! KeyCode::Left => Move(Single, Backward),
85//! KeyCode::Home => Move(Line, Backward),
86//! KeyCode::End => Move(Line, Forward),
87//! KeyCode::Char(c) => {
88//! if event.modifiers == crossterm::event::KeyModifiers::CONTROL {
89//! match c {
90//! 'm' | 'd' => Accept,
91//! 'c' => Cancel,
92//!
93//! 'b' => Move(Single, Backward),
94//! 'f' => Move(Single, Forward),
95//! 'a' => Move(Line, Backward),
96//! 'e' => Move(Line, Forward),
97//!
98//! 'j' => Delete(Relative(Word, Backward)),
99//! 'k' => Delete(Relative(Word, Forward)),
100//! 'h' => Delete(Relative(Line, Backward)),
101//! 'l' => Delete(Relative(Line, Forward)),
102//! 'w' => Delete(WholeWord),
103//! 'u' => Delete(WholeLine),
104//! _ => Noop,
105//! }
106//! } else if event.modifiers == crossterm::event::KeyModifiers::ALT {
107//! match c {
108//! 'b' => Move(Word, Backward),
109//! 'f' => Move(Word, Forward),
110//! _ => Noop,
111//! }
112//! } else {
113//! Write(c)
114//! }
115//! }
116//! _ => Noop,
117//! # }}
118//! ```
119//!
120//! > Check the test cases for [`Buffer`] to see how line edits are expected to behave.
121//!
122//! [`Action`]: enum.Action.html
123//! [`Event`]: type.Event.html
124//! [`KeyBindings`]: type.KeyBindings.html
125//! [`Noop`]: enum.Action.html#variant.Noop
126//! [`Suggest`]: enum.Action.html#variant.Suggest
127//! [`prompt`]: ../prompt/index.html
128//! [`Buffer`]: ../buffer/struct.Buffer.html
129
130use crate::Buffer;
131
132#[cfg(feature = "serde")]
133use serde::{Deserialize, Serialize};
134
135/// Alias to `crossterm::event::KeyEvent` from [`crossterm`](https://docs.rs/crossterm/).
136pub use crossterm::event::KeyEvent as Event;
137
138/// Alias to `crossterm::event::KeyCode` from [`crossterm`](https://docs.rs/crossterm/).
139pub use crossterm::event::KeyCode;
140
141/// Alias to [`HashMap<Event, Action>`](std::collections::HashMap)
142pub type KeyBindings = std::collections::HashMap<Event, Action>;
143
144/// An action that can be performed while reading a line
145#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
146#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
147pub enum Action {
148 /// Write a single character where the cursor is
149 Write(char),
150 /// Delete a section based on the cursor, defined by [`Scope`](enum.Scope.html)
151 Delete(Scope),
152 /// Move the cursor for a [`Range`](enum.Range.html) in a [`Direction`](enum.Direction.html)
153 Move(Range, Direction),
154 /// Trigger the [`suggester`](../completion/trait.Suggester.html)
155 Suggest(Direction),
156 /// Accept [`Range`](enum.Range.html) from the current completion presented by
157 /// [`completer`](../completion/trait.Completer.html), if any
158 Complete(Range),
159 /// Accept the current line
160 Accept,
161 /// Cancel the suggestions, if any. Else, discard the whole line
162 Cancel,
163 /// Do nothing and wait for the next [`Event`](type.Event.html)
164 Noop,
165}
166
167/// The scope an [`Action`](enum.Action.html) should be applied on
168#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
169#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
170pub enum Scope {
171 /// Represents a whole line
172 WholeLine,
173 /// Represents a whole word
174 WholeWord,
175 /// Represents a relative scope, with a [`Range`](enum.Range.html)
176 /// and [`Direction`](enum.Direction.html)
177 Relative(Range, Direction),
178}
179
180/// The range an [`Action`](enum.Action.html) should extend for
181#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
182#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
183pub enum Range {
184 /// Represents the remainder of the line
185 Line,
186 /// Represents a single word
187 Word,
188 /// Represents a single character
189 Single,
190}
191
192/// The direction an [`Action`](enum.Action.html) may take
193#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
194#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
195pub enum Direction {
196 /// Represents a "right" or "down" direction
197 Forward,
198 /// Represents a "left" or "up" direction
199 Backward,
200}
201
202/// Overrides the behavior for a given [`Event`].
203///
204/// This trait has a convenience implementation for [`KeyBindings`] and also a conversion
205/// from closures.
206///
207/// # Example
208///
209/// ```
210/// use rucline::actions::{Action, Event, KeyCode};
211/// use rucline::prompt::{Builder, Prompt};
212///
213/// let prompt = Prompt::new().overrider_fn(|e, _| if e == Event::from(KeyCode::Tab) {
214/// Some(Action::Write('\t'))
215/// } else {
216/// None
217/// });
218/// ```
219///
220/// [`Event`]: type.Event.html
221/// [`KeyBindings`]: type.KeyBindings.html
222pub trait Overrider {
223 /// Overrides the behavior for the given [`Event`].
224 ///
225 /// [`Buffer`] will contain the current context of the prompt, in case the behavior should
226 /// be contextual.
227 ///
228 /// # Arguments
229 /// * [`event`] - The incoming event to be processed.
230 /// * [`buffer`] - The current context in which this event is coming in.
231 ///
232 /// [`Event`]: type.Event.html
233 /// [`Buffer`]: ../buffer/struct.Buffer.html
234 fn override_for(&self, event: Event, buffer: &Buffer) -> Option<Action>;
235}
236
237impl Overrider for KeyBindings {
238 fn override_for(&self, event: Event, _: &Buffer) -> Option<Action> {
239 self.get(&event).copied()
240 }
241}
242
243impl<F> Overrider for F
244where
245 F: Fn(Event, &Buffer) -> Option<Action>,
246{
247 fn override_for(&self, event: Event, buffer: &Buffer) -> Option<Action> {
248 self(event, buffer)
249 }
250}
251
252pub(super) fn action_for<O: Overrider + ?Sized>(
253 overrider: Option<&O>,
254 event: Event,
255 buffer: &Buffer,
256) -> Action {
257 overrider
258 .as_ref()
259 .and_then(|b| b.override_for(event, buffer))
260 .unwrap_or_else(|| default_action(event, buffer))
261}
262
263#[inline]
264fn control_pressed(event: &Event) -> bool {
265 event.modifiers == crossterm::event::KeyModifiers::CONTROL
266}
267
268#[inline]
269fn alt_pressed(event: &Event) -> bool {
270 event.modifiers == crossterm::event::KeyModifiers::ALT
271}
272
273#[inline]
274fn complete_if_at_end_else_move(buffer: &Buffer, range: Range) -> Action {
275 if buffer.cursor() == buffer.len() {
276 if range == Range::Word {
277 Action::Complete(Range::Word)
278 } else {
279 Action::Complete(Range::Line)
280 }
281 } else {
282 Action::Move(range, Direction::Forward)
283 }
284}
285
286fn default_action(event: Event, buffer: &Buffer) -> Action {
287 use Action::{Accept, Cancel, Delete, Move, Noop, Suggest, Write};
288 use Direction::{Backward, Forward};
289 use Range::{Line, Single, Word};
290 use Scope::{Relative, WholeLine, WholeWord};
291
292 match event.code {
293 KeyCode::Enter => Accept,
294 KeyCode::Esc => Cancel,
295 KeyCode::Tab => Suggest(Forward),
296 KeyCode::BackTab => Suggest(Backward),
297 KeyCode::Backspace => Delete(Relative(Single, Backward)),
298 KeyCode::Delete => Delete(Relative(Single, Forward)),
299 KeyCode::Right => complete_if_at_end_else_move(buffer, Single),
300 KeyCode::Left => Move(Single, Backward),
301 KeyCode::Home => Move(Line, Backward),
302 KeyCode::End => complete_if_at_end_else_move(buffer, Line),
303 KeyCode::Char(c) => {
304 if control_pressed(&event) {
305 match c {
306 'm' | 'd' => Accept,
307 'c' => Cancel,
308
309 'b' => Move(Single, Backward),
310 'f' => complete_if_at_end_else_move(buffer, Single),
311 'a' => Move(Line, Backward),
312 'e' => complete_if_at_end_else_move(buffer, Line),
313
314 'j' => Delete(Relative(Word, Backward)),
315 'k' => Delete(Relative(Word, Forward)),
316 'h' => Delete(Relative(Line, Backward)),
317 'l' => Delete(Relative(Line, Forward)),
318 'w' => Delete(WholeWord),
319 'u' => Delete(WholeLine),
320 _ => Noop,
321 }
322 } else if alt_pressed(&event) {
323 match c {
324 'b' => Move(Word, Backward),
325 'f' => complete_if_at_end_else_move(buffer, Word),
326 _ => Noop,
327 }
328 } else {
329 Write(c)
330 }
331 }
332 _ => Noop,
333 }
334}
335
336#[cfg(test)]
337mod test {
338 use super::{action_for, default_action, Action, Buffer, Direction, Event, KeyCode, Range};
339
340 #[test]
341 fn should_complete_if_at_end() {
342 use crossterm::event::KeyModifiers;
343 use Action::{Complete, Move};
344 use Direction::Forward;
345 use KeyCode::{Char, End, Right};
346 use Range::{Line, Single, Word};
347
348 let mut c = "a".into();
349
350 assert_eq!(default_action(Event::from(Right), &c), Complete(Line));
351 assert_eq!(default_action(Event::from(End), &c), Complete(Line));
352 assert_eq!(
353 default_action(Event::new(Char('f'), KeyModifiers::CONTROL), &c),
354 Complete(Line)
355 );
356 assert_eq!(
357 default_action(Event::new(Char('f'), KeyModifiers::ALT), &c),
358 Complete(Word)
359 );
360
361 c.set_cursor(0).unwrap();
362
363 assert_eq!(
364 default_action(Event::from(Right), &c),
365 Move(Single, Forward)
366 );
367 assert_eq!(default_action(Event::from(End), &c), Move(Line, Forward));
368 assert_eq!(
369 default_action(Event::new(Char('f'), KeyModifiers::CONTROL), &c),
370 Move(Single, Forward)
371 );
372 assert_eq!(
373 default_action(Event::new(Char('f'), KeyModifiers::ALT), &c),
374 Move(Word, Forward)
375 );
376 }
377
378 #[test]
379 fn should_default_if_no_mapping() {
380 use super::KeyBindings;
381 use KeyCode::Tab;
382 let action = action_for::<KeyBindings>(None, Event::from(Tab), &Buffer::new());
383 assert_eq!(action, Action::Suggest(Direction::Forward));
384 }
385
386 mod basic {
387 use super::super::{
388 action_for, Action, Buffer, Direction, Event, KeyBindings, KeyCode::Tab,
389 };
390
391 #[test]
392 fn should_default_if_event_missing_form_mapping() {
393 let overrider = KeyBindings::new();
394 let action = action_for(Some(&overrider), Event::from(Tab), &Buffer::new());
395 assert_eq!(action, Action::Suggest(Direction::Forward));
396 }
397
398 #[test]
399 fn should_override_if_defined() {
400 let mut bindings = KeyBindings::new();
401 bindings.insert(Event::from(Tab), Action::Write('\t'));
402 let action = action_for(Some(&bindings), Event::from(Tab), &Buffer::new());
403 assert_eq!(action, Action::Write('\t'));
404 }
405 }
406
407 mod closure {
408 use super::super::{action_for, Action, Buffer, Direction, Event, KeyCode::Tab};
409
410 #[test]
411 fn should_default_if_event_missing_form_mapping() {
412 let overrider = |_, _: &Buffer| None;
413 let action = action_for(Some(&overrider), Event::from(Tab), &Buffer::new());
414 assert_eq!(action, Action::Suggest(Direction::Forward));
415 }
416
417 #[test]
418 fn should_override_if_defined() {
419 let overrider = |e, _: &Buffer| {
420 if e == Event::from(Tab) {
421 Some(Action::Write('\t'))
422 } else {
423 None
424 }
425 };
426 let action = action_for(Some(&overrider), Event::from(Tab), &Buffer::new());
427 assert_eq!(action, Action::Write('\t'));
428 }
429 }
430}