1use 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 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
87fn 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 fn disable_raw_mode(&self) -> Result<()> {
117 termios_::disable_raw_mode(self.tty_in, &self.termios)?;
118 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
127struct 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 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
181type PipeReader = Arc<Mutex<(File, mpsc::Receiver<String>)>>;
183type PipeWriter = (Arc<Mutex<File>>, SyncSender<String>);
185
186pub struct PosixRawReader {
188 tty_in: BufReader<TtyIn>,
189 timeout_ms: PollTimeout,
190 parser: Parser,
191 key_map: PosixKeyMap,
192 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'; 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';
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 fn escape_sequence(&mut self) -> Result<KeyEvent> {
268 self._do_escape_sequence(true)
269 }
270
271 fn _do_escape_sequence(&mut self, allow_recurse: bool) -> Result<KeyEvent> {
273 let seq1 = self.next_char()?;
275 if seq1 == '[' {
276 self.escape_csi()
278 } else if seq1 == 'O' {
279 self.escape_o()
282 } else if seq1 == '\x1b' {
283 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 Ok(false) | Err(_) => Ok(E::ESC),
310 Ok(true) => {
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: 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 return Ok(match self.select(Some(timeout), false )? {
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) }
729 }
730 Err(e) => Err(e.into()),
731 }
732 }
733
734 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 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 }
834 Ok(_) => {
835 key = self.escape_sequence()?
837 }
838 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; }
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 fn codepoint(&mut self, c: char) {
910 self.c = Some(c);
911 self.valid = true;
912 }
913
914 fn invalid_sequence(&mut self) {
916 self.c = None;
917 self.valid = false;
918 }
919}
920
921pub struct PosixRenderer {
923 out: AltFd,
924 cols: Unit, 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 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 let cursor_row_movement = old_rows.saturating_sub(current_row);
965 if cursor_row_movement > 0 {
967 write!(self.buffer, "\x1b[{cursor_row_movement}B").unwrap();
968 }
969 for _ in 0..old_rows {
971 self.buffer.push_str("\r\x1b[K\x1b[A");
972 }
973 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 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 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 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 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 self.buffer
1046 .push_str(&highlighter.highlight_prompt(prompt, default_prompt));
1047 self.buffer
1049 .push_str(&highlighter.highlight(line, line.pos()));
1050 } else {
1051 self.buffer.push_str(prompt);
1053 self.buffer.push_str(line);
1055 }
1056 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 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 let new_cursor_row_movement = end_pos.row - cursor.row;
1073 if new_cursor_row_movement > 0 {
1075 write!(self.buffer, "\x1b[{new_cursor_row_movement}A")?;
1076 }
1077 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 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 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 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 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 self.write_and_flush("\x1b[6n")?;
1174 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 pipe_reader: Option<PipeReader>,
1348 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); (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 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 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 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 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 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); 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 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))?; writer.write_all(b"m")?;
1569 writer.flush()?;
1570 } else {
1571 return Err(io::Error::from(ErrorKind::Other).into()); }
1573 Ok(())
1574 }
1575}
1576
1577#[cfg(not(test))]
1578pub fn suspend() -> Result<()> {
1579 use nix::sys::signal;
1580 use nix::unistd::Pid;
1581 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 raw.input_flags &= !(InputFlags::BRKINT
1604 | InputFlags::ICRNL
1605 | InputFlags::INPCK
1606 | InputFlags::ISTRIP
1607 | InputFlags::IXON);
1608 raw.control_flags |= ControlFlags::CS8;
1614 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; raw.control_chars[SCI::VTIME as usize] = 0; 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 raw.c_iflag &=
1663 !(termios::BRKINT | termios::ICRNL | termios::INPCK | termios::ISTRIP | termios::IXON);
1664 raw.c_cflag |= termios::CS8;
1670 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; raw.c_cc[termios::VTIME] = 0; 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}