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} ;
67use 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 ) ]
4513pub 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]
104104macro_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-
120118impl 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) ]
180179mod 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