Skip to main content

line_editor/
config.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// Default maximum number of history entries retained in memory.
6pub const DEFAULT_MAX_HISTORY_LEN: usize = 100;
7
8/// Default maximum length of an input line in bytes.
9pub const DEFAULT_MAX_LINE_LEN: usize = 4096;
10
11/// Default terminal column width when auto-detection fails.
12pub const DEFAULT_COLUMN_COUNT: usize = 80;
13
14/// Controls how TTY terminal capability detection is performed.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum TerminalMode {
17    /// Automatically check `std::io::stdin().is_terminal()` at runtime.
18    #[default]
19    Auto,
20    /// Force TTY mode regardless of ambient `is_terminal()` check.
21    Tty,
22    /// Force non-TTY mode regardless of ambient `is_terminal()` check.
23    NonTty,
24}
25
26/// Controls how the terminal window column width is determined.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum ColumnWidth {
29    /// Automatically determine width by querying `TIOCGWINSZ` via ioctl, falling back to ANSI
30    /// cursor position queries.
31    #[default]
32    Auto,
33    /// Determine width using only ANSI cursor position queries (`\x1b[6n`), bypassing `TIOCGWINSZ`
34    /// ioctl.
35    AnsiCursor,
36    /// Use an explicitly specified fixed column width without querying the terminal.
37    Fixed(usize),
38}
39
40/// Represents the capability level of a terminal type name (e.g., `TERM` variable or explicit
41/// configuration).
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum TerminalCapability {
44    /// Fully supported terminal with ANSI/VT100 capabilities.
45    #[default]
46    Supported,
47    /// Limited UART terminal that requires manual character echoing.
48    UartEcho,
49    /// Unsupported terminal (e.g., `dumb`, `cons25`, `emacs`) that only supports line reading with
50    /// a prompt.
51    PromptOnly,
52}
53
54/// Represents the resolved concrete operating mode of the line editor.
55///
56/// `OperatingMode` determines how `line-editor` handles input streams, character echoing, escape
57/// sequences, line editing keybindings, and prompt rendering.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum OperatingMode {
60    /// Non-TTY mode (e.g., input redirected from a file, pipe, or non-interactive script).
61    ///
62    /// - **When used**: Resolved when standard input is not a terminal stream, or when
63    ///   `TerminalMode::NonTty` is explicitly configured.
64    /// - **Consequences**: No prompt is printed to the output stream. Escape sequences, raw mode,
65    ///   completion, hints, and line-editing keybindings are completely disabled. Input bytes are
66    ///   read sequentially until a newline or EOF is reached.
67    NonTty,
68
69    /// Prompt-only mode for unsupported or dumb terminals (e.g., `TERM=dumb`, `cons25`, `emacs`).
70    ///
71    /// - **When used**: Resolved when the terminal capability check determines the terminal does
72    ///   not support ANSI/VT100 escape sequences.
73    /// - **Consequences**: The prompt is written to the output stream before reading input, but
74    ///   ANSI/VT100 control sequences (cursor positioning, line clearing, colorized hints) and raw
75    ///   mode editing are disabled. Standard stream line buffering applies.
76    PromptOnly,
77
78    /// UART character echo mode (e.g., `TERM=uart` or serial console).
79    ///
80    /// - **When used**: Resolved when connected to a serial UART console that lacks full ANSI/VT100
81    ///   screen manipulation but requires character-by-character echoing.
82    /// - **Consequences**: The prompt is printed, and typed characters are echoed back to the
83    ///   output stream byte-by-byte. Basic backspace erasing (`\x08 \x08`) is handled, but complex
84    ///   VT100 cursor movement and multi-line editing are disabled.
85    UartEcho,
86
87    /// Full interactive VT100/ANSI terminal mode.
88    ///
89    /// - **When used**: Resolved when connected to an interactive TTY terminal supporting
90    ///   ANSI/VT100 control sequences (e.g., `xterm`, `vt100`, `screen`, `tmux`).
91    /// - **Consequences**: Full raw-mode line editing with VT100 escape sequence handling, cursor
92    ///   movement, history navigation, tab autocompletion, inline hints, and multi-line refresh.
93    ///   Writes a trailing newline upon line submission.
94    Interactive,
95}
96
97/// Configuration settings for an [`Editor`](crate::Editor) instance.
98#[derive(Debug, Clone)]
99pub struct Config {
100    /// Maximum number of history entries to keep in memory.
101    pub max_history_len: usize,
102    /// Maximum line length in bytes.
103    pub max_line_len: usize,
104    /// Enable multi-line editing mode.
105    pub multiline_mode: bool,
106    /// Mode for TTY terminal detection.
107    pub terminal_mode: TerminalMode,
108    /// Mode for determining column width.
109    pub column_width: ColumnWidth,
110    /// Explicit terminal name override (or `None` to check `TERM` env var).
111    pub term_name: Option<String>,
112}
113
114impl Default for Config {
115    fn default() -> Self {
116        Self {
117            max_history_len: DEFAULT_MAX_HISTORY_LEN,
118            max_line_len: DEFAULT_MAX_LINE_LEN,
119            multiline_mode: false,
120            terminal_mode: TerminalMode::default(),
121            column_width: ColumnWidth::default(),
122            term_name: None,
123        }
124    }
125}
126
127impl Config {
128    /// Sets the terminal detection mode.
129    pub fn with_terminal_mode(mut self, mode: TerminalMode) -> Self {
130        self.terminal_mode = mode;
131        self
132    }
133
134    /// Sets the column width determination mode.
135    pub fn with_column_width(mut self, column_width: ColumnWidth) -> Self {
136        self.column_width = column_width;
137        self
138    }
139
140    /// Sets an explicit terminal name override.
141    pub fn with_term_name(mut self, name: Option<impl Into<String>>) -> Self {
142        self.term_name = name.map(Into::into);
143        self
144    }
145
146    /// Resolves this configuration specification into a `TerminalCapability` level.
147    pub fn resolve_terminal_capability(&self) -> TerminalCapability {
148        terminal_capability_from_name(self.term_name.as_deref())
149    }
150
151    /// Resolves this configuration specification into a concrete `OperatingMode` for the editor.
152    ///
153    /// `is_terminal` is invoked only when `self.terminal_mode == TerminalMode::Auto` to resolve
154    /// whether the input stream is a TTY.
155    pub fn resolve_operating_mode(&self, is_terminal: impl FnOnce() -> bool) -> OperatingMode {
156        let is_tty = match self.terminal_mode {
157            TerminalMode::Auto => is_terminal(),
158            TerminalMode::Tty => true,
159            TerminalMode::NonTty => false,
160        };
161
162        if !is_tty {
163            return OperatingMode::NonTty;
164        }
165
166        match self.resolve_terminal_capability() {
167            TerminalCapability::PromptOnly => OperatingMode::PromptOnly,
168            TerminalCapability::UartEcho => OperatingMode::UartEcho,
169            TerminalCapability::Supported => OperatingMode::Interactive,
170        }
171    }
172}
173
174/// Classifies a terminal type name into a `TerminalCapability`.
175pub fn terminal_capability_from_name(term: Option<&str>) -> TerminalCapability {
176    let term_val = match term {
177        Some(t) => t.to_string(),
178        None => std::env::var("TERM").unwrap_or_default(),
179    };
180
181    if term_val.is_empty() {
182        return TerminalCapability::Supported;
183    }
184
185    let unsupported = ["dumb", "cons25", "emacs"];
186    for name in &unsupported {
187        if term_val.eq_ignore_ascii_case(name) {
188            return TerminalCapability::PromptOnly;
189        }
190    }
191
192    if term_val.eq_ignore_ascii_case("uart") {
193        return TerminalCapability::UartEcho;
194    }
195
196    TerminalCapability::Supported
197}