1use crate::config::{ColumnWidth, Config, DEFAULT_COLUMN_COUNT, OperatingMode};
6use crate::control::{self, *};
7use crate::history::{History, HistoryDir};
8use crate::io::UnbufferedStdout;
9use crate::state::State;
10use crate::types::{CompletionHandler, HintHandler, ReadlineError};
11use bstr::{BStr, BString, ByteSlice};
12use std::io::{IsTerminal, Read, Write};
13
14pub struct Editor {
16 pub config: Config,
18 pub(crate) history: History,
20 pub(crate) completion_handler: Option<Box<dyn CompletionHandler>>,
21 pub(crate) hint_handler: Option<Box<dyn HintHandler>>,
22 pub(crate) last_cols: std::cell::Cell<usize>,
23}
24
25impl Default for Editor {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl Editor {
32 pub fn new() -> Self {
34 Self::with_config(Config::default())
35 }
36
37 pub fn with_config(config: Config) -> Self {
39 let max_history_len = config.max_history_len;
40 Self {
41 config,
42 history: History::new(max_history_len),
43 completion_handler: None,
44 hint_handler: None,
45 last_cols: std::cell::Cell::new(DEFAULT_COLUMN_COUNT),
46 }
47 }
48
49 pub fn readline(&mut self, prompt: impl AsRef<BStr>) -> Result<BString, ReadlineError> {
51 let stdin = std::io::stdin();
52 let mode = self.config.resolve_operating_mode(|| stdin.is_terminal());
53 self.readline_from(stdin.lock(), UnbufferedStdout, mode, prompt)
54 }
55
56 pub fn readline_from<R: Read, W: Write>(
58 &mut self,
59 mut reader: R,
60 mut writer: W,
61 mode: OperatingMode,
62 prompt: impl AsRef<BStr>,
63 ) -> Result<BString, ReadlineError> {
64 let prompt_bstr = prompt.as_ref();
65 match mode {
66 OperatingMode::Interactive => {
67 let res = self.readline_stream(&mut reader, &mut writer, prompt_bstr);
68 let _ = writer.write_all(b"\n");
69 let _ = writer.flush();
70 res
71 }
72 fallback_mode => {
73 self.readline_fallback_mode(reader, writer, fallback_mode, prompt_bstr)
74 }
75 }
76 }
77
78 pub fn readline_stream<R: Read, W: Write>(
80 &mut self,
81 reader: &mut R,
82 writer: &mut W,
83 prompt: &BStr,
84 ) -> Result<BString, ReadlineError> {
85 let column_count = self.get_columns(reader, writer);
86 let mut state = State {
87 reader,
88 writer,
89 buffer: BString::default(),
90 prompt,
91 prompt_length: prompt.len(),
92 cursor_position: 0,
93 previous_cursor_position: 0,
94 column_count,
95 max_rows: 0,
96 history_index: 0,
97 draft_line: None,
98 editor: self,
99 render_buf: Vec::with_capacity(512),
100 };
101
102 if state.write_all(prompt.as_bytes()).is_err() {
103 return Err(ReadlineError::Eof);
104 }
105
106 loop {
107 let Some(mut c) = control::read_byte(state.reader)? else {
108 if state.buffer.is_empty() {
109 return Err(ReadlineError::Eof);
110 } else {
111 return Ok(state.buffer);
112 }
113 };
114
115 let has_completion = state.editor.completion_handler.is_some();
116 if c == KEY_TAB && has_completion {
117 c = state.complete_line();
118 if c == 0 {
119 continue;
120 }
121 }
122
123 match c {
124 KEY_ENTER | KEY_CARRIAGE_RETURN => {
125 if state.editor.config.multiline_mode {
126 state.edit_move_end();
127 }
128 if state.editor.hint_handler.is_some() {
129 let handler = state.editor.hint_handler.take();
130 state.refresh_line();
131 state.editor.hint_handler = handler;
132 }
133 return Ok(state.buffer);
134 }
135 KEY_CTRL_C => {
136 return Err(ReadlineError::Interrupted);
137 }
138 KEY_BACKSPACE | KEY_CTRL_H => {
139 state.edit_backspace();
140 }
141 KEY_CTRL_D => {
142 if !state.buffer.is_empty() {
143 state.delete();
144 } else {
145 return Err(ReadlineError::Eof);
146 }
147 }
148 KEY_CTRL_T => {
149 if state.cursor_position > 0 && state.cursor_position < state.buffer.len() {
150 state.buffer.swap(state.cursor_position - 1, state.cursor_position);
151 if state.cursor_position != state.buffer.len() - 1 {
152 state.cursor_position += 1;
153 }
154 state.refresh_line();
155 }
156 }
157 KEY_CTRL_B => {
158 state.edit_move_left();
159 }
160 KEY_CTRL_F => {
161 state.edit_move_right();
162 }
163 KEY_CTRL_P => {
164 state.history_next(HistoryDir::Prev);
165 }
166 KEY_CTRL_N => {
167 state.history_next(HistoryDir::Next);
168 }
169 KEY_ESC => {
170 if let Some((b1, b2)) = control::read_bytes_2(state.reader)? {
171 state.handle_escape_sequence(b1, b2);
172 }
173 }
174 KEY_CTRL_U => {
175 state.buffer.clear();
176 state.cursor_position = 0;
177 state.refresh_line();
178 }
179 KEY_CTRL_K => {
180 state.buffer.truncate(state.cursor_position);
181 state.refresh_line();
182 }
183 KEY_CTRL_A => {
184 state.edit_move_home();
185 }
186 KEY_CTRL_E => {
187 state.edit_move_end();
188 }
189 KEY_CTRL_L => {
190 let _ = control::write_clear_screen(state.writer);
191 state.refresh_line();
192 }
193 KEY_CTRL_W => {
194 state.edit_delete_prev_word();
195 }
196 _ => {
197 state.edit_insert(c);
198 }
199 }
200 }
201 }
202
203 pub(crate) fn readline_fallback_mode<R: Read, W: Write>(
204 &mut self,
205 mut reader: R,
206 mut writer: W,
207 mode: OperatingMode,
208 prompt: &BStr,
209 ) -> Result<BString, ReadlineError> {
210 match mode {
211 OperatingMode::PromptOnly | OperatingMode::UartEcho => {
212 let _ = writer.write_all(prompt.as_bytes());
213 let _ = writer.flush();
214 }
215 OperatingMode::NonTty => {}
216 OperatingMode::Interactive => unreachable!(),
217 }
218
219 let mut buf = BString::default();
220 let mut hit_newline = false;
221 let is_nontty = mode == OperatingMode::NonTty;
222
223 while is_nontty || buf.len() < self.config.max_line_len - 1 {
224 let Some(mut ch) = control::read_byte(&mut reader)? else {
225 break;
226 };
227
228 if mode == OperatingMode::UartEcho {
229 if ch == KEY_CARRIAGE_RETURN {
230 continue;
231 }
232 if ch == KEY_DELETE {
233 ch = KEY_BACKSPACE;
234 }
235 if ch == KEY_BACKSPACE {
236 if !buf.is_empty() {
237 let _ = control::write_erase_previous_char_uart(&mut writer);
238 let _ = writer.flush();
239 buf.pop();
240 }
241 continue;
242 } else {
243 let _ = writer.write_all(&[ch]);
244 let _ = writer.flush();
245 }
246 }
247
248 if ch == KEY_ENTER {
249 hit_newline = true;
250 break;
251 }
252 buf.push(ch);
253 }
254
255 if buf.is_empty() && !hit_newline {
256 return Err(ReadlineError::Eof);
257 }
258
259 while buf.ends_with(b"\n") || buf.ends_with(b"\r") {
260 buf.pop();
261 }
262
263 Ok(buf)
264 }
265
266 pub fn set_completion_handler<H: CompletionHandler + 'static>(&mut self, handler: H) {
268 self.completion_handler = Some(Box::new(handler));
269 }
270
271 pub fn clear_completion_handler(&mut self) {
273 self.completion_handler = None;
274 }
275
276 pub fn set_hint_handler<H: HintHandler + 'static>(&mut self, handler: H) {
278 self.hint_handler = Some(Box::new(handler));
279 }
280
281 pub fn clear_hint_handler(&mut self) {
283 self.hint_handler = None;
284 }
285
286 pub fn add_history(&mut self, line: impl Into<BString>) -> bool {
290 self.history.add(line)
291 }
292
293 pub fn history(&self) -> &History {
295 &self.history
296 }
297
298 pub fn history_mut(&mut self) -> &mut History {
300 &mut self.history
301 }
302
303 pub fn clear_screen(&self) -> Result<(), std::io::Error> {
305 let mut stdout = UnbufferedStdout;
306 self.clear_screen_writer(&mut stdout)
307 }
308
309 pub fn clear_screen_writer<W: Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
311 control::write_clear_screen(writer)?;
312 writer.flush()
313 }
314
315 pub(crate) fn get_columns<R: Read, W: Write>(&self, reader: &mut R, writer: &mut W) -> usize {
317 match self.config.column_width {
318 ColumnWidth::Fixed(cols) => cols,
319 ColumnWidth::AnsiCursor => self.get_columns_ansi(reader, writer),
320 ColumnWidth::Auto => {
321 let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
322 if unsafe { libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut ws) } == 0
323 && ws.ws_col > 0
324 {
325 ws.ws_col as usize
326 } else {
327 self.get_columns_ansi(reader, writer)
328 }
329 }
330 }
331 }
332
333 fn get_columns_ansi<R: Read, W: Write>(&self, reader: &mut R, writer: &mut W) -> usize {
334 let start_pos = get_cursor_position(reader, writer);
335 if start_pos.is_none() {
336 return self.last_cols.get();
337 }
338 let (_start_row, start_col) = start_pos.unwrap();
339
340 if control::write_query_column_width(writer).is_err() {
341 return self.last_cols.get();
342 }
343
344 let max_pos = get_cursor_position(reader, writer);
345 let cols = match max_pos {
346 None => self.last_cols.get(),
347 Some((_, col)) => {
348 if col <= 1 {
349 self.last_cols.get()
350 } else {
351 self.last_cols.set(col);
352 col
353 }
354 }
355 };
356
357 if cols > start_col {
358 let seq = format!("\x1b[{}D", cols - start_col);
359 let _ = writer.write_all(seq.as_bytes());
360 let _ = writer.flush();
361 }
362 cols
363 }
364}
365
366fn get_cursor_position<R: Read, W: Write>(
367 reader: &mut R,
368 writer: &mut W,
369) -> Option<(usize, usize)> {
370 if control::write_query_cursor_position(writer).is_err() {
371 return None;
372 }
373
374 let mut buf = [0u8; 32];
375 let mut i = 0;
376 while i < buf.len() - 1 {
377 let Some(byte) = control::read_byte(reader).ok()? else {
378 return None;
379 };
380 buf[i] = byte;
381 if buf[i] == b'R' {
382 i += 1;
383 break;
384 }
385 i += 1;
386 }
387
388 if buf[0] != b'\x1b' || buf[1] != b'[' {
389 return None;
390 }
391
392 let s = std::str::from_utf8(&buf[2..i - 1]).ok()?;
393 let mut parts = s.split(';');
394 let row: usize = parts.next()?.parse().ok()?;
395 let col: usize = parts.next()?.parse().ok()?;
396 Some((row, col))
397}