1#![allow(clippy::manual_div_ceil)]
41#![allow(clippy::manual_range_contains)]
42
43use std::cmp::min;
44use std::collections::VecDeque;
45use std::fs::File;
46use std::io::{self, BufRead, BufReader, Write};
47use std::os::unix::io::RawFd;
48use std::sync::Mutex;
49use std::{env, mem};
50
51use libc::{c_void, tcgetattr, tcsetattr, termios};
52
53const LINENOISE_DEFAULT_HISTORY_MAX_LEN: usize = 100;
55const LINENOISE_MAX_LINE: usize = 4096;
56
57#[repr(u8)]
59#[derive(Clone, Copy, PartialEq, Eq, Debug)]
60enum Key {
61 CtrlA = 1,
62 CtrlB = 2,
63 CtrlC = 3,
64 CtrlD = 4,
65 CtrlE = 5,
66 CtrlF = 6,
67 CtrlH = 8,
68 Tab = 9,
69 CtrlK = 11,
70 CtrlL = 12,
71 Enter = 13,
72 CtrlN = 14,
73 CtrlP = 16,
74 CtrlT = 20,
75 CtrlU = 21,
76 CtrlW = 23,
77 Esc = 27,
78 Backspace = 127,
79}
80
81pub type CompletionCallback = fn(&str, &mut Vec<String>);
83pub type HintsCallback = fn(&str) -> Option<(String, i32, bool)>;
84
85lazy_static::lazy_static! {
86 static ref G: Mutex<GlobalState> = Mutex::new(GlobalState::new());
87}
88
89struct GlobalState {
90 multi_line: bool,
92 mask_mode: bool,
94 history: History,
96 completion_callback: Option<CompletionCallback>,
98 hints_callback: Option<HintsCallback>,
100 raw_mode: bool,
102 orig_termios: Option<termios>,
104}
105
106impl GlobalState {
107 fn new() -> Self {
108 GlobalState {
109 multi_line: false,
110 mask_mode: false,
111 history: History::new(),
112 completion_callback: None,
113 hints_callback: None,
114 raw_mode: false,
115 orig_termios: None,
116 }
117 }
118}
119
120#[derive(Clone)]
122struct History {
123 max_len: usize,
124 entries: VecDeque<String>,
125}
126
127impl History {
128 fn new() -> Self {
129 History {
130 max_len: LINENOISE_DEFAULT_HISTORY_MAX_LEN,
131 entries: VecDeque::new(),
132 }
133 }
134
135 fn add(&mut self, line: &str) -> bool {
136 if self.max_len == 0 || line.is_empty() {
137 return false;
138 }
139
140 if self.entries.back().is_some_and(|last| last == line) {
142 return false;
143 }
144
145 if self.entries.len() >= self.max_len {
147 self.entries.pop_front();
148 }
149
150 self.entries.push_back(line.to_string());
151 true
152 }
153
154 fn get(&self, index: usize) -> Option<&str> {
155 self.entries
156 .get(self.entries.len().wrapping_sub(index))
157 .map(|s| s.as_str())
158 }
159}
160
161struct Terminal {
163 ifd: RawFd,
164 ofd: RawFd,
165 cols: usize,
166}
167
168struct RawModeGuard {
170 ifd: RawFd,
171 orig_termios: termios,
172}
173
174impl Drop for RawModeGuard {
175 fn drop(&mut self) {
176 unsafe {
177 tcsetattr(self.ifd, libc::TCSAFLUSH, &self.orig_termios);
178 }
179 if let Ok(mut state) = G.lock() {
181 state.raw_mode = false;
182 state.orig_termios = None;
183 }
184 }
185}
186
187impl Terminal {
188 fn new(ifd: RawFd, ofd: RawFd) -> Self {
189 let cols = Self::get_columns(ifd, ofd);
190 Terminal { ifd, ofd, cols }
191 }
192
193 fn enable_raw_mode(&self) -> io::Result<RawModeGuard> {
195 if !self.is_tty() {
196 return Err(io::Error::other("Not a TTY"));
197 }
198
199 let mut orig = unsafe { mem::zeroed::<termios>() };
200 if unsafe { tcgetattr(self.ifd, &mut orig) } == -1 {
201 return Err(io::Error::last_os_error());
202 }
203
204 let mut raw = orig;
206 raw.c_iflag &= !(libc::BRKINT | libc::ICRNL | libc::INPCK | libc::ISTRIP | libc::IXON);
209 raw.c_oflag &= !libc::OPOST;
211 raw.c_cflag |= libc::CS8;
213 raw.c_lflag &= !(libc::ECHO | libc::ICANON | libc::IEXTEN | libc::ISIG);
216 raw.c_cc[libc::VMIN] = 1; raw.c_cc[libc::VTIME] = 0; if unsafe { tcsetattr(self.ifd, libc::TCSAFLUSH, &raw) } < 0 {
222 return Err(io::Error::last_os_error());
223 }
224
225 let mut state = G.lock().unwrap();
226 state.orig_termios = Some(orig);
227 state.raw_mode = true;
228 drop(state);
229
230 Ok(RawModeGuard {
231 ifd: self.ifd,
232 orig_termios: orig,
233 })
234 }
235
236 fn is_tty(&self) -> bool {
237 unsafe { libc::isatty(self.ifd) != 0 }
238 }
239
240 fn write(&self, s: &str) -> io::Result<()> {
241 self.write_bytes(s.as_bytes())
242 }
243
244 fn write_bytes(&self, buf: &[u8]) -> io::Result<()> {
245 let mut written = 0;
246 while written < buf.len() {
247 match unsafe {
248 libc::write(
249 self.ofd,
250 buf[written..].as_ptr() as *const c_void,
251 buf.len() - written,
252 )
253 } {
254 -1 => {
255 let err = io::Error::last_os_error();
256 if err.kind() != io::ErrorKind::Interrupted {
257 return Err(err);
258 }
259 }
260 0 => break,
261 n => written += n as usize,
262 }
263 }
264 if self.ofd == libc::STDOUT_FILENO {
265 io::stdout().flush()?;
266 }
267 Ok(())
268 }
269
270 fn read_byte(&self) -> io::Result<Option<u8>> {
271 let mut c = 0u8;
272 loop {
273 let n = unsafe { libc::read(self.ifd, &mut c as *mut u8 as *mut c_void, 1) };
274 if n == -1 {
275 let err = io::Error::last_os_error();
276 if err.kind() == io::ErrorKind::Interrupted {
277 continue;
278 }
279 return Err(err);
280 }
281 return Ok(if n == 0 { None } else { Some(c) });
282 }
283 }
284
285 fn read_byte_nonblocking(&self) -> io::Result<Option<u8>> {
286 use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
287
288 unsafe {
289 let flags = fcntl(self.ifd, F_GETFL, 0);
291 if flags == -1 {
292 return Err(io::Error::last_os_error());
293 }
294
295 if fcntl(self.ifd, F_SETFL, flags | O_NONBLOCK) == -1 {
297 return Err(io::Error::last_os_error());
298 }
299
300 let result = self.read_byte();
302
303 let restore_result = fcntl(self.ifd, F_SETFL, flags);
305
306 if restore_result == -1 {
308 return Err(io::Error::last_os_error());
309 }
310
311 match result {
312 Ok(b) => Ok(b),
313 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
314 Err(e) => Err(e),
315 }
316 }
317 }
318
319 fn get_cursor_position(&self) -> io::Result<(usize, usize)> {
322 self.write_bytes(b"\x1b[6n")?;
323
324 let mut buf = [0u8; 32];
325 let mut i = 0;
326
327 while i < buf.len() - 1 {
328 buf[i] = self.read_byte()?.ok_or_else(|| {
329 io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reading cursor")
330 })?;
331 i += 1;
332 if buf[i - 1] == b'R' {
333 break;
334 }
335 }
336
337 let response =
339 std::str::from_utf8(&buf[2..i - 1]).map_err(|_| io::Error::other("Invalid UTF-8"))?;
340
341 let (rows, cols) = response
342 .split_once(';')
343 .ok_or_else(|| io::Error::other("Invalid format"))?;
344
345 Ok((
346 rows.parse().map_err(|_| io::Error::other("Invalid row"))?,
347 cols.parse().map_err(|_| io::Error::other("Invalid col"))?,
348 ))
349 }
350
351 fn get_columns(ifd: RawFd, ofd: RawFd) -> usize {
354 unsafe {
356 let mut ws: libc::winsize = mem::zeroed();
357 if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 && ws.ws_col != 0 {
358 return ws.ws_col as usize;
359 }
360 }
361
362 let temp_terminal = Terminal { ifd, ofd, cols: 80 };
367
368 let orig_pos = match temp_terminal.get_cursor_position() {
370 Ok(pos) => pos,
371 Err(_) => return 80,
372 };
373
374 if temp_terminal.write_bytes(b"\x1b[999C").is_err() {
376 return 80;
377 }
378
379 let cols = match temp_terminal.get_cursor_position() {
380 Ok(pos) => pos.1,
381 Err(_) => 80,
382 };
383
384 if orig_pos != (0, 0) {
386 let _ = temp_terminal.write(&format!("\x1b[{};{}H", orig_pos.0, orig_pos.1));
387 }
388
389 cols
390 }
391
392 fn clear_screen(&self) -> io::Result<()> {
393 self.write("\x1b[H\x1b[2J")
394 }
395
396 fn beep(&self) {
397 let _ = self.write("\x07");
398 }
399}
400
401struct LineBuffer {
403 chars: Vec<char>,
404 pos: usize,
405}
406
407impl LineBuffer {
408 fn new() -> Self {
409 LineBuffer {
410 chars: Vec::with_capacity(LINENOISE_MAX_LINE),
411 pos: 0,
412 }
413 }
414
415 fn insert(&mut self, c: char) -> bool {
416 if self.chars.len() >= LINENOISE_MAX_LINE - 1 {
417 return false;
418 }
419 self.chars.insert(self.pos, c);
420 self.pos += 1;
421 true
422 }
423
424 fn delete(&mut self) -> bool {
425 if self.pos < self.chars.len() {
426 self.chars.remove(self.pos);
427 true
428 } else {
429 false
430 }
431 }
432
433 fn backspace(&mut self) -> bool {
434 if self.pos > 0 {
435 self.pos -= 1;
436 self.chars.remove(self.pos);
437 true
438 } else {
439 false
440 }
441 }
442
443 fn move_left(&mut self) -> bool {
444 if self.pos > 0 {
445 self.pos -= 1;
446 true
447 } else {
448 false
449 }
450 }
451
452 fn move_right(&mut self) -> bool {
453 if self.pos < self.chars.len() {
454 self.pos += 1;
455 true
456 } else {
457 false
458 }
459 }
460
461 fn move_home(&mut self) {
462 self.pos = 0;
463 }
464
465 fn move_end(&mut self) {
466 self.pos = self.chars.len();
467 }
468
469 fn delete_to_end(&mut self) {
470 self.chars.truncate(self.pos);
471 }
472
473 fn delete_word(&mut self) {
474 let start = self.pos;
475
476 while self.pos > 0 && self.chars[self.pos - 1] == ' ' {
478 self.pos -= 1;
479 }
480
481 while self.pos > 0 && self.chars[self.pos - 1] != ' ' {
483 self.pos -= 1;
484 }
485
486 self.chars.drain(self.pos..start);
487 }
488
489 fn clear(&mut self) {
490 self.chars.clear();
491 self.pos = 0;
492 }
493
494 fn set(&mut self, s: &str) {
495 self.chars = s.chars().take(LINENOISE_MAX_LINE - 1).collect();
496 self.pos = self.chars.len();
497 }
498
499 fn as_string(&self) -> String {
500 self.chars.iter().collect()
501 }
502}
503
504struct Editor {
505 terminal: Terminal,
506 buffer: LineBuffer,
507 prompt: String,
508 history_index: usize,
509 saved_line: Option<String>,
510 completion_state: Option<CompletionState>,
511 old_rows: usize, cursor_row_offset: usize, }
514
515struct CompletionState {
516 original_line: String,
517 current_index: usize,
518}
519
520macro_rules! key_action {
522 ($self:expr, $action:expr) => {{
523 $action;
524 $self.refresh_line()?;
525 Err(io::Error::new(
526 io::ErrorKind::WouldBlock,
527 "More input needed",
528 ))
529 }};
530}
531
532impl Editor {
533 fn new(terminal: Terminal, prompt: &str) -> Self {
534 Editor {
535 terminal,
536 buffer: LineBuffer::new(),
537 prompt: prompt.to_string(),
538 history_index: 0,
539 saved_line: None,
540 completion_state: None,
541 old_rows: 0,
542 cursor_row_offset: 0,
543 }
544 }
545
546 fn refresh_line(&mut self) -> io::Result<()> {
547 self.terminal.cols = Terminal::get_columns(self.terminal.ifd, self.terminal.ofd);
549
550 let state = G.lock().unwrap();
551
552 if state.multi_line {
553 self.refresh_multiline(&state)
554 } else {
555 self.refresh_singleline(&state)
556 }
557 }
558
559 fn refresh_singleline(&mut self, state: &GlobalState) -> io::Result<()> {
560 let mut output = String::new();
561
562 output.push('\r');
564
565 output.push_str(&self.prompt);
567
568 let content = if state.mask_mode {
570 "*".repeat(self.buffer.chars.len())
571 } else {
572 self.buffer.as_string()
573 };
574
575 let prompt_len = self.prompt.chars().count();
577 let available_cols = self.terminal.cols.saturating_sub(prompt_len);
578
579 let cursor_screen_pos = if content.chars().count() > available_cols {
580 let window_start = self.buffer.pos.saturating_sub(available_cols / 2);
582 let window_end = min(window_start + available_cols, content.chars().count());
583 let actual_window_start = window_end.saturating_sub(available_cols);
584
585 let window: String = content
586 .chars()
587 .skip(actual_window_start)
588 .take(available_cols)
589 .collect();
590 output.push_str(&window);
591
592 prompt_len + self.buffer.pos.saturating_sub(actual_window_start)
594 } else {
595 output.push_str(&content);
596
597 if self.completion_state.is_none() {
599 if let Some(ref callback) = state.hints_callback {
600 if let Some((hint, color, bold)) = callback(&self.buffer.as_string()) {
601 let remaining = available_cols.saturating_sub(content.chars().count());
602 if remaining > 0 {
603 if bold {
604 output.push_str("\x1b[1m");
605 }
606 if color >= 0 {
607 output.push_str(&format!("\x1b[{color}m"));
608 }
609 let hint_truncated: String = hint.chars().take(remaining).collect();
610 output.push_str(&hint_truncated);
611 output.push_str("\x1b[0m");
612 }
613 }
614 }
615 }
616
617 prompt_len + self.buffer.pos
619 };
620
621 output.push_str("\x1b[0K");
623
624 output.push_str(&format!("\r\x1b[{cursor_screen_pos}C"));
626
627 self.terminal.write(&output)
628 }
629
630 fn refresh_multiline(&mut self, state: &GlobalState) -> io::Result<()> {
631 let mut output = String::new();
632 let plen = self.prompt.chars().count();
633 let cols = self.terminal.cols;
634
635 let content_len = plen + self.buffer.chars.len();
637 let cursor_pos = plen + self.buffer.pos;
638
639 let content_rows = if content_len == 0 {
641 1
642 } else {
643 (content_len + cols - 1) / cols
644 };
645
646 let phantom_line =
648 self.buffer.pos == self.buffer.chars.len() && cursor_pos > 0 && cursor_pos % cols == 0;
649
650 let total_rows = if phantom_line {
651 content_rows + 1
652 } else {
653 content_rows
654 };
655
656 let cursor_row = if cursor_pos == 0 {
658 0
659 } else if phantom_line {
660 content_rows } else {
662 (cursor_pos - 1) / cols
663 };
664
665 let cursor_col = if phantom_line {
666 0
667 } else if cursor_pos == 0 {
668 plen
669 } else {
670 (cursor_pos - 1) % cols + 1
671 };
672
673 output.push('\r');
675
676 if self.cursor_row_offset > 0 {
678 output.push_str(&format!("\x1b[{}A", self.cursor_row_offset));
679 }
680
681 let rows_to_clear = self.old_rows.max(total_rows);
683 for i in 0..rows_to_clear {
684 if i > 0 {
685 output.push_str("\r\n"); }
687 output.push_str("\x1b[2K"); }
689
690 if rows_to_clear > 1 {
692 output.push_str(&format!("\x1b[{}A", rows_to_clear - 1));
693 }
694 output.push('\r');
695
696 output.push_str(&self.prompt);
698 if state.mask_mode {
699 output.push_str(&"*".repeat(self.buffer.chars.len()));
700 } else {
701 output.push_str(&self.buffer.as_string());
702 }
703
704 if content_rows == 1 && !phantom_line && self.completion_state.is_none() {
706 if let Some(ref cb) = state.hints_callback {
707 if let Some((hint, color, bold)) = cb(&self.buffer.as_string()) {
708 let last_line_len = content_len % cols;
709 let space = if last_line_len == 0 {
710 0
711 } else {
712 cols - last_line_len
713 };
714
715 if space > 0 {
716 let hint_str: String = hint.chars().take(space).collect();
717 if !hint_str.is_empty() {
718 if bold {
719 output.push_str("\x1b[1m");
720 }
721 if color >= 0 {
722 output.push_str(&format!("\x1b[{color}m"));
723 }
724 output.push_str(&hint_str);
725 output.push_str("\x1b[0m");
726 }
727 }
728 }
729 }
730 }
731
732 if phantom_line {
734 output.push_str("\r\n");
735 }
736
737 let current_row = total_rows - 1;
740
741 if cursor_row < current_row {
743 output.push_str(&format!("\x1b[{}A", current_row - cursor_row));
744 } else if cursor_row > current_row {
745 output.push_str(&format!("\x1b[{}B", cursor_row - current_row));
746 }
747
748 output.push_str(&format!("\r\x1b[{}C", cursor_col));
750
751 self.old_rows = total_rows;
753 self.cursor_row_offset = cursor_row;
754
755 self.terminal.write(&output)
756 }
757
758 fn handle_completion(&mut self) -> io::Result<bool> {
759 let callback = {
761 let state = G.lock().unwrap();
762 state.completion_callback
763 };
764
765 let Some(cb) = callback else {
766 return Ok(false);
767 };
768
769 let line_for_completion = if let Some(ref comp_state) = self.completion_state {
771 comp_state.original_line.clone()
773 } else {
774 self.buffer.as_string()
776 };
777
778 let mut completions = Vec::new();
780 cb(&line_for_completion, &mut completions);
781
782 if completions.is_empty() {
783 self.terminal.beep();
784 self.completion_state = None;
785 return Ok(false);
786 }
787
788 if let Some(ref mut comp_state) = self.completion_state {
790 comp_state.current_index = (comp_state.current_index + 1) % completions.len();
792
793 if let Some(completion) = completions.get(comp_state.current_index) {
794 self.buffer.set(completion);
795 self.refresh_line()?;
796 }
797 } else {
798 self.completion_state = Some(CompletionState {
800 original_line: line_for_completion,
801 current_index: 0,
802 });
803
804 if let Some(first) = completions.first() {
806 self.buffer.set(first);
807 self.refresh_line()?;
808 }
809 }
810
811 Ok(true)
812 }
813
814 fn accept_completion(&mut self) {
815 self.completion_state = None;
816 }
817
818 fn handle_history(&mut self, direction: isize) -> io::Result<()> {
819 let state = G.lock().unwrap();
820 let history_len = state.history.entries.len();
821
822 if history_len == 0 {
823 return Ok(());
824 }
825
826 if self.history_index == 0 && self.saved_line.is_none() {
828 self.saved_line = Some(self.buffer.as_string());
829 }
830
831 if direction > 0 {
833 if self.history_index < history_len {
834 self.history_index += 1;
835 }
836 } else if self.history_index > 0 {
837 self.history_index -= 1;
838 }
839
840 if self.history_index == 0 {
842 if let Some(saved) = &self.saved_line {
843 self.buffer.set(saved);
844 }
845 } else if let Some(entry) = state.history.get(self.history_index) {
846 self.buffer.set(entry);
847 }
848
849 drop(state);
850 self.refresh_line()
851 }
852
853 fn handle_escape_sequence(&mut self) -> io::Result<()> {
854 let seq = [
855 self.terminal.read_byte_nonblocking()?,
856 self.terminal.read_byte_nonblocking()?,
857 ];
858
859 let action: Option<fn(&mut Self) -> io::Result<()>> = match seq {
860 [Some(b'['), Some(b'A')] => Some(|s| s.handle_history(1)),
861 [Some(b'['), Some(b'B')] => Some(|s| s.handle_history(-1)),
862 [Some(b'['), Some(b'C')] => Some(|s| {
863 s.buffer.move_right();
864 Ok(())
865 }),
866 [Some(b'['), Some(b'D')] => Some(|s| {
867 s.buffer.move_left();
868 Ok(())
869 }),
870 [Some(b'['), Some(b'H')] | [Some(b'O'), Some(b'H')] => Some(|s| {
871 s.buffer.move_home();
872 Ok(())
873 }),
874 [Some(b'['), Some(b'F')] | [Some(b'O'), Some(b'F')] => Some(|s| {
875 s.buffer.move_end();
876 Ok(())
877 }),
878 _ => None,
879 };
880
881 if let Some(f) = action {
882 f(self)?;
883 self.refresh_line()?;
884 }
885
886 if matches!(seq, [Some(b'['), Some(b'3')])
888 && matches!(self.terminal.read_byte_nonblocking()?, Some(b'~'))
889 && self.buffer.delete()
890 {
891 self.refresh_line()?;
892 }
893
894 Ok(())
895 }
896
897 fn process_key(&mut self, c: u8) -> io::Result<Option<String>> {
899 if self.completion_state.is_some() && c != Key::Tab as u8 {
901 self.accept_completion();
902 }
903
904 match c {
905 c if c == Key::Enter as u8 => Ok(Some(self.buffer.as_string())),
906 c if c == Key::CtrlC as u8 => Err(io::Error::new(io::ErrorKind::Interrupted, "")),
907 c if c == Key::CtrlD as u8 => {
908 if self.buffer.chars.is_empty() {
909 Ok(None)
910 } else {
911 if self.buffer.delete() {
912 self.refresh_line()?;
913 }
914 Err(io::Error::new(
915 io::ErrorKind::WouldBlock,
916 "More input needed",
917 ))
918 }
919 }
920 c if c == Key::Tab as u8 => {
921 self.handle_completion()?;
922 Err(io::Error::new(
923 io::ErrorKind::WouldBlock,
924 "More input needed",
925 ))
926 }
927 c if c == Key::Backspace as u8 || c == Key::CtrlH as u8 => key_action!(self, {
928 self.buffer.backspace();
929 }),
930 c if c == Key::CtrlU as u8 => key_action!(self, self.buffer.clear()),
931 c if c == Key::CtrlK as u8 => key_action!(self, self.buffer.delete_to_end()),
932 c if c == Key::CtrlW as u8 => key_action!(self, self.buffer.delete_word()),
933 c if c == Key::CtrlA as u8 => key_action!(self, self.buffer.move_home()),
934 c if c == Key::CtrlE as u8 => key_action!(self, self.buffer.move_end()),
935 c if c == Key::CtrlB as u8 => key_action!(self, {
936 self.buffer.move_left();
937 }),
938 c if c == Key::CtrlF as u8 => key_action!(self, {
939 self.buffer.move_right();
940 }),
941 c if c == Key::CtrlP as u8 => {
942 self.handle_history(1)?;
943 Err(io::Error::new(
944 io::ErrorKind::WouldBlock,
945 "More input needed",
946 ))
947 }
948 c if c == Key::CtrlN as u8 => {
949 self.handle_history(-1)?;
950 Err(io::Error::new(
951 io::ErrorKind::WouldBlock,
952 "More input needed",
953 ))
954 }
955 c if c == Key::CtrlL as u8 => {
956 self.terminal.clear_screen()?;
957 self.old_rows = 0;
958 self.cursor_row_offset = 0;
959 self.refresh_line()?;
960 Err(io::Error::new(
961 io::ErrorKind::WouldBlock,
962 "More input needed",
963 ))
964 }
965 c if c == Key::CtrlT as u8 => {
966 if self.buffer.pos > 0 && self.buffer.chars.len() > 1 {
968 if self.buffer.pos == self.buffer.chars.len() {
969 self.buffer
970 .chars
971 .swap(self.buffer.pos - 2, self.buffer.pos - 1);
972 } else {
973 self.buffer.chars.swap(self.buffer.pos - 1, self.buffer.pos);
974 self.buffer.pos += 1;
975 }
976 self.refresh_line()?;
977 }
978 Err(io::Error::new(
979 io::ErrorKind::WouldBlock,
980 "More input needed",
981 ))
982 }
983 c if c == Key::Esc as u8 => {
984 self.handle_escape_sequence()?;
985 Err(io::Error::new(
986 io::ErrorKind::WouldBlock,
987 "More input needed",
988 ))
989 }
990 c if c >= 32 && c < 127 => {
991 if self.buffer.insert(c as char) {
993 self.refresh_line()?;
994 } else {
995 self.terminal.beep();
996 }
997 Err(io::Error::new(
998 io::ErrorKind::WouldBlock,
999 "More input needed",
1000 ))
1001 }
1002 c if c >= 128 => {
1003 let mut utf8_buf = vec![c];
1005
1006 let bytes_needed = if c & 0xE0 == 0xC0 {
1008 2
1009 } else if c & 0xF0 == 0xE0 {
1010 3
1011 } else if c & 0xF8 == 0xF0 {
1012 4
1013 } else {
1014 1 };
1016
1017 for _ in 1..bytes_needed {
1019 if let Ok(Some(next_byte)) = self.terminal.read_byte() {
1020 if next_byte & 0xC0 == 0x80 {
1021 utf8_buf.push(next_byte);
1022 } else {
1023 break; }
1025 } else {
1026 break;
1027 }
1028 }
1029
1030 if utf8_buf.len() == bytes_needed {
1032 if let Ok(s) = std::str::from_utf8(&utf8_buf) {
1033 if let Some(ch) = s.chars().next() {
1034 if self.buffer.insert(ch) {
1035 self.refresh_line()?;
1036 } else {
1037 self.terminal.beep();
1038 }
1039 }
1040 }
1041 } else {
1042 self.terminal.beep();
1043 }
1044 Err(io::Error::new(
1045 io::ErrorKind::WouldBlock,
1046 "More input needed",
1047 ))
1048 }
1049 _ => Err(io::Error::new(
1050 io::ErrorKind::WouldBlock,
1051 "More input needed",
1052 )),
1053 }
1054 }
1055}
1056
1057fn is_unsupported_term() -> bool {
1060 let unsupported = ["dumb", "cons25", "emacs"];
1061 if let Ok(term) = env::var("TERM") {
1062 unsupported.iter().any(|&t| term.eq_ignore_ascii_case(t))
1063 } else {
1064 false
1065 }
1066}
1067
1068pub fn linenoise(prompt: &str) -> Option<String> {
1076 let terminal = Terminal::new(libc::STDIN_FILENO, libc::STDOUT_FILENO);
1077
1078 if !terminal.is_tty() {
1079 return linenoise_no_tty();
1080 }
1081
1082 if is_unsupported_term() {
1083 return linenoise_unsupported_term(prompt);
1084 }
1085
1086 let mut state = match LinenoiseState::edit_start(-1, -1, prompt) {
1088 Ok(s) => s,
1089 Err(_) => return None,
1090 };
1091
1092 loop {
1094 match state.edit_feed() {
1095 Ok(Some(line)) => {
1096 let _ = state.edit_stop();
1097 return Some(line);
1098 }
1099 Ok(None) => {
1100 let _ = state.edit_stop();
1101 return None;
1102 }
1103 Err(e) if e.kind() == io::ErrorKind::Interrupted => {
1104 let _ = state.edit_stop();
1105 return None;
1106 }
1107 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
1108 continue;
1110 }
1111 Err(_) => {
1112 let _ = state.edit_stop();
1113 return None;
1114 }
1115 }
1116 }
1117}
1118
1119fn linenoise_no_tty() -> Option<String> {
1121 let mut line = String::new();
1122 match io::stdin().read_line(&mut line) {
1123 Ok(0) => None, Ok(_) => {
1125 if line.ends_with('\n') {
1127 line.pop();
1128 if line.ends_with('\r') {
1129 line.pop();
1130 }
1131 }
1132 Some(line)
1133 }
1134 Err(_) => None,
1135 }
1136}
1137
1138fn linenoise_unsupported_term(prompt: &str) -> Option<String> {
1140 print!("{prompt}");
1141 let _ = io::stdout().flush();
1142
1143 linenoise_no_tty()
1144}
1145
1146pub fn linenoise_set_multi_line(ml: bool) {
1148 G.lock().unwrap().multi_line = ml;
1149}
1150
1151pub fn linenoise_mask_mode_enable() {
1156 G.lock().unwrap().mask_mode = true;
1157}
1158
1159pub fn linenoise_mask_mode_disable() {
1161 G.lock().unwrap().mask_mode = false;
1162}
1163
1164pub fn linenoise_set_completion_callback(cb: CompletionCallback) {
1166 G.lock().unwrap().completion_callback = Some(cb);
1167}
1168
1169pub fn linenoise_set_hints_callback(cb: HintsCallback) {
1172 G.lock().unwrap().hints_callback = Some(cb);
1173}
1174
1175pub fn linenoise_history_add(line: &str) -> bool {
1177 G.lock().unwrap().history.add(line)
1178}
1179
1180pub fn linenoise_history_set_max_len(len: usize) -> bool {
1185 if len < 1 {
1186 return false;
1187 }
1188 let mut state = G.lock().unwrap();
1189 state.history.max_len = len;
1190 while state.history.entries.len() > len {
1191 state.history.entries.pop_front();
1192 }
1193 true
1194}
1195
1196pub fn linenoise_history_save(filename: &str) -> io::Result<()> {
1198 let state = G.lock().unwrap();
1199 let mut file = File::create(filename)?;
1200 for entry in &state.history.entries {
1201 writeln!(file, "{entry}")?;
1202 }
1203 Ok(())
1204}
1205
1206pub fn linenoise_history_load(filename: &str) -> io::Result<()> {
1211 let file = match File::open(filename) {
1212 Ok(f) => f,
1213 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(()),
1214 Err(e) => return Err(e),
1215 };
1216
1217 let reader = BufReader::new(file);
1218 let mut state = G.lock().unwrap();
1219
1220 #[allow(clippy::manual_flatten)]
1221 for line in reader.lines() {
1222 if let Ok(line) = line {
1223 let trimmed = line.trim_end();
1224 if !trimmed.is_empty() {
1225 state.history.add(trimmed);
1226 }
1227 }
1228 }
1229
1230 Ok(())
1231}
1232
1233pub fn linenoise_clear_screen() {
1235 let terminal = Terminal::new(libc::STDIN_FILENO, libc::STDOUT_FILENO);
1236 let _ = terminal.clear_screen();
1237}
1238
1239pub fn linenoise_print_key_codes() {
1243 let terminal = Terminal::new(libc::STDIN_FILENO, libc::STDOUT_FILENO);
1244
1245 println!("Linenoise key codes debugging mode.");
1246 println!("Press keys to see scan codes. Type 'quit' to exit.");
1247
1248 let _guard = match terminal.enable_raw_mode() {
1249 Ok(guard) => guard,
1250 Err(_) => return,
1251 };
1252
1253 let mut quit_buf = [0u8; 4];
1254
1255 loop {
1256 if let Ok(Some(c)) = terminal.read_byte() {
1257 quit_buf[0] = quit_buf[1];
1259 quit_buf[1] = quit_buf[2];
1260 quit_buf[2] = quit_buf[3];
1261 quit_buf[3] = c;
1262
1263 let mut output = format!(
1265 "'{}' {:#04x}",
1266 if c >= 32 && c < 127 { c as char } else { '?' },
1267 c
1268 );
1269
1270 match c {
1272 1 => output.push_str(" (ctrl-a)"),
1273 2 => output.push_str(" (ctrl-b)"),
1274 3 => output.push_str(" (ctrl-c)"),
1275 4 => output.push_str(" (ctrl-d)"),
1276 5 => output.push_str(" (ctrl-e)"),
1277 6 => output.push_str(" (ctrl-f)"),
1278 8 => output.push_str(" (ctrl-h)"),
1279 9 => output.push_str(" (tab)"),
1280 11 => output.push_str(" (ctrl-k)"),
1281 12 => output.push_str(" (ctrl-l)"),
1282 13 => output.push_str(" (enter)"),
1283 14 => output.push_str(" (ctrl-n)"),
1284 16 => output.push_str(" (ctrl-p)"),
1285 20 => output.push_str(" (ctrl-t)"),
1286 21 => output.push_str(" (ctrl-u)"),
1287 23 => output.push_str(" (ctrl-w)"),
1288 27 => output.push_str(" (esc)"),
1289 127 => output.push_str(" (backspace)"),
1290 _ => {}
1291 }
1292
1293 let _ = terminal.write(&format!("{}\r\n", output));
1295
1296 if &quit_buf == b"quit" {
1297 break;
1298 }
1299 }
1300 }
1301
1302 println!();
1304}
1305
1306pub struct LinenoiseState {
1308 editor: Editor,
1309 active: bool,
1310 _raw_guard: Option<RawModeGuard>,
1311}
1312
1313impl LinenoiseState {
1314 pub fn edit_start(stdin_fd: RawFd, stdout_fd: RawFd, prompt: &str) -> io::Result<Self> {
1318 let ifd = if stdin_fd == -1 {
1319 libc::STDIN_FILENO
1320 } else {
1321 stdin_fd
1322 };
1323
1324 let ofd = if stdout_fd == -1 {
1325 libc::STDOUT_FILENO
1326 } else {
1327 stdout_fd
1328 };
1329
1330 let terminal = Terminal::new(ifd, ofd);
1331
1332 if !terminal.is_tty() || is_unsupported_term() {
1333 return Err(io::Error::other("Not supported"));
1334 }
1335
1336 let raw_guard = terminal.enable_raw_mode()?;
1337
1338 let mut editor = Editor::new(terminal, prompt);
1339
1340 editor.history_index = 0;
1342 editor.saved_line = None;
1343
1344 editor.refresh_line()?;
1346
1347 Ok(Self {
1348 editor,
1349 active: true,
1350 _raw_guard: Some(raw_guard),
1351 })
1352 }
1353
1354 pub fn edit_feed(&mut self) -> io::Result<Option<String>> {
1358 if !self.active {
1359 return Ok(None);
1360 }
1361
1362 match self.editor.terminal.read_byte()? {
1364 Some(c) => {
1365 match self.editor.process_key(c) {
1366 Ok(result) => {
1367 if result.is_some() {
1368 let _ = self.editor.terminal.write("\r\n");
1370 self.active = false;
1371 }
1372 Ok(result)
1373 }
1374 Err(e) if e.kind() == io::ErrorKind::Interrupted => {
1375 self.active = false;
1376 Err(e)
1377 }
1378 Err(e) => Err(e),
1379 }
1380 }
1381 None => {
1382 self.active = false;
1384 Ok(None)
1385 }
1386 }
1387 }
1388
1389 pub fn edit_stop(&mut self) -> io::Result<()> {
1392 if self.active {
1393 self.active = false;
1394 self._raw_guard = None;
1396 }
1397 Ok(())
1398 }
1399
1400 pub fn hide(&self) -> io::Result<()> {
1402 self.editor.terminal.write("\r\x1b[0K")
1404 }
1405
1406 pub fn show(&mut self) -> io::Result<()> {
1408 self.editor.terminal.write("\r")?;
1411 self.editor.refresh_line()
1412 }
1413
1414 pub fn get_fd(&self) -> RawFd {
1415 self.editor.terminal.ifd
1416 }
1417}