Skip to content

Commit c5cb232

Browse files
committed
[readline_async] Switch line_state data structures to GCStringOwned
1 parent 6339aab commit c5cb232

File tree

7 files changed

+291
-268
lines changed

7 files changed

+291
-268
lines changed

tui/src/readline_async/readline_async_impl/line_state/core.rs

Lines changed: 59 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,39 @@
22

33
// cspell:words testx
44

5-
use crate::{ColIndex, ColWidth, MemoizedLenMap, SegIndex, Size, StringLength, height, ok, width};
5+
use crate::{ColIndex, ColWidth, GCStringOwned, MemoizedLenMap, SegIndex, Size,
6+
StringLength, height, ok, width};
67
use std::io::{self, Write};
78

8-
/// Controls whether [`LineState`] processes input and renders output.
9-
///
10-
/// When paused, the line state ignores keyboard events and suppresses terminal
11-
/// rendering. This allows other UI elements (like [`Spinner`]) to temporarily
12-
/// take control of the terminal display.
13-
///
14-
/// # States
15-
///
16-
/// - [`Paused`]: Input ignored, rendering suppressed
17-
/// - [`NotPaused`]: Normal operation, processes input and renders
18-
///
19-
/// # Usage
20-
///
21-
/// Use [`LineState::set_paused`] to change the liveness state. When transitioning
22-
/// from `Paused` to `NotPaused`, the line is automatically re-rendered.
23-
///
24-
/// [`Spinner`]: crate::Spinner
25-
/// [`Paused`]: LineStateLiveness::Paused
26-
/// [`NotPaused`]: LineStateLiveness::NotPaused
27-
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
28-
pub enum LineStateLiveness {
29-
/// Input is ignored and rendering is suppressed.
30-
Paused,
31-
/// Normal operation - input is processed and output is rendered.
32-
NotPaused,
33-
}
34-
35-
impl LineStateLiveness {
36-
/// Returns `true` if the state is [`Paused`](LineStateLiveness::Paused).
37-
#[must_use]
38-
pub fn is_paused(&self) -> bool { matches!(self, LineStateLiveness::Paused) }
39-
}
40-
419
/// This struct actually handles the line editing, and rendering. This works hand in hand
4210
/// with the [`crate::Readline`] to make sure that the line is rendered correctly, with
4311
/// pause and resume support.
4412
#[derive(Debug)]
4513
pub struct LineState {
46-
/// Unicode line.
47-
pub line: String,
14+
/// The user's input line with pre-computed grapheme cluster metadata.
15+
///
16+
/// Uses [`GCStringOwned`] for efficient Unicode handling: O(1) segment count,
17+
/// display width, and direct segment access by index. Mutations rebuild the
18+
/// segment array (acceptable for typical readline input lengths).
19+
///
20+
/// [`GCStringOwned`]: crate::GCStringOwned
21+
pub line: GCStringOwned,
4822

4923
/// Index of grapheme in line (0-based position within grapheme array).
5024
pub line_cursor_grapheme: SegIndex,
5125

5226
/// Column of grapheme in line (0-based terminal column).
5327
pub current_column: ColIndex,
5428

55-
/// buffer for holding partial grapheme clusters as they come in
29+
/// Buffer for holding partial grapheme clusters as they come in.
5630
pub cluster_buffer: String,
5731

32+
/// The prompt string displayed before user input (e.g., `"$ "` or `"> "`).
33+
///
34+
/// May contain ANSI escape codes for styling (colors, bold, etc.). Display width
35+
/// is calculated using [`StringLength::StripAnsi`] to exclude escape sequences.
36+
///
37+
/// [`StringLength::StripAnsi`]: crate::StringLength::StripAnsi
5838
pub prompt: String,
5939

6040
/// After pressing enter, should we print the line just submitted?
@@ -78,6 +58,39 @@ pub struct LineState {
7858
pub memoized_len_map: MemoizedLenMap,
7959
}
8060

61+
/// Controls whether [`LineState`] processes input and renders output.
62+
///
63+
/// When paused, the line state ignores keyboard events and suppresses terminal
64+
/// rendering. This allows other UI elements (like [`Spinner`]) to temporarily
65+
/// take control of the terminal display.
66+
///
67+
/// # States
68+
///
69+
/// - [`Paused`]: Input ignored, rendering suppressed
70+
/// - [`NotPaused`]: Normal operation, processes input and renders
71+
///
72+
/// # Usage
73+
///
74+
/// Use [`LineState::set_paused`] to change the liveness state. When transitioning
75+
/// from `Paused` to `NotPaused`, the line is automatically re-rendered.
76+
///
77+
/// [`Spinner`]: crate::Spinner
78+
/// [`Paused`]: LineStateLiveness::Paused
79+
/// [`NotPaused`]: LineStateLiveness::NotPaused
80+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81+
pub enum LineStateLiveness {
82+
/// Input is ignored and rendering is suppressed.
83+
Paused,
84+
/// Normal operation - input is processed and output is rendered.
85+
NotPaused,
86+
}
87+
88+
impl LineStateLiveness {
89+
/// Returns `true` if the state is [`Paused`](LineStateLiveness::Paused).
90+
#[must_use]
91+
pub fn is_paused(&self) -> bool { matches!(self, LineStateLiveness::Paused) }
92+
}
93+
8194
/// Early return from a function if [`LineState`] is paused.
8295
///
8396
/// This macro provides a consistent pattern for checking pause state at the start
@@ -87,20 +100,7 @@ pub struct LineState {
87100
///
88101
/// - `@None`: Returns `Ok(None)` - for methods returning `Result<Option<T>, E>`
89102
/// - `@Unit`: Returns `Ok(())` - for methods returning `Result<(), E>`
90-
///
91-
/// # Example
92-
///
93-
/// ```rust,ignore
94-
/// pub fn handle_input(&mut self) -> Result<Option<Event>, Error> {
95-
/// early_return_if_paused!(self @None);
96-
/// // ... process input ...
97-
/// }
98-
///
99-
/// pub fn render(&mut self) -> io::Result<()> {
100-
/// early_return_if_paused!(self @Unit);
101-
/// // ... render output ...
102-
/// }
103-
/// ```
103+
#[macro_export]
104104
macro_rules! early_return_if_paused {
105105
($self:ident @None) => {
106106
if matches!($self.is_paused, LineStateLiveness::Paused) {
@@ -115,12 +115,11 @@ macro_rules! early_return_if_paused {
115115
};
116116
}
117117

118-
pub(crate) use early_return_if_paused;
119-
120118
impl LineState {
121119
/// Create a new `LineState` with the given prompt and terminal size.
122120
///
123-
/// The `term_size` parameter accepts a `(u16, u16)` tuple: `(width_cols, height_rows)`.
121+
/// The `term_size` parameter accepts a `(u16, u16)` tuple: `(width_cols,
122+
/// height_rows)`.
124123
#[must_use]
125124
pub fn new(prompt: String, term_size: (u16, u16)) -> Self {
126125
let mut memoized_len_map = MemoizedLenMap::new();
@@ -135,7 +134,7 @@ impl LineState {
135134
current_column: crate::col(current_column),
136135
should_print_line_on_enter: true,
137136
should_print_line_on_control_c: false,
138-
line: String::new(),
137+
line: GCStringOwned::new(""),
139138
line_cursor_grapheme: 0.into(),
140139
cluster_buffer: String::new(),
141140
last_line_length: width(0),
@@ -179,8 +178,8 @@ impl LineState {
179178
#[cfg(test)]
180179
mod tests {
181180
use super::{LineState, LineStateLiveness};
182-
use crate::{History, InputEvent, Key, KeyPress, StdMutex, core::test_fixtures::StdoutMock,
183-
seg_index};
181+
use crate::{GCStringOwned, History, InputEvent, Key, KeyPress, StdMutex,
182+
core::test_fixtures::StdoutMock, seg_index};
184183
use std::sync::Arc;
185184

186185
#[tokio::test]
@@ -191,7 +190,7 @@ mod tests {
191190
let (history, _) = History::new();
192191
let safe_history = Arc::new(StdMutex::new(history));
193192

194-
line.line = "test".to_string();
193+
line.line = GCStringOwned::new("test");
195194
line.line_cursor_grapheme = seg_index(4);
196195

197196
// Pause the line state.
@@ -214,7 +213,7 @@ mod tests {
214213

215214
assert!(matches!(result, Ok(None)));
216215
// Line should be unchanged because it's paused.
217-
assert_eq!(line.line, "test");
216+
assert_eq!(line.line.as_str(), "test");
218217
assert_eq!(line.line_cursor_grapheme, seg_index(4));
219218

220219
// Resume the line state.
@@ -233,6 +232,6 @@ mod tests {
233232

234233
assert!(matches!(result, Ok(None)));
235234
// Line should now have the character appended.
236-
assert_eq!(line.line, "testx");
235+
assert_eq!(line.line.as_str(), "testx");
237236
}
238237
}

0 commit comments

Comments
 (0)