1#[cfg(feature = "buffer-redux")]
3use buffer_redux::BufReader;
4use std::cmp;
5use std::collections::HashMap;
6use std::fs::{File, OpenOptions};
7#[cfg(not(feature = "buffer-redux"))]
8use std::io::BufReader;
9use std::io::{self, ErrorKind, Read, Write};
10use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, RawFd};
11use std::os::unix::net::UnixStream;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::mpsc::{self, SyncSender};
14use std::sync::{Arc, Mutex};
15
16use log::{debug, warn};
17use nix::errno::Errno;
18use nix::poll::{self, PollFlags, PollTimeout};
19use nix::sys::select::{self, FdSet};
20#[cfg(not(feature = "termios"))]
21use nix::sys::termios::Termios;
22use nix::unistd::{close, isatty, read, write};
23#[cfg(feature = "termios")]
24use termios::Termios;
25use unicode_segmentation::UnicodeSegmentation;
26use utf8parse::{Parser, Receiver};
27
28use super::{width, Event, RawMode, RawReader, Renderer, Term};
29use crate::config::{Behavior, BellStyle, ColorMode, Config};
30use crate::highlight::Highlighter;
31use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
32use crate::layout::{Layout, Position};
33use crate::line_buffer::LineBuffer;
34use crate::{error, Cmd, ReadlineError, Result};
35
36const UNSUPPORTED_TERM: [&str; 3] = ["dumb", "cons25", "emacs"];
38
39const BRACKETED_PASTE_ON: &str = "\x1b[?2004h";
40const BRACKETED_PASTE_OFF: &str = "\x1b[?2004l";
41
42nix::ioctl_read_bad!(win_size, libc::TIOCGWINSZ, libc::winsize);
43
44fn get_win_size(fd: RawFd) -> (usize, usize) {
45 use std::mem::zeroed;
46
47 if cfg!(test) {
48 return (80, 24);
49 }
50
51 unsafe {
52 let mut size: libc::winsize = zeroed();
53 match win_size(fd, &mut size) {
54 Ok(0) => {
55 let cols = if size.ws_col == 0 {
60 80
61 } else {
62 size.ws_col as usize
63 };
64 let rows = if size.ws_row == 0 {
65 usize::MAX
66 } else {
67 size.ws_row as usize
68 };
69 (cols, rows)
70 }
71 _ => (80, 24),
72 }
73 }
74}
75
76fn is_unsupported_term() -> bool {
79 match std::env::var("TERM") {
80 Ok(term) => {
81 for iter in &UNSUPPORTED_TERM {
82 if (*iter).eq_ignore_ascii_case(&term) {
83 return true;
84 }
85 }
86 false
87 }
88 Err(_) => false,
89 }
90}
91
92fn is_a_tty(fd: RawFd) -> bool {
94 isatty(fd).unwrap_or(false)
95}
96
97#[cfg(any(not(feature = "buffer-redux"), test))]
98pub type PosixBuffer = ();
99#[cfg(all(feature = "buffer-redux", not(test)))]
100pub type PosixBuffer = buffer_redux::Buffer;
101#[cfg(not(test))]
102pub type Buffer = PosixBuffer;
103
104pub type PosixKeyMap = HashMap<KeyEvent, Cmd>;
105#[cfg(not(test))]
106pub type KeyMap = PosixKeyMap;
107
108#[must_use = "You must restore default mode (disable_raw_mode)"]
109pub struct PosixMode {
110 termios: Termios,
111 tty_in: RawFd,
112 tty_out: Option<RawFd>,
113 raw_mode: Arc<AtomicBool>,
114}
115
116#[cfg(not(test))]
117pub type Mode = PosixMode;
118
119impl RawMode for PosixMode {
120 fn disable_raw_mode(&self) -> Result<()> {
122 termios_::disable_raw_mode(self.tty_in, &self.termios)?;
123 if let Some(out) = self.tty_out {
125 write_all(out, BRACKETED_PASTE_OFF)?;
126 }
127 self.raw_mode.store(false, Ordering::SeqCst);
128 Ok(())
129 }
130}
131
132struct TtyIn {
135 fd: RawFd,
136 sigwinch_pipe: Option<RawFd>,
137}
138
139impl Read for TtyIn {
140 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
141 loop {
142 let res = unsafe {
143 libc::read(
144 self.fd,
145 buf.as_mut_ptr().cast::<libc::c_void>(),
146 buf.len() as libc::size_t,
147 )
148 };
149 if res == -1 {
150 let error = io::Error::last_os_error();
151 if error.kind() == ErrorKind::Interrupted && self.sigwinch()? {
152 return Err(io::Error::new(
153 ErrorKind::Interrupted,
154 error::WindowResizedError,
155 ));
156 } else if error.kind() != ErrorKind::Interrupted {
157 return Err(error);
158 }
159 } else {
160 #[expect(clippy::cast_sign_loss)]
161 return Ok(res as usize);
162 }
163 }
164 }
165}
166
167impl TtyIn {
168 fn sigwinch(&self) -> nix::Result<bool> {
170 if let Some(pipe) = self.sigwinch_pipe {
171 let mut buf = [0u8; 64];
172 match read(pipe, &mut buf) {
173 Ok(0) => Ok(false),
174 Ok(_) => Ok(true),
175 Err(e) if e == Errno::EWOULDBLOCK || e == Errno::EINTR => Ok(false),
176 Err(e) => Err(e),
177 }
178 } else {
179 Ok(false)
180 }
181 }
182}
183
184type PipeReader = Arc<Mutex<(File, mpsc::Receiver<String>)>>;
186type PipeWriter = (Arc<Mutex<File>>, SyncSender<String>);
188
189pub struct PosixRawReader {
191 tty_in: BufReader<TtyIn>,
192 timeout_ms: PollTimeout,
193 parser: Parser,
194 key_map: PosixKeyMap,
195 pipe_reader: Option<PipeReader>,
197}
198
199impl AsFd for PosixRawReader {
200 fn as_fd(&self) -> BorrowedFd<'_> {
201 let fd = self.tty_in.get_ref().fd;
202 unsafe { BorrowedFd::borrow_raw(fd) }
203 }
204}
205
206struct Utf8 {
207 c: Option<char>,
208 valid: bool,
209}
210
211const UP: char = 'A'; const DOWN: char = 'B'; const RIGHT: char = 'C'; const LEFT: char = 'D'; const END: char = 'F'; const HOME: char = 'H'; const INSERT: char = '2'; const DELETE: char = '3'; const PAGE_UP: char = '5'; const PAGE_DOWN: char = '6'; const RXVT_HOME: char = '7';
223const RXVT_END: char = '8';
224
225const SHIFT: char = '2';
226const ALT: char = '3';
227const ALT_SHIFT: char = '4';
228const CTRL: char = '5';
229const CTRL_SHIFT: char = '6';
230const CTRL_ALT: char = '7';
231const CTRL_ALT_SHIFT: char = '8';
232
233const RXVT_SHIFT: char = '$';
234const RXVT_CTRL: char = '\x1e';
235const RXVT_CTRL_SHIFT: char = '@';
236
237impl PosixRawReader {
238 fn new(
239 fd: RawFd,
240 sigwinch_pipe: Option<RawFd>,
241 buffer: Option<PosixBuffer>,
242 config: &Config,
243 key_map: PosixKeyMap,
244 pipe_reader: Option<PipeReader>,
245 ) -> Self {
246 let inner = TtyIn { fd, sigwinch_pipe };
247 #[cfg(any(not(feature = "buffer-redux"), test))]
248 let (tty_in, _) = (BufReader::with_capacity(1024, inner), buffer);
249 #[cfg(all(feature = "buffer-redux", not(test)))]
250 let tty_in = if let Some(buffer) = buffer {
251 BufReader::with_buffer(buffer, inner)
252 } else {
253 BufReader::with_capacity(1024, inner)
254 };
255 Self {
256 tty_in,
257 timeout_ms: config.keyseq_timeout().into(),
258 parser: Parser::new(),
259 key_map,
260 pipe_reader,
261 }
262 }
263
264 fn escape_sequence(&mut self) -> Result<KeyEvent> {
267 self._do_escape_sequence(true)
268 }
269
270 fn _do_escape_sequence(&mut self, allow_recurse: bool) -> Result<KeyEvent> {
272 let seq1 = self.next_char()?;
274 if seq1 == '[' {
275 self.escape_csi()
277 } else if seq1 == 'O' {
278 self.escape_o()
281 } else if seq1 == '\x1b' {
282 if !allow_recurse {
298 return Ok(E::ESC);
299 }
300 let timeout = if self.timeout_ms.is_none() {
301 100u8.into()
302 } else {
303 self.timeout_ms
304 };
305 match self.poll(timeout) {
306 Ok(0) | Err(_) => Ok(E::ESC),
309 Ok(n) => {
310 debug_assert!(n > 0, "{}", n);
311 let E(k, m) = self._do_escape_sequence(false)?;
313 Ok(E(k, m | M::ALT))
314 }
315 }
316 } else {
317 Ok(E::alt(seq1))
318 }
319 }
320
321 fn escape_csi(&mut self) -> Result<KeyEvent> {
323 let seq2 = self.next_char()?;
324 if seq2.is_ascii_digit() {
325 match seq2 {
326 '0' | '9' => {
327 debug!(target: "rustyline", "unsupported esc sequence: \\E[{:?}", seq2);
328 Ok(E(K::UnknownEscSeq, M::NONE))
329 }
330 _ => {
331 self.extended_escape(seq2)
333 }
334 }
335 } else if seq2 == '[' {
336 let seq3 = self.next_char()?;
337 Ok(match seq3 {
339 'A' => E(K::F(1), M::NONE),
340 'B' => E(K::F(2), M::NONE),
341 'C' => E(K::F(3), M::NONE),
342 'D' => E(K::F(4), M::NONE),
343 'E' => E(K::F(5), M::NONE),
344 _ => {
345 debug!(target: "rustyline", "unsupported esc sequence: \\E[[{:?}", seq3);
346 E(K::UnknownEscSeq, M::NONE)
347 }
348 })
349 } else {
350 Ok(match seq2 {
352 UP => E(K::Up, M::NONE),
353 DOWN => E(K::Down, M::NONE),
354 RIGHT => E(K::Right, M::NONE),
355 LEFT => E(K::Left, M::NONE),
356 END => E(K::End, M::NONE),
358 HOME => E(K::Home, M::NONE), 'Z' => E(K::BackTab, M::NONE),
366 'a' => E(K::Up, M::SHIFT), 'b' => E(K::Down, M::SHIFT), 'c' => E(K::Right, M::SHIFT), 'd' => E(K::Left, M::SHIFT), _ => {
371 debug!(target: "rustyline", "unsupported esc sequence: \\E[{:?}", seq2);
372 E(K::UnknownEscSeq, M::NONE)
373 }
374 })
375 }
376 }
377
378 #[expect(clippy::cognitive_complexity)]
380 fn extended_escape(&mut self, seq2: char) -> Result<KeyEvent> {
381 let seq3 = self.next_char()?;
382 if seq3 == '~' {
383 Ok(match seq2 {
384 '1' | RXVT_HOME => E(K::Home, M::NONE), INSERT => E(K::Insert, M::NONE),
386 DELETE => E(K::Delete, M::NONE),
387 '4' | RXVT_END => E(K::End, M::NONE), PAGE_UP => E(K::PageUp, M::NONE),
389 PAGE_DOWN => E(K::PageDown, M::NONE),
390 _ => {
391 debug!(target: "rustyline",
392 "unsupported esc sequence: \\E[{}~", seq2);
393 E(K::UnknownEscSeq, M::NONE)
394 }
395 })
396 } else if seq3.is_ascii_digit() {
397 let seq4 = self.next_char()?;
398 if seq4 == '~' {
399 Ok(match (seq2, seq3) {
400 ('1', '1') => E(K::F(1), M::NONE), ('1', '2') => E(K::F(2), M::NONE), ('1', '3') => E(K::F(3), M::NONE), ('1', '4') => E(K::F(4), M::NONE), ('1', '5') => E(K::F(5), M::NONE), ('1', '7') => E(K::F(6), M::NONE), ('1', '8') => E(K::F(7), M::NONE), ('1', '9') => E(K::F(8), M::NONE), ('2', '0') => E(K::F(9), M::NONE), ('2', '1') => E(K::F(10), M::NONE), ('2', '3') => E(K::F(11), M::NONE), ('2', '4') => E(K::F(12), M::NONE), _ => {
415 debug!(target: "rustyline",
416 "unsupported esc sequence: \\E[{}{}~", seq2, seq3);
417 E(K::UnknownEscSeq, M::NONE)
418 }
419 })
420 } else if seq4 == ';' {
421 let seq5 = self.next_char()?;
422 if seq5.is_ascii_digit() {
423 let seq6 = self.next_char()?;
424 if seq6.is_ascii_digit() {
425 self.next_char()?; Ok(E(K::UnknownEscSeq, M::NONE))
427 } else if seq6 == 'R' {
428 Ok(E(K::UnknownEscSeq, M::NONE))
429 } else if seq6 == '~' {
430 Ok(match (seq2, seq3, seq5) {
431 ('1', '5', CTRL) => E(K::F(5), M::CTRL),
432 ('1', '7', CTRL) => E(K::F(6), M::CTRL),
434 ('1', '8', CTRL) => E(K::F(7), M::CTRL),
436 ('1', '9', CTRL) => E(K::F(8), M::CTRL),
437 ('2', '0', CTRL) => E(K::F(9), M::CTRL),
439 ('2', '1', CTRL) => E(K::F(10), M::CTRL),
441 ('2', '3', CTRL) => E(K::F(11), M::CTRL),
443 ('2', '4', CTRL) => E(K::F(12), M::CTRL),
445 _ => {
447 debug!(target: "rustyline",
448 "unsupported esc sequence: \\E[{}{};{}~", seq2, seq3, seq5);
449 E(K::UnknownEscSeq, M::NONE)
450 }
451 })
452 } else {
453 debug!(target: "rustyline",
454 "unsupported esc sequence: \\E[{}{};{}{}", seq2, seq3, seq5, seq6);
455 Ok(E(K::UnknownEscSeq, M::NONE))
456 }
457 } else {
458 debug!(target: "rustyline",
459 "unsupported esc sequence: \\E[{}{};{:?}", seq2, seq3, seq5);
460 Ok(E(K::UnknownEscSeq, M::NONE))
461 }
462 } else if seq4.is_ascii_digit() {
463 let seq5 = self.next_char()?;
464 if seq5 == '~' {
465 Ok(match (seq2, seq3, seq4) {
466 ('2', '0', '0') => E(K::BracketedPasteStart, M::NONE),
467 ('2', '0', '1') => E(K::BracketedPasteEnd, M::NONE),
468 _ => {
469 debug!(target: "rustyline",
470 "unsupported esc sequence: \\E[{}{}{}~", seq2, seq3, seq4);
471 E(K::UnknownEscSeq, M::NONE)
472 }
473 })
474 } else {
475 debug!(target: "rustyline",
476 "unsupported esc sequence: \\E[{}{}{}{}", seq2, seq3, seq4, seq5);
477 Ok(E(K::UnknownEscSeq, M::NONE))
478 }
479 } else {
480 debug!(target: "rustyline",
481 "unsupported esc sequence: \\E[{}{}{:?}", seq2, seq3, seq4);
482 Ok(E(K::UnknownEscSeq, M::NONE))
483 }
484 } else if seq3 == ';' {
485 let seq4 = self.next_char()?;
486 if seq4.is_ascii_digit() {
487 let seq5 = self.next_char()?;
488 if seq5.is_ascii_digit() {
489 self.next_char()?; Ok(E(K::UnknownEscSeq, M::NONE))
492 } else if seq2 == '1' {
493 Ok(match (seq4, seq5) {
494 (SHIFT, UP) => E(K::Up, M::SHIFT), (SHIFT, DOWN) => E(K::Down, M::SHIFT), (SHIFT, RIGHT) => E(K::Right, M::SHIFT),
497 (SHIFT, LEFT) => E(K::Left, M::SHIFT),
498 (SHIFT, END) => E(K::End, M::SHIFT), (SHIFT, HOME) => E(K::Home, M::SHIFT), (ALT, UP) => E(K::Up, M::ALT),
504 (ALT, DOWN) => E(K::Down, M::ALT),
505 (ALT, RIGHT) => E(K::Right, M::ALT),
506 (ALT, LEFT) => E(K::Left, M::ALT),
507 (ALT, END) => E(K::End, M::ALT),
508 (ALT, HOME) => E(K::Home, M::ALT),
509 (ALT_SHIFT, UP) => E(K::Up, M::ALT_SHIFT),
510 (ALT_SHIFT, DOWN) => E(K::Down, M::ALT_SHIFT),
511 (ALT_SHIFT, RIGHT) => E(K::Right, M::ALT_SHIFT),
512 (ALT_SHIFT, LEFT) => E(K::Left, M::ALT_SHIFT),
513 (ALT_SHIFT, END) => E(K::End, M::ALT_SHIFT),
514 (ALT_SHIFT, HOME) => E(K::Home, M::ALT_SHIFT),
515 (CTRL, UP) => E(K::Up, M::CTRL),
516 (CTRL, DOWN) => E(K::Down, M::CTRL),
517 (CTRL, RIGHT) => E(K::Right, M::CTRL),
518 (CTRL, LEFT) => E(K::Left, M::CTRL),
519 (CTRL, END) => E(K::End, M::CTRL),
520 (CTRL, HOME) => E(K::Home, M::CTRL),
521 (CTRL, 'P') => E(K::F(1), M::CTRL),
522 (CTRL, 'Q') => E(K::F(2), M::CTRL),
523 (CTRL, 'S') => E(K::F(4), M::CTRL),
524 (CTRL, 'p') => E(K::Char('0'), M::CTRL),
525 (CTRL, 'q') => E(K::Char('1'), M::CTRL),
526 (CTRL, 'r') => E(K::Char('2'), M::CTRL),
527 (CTRL, 's') => E(K::Char('3'), M::CTRL),
528 (CTRL, 't') => E(K::Char('4'), M::CTRL),
529 (CTRL, 'u') => E(K::Char('5'), M::CTRL),
530 (CTRL, 'v') => E(K::Char('6'), M::CTRL),
531 (CTRL, 'w') => E(K::Char('7'), M::CTRL),
532 (CTRL, 'x') => E(K::Char('8'), M::CTRL),
533 (CTRL, 'y') => E(K::Char('9'), M::CTRL),
534 (CTRL_SHIFT, UP) => E(K::Up, M::CTRL_SHIFT),
535 (CTRL_SHIFT, DOWN) => E(K::Down, M::CTRL_SHIFT),
536 (CTRL_SHIFT, RIGHT) => E(K::Right, M::CTRL_SHIFT),
537 (CTRL_SHIFT, LEFT) => E(K::Left, M::CTRL_SHIFT),
538 (CTRL_SHIFT, END) => E(K::End, M::CTRL_SHIFT),
539 (CTRL_SHIFT, HOME) => E(K::Home, M::CTRL_SHIFT),
540 (CTRL_SHIFT, 'p') => E(K::Char('0'), M::CTRL_SHIFT),
544 (CTRL_SHIFT, 'q') => E(K::Char('1'), M::CTRL_SHIFT),
545 (CTRL_SHIFT, 'r') => E(K::Char('2'), M::CTRL_SHIFT),
546 (CTRL_SHIFT, 's') => E(K::Char('3'), M::CTRL_SHIFT),
547 (CTRL_SHIFT, 't') => E(K::Char('4'), M::CTRL_SHIFT),
548 (CTRL_SHIFT, 'u') => E(K::Char('5'), M::CTRL_SHIFT),
549 (CTRL_SHIFT, 'v') => E(K::Char('6'), M::CTRL_SHIFT),
550 (CTRL_SHIFT, 'w') => E(K::Char('7'), M::CTRL_SHIFT),
551 (CTRL_SHIFT, 'x') => E(K::Char('8'), M::CTRL_SHIFT),
552 (CTRL_SHIFT, 'y') => E(K::Char('9'), M::CTRL_SHIFT),
553 (CTRL_ALT, UP) => E(K::Up, M::CTRL_ALT),
554 (CTRL_ALT, DOWN) => E(K::Down, M::CTRL_ALT),
555 (CTRL_ALT, RIGHT) => E(K::Right, M::CTRL_ALT),
556 (CTRL_ALT, LEFT) => E(K::Left, M::CTRL_ALT),
557 (CTRL_ALT, END) => E(K::End, M::CTRL_ALT),
558 (CTRL_ALT, HOME) => E(K::Home, M::CTRL_ALT),
559 (CTRL_ALT, 'p') => E(K::Char('0'), M::CTRL_ALT),
560 (CTRL_ALT, 'q') => E(K::Char('1'), M::CTRL_ALT),
561 (CTRL_ALT, 'r') => E(K::Char('2'), M::CTRL_ALT),
562 (CTRL_ALT, 's') => E(K::Char('3'), M::CTRL_ALT),
563 (CTRL_ALT, 't') => E(K::Char('4'), M::CTRL_ALT),
564 (CTRL_ALT, 'u') => E(K::Char('5'), M::CTRL_ALT),
565 (CTRL_ALT, 'v') => E(K::Char('6'), M::CTRL_ALT),
566 (CTRL_ALT, 'w') => E(K::Char('7'), M::CTRL_ALT),
567 (CTRL_ALT, 'x') => E(K::Char('8'), M::CTRL_ALT),
568 (CTRL_ALT, 'y') => E(K::Char('9'), M::CTRL_ALT),
569 (CTRL_ALT_SHIFT, UP) => E(K::Up, M::CTRL_ALT_SHIFT),
570 (CTRL_ALT_SHIFT, DOWN) => E(K::Down, M::CTRL_ALT_SHIFT),
571 (CTRL_ALT_SHIFT, RIGHT) => E(K::Right, M::CTRL_ALT_SHIFT),
572 (CTRL_ALT_SHIFT, LEFT) => E(K::Left, M::CTRL_ALT_SHIFT),
573 (CTRL_ALT_SHIFT, END) => E(K::End, M::CTRL_ALT_SHIFT),
574 (CTRL_ALT_SHIFT, HOME) => E(K::Home, M::CTRL_ALT_SHIFT),
575 (CTRL_ALT_SHIFT, 'p') => E(K::Char('0'), M::CTRL_ALT_SHIFT),
576 (CTRL_ALT_SHIFT, 'q') => E(K::Char('1'), M::CTRL_ALT_SHIFT),
577 (CTRL_ALT_SHIFT, 'r') => E(K::Char('2'), M::CTRL_ALT_SHIFT),
578 (CTRL_ALT_SHIFT, 's') => E(K::Char('3'), M::CTRL_ALT_SHIFT),
579 (CTRL_ALT_SHIFT, 't') => E(K::Char('4'), M::CTRL_ALT_SHIFT),
580 (CTRL_ALT_SHIFT, 'u') => E(K::Char('5'), M::CTRL_ALT_SHIFT),
581 (CTRL_ALT_SHIFT, 'v') => E(K::Char('6'), M::CTRL_ALT_SHIFT),
582 (CTRL_ALT_SHIFT, 'w') => E(K::Char('7'), M::CTRL_ALT_SHIFT),
583 (CTRL_ALT_SHIFT, 'x') => E(K::Char('8'), M::CTRL_ALT_SHIFT),
584 (CTRL_ALT_SHIFT, 'y') => E(K::Char('9'), M::CTRL_ALT_SHIFT),
585 ('9', UP) => E(K::Up, M::ALT),
587 ('9', DOWN) => E(K::Down, M::ALT),
588 ('9', RIGHT) => E(K::Right, M::ALT),
589 ('9', LEFT) => E(K::Left, M::ALT),
590 _ => {
591 debug!(target: "rustyline",
592 "unsupported esc sequence: \\E[1;{}{:?}", seq4, seq5);
593 E(K::UnknownEscSeq, M::NONE)
594 }
595 })
596 } else if seq5 == '~' {
597 Ok(match (seq2, seq4) {
598 (INSERT, SHIFT) => E(K::Insert, M::SHIFT),
599 (INSERT, ALT) => E(K::Insert, M::ALT),
600 (INSERT, ALT_SHIFT) => E(K::Insert, M::ALT_SHIFT),
601 (INSERT, CTRL) => E(K::Insert, M::CTRL),
602 (INSERT, CTRL_SHIFT) => E(K::Insert, M::CTRL_SHIFT),
603 (INSERT, CTRL_ALT) => E(K::Insert, M::CTRL_ALT),
604 (INSERT, CTRL_ALT_SHIFT) => E(K::Insert, M::CTRL_ALT_SHIFT),
605 (DELETE, SHIFT) => E(K::Delete, M::SHIFT),
606 (DELETE, ALT) => E(K::Delete, M::ALT),
607 (DELETE, ALT_SHIFT) => E(K::Delete, M::ALT_SHIFT),
608 (DELETE, CTRL) => E(K::Delete, M::CTRL),
609 (DELETE, CTRL_SHIFT) => E(K::Delete, M::CTRL_SHIFT),
610 (DELETE, CTRL_ALT) => E(K::Delete, M::CTRL_ALT),
611 (DELETE, CTRL_ALT_SHIFT) => E(K::Delete, M::CTRL_ALT_SHIFT),
612 (PAGE_UP, SHIFT) => E(K::PageUp, M::SHIFT),
613 (PAGE_UP, ALT) => E(K::PageUp, M::ALT),
614 (PAGE_UP, ALT_SHIFT) => E(K::PageUp, M::ALT_SHIFT),
615 (PAGE_UP, CTRL) => E(K::PageUp, M::CTRL),
616 (PAGE_UP, CTRL_SHIFT) => E(K::PageUp, M::CTRL_SHIFT),
617 (PAGE_UP, CTRL_ALT) => E(K::PageUp, M::CTRL_ALT),
618 (PAGE_UP, CTRL_ALT_SHIFT) => E(K::PageUp, M::CTRL_ALT_SHIFT),
619 (PAGE_DOWN, SHIFT) => E(K::PageDown, M::SHIFT),
620 (PAGE_DOWN, ALT) => E(K::PageDown, M::ALT),
621 (PAGE_DOWN, ALT_SHIFT) => E(K::PageDown, M::ALT_SHIFT),
622 (PAGE_DOWN, CTRL) => E(K::PageDown, M::CTRL),
623 (PAGE_DOWN, CTRL_SHIFT) => E(K::PageDown, M::CTRL_SHIFT),
624 (PAGE_DOWN, CTRL_ALT) => E(K::PageDown, M::CTRL_ALT),
625 (PAGE_DOWN, CTRL_ALT_SHIFT) => E(K::PageDown, M::CTRL_ALT_SHIFT),
626 _ => {
627 debug!(target: "rustyline",
628 "unsupported esc sequence: \\E[{};{:?}~", seq2, seq4);
629 E(K::UnknownEscSeq, M::NONE)
630 }
631 })
632 } else {
633 debug!(target: "rustyline",
634 "unsupported esc sequence: \\E[{};{}{:?}", seq2, seq4, seq5);
635 Ok(E(K::UnknownEscSeq, M::NONE))
636 }
637 } else {
638 debug!(target: "rustyline",
639 "unsupported esc sequence: \\E[{};{:?}", seq2, seq4);
640 Ok(E(K::UnknownEscSeq, M::NONE))
641 }
642 } else {
643 Ok(match (seq2, seq3) {
644 (DELETE, RXVT_CTRL) => E(K::Delete, M::CTRL),
645 (DELETE, RXVT_CTRL_SHIFT) => E(K::Delete, M::CTRL_SHIFT),
646 (CTRL, UP) => E(K::Up, M::CTRL),
647 (CTRL, DOWN) => E(K::Down, M::CTRL),
648 (CTRL, RIGHT) => E(K::Right, M::CTRL),
649 (CTRL, LEFT) => E(K::Left, M::CTRL),
650 (PAGE_UP, RXVT_CTRL) => E(K::PageUp, M::CTRL),
651 (PAGE_UP, RXVT_SHIFT) => E(K::PageUp, M::SHIFT),
652 (PAGE_UP, RXVT_CTRL_SHIFT) => E(K::PageUp, M::CTRL_SHIFT),
653 (PAGE_DOWN, RXVT_CTRL) => E(K::PageDown, M::CTRL),
654 (PAGE_DOWN, RXVT_SHIFT) => E(K::PageDown, M::SHIFT),
655 (PAGE_DOWN, RXVT_CTRL_SHIFT) => E(K::PageDown, M::CTRL_SHIFT),
656 (RXVT_HOME, RXVT_CTRL) => E(K::Home, M::CTRL),
657 (RXVT_HOME, RXVT_SHIFT) => E(K::Home, M::SHIFT),
658 (RXVT_HOME, RXVT_CTRL_SHIFT) => E(K::Home, M::CTRL_SHIFT),
659 (RXVT_END, RXVT_CTRL) => E(K::End, M::CTRL), (RXVT_END, RXVT_SHIFT) => E(K::End, M::SHIFT),
661 (RXVT_END, RXVT_CTRL_SHIFT) => E(K::End, M::CTRL_SHIFT),
662 _ => {
663 debug!(target: "rustyline",
664 "unsupported esc sequence: \\E[{}{:?}", seq2, seq3);
665 E(K::UnknownEscSeq, M::NONE)
666 }
667 })
668 }
669 }
670
671 fn escape_o(&mut self) -> Result<KeyEvent> {
673 let seq2 = self.next_char()?;
674 Ok(match seq2 {
675 UP => E(K::Up, M::NONE),
676 DOWN => E(K::Down, M::NONE),
677 RIGHT => E(K::Right, M::NONE),
678 LEFT => E(K::Left, M::NONE),
679 END => E(K::End, M::NONE), HOME => E(K::Home, M::NONE), 'M' => E::ENTER, 'P' => E(K::F(1), M::NONE), 'Q' => E(K::F(2), M::NONE), 'R' => E(K::F(3), M::NONE), 'S' => E(K::F(4), M::NONE), 'a' => E(K::Up, M::CTRL),
688 'b' => E(K::Down, M::CTRL),
689 'c' => E(K::Right, M::CTRL), 'd' => E(K::Left, M::CTRL), 'l' => E(K::F(8), M::NONE),
692 't' => E(K::F(5), M::NONE), 'u' => E(K::F(6), M::NONE), 'v' => E(K::F(7), M::NONE), 'w' => E(K::F(9), M::NONE), 'x' => E(K::F(10), M::NONE), _ => {
698 debug!(target: "rustyline", "unsupported esc sequence: \\EO{:?}", seq2);
699 E(K::UnknownEscSeq, M::NONE)
700 }
701 })
702 }
703
704 fn poll(&mut self, timeout_ms: PollTimeout) -> Result<i32> {
705 let n = self.tty_in.buffer().len();
706 if n > 0 {
707 return Ok(n as i32);
708 }
709 let mut fds = [poll::PollFd::new(self.as_fd(), PollFlags::POLLIN)];
710 let r = poll::poll(&mut fds, timeout_ms);
711 match r {
712 Ok(n) => Ok(n),
713 Err(Errno::EINTR) => {
714 if self.tty_in.get_ref().sigwinch()? {
715 Err(ReadlineError::WindowResized)
716 } else {
717 Ok(0) }
719 }
720 Err(e) => Err(e.into()),
721 }
722 }
723
724 fn select(&mut self, single_esc_abort: bool) -> Result<Event> {
725 let tty_in = self.as_fd();
726 let sigwinch_pipe = self
727 .tty_in
728 .get_ref()
729 .sigwinch_pipe
730 .map(|fd| unsafe { BorrowedFd::borrow_raw(fd) });
731 let pipe_reader = self
732 .pipe_reader
733 .as_ref()
734 .map(|pr| pr.lock().unwrap().0.as_raw_fd())
735 .map(|fd| unsafe { BorrowedFd::borrow_raw(fd) });
736 loop {
737 let mut readfds = FdSet::new();
738 if let Some(sigwinch_pipe) = sigwinch_pipe {
739 readfds.insert(sigwinch_pipe);
740 }
741 readfds.insert(tty_in);
742 if let Some(pipe_reader) = pipe_reader {
743 readfds.insert(pipe_reader);
744 }
745 if let Err(err) = select::select(None, Some(&mut readfds), None, None, None) {
746 if err == Errno::EINTR && self.tty_in.get_ref().sigwinch()? {
747 return Err(ReadlineError::WindowResized);
748 } else if err != Errno::EINTR {
749 return Err(err.into());
750 } else {
751 continue;
752 }
753 };
754 if sigwinch_pipe.map_or(false, |fd| readfds.contains(fd)) {
755 self.tty_in.get_ref().sigwinch()?;
756 return Err(ReadlineError::WindowResized);
757 } else if readfds.contains(tty_in) {
758 return self.next_key(single_esc_abort).map(Event::KeyPress);
760 } else if let Some(ref pipe_reader) = self.pipe_reader {
761 let mut guard = pipe_reader.lock().unwrap();
762 let mut buf = [0; 1];
763 guard.0.read_exact(&mut buf)?;
764 if let Ok(msg) = guard.1.try_recv() {
765 return Ok(Event::ExternalPrint(msg));
766 }
767 }
768 }
769 }
770}
771
772impl RawReader for PosixRawReader {
773 type Buffer = PosixBuffer;
774
775 #[cfg(not(feature = "signal-hook"))]
776 fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
777 match self.pipe_reader {
778 Some(_) => self.select(single_esc_abort),
779 None => self.next_key(single_esc_abort).map(Event::KeyPress),
780 }
781 }
782
783 #[cfg(feature = "signal-hook")]
784 fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
785 self.select(single_esc_abort)
786 }
787
788 fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent> {
789 let c = self.next_char()?;
790
791 let mut key = KeyEvent::new(c, M::NONE);
792 if key == E::ESC {
793 if !self.tty_in.buffer().is_empty() {
794 debug!(target: "rustyline", "read buffer {:?}", self.tty_in.buffer());
795 }
796 let timeout_ms = if single_esc_abort && self.timeout_ms.is_none() {
797 PollTimeout::ZERO
798 } else {
799 self.timeout_ms
800 };
801 match self.poll(timeout_ms) {
802 Ok(0) => {
803 }
805 Ok(_) => {
806 key = self.escape_sequence()?
808 }
809 Err(e) => return Err(e),
811 }
812 }
813 debug!(target: "rustyline", "c: {:?} => key: {:?}", c, key);
814 Ok(key)
815 }
816
817 fn next_char(&mut self) -> Result<char> {
818 let mut buf = [0; 1];
819 let mut receiver = Utf8 {
820 c: None,
821 valid: true,
822 };
823 loop {
824 let n = self.tty_in.read(&mut buf)?;
825 if n == 0 {
826 return Err(ReadlineError::Eof);
827 }
828 let b = buf[0];
829 self.parser.advance(&mut receiver, b);
830 if !receiver.valid {
831 return Err(ReadlineError::from(ErrorKind::InvalidData));
832 } else if let Some(c) = receiver.c.take() {
833 return Ok(c);
834 }
835 }
836 }
837
838 fn read_pasted_text(&mut self) -> Result<String> {
839 let mut buffer = String::new();
840 loop {
841 match self.next_char()? {
842 '\x1b' => {
843 let key = self.escape_sequence()?;
844 if key == E(K::BracketedPasteEnd, M::NONE) {
845 break;
846 } else {
847 continue; }
849 }
850 c => buffer.push(c),
851 };
852 }
853 let buffer = buffer.replace("\r\n", "\n");
854 let buffer = buffer.replace('\r', "\n");
855 Ok(buffer)
856 }
857
858 fn find_binding(&self, key: &KeyEvent) -> Option<Cmd> {
859 let cmd = self.key_map.get(key).cloned();
860 if let Some(ref cmd) = cmd {
861 debug!(target: "rustyline", "terminal key binding: {:?} => {:?}", key, cmd);
862 }
863 cmd
864 }
865
866 #[cfg(any(not(feature = "buffer-redux"), test))]
867 fn unbuffer(self) -> Option<PosixBuffer> {
868 None
869 }
870
871 #[cfg(all(feature = "buffer-redux", not(test)))]
872 fn unbuffer(self) -> Option<PosixBuffer> {
873 let (_, buffer) = self.tty_in.into_inner_with_buffer();
874 Some(buffer)
875 }
876}
877
878impl Receiver for Utf8 {
879 fn codepoint(&mut self, c: char) {
881 self.c = Some(c);
882 self.valid = true;
883 }
884
885 fn invalid_sequence(&mut self) {
887 self.c = None;
888 self.valid = false;
889 }
890}
891
892pub struct PosixRenderer {
894 out: RawFd,
895 cols: usize, buffer: String,
897 tab_stop: usize,
898 colors_enabled: bool,
899 bell_style: BellStyle,
900}
901
902impl PosixRenderer {
903 fn new(out: RawFd, tab_stop: usize, colors_enabled: bool, bell_style: BellStyle) -> Self {
904 let (cols, _) = get_win_size(out);
905 Self {
906 out,
907 cols,
908 buffer: String::with_capacity(1024),
909 tab_stop,
910 colors_enabled,
911 bell_style,
912 }
913 }
914
915 fn clear_old_rows(&mut self, layout: &Layout) {
916 use std::fmt::Write;
917 let current_row = layout.cursor.row;
918 let old_rows = layout.end.row;
919 let cursor_row_movement = old_rows.saturating_sub(current_row);
922 if cursor_row_movement > 0 {
924 write!(self.buffer, "\x1b[{cursor_row_movement}B").unwrap();
925 }
926 for _ in 0..old_rows {
928 self.buffer.push_str("\r\x1b[K\x1b[A");
929 }
930 self.buffer.push_str("\r\x1b[K");
932 }
933}
934
935impl Renderer for PosixRenderer {
936 type Reader = PosixRawReader;
937
938 fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
939 use std::fmt::Write;
940 self.buffer.clear();
941 let row_ordering = new.row.cmp(&old.row);
942 if row_ordering == cmp::Ordering::Greater {
943 let row_shift = new.row - old.row;
945 if row_shift == 1 {
946 self.buffer.push_str("\x1b[B");
947 } else {
948 write!(self.buffer, "\x1b[{row_shift}B")?;
949 }
950 } else if row_ordering == cmp::Ordering::Less {
951 let row_shift = old.row - new.row;
953 if row_shift == 1 {
954 self.buffer.push_str("\x1b[A");
955 } else {
956 write!(self.buffer, "\x1b[{row_shift}A")?;
957 }
958 }
959 let col_ordering = new.col.cmp(&old.col);
960 if col_ordering == cmp::Ordering::Greater {
961 let col_shift = new.col - old.col;
963 if col_shift == 1 {
964 self.buffer.push_str("\x1b[C");
965 } else {
966 write!(self.buffer, "\x1b[{col_shift}C")?;
967 }
968 } else if col_ordering == cmp::Ordering::Less {
969 let col_shift = old.col - new.col;
971 if col_shift == 1 {
972 self.buffer.push_str("\x1b[D");
973 } else {
974 write!(self.buffer, "\x1b[{col_shift}D")?;
975 }
976 }
977 write_all(self.out, self.buffer.as_str())?;
978 Ok(())
979 }
980
981 fn refresh_line(
982 &mut self,
983 prompt: &str,
984 line: &LineBuffer,
985 hint: Option<&str>,
986 old_layout: &Layout,
987 new_layout: &Layout,
988 highlighter: Option<&dyn Highlighter>,
989 ) -> Result<()> {
990 use std::fmt::Write;
991 self.buffer.clear();
992
993 let default_prompt = new_layout.default_prompt;
994 let cursor = new_layout.cursor;
995 let end_pos = new_layout.end;
996
997 self.clear_old_rows(old_layout);
998
999 if let Some(highlighter) = highlighter {
1000 self.buffer
1002 .push_str(&highlighter.highlight_prompt(prompt, default_prompt));
1003 self.buffer
1005 .push_str(&highlighter.highlight(line, line.pos()));
1006 } else {
1007 self.buffer.push_str(prompt);
1009 self.buffer.push_str(line);
1011 }
1012 if let Some(hint) = hint {
1014 if let Some(highlighter) = highlighter {
1015 self.buffer.push_str(&highlighter.highlight_hint(hint));
1016 } else {
1017 self.buffer.push_str(hint);
1018 }
1019 }
1020 if end_pos.col == 0
1022 && end_pos.row > 0
1023 && !hint.map_or_else(|| line.ends_with('\n'), |h| h.ends_with('\n'))
1024 {
1025 self.buffer.push('\n');
1026 }
1027 let new_cursor_row_movement = end_pos.row - cursor.row;
1029 if new_cursor_row_movement > 0 {
1031 write!(self.buffer, "\x1b[{new_cursor_row_movement}A")?;
1032 }
1033 if cursor.col > 0 {
1035 write!(self.buffer, "\r\x1b[{}C", cursor.col)?;
1036 } else {
1037 self.buffer.push('\r');
1038 }
1039
1040 write_all(self.out, self.buffer.as_str())?;
1041 Ok(())
1042 }
1043
1044 fn write_and_flush(&mut self, buf: &str) -> Result<()> {
1045 write_all(self.out, buf)?;
1046 Ok(())
1047 }
1048
1049 fn calculate_position(&self, s: &str, orig: Position) -> Position {
1052 let mut pos = orig;
1053 let mut esc_seq = 0;
1054 for c in s.graphemes(true) {
1055 if c == "\n" {
1056 pos.row += 1;
1057 pos.col = 0;
1058 continue;
1059 }
1060 let cw = if c == "\t" {
1061 self.tab_stop - (pos.col % self.tab_stop)
1062 } else {
1063 width(c, &mut esc_seq)
1064 };
1065 pos.col += cw;
1066 if pos.col > self.cols {
1067 pos.row += 1;
1068 pos.col = cw;
1069 }
1070 }
1071 if pos.col == self.cols {
1072 pos.col = 0;
1073 pos.row += 1;
1074 }
1075 pos
1076 }
1077
1078 fn beep(&mut self) -> Result<()> {
1079 match self.bell_style {
1080 BellStyle::Audible => self.write_and_flush("\x07"),
1081 _ => Ok(()),
1082 }
1083 }
1084
1085 fn clear_screen(&mut self) -> Result<()> {
1087 self.write_and_flush("\x1b[H\x1b[J")
1088 }
1089
1090 fn clear_rows(&mut self, layout: &Layout) -> Result<()> {
1091 self.buffer.clear();
1092 self.clear_old_rows(layout);
1093 write_all(self.out, self.buffer.as_str())?;
1094 Ok(())
1095 }
1096
1097 fn update_size(&mut self) {
1099 let (cols, _) = get_win_size(self.out);
1100 self.cols = cols;
1101 }
1102
1103 fn get_columns(&self) -> usize {
1104 self.cols
1105 }
1106
1107 fn get_rows(&self) -> usize {
1110 let (_, rows) = get_win_size(self.out);
1111 rows
1112 }
1113
1114 fn colors_enabled(&self) -> bool {
1115 self.colors_enabled
1116 }
1117
1118 fn move_cursor_at_leftmost(&mut self, rdr: &mut PosixRawReader) -> Result<()> {
1119 if rdr.poll(PollTimeout::ZERO)? != 0 {
1120 debug!(target: "rustyline", "cannot request cursor location");
1121 return Ok(());
1122 }
1123 self.write_and_flush("\x1b[6n")?;
1125 if rdr.poll(PollTimeout::from(100u8))? == 0
1127 || rdr.next_char()? != '\x1b'
1128 || rdr.next_char()? != '['
1129 || read_digits_until(rdr, ';')?.is_none()
1130 {
1131 warn!(target: "rustyline", "cannot read initial cursor location");
1132 return Ok(());
1133 }
1134 let col = read_digits_until(rdr, 'R')?;
1135 debug!(target: "rustyline", "initial cursor location: {:?}", col);
1136 if col != Some(1) {
1137 self.write_and_flush("\n")?;
1138 }
1139 Ok(())
1140 }
1141}
1142
1143fn read_digits_until(rdr: &mut PosixRawReader, sep: char) -> Result<Option<u32>> {
1144 let mut num: u32 = 0;
1145 loop {
1146 match rdr.next_char()? {
1147 digit @ '0'..='9' => {
1148 num = num
1149 .saturating_mul(10)
1150 .saturating_add(digit.to_digit(10).unwrap());
1151 continue;
1152 }
1153 c if c == sep => break,
1154 _ => return Ok(None),
1155 }
1156 }
1157 Ok(Some(num))
1158}
1159
1160fn write_all(fd: RawFd, buf: &str) -> nix::Result<()> {
1161 let mut bytes = buf.as_bytes();
1162 while !bytes.is_empty() {
1163 match write(unsafe { BorrowedFd::borrow_raw(fd) }, bytes) {
1164 Ok(0) => return Err(Errno::EIO),
1165 Ok(n) => bytes = &bytes[n..],
1166 Err(Errno::EINTR) => {}
1167 Err(r) => return Err(r),
1168 }
1169 }
1170 Ok(())
1171}
1172
1173pub struct PosixCursorGuard(RawFd);
1174
1175impl Drop for PosixCursorGuard {
1176 fn drop(&mut self) {
1177 let _ = set_cursor_visibility(self.0, true);
1178 }
1179}
1180
1181fn set_cursor_visibility(fd: RawFd, visible: bool) -> Result<Option<PosixCursorGuard>> {
1182 write_all(fd, if visible { "\x1b[?25h" } else { "\x1b[?25l" })?;
1183 Ok(if visible {
1184 None
1185 } else {
1186 Some(PosixCursorGuard(fd))
1187 })
1188}
1189
1190#[cfg(not(feature = "signal-hook"))]
1191static mut SIGWINCH_PIPE: RawFd = -1;
1192#[cfg(not(feature = "signal-hook"))]
1193extern "C" fn sigwinch_handler(_: libc::c_int) {
1194 let _ = unsafe { write(BorrowedFd::borrow_raw(SIGWINCH_PIPE), b"s") };
1195}
1196
1197#[derive(Clone, Debug)]
1198struct SigWinCh {
1199 pipe: RawFd,
1200 #[cfg(not(feature = "signal-hook"))]
1201 original: nix::sys::signal::SigAction,
1202 #[cfg(feature = "signal-hook")]
1203 id: signal_hook::SigId,
1204}
1205impl SigWinCh {
1206 #[cfg(not(feature = "signal-hook"))]
1207 fn install_sigwinch_handler() -> Result<Self> {
1208 use nix::sys::signal;
1209 let (pipe, pipe_write) = UnixStream::pair()?;
1210 pipe.set_nonblocking(true)?;
1211 unsafe { SIGWINCH_PIPE = pipe_write.into_raw_fd() };
1212 let sigwinch = signal::SigAction::new(
1213 signal::SigHandler::Handler(sigwinch_handler),
1214 signal::SaFlags::empty(),
1215 signal::SigSet::empty(),
1216 );
1217 let original = unsafe { signal::sigaction(signal::SIGWINCH, &sigwinch)? };
1218 Ok(Self {
1219 pipe: pipe.into_raw_fd(),
1220 original,
1221 })
1222 }
1223
1224 #[cfg(feature = "signal-hook")]
1225 fn install_sigwinch_handler() -> Result<Self> {
1226 let (pipe, pipe_write) = UnixStream::pair()?;
1227 pipe.set_nonblocking(true)?;
1228 let id = signal_hook::low_level::pipe::register(libc::SIGWINCH, pipe_write)?;
1229 Ok(Self {
1230 pipe: pipe.into_raw_fd(),
1231 id,
1232 })
1233 }
1234
1235 #[cfg(not(feature = "signal-hook"))]
1236 fn uninstall_sigwinch_handler(self) -> Result<()> {
1237 use nix::sys::signal;
1238 let _ = unsafe { signal::sigaction(signal::SIGWINCH, &self.original)? };
1239 close(self.pipe)?;
1240 unsafe { close(SIGWINCH_PIPE)? };
1241 unsafe { SIGWINCH_PIPE = -1 };
1242 Ok(())
1243 }
1244
1245 #[cfg(feature = "signal-hook")]
1246 fn uninstall_sigwinch_handler(self) -> Result<()> {
1247 signal_hook::low_level::unregister(self.id);
1248 close(self.pipe)?;
1249 Ok(())
1250 }
1251}
1252
1253#[cfg(not(test))]
1254pub type Terminal = PosixTerminal;
1255
1256#[derive(Clone, Debug)]
1257pub struct PosixTerminal {
1258 unsupported: bool,
1259 tty_in: RawFd,
1260 is_in_a_tty: bool,
1261 tty_out: RawFd,
1262 is_out_a_tty: bool,
1263 close_on_drop: bool,
1264 pub(crate) color_mode: ColorMode,
1265 tab_stop: usize,
1266 bell_style: BellStyle,
1267 enable_bracketed_paste: bool,
1268 raw_mode: Arc<AtomicBool>,
1269 pipe_reader: Option<PipeReader>,
1271 pipe_writer: Option<PipeWriter>,
1273 sigwinch: Option<SigWinCh>,
1274 enable_signals: bool,
1275}
1276
1277impl PosixTerminal {
1278 fn colors_enabled(&self) -> bool {
1279 match self.color_mode {
1280 ColorMode::Enabled => self.is_out_a_tty,
1281 ColorMode::Forced => true,
1282 ColorMode::Disabled => false,
1283 }
1284 }
1285}
1286
1287impl Term for PosixTerminal {
1288 type Buffer = PosixBuffer;
1289 type CursorGuard = PosixCursorGuard;
1290 type ExternalPrinter = ExternalPrinter;
1291 type KeyMap = PosixKeyMap;
1292 type Mode = PosixMode;
1293 type Reader = PosixRawReader;
1294 type Writer = PosixRenderer;
1295
1296 fn new(
1297 color_mode: ColorMode,
1298 behavior: Behavior,
1299 tab_stop: usize,
1300 bell_style: BellStyle,
1301 enable_bracketed_paste: bool,
1302 enable_signals: bool,
1303 ) -> Result<Self> {
1304 let (tty_in, is_in_a_tty, tty_out, is_out_a_tty, close_on_drop) =
1305 if behavior == Behavior::PreferTerm {
1306 let tty = OpenOptions::new().read(true).write(true).open("/dev/tty");
1307 if let Ok(tty) = tty {
1308 let fd = tty.into_raw_fd();
1309 let is_a_tty = is_a_tty(fd); (fd, is_a_tty, fd, is_a_tty, true)
1311 } else {
1312 (
1313 libc::STDIN_FILENO,
1314 is_a_tty(libc::STDIN_FILENO),
1315 libc::STDOUT_FILENO,
1316 is_a_tty(libc::STDOUT_FILENO),
1317 false,
1318 )
1319 }
1320 } else {
1321 (
1322 libc::STDIN_FILENO,
1323 is_a_tty(libc::STDIN_FILENO),
1324 libc::STDOUT_FILENO,
1325 is_a_tty(libc::STDOUT_FILENO),
1326 false,
1327 )
1328 };
1329 let unsupported = is_unsupported_term();
1330 let sigwinch = if !unsupported && is_in_a_tty && is_out_a_tty {
1331 Some(SigWinCh::install_sigwinch_handler()?)
1332 } else {
1333 None
1334 };
1335 Ok(Self {
1336 unsupported,
1337 tty_in,
1338 is_in_a_tty,
1339 tty_out,
1340 is_out_a_tty,
1341 close_on_drop,
1342 color_mode,
1343 tab_stop,
1344 bell_style,
1345 enable_bracketed_paste,
1346 raw_mode: Arc::new(AtomicBool::new(false)),
1347 pipe_reader: None,
1348 pipe_writer: None,
1349 sigwinch,
1350 enable_signals,
1351 })
1352 }
1353
1354 fn is_unsupported(&self) -> bool {
1359 self.unsupported
1360 }
1361
1362 fn is_input_tty(&self) -> bool {
1363 self.is_in_a_tty
1364 }
1365
1366 fn is_output_tty(&self) -> bool {
1367 self.is_out_a_tty
1368 }
1369
1370 fn enable_raw_mode(&mut self) -> Result<(Self::Mode, PosixKeyMap)> {
1373 use nix::errno::Errno::ENOTTY;
1374 if !self.is_in_a_tty {
1375 return Err(ENOTTY.into());
1376 }
1377 let (original_mode, key_map) = termios_::enable_raw_mode(self.tty_in, self.enable_signals)?;
1378
1379 self.raw_mode.store(true, Ordering::SeqCst);
1380 let out = if !self.enable_bracketed_paste {
1382 None
1383 } else if let Err(e) = write_all(self.tty_out, BRACKETED_PASTE_ON) {
1384 debug!(target: "rustyline", "Cannot enable bracketed paste: {}", e);
1385 None
1386 } else {
1387 Some(self.tty_out)
1388 };
1389
1390 if Arc::strong_count(&self.raw_mode) == 1 {
1392 self.pipe_writer = None;
1393 self.pipe_reader = None;
1394 }
1395
1396 Ok((
1397 PosixMode {
1398 termios: original_mode,
1399 tty_in: self.tty_in,
1400 tty_out: out,
1401 raw_mode: self.raw_mode.clone(),
1402 },
1403 key_map,
1404 ))
1405 }
1406
1407 fn create_reader(
1409 &self,
1410 buffer: Option<PosixBuffer>,
1411 config: &Config,
1412 key_map: PosixKeyMap,
1413 ) -> PosixRawReader {
1414 PosixRawReader::new(
1415 self.tty_in,
1416 self.sigwinch.as_ref().map(|s| s.pipe),
1417 buffer,
1418 config,
1419 key_map,
1420 self.pipe_reader.clone(),
1421 )
1422 }
1423
1424 fn create_writer(&self) -> PosixRenderer {
1425 PosixRenderer::new(
1426 self.tty_out,
1427 self.tab_stop,
1428 self.colors_enabled(),
1429 self.bell_style,
1430 )
1431 }
1432
1433 fn writeln(&self) -> Result<()> {
1434 write_all(self.tty_out, "\n")?;
1435 Ok(())
1436 }
1437
1438 fn create_external_printer(&mut self) -> Result<ExternalPrinter> {
1439 use nix::unistd::pipe;
1440 if let Some(ref writer) = self.pipe_writer {
1441 return Ok(ExternalPrinter {
1442 writer: writer.clone(),
1443 raw_mode: self.raw_mode.clone(),
1444 tty_out: self.tty_out,
1445 });
1446 }
1447 if self.unsupported || !self.is_input_tty() || !self.is_output_tty() {
1448 return Err(nix::Error::ENOTTY.into());
1449 }
1450 let (sender, receiver) = mpsc::sync_channel(1); let (r, w) = pipe()?;
1452 let reader = Arc::new(Mutex::new((r.into(), receiver)));
1453 let writer = (Arc::new(Mutex::new(w.into())), sender);
1454 self.pipe_reader.replace(reader);
1455 self.pipe_writer.replace(writer.clone());
1456 Ok(ExternalPrinter {
1457 writer,
1458 raw_mode: self.raw_mode.clone(),
1459 tty_out: self.tty_out,
1460 })
1461 }
1462
1463 fn set_cursor_visibility(&mut self, visible: bool) -> Result<Option<PosixCursorGuard>> {
1464 if self.is_out_a_tty {
1465 set_cursor_visibility(self.tty_out, visible)
1466 } else {
1467 Ok(None)
1468 }
1469 }
1470}
1471
1472#[expect(unused_must_use)]
1473impl Drop for PosixTerminal {
1474 fn drop(&mut self) {
1475 if self.close_on_drop {
1476 close(self.tty_in);
1477 debug_assert_eq!(self.tty_in, self.tty_out);
1478 }
1479 if let Some(sigwinch) = self.sigwinch.take() {
1480 sigwinch.uninstall_sigwinch_handler();
1481 }
1482 }
1483}
1484
1485#[derive(Debug)]
1486pub struct ExternalPrinter {
1487 writer: PipeWriter,
1488 raw_mode: Arc<AtomicBool>,
1489 tty_out: RawFd,
1490}
1491
1492impl super::ExternalPrinter for ExternalPrinter {
1493 fn print(&mut self, msg: String) -> Result<()> {
1494 if !self.raw_mode.load(Ordering::SeqCst) {
1496 write_all(self.tty_out, msg.as_str())?;
1497 } else if let Ok(mut writer) = self.writer.0.lock() {
1498 self.writer
1499 .1
1500 .send(msg)
1501 .map_err(|_| io::Error::from(ErrorKind::Other))?; writer.write_all(b"m")?;
1503 writer.flush()?;
1504 } else {
1505 return Err(io::Error::from(ErrorKind::Other).into()); }
1507 Ok(())
1508 }
1509}
1510
1511#[cfg(not(test))]
1512pub fn suspend() -> Result<()> {
1513 use nix::sys::signal;
1514 use nix::unistd::Pid;
1515 signal::kill(Pid::from_raw(0), signal::SIGTSTP)?;
1517 Ok(())
1518}
1519
1520#[cfg(not(feature = "termios"))]
1521mod termios_ {
1522 use super::PosixKeyMap;
1523 use crate::keys::{KeyEvent, Modifiers as M};
1524 use crate::{Cmd, Result};
1525 use nix::sys::termios::{self, SetArg, SpecialCharacterIndices as SCI, Termios};
1526 use std::collections::HashMap;
1527 use std::os::unix::io::{BorrowedFd, RawFd};
1528 pub fn disable_raw_mode(tty_in: RawFd, termios: &Termios) -> Result<()> {
1529 let fd = unsafe { BorrowedFd::borrow_raw(tty_in) };
1530 Ok(termios::tcsetattr(fd, SetArg::TCSADRAIN, termios)?)
1531 }
1532 pub fn enable_raw_mode(tty_in: RawFd, enable_signals: bool) -> Result<(Termios, PosixKeyMap)> {
1533 use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags};
1534
1535 let fd = unsafe { BorrowedFd::borrow_raw(tty_in) };
1536 let original_mode = termios::tcgetattr(fd)?;
1537 let mut raw = original_mode.clone();
1538 raw.input_flags &= !(InputFlags::BRKINT
1541 | InputFlags::ICRNL
1542 | InputFlags::INPCK
1543 | InputFlags::ISTRIP
1544 | InputFlags::IXON);
1545 raw.control_flags |= ControlFlags::CS8;
1551 raw.local_flags &=
1553 !(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
1554
1555 if enable_signals {
1556 raw.local_flags |= LocalFlags::ISIG;
1557 }
1558
1559 raw.control_chars[SCI::VMIN as usize] = 1; raw.control_chars[SCI::VTIME as usize] = 0; let mut key_map: HashMap<KeyEvent, Cmd> = HashMap::with_capacity(4);
1563 map_key(&mut key_map, &raw, SCI::VEOF, "VEOF", Cmd::EndOfFile);
1564 map_key(&mut key_map, &raw, SCI::VINTR, "VINTR", Cmd::Interrupt);
1565 map_key(&mut key_map, &raw, SCI::VQUIT, "VQUIT", Cmd::Interrupt);
1566 map_key(&mut key_map, &raw, SCI::VSUSP, "VSUSP", Cmd::Suspend);
1567
1568 termios::tcsetattr(fd, SetArg::TCSADRAIN, &raw)?;
1569 Ok((original_mode, key_map))
1570 }
1571 fn map_key(
1572 key_map: &mut HashMap<KeyEvent, Cmd>,
1573 raw: &Termios,
1574 index: SCI,
1575 name: &str,
1576 cmd: Cmd,
1577 ) {
1578 let cc = char::from(raw.control_chars[index as usize]);
1579 let key = KeyEvent::new(cc, M::NONE);
1580 log::debug!(target: "rustyline", "{}: {:?}", name, key);
1581 key_map.insert(key, cmd);
1582 }
1583}
1584#[cfg(feature = "termios")]
1585mod termios_ {
1586 use super::PosixKeyMap;
1587 use crate::keys::{KeyEvent, Modifiers as M};
1588 use crate::{Cmd, Result};
1589 use std::collections::HashMap;
1590 use std::os::unix::io::RawFd;
1591 use termios::{self, Termios};
1592 pub fn disable_raw_mode(tty_in: RawFd, termios: &Termios) -> Result<()> {
1593 Ok(termios::tcsetattr(tty_in, termios::TCSADRAIN, termios)?)
1594 }
1595 pub fn enable_raw_mode(tty_in: RawFd, enable_signals: bool) -> Result<(Termios, PosixKeyMap)> {
1596 let original_mode = Termios::from_fd(tty_in)?;
1597 let mut raw = original_mode;
1598 raw.c_iflag &=
1601 !(termios::BRKINT | termios::ICRNL | termios::INPCK | termios::ISTRIP | termios::IXON);
1602 raw.c_cflag |= termios::CS8;
1608 raw.c_lflag &= !(termios::ECHO | termios::ICANON | termios::IEXTEN | termios::ISIG);
1610
1611 if enable_signals {
1612 raw.c_lflag |= termios::ISIG;
1613 }
1614
1615 raw.c_cc[termios::VMIN] = 1; raw.c_cc[termios::VTIME] = 0; let mut key_map: HashMap<KeyEvent, Cmd> = HashMap::with_capacity(4);
1619 map_key(&mut key_map, &raw, termios::VEOF, "VEOF", Cmd::EndOfFile);
1620 map_key(&mut key_map, &raw, termios::VINTR, "VINTR", Cmd::Interrupt);
1621 map_key(&mut key_map, &raw, termios::VQUIT, "VQUIT", Cmd::Interrupt);
1622 map_key(&mut key_map, &raw, termios::VSUSP, "VSUSP", Cmd::Suspend);
1623
1624 termios::tcsetattr(tty_in, termios::TCSADRAIN, &raw)?;
1625 Ok((original_mode, key_map))
1626 }
1627 fn map_key(
1628 key_map: &mut HashMap<KeyEvent, Cmd>,
1629 raw: &Termios,
1630 index: usize,
1631 name: &str,
1632 cmd: Cmd,
1633 ) {
1634 let cc = char::from(raw.c_cc[index]);
1635 let key = KeyEvent::new(cc, M::NONE);
1636 log::debug!(target: "rustyline", "{}: {:?}", name, key);
1637 key_map.insert(key, cmd);
1638 }
1639}
1640
1641#[cfg(test)]
1642mod test {
1643 use super::{Position, PosixRenderer, PosixTerminal, Renderer};
1644 use crate::config::BellStyle;
1645 use crate::line_buffer::{LineBuffer, NoListener};
1646
1647 #[test]
1648 #[ignore]
1649 fn prompt_with_ansi_escape_codes() {
1650 let out = PosixRenderer::new(libc::STDOUT_FILENO, 4, true, BellStyle::default());
1651 let pos = out.calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default());
1652 assert_eq!(3, pos.col);
1653 assert_eq!(0, pos.row);
1654 }
1655
1656 #[test]
1657 fn test_unsupported_term() {
1658 std::env::set_var("TERM", "xterm");
1659 assert!(!super::is_unsupported_term());
1660
1661 std::env::set_var("TERM", "dumb");
1662 assert!(super::is_unsupported_term());
1663 }
1664
1665 #[test]
1666 fn test_send() {
1667 fn assert_send<T: Send>() {}
1668 assert_send::<PosixTerminal>();
1669 }
1670
1671 #[test]
1672 fn test_sync() {
1673 fn assert_sync<T: Sync>() {}
1674 assert_sync::<PosixTerminal>();
1675 }
1676
1677 #[test]
1678 fn test_line_wrap() {
1679 let mut out = PosixRenderer::new(libc::STDOUT_FILENO, 4, true, BellStyle::default());
1680 let prompt = "> ";
1681 let default_prompt = true;
1682 let prompt_size = out.calculate_position(prompt, Position::default());
1683
1684 let mut line = LineBuffer::init("", 0);
1685 let old_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
1686 assert_eq!(Position { col: 2, row: 0 }, old_layout.cursor);
1687 assert_eq!(old_layout.cursor, old_layout.end);
1688
1689 assert_eq!(
1690 Some(true),
1691 line.insert('a', out.cols - prompt_size.col + 1, &mut NoListener)
1692 );
1693 let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
1694 assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor);
1695 assert_eq!(new_layout.cursor, new_layout.end);
1696 out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None)
1697 .unwrap();
1698 #[rustfmt::skip]
1699 assert_eq!(
1700 "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C",
1701 out.buffer
1702 );
1703 }
1704}