diff --git a/Cargo.toml b/Cargo.toml index 2f03621af..58b99eb2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ utf8parse = "0.2" skim = { version = "0.10", optional = true, default-features = false } signal-hook = { version = "0.3", optional = true, default-features = false } termios = { version = "0.3.3", optional = true } +buffer-redux = { version = "1.0", optional = true, default-features = false } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52.0", features = ["Win32_Foundation", "Win32_System_Console", "Win32_Security", "Win32_System_Threading", "Win32_UI_Input_KeyboardAndMouse"] } diff --git a/src/binding.rs b/src/binding.rs index edb9afb0a..7c083839d 100644 --- a/src/binding.rs +++ b/src/binding.rs @@ -250,6 +250,7 @@ mod test { #[test] #[ignore] + #[cfg(target_arch = "x86_64")] fn size_of_event() { use core::mem::size_of; assert_eq!(size_of::(), 32); diff --git a/src/lib.rs b/src/lib.rs index 972af0f44..fcea11d5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ use log::debug; pub use rustyline_derive::{Completer, Helper, Highlighter, Hinter, Validator}; use unicode_width::UnicodeWidthStr; -use crate::tty::{RawMode, RawReader, Renderer, Term, Terminal}; +use crate::tty::{Buffer, RawMode, RawReader, Renderer, Term, Terminal}; #[cfg(feature = "custom-bindings")] pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler}; @@ -586,6 +586,7 @@ impl<'h> Context<'h> { #[must_use] pub struct Editor { term: Terminal, + buffer: Option, history: I, helper: Option, kill_ring: KillRing, @@ -621,6 +622,7 @@ impl Editor { )?; Ok(Self { term, + buffer: None, history, helper: None, kill_ring: KillRing::new(60), @@ -704,7 +706,9 @@ impl Editor { ); } - let mut rdr = self.term.create_reader(&self.config, term_key_map); + let mut rdr = self + .term + .create_reader(self.buffer.take(), &self.config, term_key_map); if self.term.is_output_tty() && self.config.check_cursor_position() { if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) { if let ReadlineError::WindowResized = e { @@ -794,6 +798,7 @@ impl Editor { if cfg!(windows) { let _ = original_mode; // silent warning } + self.buffer = rdr.unbuffer(); Ok(s.line.into_string()) } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index e79d2a96b..8ac335d69 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -23,6 +23,7 @@ pub enum Event { /// Translate bytes read from stdin to keys. pub trait RawReader { + type Buffer; /// Blocking wait for either a key press or an external print fn wait_for_input(&mut self, single_esc_abort: bool) -> Result; // TODO replace calls to `next_key` by `wait_for_input` where relevant /// Blocking read of key pressed. @@ -34,6 +35,8 @@ pub trait RawReader { fn read_pasted_text(&mut self) -> Result; /// Check if `key` is bound to a peculiar command fn find_binding(&self, key: &KeyEvent) -> Option; + /// Backup type ahead + fn unbuffer(self) -> Option; } /// Display prompt, line and cursor in terminal output @@ -215,8 +218,9 @@ pub trait ExternalPrinter { /// Terminal contract pub trait Term { + type Buffer; type KeyMap; - type Reader: RawReader; // rl_instream + type Reader: RawReader; // rl_instream type Writer: Renderer; // rl_outstream type Mode: RawMode; type ExternalPrinter: ExternalPrinter; @@ -241,7 +245,12 @@ pub trait Term { /// Enable RAW mode for the terminal. fn enable_raw_mode(&mut self) -> Result<(Self::Mode, Self::KeyMap)>; /// Create a RAW reader - fn create_reader(&self, config: &Config, key_map: Self::KeyMap) -> Self::Reader; + fn create_reader( + &self, + buffer: Option, + config: &Config, + key_map: Self::KeyMap, + ) -> Self::Reader; /// Create a writer fn create_writer(&self) -> Self::Writer; fn writeln(&self) -> Result<()>; diff --git a/src/tty/test.rs b/src/tty/test.rs index d26af5e0a..755e52366 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -12,6 +12,7 @@ use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; use crate::{Cmd, Result}; +pub type Buffer = (); pub type KeyMap = (); pub type Mode = (); @@ -22,6 +23,8 @@ impl RawMode for Mode { } impl<'a> RawReader for Iter<'a, KeyEvent> { + type Buffer = Buffer; + fn wait_for_input(&mut self, single_esc_abort: bool) -> Result { self.next_key(single_esc_abort).map(Event::KeyPress) } @@ -45,9 +48,15 @@ impl<'a> RawReader for Iter<'a, KeyEvent> { fn find_binding(&self, _: &KeyEvent) -> Option { None } + + fn unbuffer(self) -> Option { + None + } } impl RawReader for IntoIter { + type Buffer = Buffer; + fn wait_for_input(&mut self, single_esc_abort: bool) -> Result { self.next_key(single_esc_abort).map(Event::KeyPress) } @@ -76,6 +85,10 @@ impl RawReader for IntoIter { fn find_binding(&self, _: &KeyEvent) -> Option { None } + + fn unbuffer(self) -> Option { + None + } } #[derive(Default)] @@ -160,6 +173,7 @@ pub struct DummyTerminal { } impl Term for DummyTerminal { + type Buffer = Buffer; type CursorGuard = (); type ExternalPrinter = DummyExternalPrinter; type KeyMap = KeyMap; @@ -208,7 +222,7 @@ impl Term for DummyTerminal { Ok(((), ())) } - fn create_reader(&self, _: &Config, _: KeyMap) -> Self::Reader { + fn create_reader(&self, _: Option, _: &Config, _: KeyMap) -> Self::Reader { self.keys.clone().into_iter() } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 75dace14a..700e46fdf 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,8 +1,12 @@ //! Unix specific definitions +#[cfg(feature = "buffer-redux")] +use buffer_redux::BufReader; use std::cmp; use std::collections::HashMap; use std::fs::{File, OpenOptions}; -use std::io::{self, BufReader, ErrorKind, Read, Write}; +#[cfg(not(feature = "buffer-redux"))] +use std::io::BufReader; +use std::io::{self, ErrorKind, Read, Write}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, RawFd}; use std::os::unix::net::UnixStream; use std::sync::atomic::{AtomicBool, Ordering}; @@ -91,6 +95,13 @@ fn is_a_tty(fd: RawFd) -> bool { isatty(fd).unwrap_or(false) } +#[cfg(any(not(feature = "buffer-redux"), test))] +pub type PosixBuffer = (); +#[cfg(all(feature = "buffer-redux", not(test)))] +pub type PosixBuffer = buffer_redux::Buffer; +#[cfg(not(test))] +pub type Buffer = PosixBuffer; + pub type PosixKeyMap = HashMap; #[cfg(not(test))] pub type KeyMap = PosixKeyMap; @@ -228,12 +239,22 @@ impl PosixRawReader { fn new( fd: RawFd, sigwinch_pipe: Option, + buffer: Option, config: &Config, key_map: PosixKeyMap, pipe_reader: Option, ) -> Self { + let inner = TtyIn { fd, sigwinch_pipe }; + #[cfg(any(not(feature = "buffer-redux"), test))] + let (tty_in, _) = (BufReader::with_capacity(1024, inner), buffer); + #[cfg(all(feature = "buffer-redux", not(test)))] + let tty_in = if let Some(buffer) = buffer { + BufReader::with_buffer(buffer, inner) + } else { + BufReader::with_capacity(1024, inner) + }; Self { - tty_in: BufReader::with_capacity(1024, TtyIn { fd, sigwinch_pipe }), + tty_in, timeout_ms: config.keyseq_timeout(), parser: Parser::new(), key_map, @@ -750,6 +771,8 @@ impl PosixRawReader { } impl RawReader for PosixRawReader { + type Buffer = PosixBuffer; + #[cfg(not(feature = "signal-hook"))] fn wait_for_input(&mut self, single_esc_abort: bool) -> Result { match self.pipe_reader { @@ -840,6 +863,17 @@ impl RawReader for PosixRawReader { } cmd } + + #[cfg(any(not(feature = "buffer-redux"), test))] + fn unbuffer(self) -> Option { + None + } + + #[cfg(all(feature = "buffer-redux", not(test)))] + fn unbuffer(self) -> Option { + let (_, buffer) = self.tty_in.into_inner_with_buffer(); + Some(buffer) + } } impl Receiver for Utf8 { @@ -1253,6 +1287,7 @@ impl PosixTerminal { } impl Term for PosixTerminal { + type Buffer = PosixBuffer; type CursorGuard = PosixCursorGuard; type ExternalPrinter = ExternalPrinter; type KeyMap = PosixKeyMap; @@ -1371,10 +1406,16 @@ impl Term for PosixTerminal { } /// Create a RAW reader - fn create_reader(&self, config: &Config, key_map: PosixKeyMap) -> PosixRawReader { + fn create_reader( + &self, + buffer: Option, + config: &Config, + key_map: PosixKeyMap, + ) -> PosixRawReader { PosixRawReader::new( self.tty_in, self.sigwinch.as_ref().map(|s| s.pipe), + buffer, config, key_map, self.pipe_reader.clone(), diff --git a/src/tty/windows.rs b/src/tty/windows.rs index f1598cbc3..3ce4169d9 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -69,6 +69,10 @@ fn get_console_mode(handle: HANDLE) -> Result { Ok(original_mode) } +type ConsoleBuffer = (); +#[cfg(not(test))] +pub type Buffer = ConsoleBuffer; + type ConsoleKeyMap = (); #[cfg(not(test))] pub type KeyMap = ConsoleKeyMap; @@ -141,6 +145,8 @@ impl ConsoleRawReader { } impl RawReader for ConsoleRawReader { + type Buffer = ConsoleBuffer; + fn wait_for_input(&mut self, single_esc_abort: bool) -> Result { match self.pipe_reader { Some(_) => self.select(), @@ -159,6 +165,10 @@ impl RawReader for ConsoleRawReader { fn find_binding(&self, _: &KeyEvent) -> Option { None } + + fn unbuffer(self) -> Option { + None + } } fn read_input(handle: HANDLE, max_count: u32) -> Result { @@ -640,6 +650,7 @@ impl Console { } impl Term for Console { + type Buffer = ConsoleBuffer; type CursorGuard = ConsoleCursorGuard; type ExternalPrinter = ExternalPrinter; type KeyMap = ConsoleKeyMap; @@ -801,7 +812,12 @@ impl Term for Console { )) } - fn create_reader(&self, _: &Config, _: ConsoleKeyMap) -> ConsoleRawReader { + fn create_reader( + &self, + _: Option, + _: &Config, + _: ConsoleKeyMap, + ) -> ConsoleRawReader { ConsoleRawReader::create(self.conin, self.pipe_reader.clone()) }