rustyline/tty/
unix.rs

1//! Unix specific definitions
2use std::cmp;
3use std::collections::HashMap;
4use std::fs::{File, OpenOptions};
5#[cfg(not(feature = "buffer-redux"))]
6use std::io::BufReader;
7use std::io::{self, ErrorKind, Read, Write};
8use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, RawFd};
9use std::os::unix::net::UnixStream;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::mpsc::{self, SyncSender};
12use std::sync::{Arc, Mutex};
13
14#[cfg(feature = "buffer-redux")]
15use buffer_redux::BufReader;
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::sys::time::TimeValLike;
23use nix::unistd::{close, isatty, read, write};
24#[cfg(feature = "termios")]
25use termios::Termios;
26use unicode_segmentation::UnicodeSegmentation;
27use utf8parse::{Parser, Receiver};
28
29use super::{width, Event, RawMode, RawReader, Renderer, Term};
30use crate::config::{Behavior, BellStyle, ColorMode, Config};
31use crate::highlight::Highlighter;
32use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
33use crate::layout::{GraphemeClusterMode, Layout, Position, Unit};
34use crate::line_buffer::LineBuffer;
35use crate::{error, error::Signal, Cmd, ReadlineError, Result};
36
37const BRACKETED_PASTE_ON: &str = "\x1b[?2004h";
38const BRACKETED_PASTE_OFF: &str = "\x1b[?2004l";
39const BEGIN_SYNCHRONIZED_UPDATE: &str = "\x1b[?2026h";
40const END_SYNCHRONIZED_UPDATE: &str = "\x1b[?2026l";
41
42nix::ioctl_read_bad!(win_size, libc::TIOCGWINSZ, libc::winsize);
43
44fn get_win_size(fd: AltFd) -> (Unit, Unit) {
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.0, &mut size) {
54            Ok(0) => {
55                // In linux pseudo-terminals are created with dimensions of
56                // zero. If host application didn't initialize the correct
57                // size before start we treat zero size as 80 columns and
58                // infinite rows
59                let cols = if size.ws_col == 0 { 80 } else { size.ws_col };
60                let rows = if size.ws_row == 0 {
61                    Unit::MAX
62                } else {
63                    size.ws_row
64                };
65                (cols, rows)
66            }
67            _ => (80, 24),
68        }
69    }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq)]
73struct AltFd(RawFd);
74impl IntoRawFd for AltFd {
75    #[inline]
76    fn into_raw_fd(self) -> RawFd {
77        self.0
78    }
79}
80impl AsFd for AltFd {
81    #[inline]
82    fn as_fd(&self) -> BorrowedFd<'_> {
83        unsafe { BorrowedFd::borrow_raw(self.0) }
84    }
85}
86
87/// Return whether or not STDIN, STDOUT or STDERR is a TTY
88fn is_a_tty(fd: AltFd) -> bool {
89    isatty(fd).unwrap_or(false)
90}
91
92#[cfg(any(not(feature = "buffer-redux"), test))]
93pub type PosixBuffer = ();
94#[cfg(all(feature = "buffer-redux", not(test)))]
95pub type PosixBuffer = buffer_redux::Buffer;
96#[cfg(not(test))]
97pub type Buffer = PosixBuffer;
98
99pub type PosixKeyMap = HashMap<KeyEvent, Cmd>;
100#[cfg(not(test))]
101pub type KeyMap = PosixKeyMap;
102
103#[must_use = "You must restore default mode (disable_raw_mode)"]
104pub struct PosixMode {
105    termios: Termios,
106    tty_in: AltFd,
107    tty_out: Option<AltFd>,
108    raw_mode: Arc<AtomicBool>,
109}
110
111#[cfg(not(test))]
112pub type Mode = PosixMode;
113
114impl RawMode for PosixMode {
115    /// Disable RAW mode for the terminal.
116    fn disable_raw_mode(&self) -> Result<()> {
117        termios_::disable_raw_mode(self.tty_in, &self.termios)?;
118        // disable bracketed paste
119        if let Some(out) = self.tty_out {
120            write_all(out, BRACKETED_PASTE_OFF)?;
121        }
122        self.raw_mode.store(false, Ordering::SeqCst);
123        Ok(())
124    }
125}
126
127// Rust std::io::Stdin is buffered with no way to know if bytes are available.
128// So we use low-level stuff instead...
129struct TtyIn {
130    fd: AltFd,
131    sig_pipe: Option<AltFd>,
132}
133
134impl Read for TtyIn {
135    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
136        loop {
137            let res = unsafe {
138                libc::read(
139                    self.fd.0,
140                    buf.as_mut_ptr().cast::<libc::c_void>(),
141                    buf.len() as libc::size_t,
142                )
143            };
144            if res == -1 {
145                let error = io::Error::last_os_error();
146                if error.kind() == ErrorKind::Interrupted {
147                    if let Some(signal) = self.sig()? {
148                        return Err(io::Error::new(
149                            ErrorKind::Interrupted,
150                            error::SignalError(signal),
151                        ));
152                    }
153                } else {
154                    return Err(error);
155                }
156            } else {
157                #[expect(clippy::cast_sign_loss)]
158                return Ok(res as usize);
159            }
160        }
161    }
162}
163
164impl TtyIn {
165    /// Check if a signal has been received
166    fn sig(&self) -> nix::Result<Option<Signal>> {
167        if let Some(pipe) = self.sig_pipe {
168            let mut buf = [0u8; 64];
169            match read(pipe, &mut buf) {
170                Ok(0) => Ok(None),
171                Ok(_) => Ok(Some(Signal::from(buf[0]))),
172                Err(e) if e == Errno::EWOULDBLOCK || e == Errno::EINTR => Ok(None),
173                Err(e) => Err(e),
174            }
175        } else {
176            Ok(None)
177        }
178    }
179}
180
181// (native receiver with a selectable file descriptor, actual message receiver)
182type PipeReader = Arc<Mutex<(File, mpsc::Receiver<String>)>>;
183// (native sender, actual message sender)
184type PipeWriter = (Arc<Mutex<File>>, SyncSender<String>);
185
186/// Console input reader
187pub struct PosixRawReader {
188    tty_in: BufReader<TtyIn>,
189    timeout_ms: PollTimeout,
190    parser: Parser,
191    key_map: PosixKeyMap,
192    // external print reader
193    pipe_reader: Option<PipeReader>,
194    #[cfg(target_os = "macos")]
195    is_dev_tty: bool,
196}
197
198impl AsFd for PosixRawReader {
199    fn as_fd(&self) -> BorrowedFd<'_> {
200        self.tty_in.get_ref().fd.as_fd()
201    }
202}
203
204struct Utf8 {
205    c: Option<char>,
206    valid: bool,
207}
208
209const UP: char = 'A'; // kcuu1, kUP*
210const DOWN: char = 'B'; // kcud1, kDN*
211const RIGHT: char = 'C'; // kcuf1, kRIT*
212const LEFT: char = 'D'; // kcub1, kLFT*
213const END: char = 'F'; // kend*
214const HOME: char = 'H'; // khom*
215const INSERT: char = '2'; // kic*
216const DELETE: char = '3'; // kdch1, kDC*
217const PAGE_UP: char = '5'; // kpp, kPRV*
218const PAGE_DOWN: char = '6'; // knp, kNXT*
219
220const RXVT_HOME: char = '7';
221const RXVT_END: char = '8';
222
223const SHIFT: char = '2';
224const ALT: char = '3';
225const ALT_SHIFT: char = '4';
226const CTRL: char = '5';
227const CTRL_SHIFT: char = '6';
228const CTRL_ALT: char = '7';
229const CTRL_ALT_SHIFT: char = '8';
230
231const RXVT_SHIFT: char = '$';
232const RXVT_CTRL: char = '\x1e';
233const RXVT_CTRL_SHIFT: char = '@';
234
235impl PosixRawReader {
236    fn new(
237        fd: AltFd,
238        sig_pipe: Option<AltFd>,
239        buffer: Option<PosixBuffer>,
240        config: &Config,
241        key_map: PosixKeyMap,
242        pipe_reader: Option<PipeReader>,
243        #[cfg(target_os = "macos")] is_dev_tty: bool,
244    ) -> Self {
245        let inner = TtyIn { fd, sig_pipe };
246        #[cfg(any(not(feature = "buffer-redux"), test))]
247        let (tty_in, _) = (BufReader::with_capacity(1024, inner), buffer);
248        #[cfg(all(feature = "buffer-redux", not(test)))]
249        let tty_in = if let Some(buffer) = buffer {
250            BufReader::with_buffer(buffer, inner)
251        } else {
252            BufReader::with_capacity(1024, inner)
253        };
254        Self {
255            tty_in,
256            timeout_ms: config.keyseq_timeout().into(),
257            parser: Parser::new(),
258            key_map,
259            pipe_reader,
260            #[cfg(target_os = "macos")]
261            is_dev_tty,
262        }
263    }
264
265    /// Handle \E <seq1> sequences
266    // https://invisible-island.net/xterm/xterm-function-keys.html
267    fn escape_sequence(&mut self) -> Result<KeyEvent> {
268        self._do_escape_sequence(true)
269    }
270
271    /// Don't call directly, call `PosixRawReader::escape_sequence` instead
272    fn _do_escape_sequence(&mut self, allow_recurse: bool) -> Result<KeyEvent> {
273        // Read the next byte representing the escape sequence.
274        let seq1 = self.next_char()?;
275        if seq1 == '[' {
276            // \E[ sequences. (CSI)
277            self.escape_csi()
278        } else if seq1 == 'O' {
279            // xterm
280            // \EO sequences. (SS3)
281            self.escape_o()
282        } else if seq1 == '\x1b' {
283            // \E\E — used by rxvt, iTerm (under default config), etc.
284            // ```
285            // \E\E[A => Alt-Up
286            // \E\E[B => Alt-Down
287            // \E\E[C => Alt-Right
288            // \E\E[D => Alt-Left
289            // ```
290            //
291            // In general this more or less works just adding ALT to an existing
292            // key, but has a wrinkle in that `ESC ESC` without anything
293            // following should be interpreted as the the escape key.
294            //
295            // We handle this by polling to see if there's anything coming
296            // within our timeout, and if so, recursing once, but adding alt to
297            // what we read.
298            if !allow_recurse {
299                return Ok(E::ESC);
300            }
301            let timeout = if self.timeout_ms.is_none() {
302                100u8.into()
303            } else {
304                self.timeout_ms
305            };
306            match self.poll(timeout) {
307                // Ignore poll errors, it's very likely we'll pick them up on
308                // the next read anyway.
309                Ok(false) | Err(_) => Ok(E::ESC),
310                Ok(true) => {
311                    // recurse, and add the alt modifier.
312                    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    /// Handle \E[ <seq2> escape sequences
322    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                    // Extended escape, read additional byte.
332                    self.extended_escape(seq2)
333                }
334            }
335        } else if seq2 == '[' {
336            let seq3 = self.next_char()?;
337            // Linux console
338            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            // ANSI
351            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                //'E' => E(K::, M::), // Ignore
357                END => E(K::End, M::NONE),
358                //'G' => E(K::, M::), // Ignore
359                HOME => E(K::Home, M::NONE), // khome
360                //'J' => E(K::, M::), // clr_eos
361                //'K' => E(K::, M::), // clr_eol
362                //'L' => E(K::, M::), // il1
363                //'M' => E(K::, M::), // kmous
364                //'P' => E(K::Delete, M::NONE), // dch1
365                'Z' => E(K::BackTab, M::NONE),
366                'a' => E(K::Up, M::SHIFT),    // rxvt: kind or kUP
367                'b' => E(K::Down, M::SHIFT),  // rxvt: kri or kDN
368                'c' => E(K::Right, M::SHIFT), // rxvt
369                'd' => E(K::Left, M::SHIFT),  // rxvt
370                _ => {
371                    debug!(target: "rustyline", "unsupported esc sequence: \\E[{seq2:?}");
372                    E(K::UnknownEscSeq, M::NONE)
373                }
374            })
375        }
376    }
377
378    /// Handle \E[ <seq2:digit> escape sequences
379    #[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), // tmux, xrvt
385                INSERT => E(K::Insert, M::NONE),
386                DELETE => E(K::Delete, M::NONE),
387                '4' | RXVT_END => E(K::End, M::NONE), // tmux, xrvt
388                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),  // rxvt-unicode
401                    ('1', '2') => E(K::F(2), M::NONE),  // rxvt-unicode
402                    ('1', '3') => E(K::F(3), M::NONE),  // rxvt-unicode
403                    ('1', '4') => E(K::F(4), M::NONE),  // rxvt-unicode
404                    ('1', '5') => E(K::F(5), M::NONE),  // kf5
405                    ('1', '7') => E(K::F(6), M::NONE),  // kf6
406                    ('1', '8') => E(K::F(7), M::NONE),  // kf7
407                    ('1', '9') => E(K::F(8), M::NONE),  // kf8
408                    ('2', '0') => E(K::F(9), M::NONE),  // kf9
409                    ('2', '1') => E(K::F(10), M::NONE), // kf10
410                    ('2', '3') => E(K::F(11), M::NONE), // kf11
411                    ('2', '4') => E(K::F(12), M::NONE), // kf12
412                    //('6', '2') => KeyCode::ScrollUp,
413                    //('6', '3') => KeyCode::ScrollDown,
414                    _ => {
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()?; // 'R' expected
426                        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', '5', '6') => E(K::F(17), M::CTRL),
433                            ('1', '7', CTRL) => E(K::F(6), M::CTRL),
434                            //('1', '7', '6') => E(K::F(18), M::CTRL),
435                            ('1', '8', CTRL) => E(K::F(7), M::CTRL),
436                            ('1', '9', CTRL) => E(K::F(8), M::CTRL),
437                            //('1', '9', '6') => E(K::F(19), M::CTRL),
438                            ('2', '0', CTRL) => E(K::F(9), M::CTRL),
439                            //('2', '0', '6') => E(K::F(21), M::CTRL),
440                            ('2', '1', CTRL) => E(K::F(10), M::CTRL),
441                            //('2', '1', '6') => E(K::F(22), M::CTRL),
442                            ('2', '3', CTRL) => E(K::F(11), M::CTRL),
443                            //('2', '3', '6') => E(K::F(23), M::CTRL),
444                            ('2', '4', CTRL) => E(K::F(12), M::CTRL),
445                            //('2', '4', '6') => E(K::F(24), M::CTRL),
446                            _ => {
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()?; // 'R' expected
490                                       //('1', '0', UP) => E(K::, M::), // Alt + Shift + Up
491                    Ok(E(K::UnknownEscSeq, M::NONE))
492                } else if seq2 == '1' {
493                    Ok(match (seq4, seq5) {
494                        (SHIFT, UP) => E(K::Up, M::SHIFT),     // ~ key_sr
495                        (SHIFT, DOWN) => E(K::Down, M::SHIFT), // ~ key_sf
496                        (SHIFT, RIGHT) => E(K::Right, M::SHIFT),
497                        (SHIFT, LEFT) => E(K::Left, M::SHIFT),
498                        (SHIFT, END) => E(K::End, M::SHIFT), // kEND
499                        (SHIFT, HOME) => E(K::Home, M::SHIFT), // kHOM
500                        //('2', 'P') => E(K::F(13), M::NONE),
501                        //('2', 'Q') => E(K::F(14), M::NONE),
502                        //('2', 'S') => E(K::F(16), M::NONE),
503                        (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                        //('6', 'P') => E(K::F(13), M::CTRL),
541                        //('6', 'Q') => E(K::F(14), M::CTRL),
542                        //('6', 'S') => E(K::F(16), M::CTRL),
543                        (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                        // Meta + arrow on (some?) Macs when using iTerm defaults
586                        ('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), // kEND5 or kel
660                (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    /// Handle \EO <seq2> escape sequences
672    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            //'E' => E(K::, M::),// key_b2, kb2
680            END => E(K::End, M::NONE),   // kend
681            HOME => E(K::Home, M::NONE), // khome
682            'M' => E::ENTER,             // kent
683            'P' => E(K::F(1), M::NONE),  // kf1
684            'Q' => E(K::F(2), M::NONE),  // kf2
685            'R' => E(K::F(3), M::NONE),  // kf3
686            'S' => E(K::F(4), M::NONE),  // kf4
687            'a' => E(K::Up, M::CTRL),
688            'b' => E(K::Down, M::CTRL),
689            'c' => E(K::Right, M::CTRL), // rxvt
690            'd' => E(K::Left, M::CTRL),  // rxvt
691            'l' => E(K::F(8), M::NONE),
692            't' => E(K::F(5), M::NONE),  // kf5 or kb1
693            'u' => E(K::F(6), M::NONE),  // kf6 or kb2
694            'v' => E(K::F(7), M::NONE),  // kf7 or kb3
695            'w' => E(K::F(9), M::NONE),  // kf9 or ka1
696            'x' => E(K::F(10), M::NONE), // kf10 or ka2
697            _ => {
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: PollTimeout) -> Result<bool> {
705        let n = self.tty_in.buffer().len();
706        if n > 0 {
707            return Ok(true);
708        }
709        #[cfg(target_os = "macos")]
710        if self.is_dev_tty {
711            // poll doesn't work for /dev/tty on MacOS but select does
712            return Ok(match self.select(Some(timeout), false /* ignored */)? {
713                Event::Timeout(true) => false,
714                _ => true,
715            });
716        }
717        debug!(target: "rustyline", "poll with: {timeout:?}");
718        let mut fds = [poll::PollFd::new(self.as_fd(), PollFlags::POLLIN)];
719        let r = poll::poll(&mut fds, timeout);
720        debug!(target: "rustyline", "poll returns: {r:?}");
721        match r {
722            Ok(n) => Ok(n != 0),
723            Err(Errno::EINTR) => {
724                if let Some(signal) = self.tty_in.get_ref().sig()? {
725                    Err(ReadlineError::Signal(signal))
726                } else {
727                    Ok(false) // Ignore EINTR while polling
728                }
729            }
730            Err(e) => Err(e.into()),
731        }
732    }
733
734    // timeout is used only with /dev/tty on MacOs
735    fn select(&mut self, timeout: Option<PollTimeout>, single_esc_abort: bool) -> Result<Event> {
736        let tty_in = self.as_fd();
737        let sig_pipe = self.tty_in.get_ref().sig_pipe.as_ref().map(|fd| fd.as_fd());
738        let pipe_reader = if timeout.is_some() {
739            None
740        } else {
741            self.pipe_reader
742                .as_ref()
743                .map(|pr| pr.lock().unwrap().0.as_raw_fd())
744                .map(|fd| unsafe { BorrowedFd::borrow_raw(fd) })
745        };
746        loop {
747            let mut readfds = FdSet::new();
748            if let Some(sig_pipe) = sig_pipe {
749                readfds.insert(sig_pipe);
750            }
751            readfds.insert(tty_in);
752            if let Some(pipe_reader) = pipe_reader {
753                readfds.insert(pipe_reader);
754            }
755            let mut timeout = match timeout {
756                Some(pt) => pt
757                    .as_millis()
758                    .map(|ms| nix::sys::time::TimeVal::milliseconds(ms as i64)),
759                None => None,
760            };
761            if let Err(err) = select::select(None, Some(&mut readfds), None, None, timeout.as_mut())
762            {
763                if err == Errno::EINTR {
764                    if let Some(signal) = self.tty_in.get_ref().sig()? {
765                        return Err(ReadlineError::Signal(signal));
766                    } else {
767                        continue;
768                    }
769                } else {
770                    return Err(err.into());
771                }
772            };
773            if sig_pipe.is_some_and(|fd| readfds.contains(fd)) {
774                if let Some(signal) = self.tty_in.get_ref().sig()? {
775                    return Err(ReadlineError::Signal(signal));
776                }
777            } else if readfds.contains(tty_in) {
778                #[cfg(target_os = "macos")]
779                if timeout.is_some() {
780                    return Ok(Event::Timeout(false));
781                }
782                // prefer user input over external print
783                return self.next_key(single_esc_abort).map(Event::KeyPress);
784            } else if timeout.is_some() {
785                #[cfg(target_os = "macos")]
786                return Ok(Event::Timeout(true));
787                #[cfg(not(target_os = "macos"))]
788                unreachable!()
789            } else if let Some(ref pipe_reader) = self.pipe_reader {
790                let mut guard = pipe_reader.lock().unwrap();
791                let mut buf = [0; 1];
792                guard.0.read_exact(&mut buf)?;
793                if let Ok(msg) = guard.1.try_recv() {
794                    return Ok(Event::ExternalPrint(msg));
795                }
796            }
797        }
798    }
799}
800
801impl RawReader for PosixRawReader {
802    type Buffer = PosixBuffer;
803
804    #[cfg(not(feature = "signal-hook"))]
805    fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
806        match self.pipe_reader {
807            Some(_) => self.select(None, single_esc_abort),
808            None => self.next_key(single_esc_abort).map(Event::KeyPress),
809        }
810    }
811
812    #[cfg(feature = "signal-hook")]
813    fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
814        self.select(None, single_esc_abort)
815    }
816
817    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent> {
818        let c = self.next_char()?;
819
820        let mut key = KeyEvent::new(c, M::NONE);
821        if key == E::ESC {
822            if !self.tty_in.buffer().is_empty() {
823                debug!(target: "rustyline", "read buffer {:?}", self.tty_in.buffer());
824            }
825            let timeout_ms = if single_esc_abort && self.timeout_ms.is_none() {
826                PollTimeout::ZERO
827            } else {
828                self.timeout_ms
829            };
830            match self.poll(timeout_ms) {
831                Ok(false) => {
832                    // single escape
833                }
834                Ok(_) => {
835                    // escape sequence
836                    key = self.escape_sequence()?
837                }
838                // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
839                Err(e) => return Err(e),
840            }
841        }
842        debug!(target: "rustyline", "c: {c:?} => key: {key:?}");
843        Ok(key)
844    }
845
846    fn next_char(&mut self) -> Result<char> {
847        let mut buf = [0; 1];
848        let mut receiver = Utf8 {
849            c: None,
850            valid: true,
851        };
852        loop {
853            let n = self.tty_in.read(&mut buf)?;
854            if n == 0 {
855                return Err(ReadlineError::Eof);
856            }
857            let b = buf[0];
858            self.parser.advance(&mut receiver, b);
859            if !receiver.valid {
860                return Err(ReadlineError::from(ErrorKind::InvalidData));
861            } else if let Some(c) = receiver.c.take() {
862                return Ok(c);
863            }
864        }
865    }
866
867    fn read_pasted_text(&mut self) -> Result<String> {
868        let mut buffer = String::new();
869        loop {
870            match self.next_char()? {
871                '\x1b' => {
872                    let key = self.escape_sequence()?;
873                    if key == E(K::BracketedPasteEnd, M::NONE) {
874                        break;
875                    } else {
876                        continue; // TODO validate
877                    }
878                }
879                c => buffer.push(c),
880            };
881        }
882        let buffer = buffer.replace("\r\n", "\n");
883        let buffer = buffer.replace('\r', "\n");
884        Ok(buffer)
885    }
886
887    fn find_binding(&self, key: &KeyEvent) -> Option<Cmd> {
888        let cmd = self.key_map.get(key).cloned();
889        if let Some(ref cmd) = cmd {
890            debug!(target: "rustyline", "terminal key binding: {key:?} => {cmd:?}");
891        }
892        cmd
893    }
894
895    #[cfg(any(not(feature = "buffer-redux"), test))]
896    fn unbuffer(self) -> Option<PosixBuffer> {
897        None
898    }
899
900    #[cfg(all(feature = "buffer-redux", not(test)))]
901    fn unbuffer(self) -> Option<PosixBuffer> {
902        let (_, buffer) = self.tty_in.into_inner_with_buffer();
903        Some(buffer)
904    }
905}
906
907impl Receiver for Utf8 {
908    /// Called whenever a code point is parsed successfully
909    fn codepoint(&mut self, c: char) {
910        self.c = Some(c);
911        self.valid = true;
912    }
913
914    /// Called when an invalid sequence is detected
915    fn invalid_sequence(&mut self) {
916        self.c = None;
917        self.valid = false;
918    }
919}
920
921/// Console output writer
922pub struct PosixRenderer {
923    out: AltFd,
924    cols: Unit, // Number of columns in terminal
925    buffer: String,
926    tab_stop: Unit,
927    colors_enabled: bool,
928    enable_synchronized_output: bool,
929    grapheme_cluster_mode: GraphemeClusterMode,
930    bell_style: BellStyle,
931    /// 0 when BSU is first used or after last ESU
932    synchronized_update: usize,
933}
934
935impl PosixRenderer {
936    fn new(
937        out: AltFd,
938        tab_stop: Unit,
939        colors_enabled: bool,
940        enable_synchronized_output: bool,
941        grapheme_cluster_mode: GraphemeClusterMode,
942        bell_style: BellStyle,
943    ) -> Self {
944        let (cols, _) = get_win_size(out);
945        Self {
946            out,
947            cols,
948            buffer: String::with_capacity(1024),
949            tab_stop,
950            colors_enabled,
951            enable_synchronized_output,
952            grapheme_cluster_mode,
953            bell_style,
954            synchronized_update: 0,
955        }
956    }
957
958    fn clear_old_rows(&mut self, layout: &Layout) {
959        use std::fmt::Write;
960        let current_row = layout.cursor.row;
961        let old_rows = layout.end.row;
962        // old_rows < cursor_row if the prompt spans multiple lines and if
963        // this is the default State.
964        let cursor_row_movement = old_rows.saturating_sub(current_row);
965        // move the cursor down as required
966        if cursor_row_movement > 0 {
967            write!(self.buffer, "\x1b[{cursor_row_movement}B").unwrap();
968        }
969        // clear old rows
970        for _ in 0..old_rows {
971            self.buffer.push_str("\r\x1b[K\x1b[A");
972        }
973        // clear the line
974        self.buffer.push_str("\r\x1b[K");
975    }
976}
977
978impl Renderer for PosixRenderer {
979    type Reader = PosixRawReader;
980
981    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
982        use std::fmt::Write;
983        self.buffer.clear();
984        let row_ordering = new.row.cmp(&old.row);
985        if row_ordering == cmp::Ordering::Greater {
986            // move down
987            let row_shift = new.row - old.row;
988            if row_shift == 1 {
989                self.buffer.push_str("\x1b[B");
990            } else {
991                write!(self.buffer, "\x1b[{row_shift}B")?;
992            }
993        } else if row_ordering == cmp::Ordering::Less {
994            // move up
995            let row_shift = old.row - new.row;
996            if row_shift == 1 {
997                self.buffer.push_str("\x1b[A");
998            } else {
999                write!(self.buffer, "\x1b[{row_shift}A")?;
1000            }
1001        }
1002        let col_ordering = new.col.cmp(&old.col);
1003        if col_ordering == cmp::Ordering::Greater {
1004            // move right
1005            let col_shift = new.col - old.col;
1006            if col_shift == 1 {
1007                self.buffer.push_str("\x1b[C");
1008            } else {
1009                write!(self.buffer, "\x1b[{col_shift}C")?;
1010            }
1011        } else if col_ordering == cmp::Ordering::Less {
1012            // move left
1013            let col_shift = old.col - new.col;
1014            if col_shift == 1 {
1015                self.buffer.push_str("\x1b[D");
1016            } else {
1017                write!(self.buffer, "\x1b[{col_shift}D")?;
1018            }
1019        }
1020        write_all(self.out, self.buffer.as_str())?;
1021        Ok(())
1022    }
1023
1024    fn refresh_line(
1025        &mut self,
1026        prompt: &str,
1027        line: &LineBuffer,
1028        hint: Option<&str>,
1029        old_layout: &Layout,
1030        new_layout: &Layout,
1031        highlighter: Option<&dyn Highlighter>,
1032    ) -> Result<()> {
1033        use std::fmt::Write;
1034        self.begin_synchronized_update()?;
1035        self.buffer.clear();
1036
1037        let default_prompt = new_layout.default_prompt;
1038        let cursor = new_layout.cursor;
1039        let end_pos = new_layout.end;
1040
1041        self.clear_old_rows(old_layout);
1042
1043        if let Some(highlighter) = highlighter {
1044            // display the prompt
1045            self.buffer
1046                .push_str(&highlighter.highlight_prompt(prompt, default_prompt));
1047            // display the input line
1048            self.buffer
1049                .push_str(&highlighter.highlight(line, line.pos()));
1050        } else {
1051            // display the prompt
1052            self.buffer.push_str(prompt);
1053            // display the input line
1054            self.buffer.push_str(line);
1055        }
1056        // display hint
1057        if let Some(hint) = hint {
1058            if let Some(highlighter) = highlighter {
1059                self.buffer.push_str(&highlighter.highlight_hint(hint));
1060            } else {
1061                self.buffer.push_str(hint);
1062            }
1063        }
1064        // we have to generate our own newline on line wrap
1065        if end_pos.col == 0
1066            && end_pos.row > 0
1067            && !hint.map_or_else(|| line.ends_with('\n'), |h| h.ends_with('\n'))
1068        {
1069            self.buffer.push('\n');
1070        }
1071        // position the cursor
1072        let new_cursor_row_movement = end_pos.row - cursor.row;
1073        // move the cursor up as required
1074        if new_cursor_row_movement > 0 {
1075            write!(self.buffer, "\x1b[{new_cursor_row_movement}A")?;
1076        }
1077        // position the cursor within the line
1078        if cursor.col > 0 {
1079            write!(self.buffer, "\r\x1b[{}C", cursor.col)?;
1080        } else {
1081            self.buffer.push('\r');
1082        }
1083
1084        write_all(self.out, self.buffer.as_str())?;
1085        self.end_synchronized_update()?;
1086        Ok(())
1087    }
1088
1089    fn write_and_flush(&mut self, buf: &str) -> Result<()> {
1090        write_all(self.out, buf)?;
1091        Ok(())
1092    }
1093
1094    /// Control characters are treated as having zero width.
1095    /// Characters with 2 column width are correctly handled (not split).
1096    fn calculate_position(&self, s: &str, orig: Position) -> Position {
1097        let mut pos = orig;
1098        let mut esc_seq = 0;
1099        for c in s.graphemes(true) {
1100            if c == "\n" {
1101                pos.row += 1;
1102                pos.col = 0;
1103                continue;
1104            }
1105            let cw = if c == "\t" {
1106                self.tab_stop - (pos.col % self.tab_stop)
1107            } else {
1108                width(self.grapheme_cluster_mode, c, &mut esc_seq)
1109            };
1110            pos.col += cw;
1111            if pos.col > self.cols {
1112                pos.row += 1;
1113                pos.col = cw;
1114            }
1115        }
1116        if pos.col == self.cols {
1117            pos.col = 0;
1118            pos.row += 1;
1119        }
1120        pos
1121    }
1122
1123    fn beep(&mut self) -> Result<()> {
1124        match self.bell_style {
1125            BellStyle::Audible => self.write_and_flush("\x07"),
1126            _ => Ok(()),
1127        }
1128    }
1129
1130    /// Clear the screen. Used to handle ctrl+l
1131    fn clear_screen(&mut self) -> Result<()> {
1132        self.write_and_flush("\x1b[H\x1b[J")
1133    }
1134
1135    fn clear_rows(&mut self, layout: &Layout) -> Result<()> {
1136        self.buffer.clear();
1137        self.clear_old_rows(layout);
1138        write_all(self.out, self.buffer.as_str())?;
1139        Ok(())
1140    }
1141
1142    /// Try to update the number of columns in the current terminal,
1143    fn update_size(&mut self) {
1144        let (cols, _) = get_win_size(self.out);
1145        self.cols = cols;
1146    }
1147
1148    fn get_columns(&self) -> Unit {
1149        self.cols
1150    }
1151
1152    /// Try to get the number of rows in the current terminal,
1153    /// or assume 24 if it fails.
1154    fn get_rows(&self) -> Unit {
1155        let (_, rows) = get_win_size(self.out);
1156        rows
1157    }
1158
1159    fn colors_enabled(&self) -> bool {
1160        self.colors_enabled
1161    }
1162
1163    fn grapheme_cluster_mode(&self) -> GraphemeClusterMode {
1164        self.grapheme_cluster_mode
1165    }
1166
1167    fn move_cursor_at_leftmost(&mut self, rdr: &mut PosixRawReader) -> Result<()> {
1168        if rdr.poll(PollTimeout::ZERO)? {
1169            debug!(target: "rustyline", "cannot request cursor location");
1170            return Ok(());
1171        }
1172        /* Report cursor location */
1173        self.write_and_flush("\x1b[6n")?;
1174        /* Read the response: ESC [ rows ; cols R */
1175        if !rdr.poll(PollTimeout::from(100u8))?
1176            || rdr.next_char()? != '\x1b'
1177            || rdr.next_char()? != '['
1178            || read_digits_until(rdr, ';')?.is_none()
1179        {
1180            warn!(target: "rustyline", "cannot read initial cursor location");
1181            return Ok(());
1182        }
1183        let col = read_digits_until(rdr, 'R')?;
1184        debug!(target: "rustyline", "initial cursor location: {col:?}");
1185        if col != Some(1) {
1186            self.write_and_flush("\n")?;
1187        }
1188        Ok(())
1189    }
1190
1191    fn begin_synchronized_update(&mut self) -> Result<()> {
1192        if self.enable_synchronized_output {
1193            if self.synchronized_update == 0 {
1194                self.write_and_flush(BEGIN_SYNCHRONIZED_UPDATE)?;
1195            }
1196            self.synchronized_update = self.synchronized_update.saturating_add(1);
1197        }
1198        Ok(())
1199    }
1200
1201    fn end_synchronized_update(&mut self) -> Result<()> {
1202        if self.enable_synchronized_output {
1203            self.synchronized_update = self.synchronized_update.saturating_sub(1);
1204            if self.synchronized_update == 0 {
1205                self.write_and_flush(END_SYNCHRONIZED_UPDATE)?;
1206            }
1207        }
1208        Ok(())
1209    }
1210}
1211
1212fn read_digits_until(rdr: &mut PosixRawReader, sep: char) -> Result<Option<u32>> {
1213    let mut num: u32 = 0;
1214    loop {
1215        match rdr.next_char()? {
1216            digit @ '0'..='9' => {
1217                num = num
1218                    .saturating_mul(10)
1219                    .saturating_add(digit.to_digit(10).unwrap());
1220                continue;
1221            }
1222            c if c == sep => break,
1223            _ => return Ok(None),
1224        }
1225    }
1226    Ok(Some(num))
1227}
1228
1229fn write_all(fd: AltFd, buf: &str) -> nix::Result<()> {
1230    let mut bytes = buf.as_bytes();
1231    while !bytes.is_empty() {
1232        match write(fd, bytes) {
1233            Ok(0) => return Err(Errno::EIO),
1234            Ok(n) => bytes = &bytes[n..],
1235            Err(Errno::EINTR) => {}
1236            Err(r) => return Err(r),
1237        }
1238    }
1239    Ok(())
1240}
1241
1242pub struct PosixCursorGuard(AltFd);
1243
1244impl Drop for PosixCursorGuard {
1245    fn drop(&mut self) {
1246        let _ = set_cursor_visibility(self.0, true);
1247    }
1248}
1249
1250fn set_cursor_visibility(fd: AltFd, visible: bool) -> Result<Option<PosixCursorGuard>> {
1251    write_all(fd, if visible { "\x1b[?25h" } else { "\x1b[?25l" })?;
1252    Ok(if visible {
1253        None
1254    } else {
1255        Some(PosixCursorGuard(fd))
1256    })
1257}
1258
1259#[cfg(not(feature = "signal-hook"))]
1260static mut SIG_PIPE: AltFd = AltFd(-1);
1261#[cfg(not(feature = "signal-hook"))]
1262extern "C" fn sig_handler(sig: libc::c_int) {
1263    let b = error::Signal::to_byte(sig);
1264    let _ = unsafe { write(SIG_PIPE, &[b]) };
1265}
1266
1267#[derive(Clone, Debug)]
1268struct Sig {
1269    pipe: AltFd,
1270    #[cfg(not(feature = "signal-hook"))]
1271    original_sigint: nix::sys::signal::SigAction,
1272    #[cfg(not(feature = "signal-hook"))]
1273    original_sigwinch: nix::sys::signal::SigAction,
1274    #[cfg(feature = "signal-hook")]
1275    id: signal_hook::SigId,
1276}
1277impl Sig {
1278    #[cfg(not(feature = "signal-hook"))]
1279    fn install_sigwinch_handler() -> Result<Self> {
1280        use nix::sys::signal;
1281        let (pipe, pipe_write) = UnixStream::pair()?;
1282        pipe.set_nonblocking(true)?;
1283        unsafe { SIG_PIPE = AltFd(pipe_write.into_raw_fd()) };
1284        let sa = signal::SigAction::new(
1285            signal::SigHandler::Handler(sig_handler),
1286            signal::SaFlags::empty(),
1287            signal::SigSet::empty(),
1288        );
1289        let original_sigint = unsafe { signal::sigaction(signal::SIGINT, &sa)? };
1290        let original_sigwinch = unsafe { signal::sigaction(signal::SIGWINCH, &sa)? };
1291        Ok(Self {
1292            pipe: AltFd(pipe.into_raw_fd()),
1293            original_sigint,
1294            original_sigwinch,
1295        })
1296    }
1297
1298    #[cfg(feature = "signal-hook")]
1299    fn install_sigwinch_handler() -> Result<Self> {
1300        let (pipe, pipe_write) = UnixStream::pair()?;
1301        pipe.set_nonblocking(true)?;
1302        let id = signal_hook::low_level::pipe::register(libc::SIGWINCH, pipe_write)?;
1303        Ok(Self {
1304            pipe: AltFd(pipe.into_raw_fd()),
1305            id,
1306        })
1307    }
1308
1309    #[cfg(not(feature = "signal-hook"))]
1310    fn uninstall_sigwinch_handler(self) -> Result<()> {
1311        use nix::sys::signal;
1312        let _ = unsafe { signal::sigaction(signal::SIGINT, &self.original_sigint)? };
1313        let _ = unsafe { signal::sigaction(signal::SIGWINCH, &self.original_sigwinch)? };
1314        close(self.pipe)?;
1315        unsafe { close(SIG_PIPE)? };
1316        unsafe { SIG_PIPE = AltFd(-1) };
1317        Ok(())
1318    }
1319
1320    #[cfg(feature = "signal-hook")]
1321    fn uninstall_sigwinch_handler(self) -> Result<()> {
1322        signal_hook::low_level::unregister(self.id);
1323        close(self.pipe)?;
1324        Ok(())
1325    }
1326}
1327
1328#[cfg(not(test))]
1329pub type Terminal = PosixTerminal;
1330
1331#[derive(Clone, Debug)]
1332pub struct PosixTerminal {
1333    unsupported: bool,
1334    tty_in: AltFd,
1335    is_in_a_tty: bool,
1336    tty_out: AltFd,
1337    is_out_a_tty: bool,
1338    close_on_drop: bool,
1339    pub(crate) color_mode: ColorMode,
1340    grapheme_cluster_mode: GraphemeClusterMode,
1341    tab_stop: u8,
1342    bell_style: BellStyle,
1343    enable_bracketed_paste: bool,
1344    enable_synchronized_output: bool,
1345    raw_mode: Arc<AtomicBool>,
1346    // external print reader
1347    pipe_reader: Option<PipeReader>,
1348    // external print writer
1349    pipe_writer: Option<PipeWriter>,
1350    sig: Option<Sig>,
1351    enable_signals: bool,
1352}
1353
1354impl PosixTerminal {
1355    fn colors_enabled(&self) -> bool {
1356        match self.color_mode {
1357            ColorMode::Enabled => self.is_out_a_tty,
1358            ColorMode::Forced => true,
1359            ColorMode::Disabled => false,
1360        }
1361    }
1362}
1363
1364impl Term for PosixTerminal {
1365    type Buffer = PosixBuffer;
1366    type CursorGuard = PosixCursorGuard;
1367    type ExternalPrinter = ExternalPrinter;
1368    type KeyMap = PosixKeyMap;
1369    type Mode = PosixMode;
1370    type Reader = PosixRawReader;
1371    type Writer = PosixRenderer;
1372
1373    fn new(config: Config) -> Result<Self> {
1374        let (tty_in, is_in_a_tty, tty_out, is_out_a_tty, close_on_drop) =
1375            if config.behavior() == Behavior::PreferTerm {
1376                let tty = OpenOptions::new().read(true).write(true).open("/dev/tty");
1377                if let Ok(tty) = tty {
1378                    let fd = AltFd(tty.into_raw_fd());
1379                    let is_a_tty = is_a_tty(fd); // TODO: useless ?
1380                    (fd, is_a_tty, fd, is_a_tty, true)
1381                } else {
1382                    let (i, o) = (AltFd(libc::STDIN_FILENO), AltFd(libc::STDOUT_FILENO));
1383                    (i, is_a_tty(i), o, is_a_tty(o), false)
1384                }
1385            } else {
1386                let (i, o) = (AltFd(libc::STDIN_FILENO), AltFd(libc::STDOUT_FILENO));
1387                (i, is_a_tty(i), o, is_a_tty(o), false)
1388            };
1389        let unsupported = super::is_unsupported_term();
1390        let sig = if !unsupported && is_in_a_tty && is_out_a_tty {
1391            Some(Sig::install_sigwinch_handler()?)
1392        } else {
1393            None
1394        };
1395        Ok(Self {
1396            unsupported,
1397            tty_in,
1398            is_in_a_tty,
1399            tty_out,
1400            is_out_a_tty,
1401            close_on_drop,
1402            color_mode: config.color_mode(),
1403            grapheme_cluster_mode: config.grapheme_cluster_mode(),
1404            tab_stop: config.tab_stop(),
1405            bell_style: config.bell_style(),
1406            enable_bracketed_paste: config.enable_bracketed_paste(),
1407            enable_synchronized_output: config.enable_synchronized_output(),
1408            raw_mode: Arc::new(AtomicBool::new(false)),
1409            pipe_reader: None,
1410            pipe_writer: None,
1411            sig,
1412            enable_signals: config.enable_signals(),
1413        })
1414    }
1415
1416    // Init checks:
1417
1418    /// Check if current terminal can provide a rich line-editing user
1419    /// interface.
1420    fn is_unsupported(&self) -> bool {
1421        self.unsupported
1422    }
1423
1424    fn is_input_tty(&self) -> bool {
1425        self.is_in_a_tty
1426    }
1427
1428    fn is_output_tty(&self) -> bool {
1429        self.is_out_a_tty
1430    }
1431
1432    // Interactive loop:
1433
1434    fn enable_raw_mode(&mut self) -> Result<(Self::Mode, PosixKeyMap)> {
1435        use nix::errno::Errno::ENOTTY;
1436        if !self.is_in_a_tty {
1437            return Err(ENOTTY.into());
1438        }
1439        let (original_mode, key_map) = termios_::enable_raw_mode(self.tty_in, self.enable_signals)?;
1440
1441        self.raw_mode.store(true, Ordering::SeqCst);
1442        // enable bracketed paste
1443        let out = if !self.enable_bracketed_paste {
1444            None
1445        } else if let Err(e) = write_all(self.tty_out, BRACKETED_PASTE_ON) {
1446            debug!(target: "rustyline", "Cannot enable bracketed paste: {e}");
1447            None
1448        } else {
1449            Some(self.tty_out)
1450        };
1451
1452        // when all ExternalPrinter are dropped there is no need to use `pipe_reader`
1453        if Arc::strong_count(&self.raw_mode) == 1 {
1454            self.pipe_writer = None;
1455            self.pipe_reader = None;
1456        }
1457
1458        Ok((
1459            PosixMode {
1460                termios: original_mode,
1461                tty_in: self.tty_in,
1462                tty_out: out,
1463                raw_mode: self.raw_mode.clone(),
1464            },
1465            key_map,
1466        ))
1467    }
1468
1469    /// Create a RAW reader
1470    fn create_reader(
1471        &self,
1472        buffer: Option<PosixBuffer>,
1473        config: &Config,
1474        key_map: PosixKeyMap,
1475    ) -> PosixRawReader {
1476        PosixRawReader::new(
1477            self.tty_in,
1478            self.sig.as_ref().map(|s| s.pipe),
1479            buffer,
1480            config,
1481            key_map,
1482            self.pipe_reader.clone(),
1483            #[cfg(target_os = "macos")]
1484            self.close_on_drop,
1485        )
1486    }
1487
1488    fn create_writer(&self) -> PosixRenderer {
1489        PosixRenderer::new(
1490            self.tty_out,
1491            Unit::from(self.tab_stop),
1492            self.colors_enabled(),
1493            self.enable_synchronized_output,
1494            self.grapheme_cluster_mode,
1495            self.bell_style,
1496        )
1497    }
1498
1499    fn writeln(&self) -> Result<()> {
1500        write_all(self.tty_out, "\n")?;
1501        Ok(())
1502    }
1503
1504    fn create_external_printer(&mut self) -> Result<ExternalPrinter> {
1505        use nix::unistd::pipe;
1506        if let Some(ref writer) = self.pipe_writer {
1507            return Ok(ExternalPrinter {
1508                writer: writer.clone(),
1509                raw_mode: self.raw_mode.clone(),
1510                tty_out: self.tty_out,
1511            });
1512        }
1513        if self.unsupported || !self.is_input_tty() || !self.is_output_tty() {
1514            return Err(nix::Error::ENOTTY.into());
1515        }
1516        let (sender, receiver) = mpsc::sync_channel(1); // TODO validate: bound
1517        let (r, w) = pipe()?;
1518        let reader = Arc::new(Mutex::new((r.into(), receiver)));
1519        let writer = (Arc::new(Mutex::new(w.into())), sender);
1520        self.pipe_reader.replace(reader);
1521        self.pipe_writer.replace(writer.clone());
1522        Ok(ExternalPrinter {
1523            writer,
1524            raw_mode: self.raw_mode.clone(),
1525            tty_out: self.tty_out,
1526        })
1527    }
1528
1529    fn set_cursor_visibility(&mut self, visible: bool) -> Result<Option<PosixCursorGuard>> {
1530        if self.is_out_a_tty {
1531            set_cursor_visibility(self.tty_out, visible)
1532        } else {
1533            Ok(None)
1534        }
1535    }
1536}
1537
1538#[expect(unused_must_use)]
1539impl Drop for PosixTerminal {
1540    fn drop(&mut self) {
1541        if self.close_on_drop {
1542            close(self.tty_in);
1543            debug_assert_eq!(self.tty_in, self.tty_out);
1544        }
1545        if let Some(sig) = self.sig.take() {
1546            sig.uninstall_sigwinch_handler();
1547        }
1548    }
1549}
1550
1551#[derive(Debug)]
1552pub struct ExternalPrinter {
1553    writer: PipeWriter,
1554    raw_mode: Arc<AtomicBool>,
1555    tty_out: AltFd,
1556}
1557
1558impl super::ExternalPrinter for ExternalPrinter {
1559    fn print(&mut self, msg: String) -> Result<()> {
1560        // write directly to stdout/stderr while not in raw mode
1561        if !self.raw_mode.load(Ordering::SeqCst) {
1562            write_all(self.tty_out, msg.as_str())?;
1563        } else if let Ok(mut writer) = self.writer.0.lock() {
1564            self.writer
1565                .1
1566                .send(msg)
1567                .map_err(|_| io::Error::from(ErrorKind::Other))?; // FIXME
1568            writer.write_all(b"m")?;
1569            writer.flush()?;
1570        } else {
1571            return Err(io::Error::from(ErrorKind::Other).into()); // FIXME
1572        }
1573        Ok(())
1574    }
1575}
1576
1577#[cfg(not(test))]
1578pub fn suspend() -> Result<()> {
1579    use nix::sys::signal;
1580    use nix::unistd::Pid;
1581    // suspend the whole process group
1582    signal::kill(Pid::from_raw(0), signal::SIGTSTP)?;
1583    Ok(())
1584}
1585
1586#[cfg(not(feature = "termios"))]
1587mod termios_ {
1588    use super::{AltFd, PosixKeyMap};
1589    use crate::keys::{KeyEvent, Modifiers as M};
1590    use crate::{Cmd, Result};
1591    use nix::sys::termios::{self, SetArg, SpecialCharacterIndices as SCI, Termios};
1592    use std::collections::HashMap;
1593    pub fn disable_raw_mode(tty_in: AltFd, termios: &Termios) -> Result<()> {
1594        Ok(termios::tcsetattr(tty_in, SetArg::TCSADRAIN, termios)?)
1595    }
1596    pub fn enable_raw_mode(tty_in: AltFd, enable_signals: bool) -> Result<(Termios, PosixKeyMap)> {
1597        use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags};
1598
1599        let original_mode = termios::tcgetattr(tty_in)?;
1600        let mut raw = original_mode.clone();
1601        // disable BREAK interrupt, CR to NL conversion on input,
1602        // input parity check, strip high bit (bit 8), output flow control
1603        raw.input_flags &= !(InputFlags::BRKINT
1604            | InputFlags::ICRNL
1605            | InputFlags::INPCK
1606            | InputFlags::ISTRIP
1607            | InputFlags::IXON);
1608        // we don't want raw output, it turns newlines into straight line feeds
1609        // disable all output processing
1610        // raw.c_oflag = raw.c_oflag & !(OutputFlags::OPOST);
1611
1612        // character-size mark (8 bits)
1613        raw.control_flags |= ControlFlags::CS8;
1614        // disable echoing, canonical mode, extended input processing and signals
1615        raw.local_flags &=
1616            !(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
1617
1618        if enable_signals {
1619            raw.local_flags |= LocalFlags::ISIG;
1620        }
1621
1622        raw.control_chars[SCI::VMIN as usize] = 1; // One character-at-a-time input
1623        raw.control_chars[SCI::VTIME as usize] = 0; // with blocking read
1624
1625        let mut key_map: HashMap<KeyEvent, Cmd> = HashMap::with_capacity(4);
1626        map_key(&mut key_map, &raw, SCI::VEOF, "VEOF", Cmd::EndOfFile);
1627        map_key(&mut key_map, &raw, SCI::VINTR, "VINTR", Cmd::Interrupt);
1628        map_key(&mut key_map, &raw, SCI::VQUIT, "VQUIT", Cmd::Interrupt);
1629        map_key(&mut key_map, &raw, SCI::VSUSP, "VSUSP", Cmd::Suspend);
1630
1631        termios::tcsetattr(tty_in, SetArg::TCSADRAIN, &raw)?;
1632        Ok((original_mode, key_map))
1633    }
1634    fn map_key(
1635        key_map: &mut HashMap<KeyEvent, Cmd>,
1636        raw: &Termios,
1637        index: SCI,
1638        name: &str,
1639        cmd: Cmd,
1640    ) {
1641        let cc = char::from(raw.control_chars[index as usize]);
1642        let key = KeyEvent::new(cc, M::NONE);
1643        log::debug!(target: "rustyline", "{name}: {key:?}");
1644        key_map.insert(key, cmd);
1645    }
1646}
1647#[cfg(feature = "termios")]
1648mod termios_ {
1649    use super::{AltFd, PosixKeyMap};
1650    use crate::keys::{KeyEvent, Modifiers as M};
1651    use crate::{Cmd, Result};
1652    use std::collections::HashMap;
1653    use termios::{self, Termios};
1654    pub fn disable_raw_mode(tty_in: AltFd, termios: &Termios) -> Result<()> {
1655        Ok(termios::tcsetattr(tty_in.0, termios::TCSADRAIN, termios)?)
1656    }
1657    pub fn enable_raw_mode(tty_in: AltFd, enable_signals: bool) -> Result<(Termios, PosixKeyMap)> {
1658        let original_mode = Termios::from_fd(tty_in.0)?;
1659        let mut raw = original_mode;
1660        // disable BREAK interrupt, CR to NL conversion on input,
1661        // input parity check, strip high bit (bit 8), output flow control
1662        raw.c_iflag &=
1663            !(termios::BRKINT | termios::ICRNL | termios::INPCK | termios::ISTRIP | termios::IXON);
1664        // we don't want raw output, it turns newlines into straight line feeds
1665        // disable all output processing
1666        // raw.c_oflag = raw.c_oflag & !(OutputFlags::OPOST);
1667
1668        // character-size mark (8 bits)
1669        raw.c_cflag |= termios::CS8;
1670        // disable echoing, canonical mode, extended input processing and signals
1671        raw.c_lflag &= !(termios::ECHO | termios::ICANON | termios::IEXTEN | termios::ISIG);
1672
1673        if enable_signals {
1674            raw.c_lflag |= termios::ISIG;
1675        }
1676
1677        raw.c_cc[termios::VMIN] = 1; // One character-at-a-time input
1678        raw.c_cc[termios::VTIME] = 0; // with blocking read
1679
1680        let mut key_map: HashMap<KeyEvent, Cmd> = HashMap::with_capacity(4);
1681        map_key(&mut key_map, &raw, termios::VEOF, "VEOF", Cmd::EndOfFile);
1682        map_key(&mut key_map, &raw, termios::VINTR, "VINTR", Cmd::Interrupt);
1683        map_key(&mut key_map, &raw, termios::VQUIT, "VQUIT", Cmd::Interrupt);
1684        map_key(&mut key_map, &raw, termios::VSUSP, "VSUSP", Cmd::Suspend);
1685
1686        termios::tcsetattr(tty_in.0, termios::TCSADRAIN, &raw)?;
1687        Ok((original_mode, key_map))
1688    }
1689    fn map_key(
1690        key_map: &mut HashMap<KeyEvent, Cmd>,
1691        raw: &Termios,
1692        index: usize,
1693        name: &str,
1694        cmd: Cmd,
1695    ) {
1696        let cc = char::from(raw.c_cc[index]);
1697        let key = KeyEvent::new(cc, M::NONE);
1698        log::debug!(target: "rustyline", "{name}: {key:?}");
1699        key_map.insert(key, cmd);
1700    }
1701}
1702
1703#[cfg(test)]
1704mod test {
1705    use super::{AltFd, Position, PosixRenderer, PosixTerminal, Renderer};
1706    use crate::config::BellStyle;
1707    use crate::layout::GraphemeClusterMode;
1708    use crate::line_buffer::{LineBuffer, NoListener};
1709
1710    #[test]
1711    #[ignore]
1712    fn prompt_with_ansi_escape_codes() {
1713        let out = PosixRenderer::new(
1714            AltFd(libc::STDOUT_FILENO),
1715            4,
1716            true,
1717            true,
1718            GraphemeClusterMode::default(),
1719            BellStyle::default(),
1720        );
1721        let pos = out.calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default());
1722        assert_eq!(3, pos.col);
1723        assert_eq!(0, pos.row);
1724    }
1725
1726    #[test]
1727    fn test_send() {
1728        fn assert_send<T: Send>() {}
1729        assert_send::<PosixTerminal>();
1730    }
1731
1732    #[test]
1733    fn test_sync() {
1734        fn assert_sync<T: Sync>() {}
1735        assert_sync::<PosixTerminal>();
1736    }
1737
1738    #[test]
1739    fn test_line_wrap() {
1740        let mut out = PosixRenderer::new(
1741            AltFd(libc::STDOUT_FILENO),
1742            4,
1743            true,
1744            true,
1745            GraphemeClusterMode::default(),
1746            BellStyle::default(),
1747        );
1748        let prompt = "> ";
1749        let default_prompt = true;
1750        let prompt_size = out.calculate_position(prompt, Position::default());
1751
1752        let mut line = LineBuffer::init("", 0);
1753        let old_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
1754        assert_eq!(Position { col: 2, row: 0 }, old_layout.cursor);
1755        assert_eq!(old_layout.cursor, old_layout.end);
1756
1757        assert_eq!(
1758            Some(true),
1759            line.insert('a', out.cols - prompt_size.col + 1, &mut NoListener)
1760        );
1761        let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
1762        assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor);
1763        assert_eq!(new_layout.cursor, new_layout.end);
1764        out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None)
1765            .unwrap();
1766        #[rustfmt::skip]
1767        assert_eq!(
1768            "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C",
1769            out.buffer
1770        );
1771    }
1772}