Skip to main content

rustyline/
config.rs

1//! Customize line editor
2use crate::{layout::GraphemeClusterMode, Result};
3use std::default::Default;
4
5/// User preferences
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Config {
8    /// Maximum number of entries in History.
9    max_history_size: usize, // history_max_entries
10    history_duplicates: HistoryDuplicates,
11    history_ignore_space: bool,
12    completion_type: CompletionType,
13    /// Directly show all alternatives or not when [`CompletionType::List`] is
14    /// being used
15    completion_show_all_if_ambiguous: bool,
16    /// When listing completion alternatives, only display
17    /// one screen of possibilities at a time.
18    completion_prompt_limit: usize,
19    /// Duration (milliseconds) Rustyline will wait for a character when
20    /// reading an ambiguous key sequence.
21    keyseq_timeout: Option<u16>,
22    /// Emacs or Vi mode
23    edit_mode: EditMode,
24    /// If true, each nonblank line returned by `readline` will be
25    /// automatically added to the history.
26    auto_add_history: bool,
27    /// Beep or Flash or nothing
28    bell_style: BellStyle,
29    /// if colors should be enabled.
30    color_mode: ColorMode,
31    /// if terminal supports grapheme clustering
32    grapheme_cluster_mode: GraphemeClusterMode,
33    /// Whether to use stdio or not
34    behavior: Behavior,
35    /// Horizontal space taken by a tab.
36    tab_stop: u8,
37    /// Indentation size for indent/dedent commands
38    indent_size: u8,
39    /// Check if cursor position is at leftmost before displaying prompt
40    check_cursor_position: bool,
41    /// Bracketed paste on unix platform
42    enable_bracketed_paste: bool,
43    /// Synchronized output on unix platform
44    enable_synchronized_output: bool,
45    /// Whether to disable or not the signals in termios
46    enable_signals: bool,
47}
48
49impl Config {
50    /// Returns a `Config` builder.
51    #[must_use]
52    pub fn builder() -> Builder {
53        Builder::new()
54    }
55
56    /// Tell the maximum length (i.e. number of entries) for the history.
57    #[must_use]
58    pub fn max_history_size(&self) -> usize {
59        self.max_history_size
60    }
61
62    pub(crate) fn set_max_history_size(&mut self, max_size: usize) {
63        self.max_history_size = max_size;
64    }
65
66    /// Tell if lines which match the previous history entry are saved or not
67    /// in the history list.
68    ///
69    /// By default, they are ignored.
70    #[must_use]
71    pub fn history_duplicates(&self) -> HistoryDuplicates {
72        self.history_duplicates
73    }
74
75    pub(crate) fn set_history_ignore_dups(&mut self, yes: bool) {
76        self.history_duplicates = if yes {
77            HistoryDuplicates::IgnoreConsecutive
78        } else {
79            HistoryDuplicates::AlwaysAdd
80        };
81    }
82
83    /// Tell if lines which begin with a space character are saved or not in
84    /// the history list.
85    ///
86    /// By default, they are saved.
87    #[must_use]
88    pub fn history_ignore_space(&self) -> bool {
89        self.history_ignore_space
90    }
91
92    pub(crate) fn set_history_ignore_space(&mut self, yes: bool) {
93        self.history_ignore_space = yes;
94    }
95
96    /// Completion behaviour.
97    ///
98    /// By default, [`CompletionType::Circular`].
99    #[must_use]
100    pub fn completion_type(&self) -> CompletionType {
101        self.completion_type
102    }
103
104    /// When listing completion alternatives, only display
105    /// one screen of possibilities at a time (used for [`CompletionType::List`]
106    /// mode).
107    #[must_use]
108    pub fn completion_prompt_limit(&self) -> usize {
109        self.completion_prompt_limit
110    }
111
112    /// Directly show all alternatives when using list completion
113    ///
114    /// By default, they are not, a second tab is needed
115    #[must_use]
116    pub fn completion_show_all_if_ambiguous(&self) -> bool {
117        self.completion_show_all_if_ambiguous
118    }
119
120    /// Duration (milliseconds) Rustyline will wait for a character when
121    /// reading an ambiguous key sequence (used for [`EditMode::Vi`] mode on
122    /// unix platform).
123    ///
124    /// By default, no timeout (-1) or 500ms if [`EditMode::Vi`] is activated.
125    #[must_use]
126    pub fn keyseq_timeout(&self) -> Option<u16> {
127        self.keyseq_timeout
128    }
129
130    /// Emacs or Vi mode
131    #[must_use]
132    pub fn edit_mode(&self) -> EditMode {
133        self.edit_mode
134    }
135
136    /// Tell if lines are automatically added to the history.
137    ///
138    /// By default, they are not.
139    #[must_use]
140    pub fn auto_add_history(&self) -> bool {
141        self.auto_add_history
142    }
143
144    /// Bell style: beep, flash or nothing.
145    #[must_use]
146    pub fn bell_style(&self) -> BellStyle {
147        self.bell_style
148    }
149
150    /// Tell if colors should be enabled.
151    ///
152    /// By default, they are except if stdout is not a TTY or `NO_COLOR`
153    /// environment variable is set.
154    #[must_use]
155    pub fn color_mode(&self) -> ColorMode {
156        if self.color_mode == ColorMode::Enabled
157            && std::env::var_os("NO_COLOR").is_some_and(|os| !os.is_empty())
158        {
159            return ColorMode::Disabled;
160        }
161        self.color_mode
162    }
163
164    /// Tell if terminal supports grapheme clustering
165    #[must_use]
166    pub fn grapheme_cluster_mode(&self) -> GraphemeClusterMode {
167        self.grapheme_cluster_mode
168    }
169
170    pub(crate) fn set_color_mode(&mut self, color_mode: ColorMode) {
171        self.color_mode = color_mode;
172    }
173
174    /// Whether to use stdio or not
175    ///
176    /// By default, stdio is used.
177    #[must_use]
178    pub fn behavior(&self) -> Behavior {
179        self.behavior
180    }
181
182    pub(crate) fn set_behavior(&mut self, behavior: Behavior) {
183        self.behavior = behavior;
184    }
185
186    /// Horizontal space taken by a tab.
187    ///
188    /// By default, 8.
189    #[must_use]
190    pub fn tab_stop(&self) -> u8 {
191        self.tab_stop
192    }
193
194    pub(crate) fn set_tab_stop(&mut self, tab_stop: u8) {
195        self.tab_stop = tab_stop;
196    }
197
198    /// Check if cursor position is at leftmost before displaying prompt.
199    ///
200    /// By default, we don't check.
201    #[must_use]
202    pub fn check_cursor_position(&self) -> bool {
203        self.check_cursor_position
204    }
205
206    /// Indentation size used by indentation commands
207    ///
208    /// By default, 2.
209    #[must_use]
210    pub fn indent_size(&self) -> u8 {
211        self.indent_size
212    }
213
214    pub(crate) fn set_indent_size(&mut self, indent_size: u8) {
215        self.indent_size = indent_size;
216    }
217
218    /// Bracketed paste on unix platform
219    ///
220    /// By default, it's enabled.
221    #[must_use]
222    pub fn enable_bracketed_paste(&self) -> bool {
223        self.enable_bracketed_paste
224    }
225
226    /// Synchronized output on unix platform
227    ///
228    /// By default, it's enabled.
229    #[must_use]
230    pub fn enable_synchronized_output(&self) -> bool {
231        self.enable_synchronized_output
232    }
233
234    /// Enable or disable signals in termios
235    ///
236    /// By default, it's disabled.
237    #[must_use]
238    pub fn enable_signals(&self) -> bool {
239        self.enable_signals
240    }
241
242    pub(crate) fn set_enable_signals(&mut self, enable_signals: bool) {
243        self.enable_signals = enable_signals;
244    }
245}
246
247impl Default for Config {
248    fn default() -> Self {
249        Self {
250            max_history_size: 100,
251            history_duplicates: HistoryDuplicates::IgnoreConsecutive,
252            history_ignore_space: false,
253            completion_type: CompletionType::Circular, // TODO Validate
254            completion_prompt_limit: 100,
255            completion_show_all_if_ambiguous: false,
256            keyseq_timeout: None,
257            edit_mode: EditMode::Emacs,
258            auto_add_history: false,
259            bell_style: BellStyle::default(),
260            color_mode: ColorMode::Enabled,
261            grapheme_cluster_mode: GraphemeClusterMode::from_env(),
262            behavior: Behavior::default(),
263            tab_stop: 8,
264            indent_size: 2,
265            check_cursor_position: false,
266            enable_bracketed_paste: true,
267            enable_synchronized_output: true,
268            enable_signals: false,
269        }
270    }
271}
272
273/// Beep or flash or nothing
274#[derive(Clone, Copy, Debug, PartialEq, Eq)]
275pub enum BellStyle {
276    /// Beep
277    Audible,
278    /// Silent
279    None,
280    /// Flash screen (not supported)
281    Visible,
282}
283
284/// `Audible` by default on unix (overridden by current Terminal settings).
285/// `None` on windows.
286impl Default for BellStyle {
287    #[cfg(any(windows, target_arch = "wasm32"))]
288    fn default() -> Self {
289        Self::None
290    }
291
292    #[cfg(unix)]
293    fn default() -> Self {
294        Self::Audible
295    }
296}
297
298/// History filter
299#[derive(Clone, Copy, Debug, PartialEq, Eq)]
300pub enum HistoryDuplicates {
301    /// No filter
302    AlwaysAdd,
303    /// a line will not be added to the history if it matches the previous entry
304    IgnoreConsecutive,
305}
306
307/// Tab completion style
308#[derive(Clone, Copy, Debug, PartialEq, Eq)]
309#[non_exhaustive]
310pub enum CompletionType {
311    /// Complete the next full match (like in Vim by default)
312    Circular,
313    /// Complete till longest match.
314    /// When more than one match, list all matches
315    /// (like in Bash/Readline).
316    List,
317
318    /// Complete the match using fuzzy search and selection
319    /// (like fzf and plugins)
320    /// Currently only available for unix platforms as dependency on
321    /// skim->tuikit Compile with `--features=fuzzy` to enable
322    #[cfg(all(unix, feature = "with-fuzzy"))]
323    Fuzzy,
324}
325
326/// Style of editing / Standard keymaps
327#[derive(Clone, Copy, Debug, PartialEq, Eq)]
328#[non_exhaustive]
329pub enum EditMode {
330    /// Emacs keymap
331    Emacs,
332    /// Vi keymap
333    Vi,
334}
335
336/// Colorization mode
337#[derive(Clone, Copy, Debug, PartialEq, Eq)]
338#[non_exhaustive]
339pub enum ColorMode {
340    /// Activate highlighting if platform/terminal is supported.
341    Enabled,
342    /// Activate highlighting even if platform is not supported (windows < 10).
343    Forced,
344    /// Deactivate highlighting even if platform/terminal is supported.
345    Disabled,
346}
347
348/// Should the editor use stdio
349#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
350#[non_exhaustive]
351pub enum Behavior {
352    /// Use stdin / stdout
353    #[default]
354    Stdio,
355    /// Use terminal-style interaction whenever possible, even if 'stdin' and/or
356    /// 'stdout' are not terminals.
357    PreferTerm,
358    // TODO
359    // Use file-style interaction, reading input from the given file.
360    // useFile
361}
362
363/// Configuration builder
364#[derive(Clone, Debug, Default)]
365pub struct Builder {
366    p: Config,
367}
368
369impl Builder {
370    /// Returns a [`Config`] builder.
371    #[must_use]
372    pub fn new() -> Self {
373        Self {
374            p: Config::default(),
375        }
376    }
377
378    /// Set the maximum length for the history.
379    pub fn max_history_size(mut self, max_size: usize) -> Result<Self> {
380        self.set_max_history_size(max_size)?;
381        Ok(self)
382    }
383
384    /// Tell if lines which match the previous history entry are saved or not
385    /// in the history list.
386    ///
387    /// By default, they are ignored.
388    pub fn history_ignore_dups(mut self, yes: bool) -> Result<Self> {
389        self.set_history_ignore_dups(yes)?;
390        Ok(self)
391    }
392
393    /// Tell if lines which begin with a space character are saved or not in
394    /// the history list.
395    ///
396    /// By default, they are saved.
397    #[must_use]
398    pub fn history_ignore_space(mut self, yes: bool) -> Self {
399        self.set_history_ignore_space(yes);
400        self
401    }
402
403    /// Set `completion_type`.
404    #[must_use]
405    pub fn completion_type(mut self, completion_type: CompletionType) -> Self {
406        self.set_completion_type(completion_type);
407        self
408    }
409
410    /// The number of possible completions that determines when the user is
411    /// asked whether the list of possibilities should be displayed.
412    #[must_use]
413    pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Self {
414        self.set_completion_prompt_limit(completion_prompt_limit);
415        self
416    }
417
418    /// Choose whether or not to show all alternatives immediately when using
419    /// list completion
420    ///
421    /// By default, a second tab is needed.
422    #[must_use]
423    pub fn completion_show_all_if_ambiguous(
424        mut self,
425        completion_show_all_if_ambiguous: bool,
426    ) -> Self {
427        self.set_completion_show_all_if_ambiguous(completion_show_all_if_ambiguous);
428        self
429    }
430
431    /// Timeout for ambiguous key sequences in milliseconds.
432    /// Currently, it is used only to distinguish a single ESC from an ESC
433    /// sequence.
434    /// After seeing an ESC key, wait at most `keyseq_timeout_ms` for another
435    /// byte.
436    #[must_use]
437    pub fn keyseq_timeout(mut self, keyseq_timeout_ms: Option<u16>) -> Self {
438        self.set_keyseq_timeout(keyseq_timeout_ms);
439        self
440    }
441
442    /// Choose between Emacs or Vi mode.
443    #[must_use]
444    pub fn edit_mode(mut self, edit_mode: EditMode) -> Self {
445        self.set_edit_mode(edit_mode);
446        self
447    }
448
449    /// Tell if lines are automatically added to the history.
450    ///
451    /// By default, they are not.
452    #[must_use]
453    pub fn auto_add_history(mut self, yes: bool) -> Self {
454        self.set_auto_add_history(yes);
455        self
456    }
457
458    /// Set bell style: beep, flash or nothing.
459    #[must_use]
460    pub fn bell_style(mut self, bell_style: BellStyle) -> Self {
461        self.set_bell_style(bell_style);
462        self
463    }
464
465    /// Forces colorization on or off.
466    ///
467    /// By default, colorization is on except if stdout is not a TTY.
468    #[must_use]
469    pub fn color_mode(mut self, color_mode: ColorMode) -> Self {
470        self.set_color_mode(color_mode);
471        self
472    }
473
474    /// Tell if terminal supports grapheme clustering
475    #[must_use]
476    pub fn grapheme_cluster_mode(mut self, grapheme_cluster_mode: GraphemeClusterMode) -> Self {
477        self.set_grapheme_cluster_mode(grapheme_cluster_mode);
478        self
479    }
480
481    /// Whether to use stdio or not
482    ///
483    /// By default, stdio is used.
484    #[must_use]
485    pub fn behavior(mut self, behavior: Behavior) -> Self {
486        self.p.set_behavior(behavior); // cannot be touched after editor / terminal creation
487        self
488    }
489
490    /// Horizontal space taken by a tab.
491    ///
492    /// By default, `8`
493    #[must_use]
494    pub fn tab_stop(mut self, tab_stop: u8) -> Self {
495        self.set_tab_stop(tab_stop);
496        self
497    }
498
499    /// Check if cursor position is at leftmost before displaying prompt.
500    ///
501    /// By default, we don't check.
502    #[must_use]
503    pub fn check_cursor_position(mut self, yes: bool) -> Self {
504        self.set_check_cursor_position(yes);
505        self
506    }
507
508    /// Indentation size
509    ///
510    /// By default, `2`
511    #[must_use]
512    pub fn indent_size(mut self, indent_size: u8) -> Self {
513        self.set_indent_size(indent_size);
514        self
515    }
516
517    /// Enable or disable bracketed paste on unix platform
518    ///
519    /// By default, it's enabled.
520    #[must_use]
521    pub fn bracketed_paste(mut self, enabled: bool) -> Self {
522        self.enable_bracketed_paste(enabled);
523        self
524    }
525
526    /// Enable or disable signals in termios
527    ///
528    /// By default, it's disabled.
529    #[must_use]
530    pub fn enable_signals(mut self, enable_signals: bool) -> Self {
531        self.set_enable_signals(enable_signals);
532        self
533    }
534
535    /// Builds a [`Config`] with the settings specified so far.
536    #[must_use]
537    pub fn build(self) -> Config {
538        self.p
539    }
540}
541
542impl Configurer for Builder {
543    fn config_mut(&mut self) -> &mut Config {
544        &mut self.p
545    }
546}
547
548/// Trait for component that holds a [`Config`].
549pub trait Configurer {
550    /// `Config` accessor.
551    fn config_mut(&mut self) -> &mut Config;
552
553    /// Set the maximum length for the history.
554    fn set_max_history_size(&mut self, max_size: usize) -> Result<()> {
555        self.config_mut().set_max_history_size(max_size);
556        Ok(())
557    }
558
559    /// Tell if lines which match the previous history entry are saved or not
560    /// in the history list.
561    ///
562    /// By default, they are ignored.
563    fn set_history_ignore_dups(&mut self, yes: bool) -> Result<()> {
564        self.config_mut().set_history_ignore_dups(yes);
565        Ok(())
566    }
567
568    /// Tell if lines which begin with a space character are saved or not in
569    /// the history list.
570    ///
571    /// By default, they are saved.
572    fn set_history_ignore_space(&mut self, yes: bool) {
573        self.config_mut().set_history_ignore_space(yes);
574    }
575    /// Set `completion_type`.
576    fn set_completion_type(&mut self, completion_type: CompletionType) {
577        self.config_mut().completion_type = completion_type;
578    }
579
580    /// Choose whether or not to show all alternatives immediately when using
581    /// list completion
582    ///
583    /// By default, a second tab is needed.
584    fn set_completion_show_all_if_ambiguous(&mut self, completion_show_all_if_ambiguous: bool) {
585        self.config_mut().completion_show_all_if_ambiguous = completion_show_all_if_ambiguous;
586    }
587
588    /// The number of possible completions that determines when the user is
589    /// asked whether the list of possibilities should be displayed.
590    fn set_completion_prompt_limit(&mut self, completion_prompt_limit: usize) {
591        self.config_mut().completion_prompt_limit = completion_prompt_limit;
592    }
593
594    /// Timeout for ambiguous key sequences in milliseconds.
595    fn set_keyseq_timeout(&mut self, keyseq_timeout_ms: Option<u16>) {
596        self.config_mut().keyseq_timeout = keyseq_timeout_ms;
597    }
598
599    /// Choose between Emacs or Vi mode.
600    fn set_edit_mode(&mut self, edit_mode: EditMode) {
601        self.config_mut().edit_mode = edit_mode;
602        match edit_mode {
603            EditMode::Emacs => self.set_keyseq_timeout(None), // no timeout
604            EditMode::Vi => self.set_keyseq_timeout(Some(500)),
605        }
606    }
607
608    /// Tell if lines are automatically added to the history.
609    ///
610    /// By default, they are not.
611    fn set_auto_add_history(&mut self, yes: bool) {
612        self.config_mut().auto_add_history = yes;
613    }
614
615    /// Set bell style: beep, flash or nothing.
616    fn set_bell_style(&mut self, bell_style: BellStyle) {
617        self.config_mut().bell_style = bell_style;
618    }
619
620    /// Forces colorization on or off.
621    ///
622    /// By default, colorization is on except if stdout is not a TTY.
623    fn set_color_mode(&mut self, color_mode: ColorMode) {
624        self.config_mut().set_color_mode(color_mode);
625    }
626
627    /// Tell if terminal supports grapheme clustering
628    fn set_grapheme_cluster_mode(&mut self, grapheme_cluster_mode: GraphemeClusterMode) {
629        self.config_mut().grapheme_cluster_mode = grapheme_cluster_mode;
630    }
631
632    /// Horizontal space taken by a tab.
633    ///
634    /// By default, `8`
635    fn set_tab_stop(&mut self, tab_stop: u8) {
636        self.config_mut().set_tab_stop(tab_stop);
637    }
638
639    /// Check if cursor position is at leftmost before displaying prompt.
640    ///
641    /// By default, we don't check.
642    fn set_check_cursor_position(&mut self, yes: bool) {
643        self.config_mut().check_cursor_position = yes;
644    }
645    /// Indentation size for indent/dedent commands
646    ///
647    /// By default, `2`
648    fn set_indent_size(&mut self, size: u8) {
649        self.config_mut().set_indent_size(size);
650    }
651
652    /// Enable or disable bracketed paste on unix platform
653    ///
654    /// By default, it's enabled.
655    fn enable_bracketed_paste(&mut self, enabled: bool) {
656        self.config_mut().enable_bracketed_paste = enabled;
657    }
658
659    /// Enable or disable synchronized output on unix platform
660    ///
661    /// By default, it's enabled.
662    fn enable_synchronized_output(&mut self, enabled: bool) {
663        self.config_mut().enable_synchronized_output = enabled;
664    }
665
666    /// Enable or disable signals in termios
667    ///
668    /// By default, it's disabled.
669    fn set_enable_signals(&mut self, enable_signals: bool) {
670        self.config_mut().set_enable_signals(enable_signals);
671    }
672}