Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ fn main() -> rustyline::Result<()> {
println!("Line: {}", line);
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
println!("Interrupted");
break;
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
println!("Encountered Eof");
break;
}
Err(err) => {
Expand Down
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use std::io;
pub enum ReadlineError {
/// I/O Error
Io(io::Error),
/// EOF (Ctrl-D)
/// EOF (VEOF / Ctrl-D)
Eof,
/// Ctrl-C
/// Interrupt signal (VINTR / VQUIT / Ctrl-C)
Interrupted,
/// Chars Error
#[cfg(unix)]
Expand Down
27 changes: 25 additions & 2 deletions src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ impl InputState {
}
}

/// Application customized binding
fn custom_binding(
&self,
wrt: &mut dyn Refresher,
Expand All @@ -456,6 +457,20 @@ impl InputState {
}
}

/// Terminal peculiar binding
fn term_binding<R: RawReader>(
rdr: &mut R,
wrt: &mut dyn Refresher,
key: &KeyEvent,
) -> Option<Cmd> {
let cmd = rdr.find_binding(key);
if cmd == Some(Cmd::EndOfFile) && !wrt.line().is_empty() {
None // ReadlineError::Eof only if line is empty
} else {
cmd
}
}

fn custom_seq_binding<R: RawReader>(
&self,
rdr: &mut R,
Expand Down Expand Up @@ -550,6 +565,8 @@ impl InputState {
} else {
cmd
});
} else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
return Ok(cmd);
}
let cmd = match key {
E(K::Char(c), M::NONE) => {
Expand Down Expand Up @@ -725,6 +742,8 @@ impl InputState {
} else {
cmd
});
} else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
return Ok(cmd);
}
let cmd = match key {
E(K::Char('$'), M::NONE) | E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
Expand Down Expand Up @@ -896,6 +915,8 @@ impl InputState {
} else {
cmd
});
} else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
return Ok(cmd);
}
let cmd = match key {
E(K::Char(c), M::NONE) => {
Expand Down Expand Up @@ -1037,6 +1058,7 @@ impl InputState {
} else {
Movement::ForwardChar(n)
}),
#[cfg(any(windows, test))]
E(K::Char('C'), M::CTRL) => Cmd::Interrupt,
E(K::Char('D'), M::CTRL) => {
if self.is_emacs_mode() && !wrt.line().is_empty() {
Expand All @@ -1045,8 +1067,10 @@ impl InputState {
} else {
Movement::BackwardChar(n)
})
} else {
} else if cfg!(window) || cfg!(test) || !wrt.line().is_empty() {
Cmd::EndOfFile
} else {
Cmd::Unknown
}
}
E(K::Delete, M::NONE) => Cmd::Kill(if positive {
Expand Down Expand Up @@ -1092,7 +1116,6 @@ impl InputState {
Cmd::Unknown // TODO Validate
}
}
E(K::Char('Z'), M::CTRL) => Cmd::Suspend,
E(K::Char('_'), M::CTRL) => Cmd::Undo(n),
E(K::UnknownEscSeq, M::NONE) => Cmd::Noop,
E(K::BracketedPasteStart, M::NONE) => {
Expand Down
7 changes: 4 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ fn readline_edit<H: Helper>(
initial: Option<(&str, &str)>,
editor: &mut Editor<H>,
original_mode: &tty::Mode,
term_key_map: tty::KeyMap,
) -> Result<String> {
let mut stdout = editor.term.create_writer();

Expand All @@ -460,7 +461,7 @@ fn readline_edit<H: Helper>(
.update((left.to_owned() + right).as_ref(), left.len());
}

let mut rdr = editor.term.create_reader(&editor.config)?;
let mut rdr = editor.term.create_reader(&editor.config, term_key_map)?;
if editor.term.is_output_tty() && editor.config.check_cursor_position() {
if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
if s.out.sigwinch() {
Expand Down Expand Up @@ -569,9 +570,9 @@ fn readline_raw<H: Helper>(
initial: Option<(&str, &str)>,
editor: &mut Editor<H>,
) -> Result<String> {
let original_mode = editor.term.enable_raw_mode()?;
let (original_mode, term_key_map) = editor.term.enable_raw_mode()?;
let guard = Guard(&original_mode);
let user_input = readline_edit(prompt, initial, editor, &original_mode);
let user_input = readline_edit(prompt, initial, editor, &original_mode, term_key_map);
if editor.config.auto_add_history() {
if let Ok(ref line) = user_input {
editor.add_history_entry(line.as_str());
Expand Down
9 changes: 6 additions & 3 deletions src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::highlight::Highlighter;
use crate::keys::KeyEvent;
use crate::layout::{Layout, Position};
use crate::line_buffer::LineBuffer;
use crate::Result;
use crate::{Cmd, Result};

/// Terminal state
pub trait RawMode: Sized {
Expand All @@ -24,6 +24,8 @@ pub trait RawReader {
fn next_char(&mut self) -> Result<char>;
/// Bracketed paste
fn read_pasted_text(&mut self) -> Result<String>;
/// Check if `key` is bound to a peculiar command
fn find_binding(&self, key: &KeyEvent) -> Option<Cmd>;
}

/// Display prompt, line and cursor in terminal output
Expand Down Expand Up @@ -199,6 +201,7 @@ fn width(s: &str, esc_seq: &mut u8) -> usize {

/// Terminal contract
pub trait Term {
type KeyMap;
type Reader: RawReader; // rl_instream
type Writer: Renderer<Reader = Self::Reader>; // rl_outstream
type Mode: RawMode;
Expand All @@ -218,9 +221,9 @@ pub trait Term {
/// check if output stream is connected to a terminal.
fn is_output_tty(&self) -> bool;
/// Enable RAW mode for the terminal.
fn enable_raw_mode(&mut self) -> Result<Self::Mode>;
fn enable_raw_mode(&mut self) -> Result<(Self::Mode, Self::KeyMap)>;
/// Create a RAW reader
fn create_reader(&self, config: &Config) -> Result<Self::Reader>;
fn create_reader(&self, config: &Config, key_map: Self::KeyMap) -> Result<Self::Reader>;
/// Create a writer
fn create_writer(&self) -> Self::Writer;
}
Expand Down
18 changes: 14 additions & 4 deletions src/tty/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use crate::highlight::Highlighter;
use crate::keys::KeyEvent;
use crate::layout::{Layout, Position};
use crate::line_buffer::LineBuffer;
use crate::Result;
use crate::{Cmd, Result};

pub type KeyMap = ();
pub type Mode = ();

impl RawMode for Mode {
Expand All @@ -36,6 +37,10 @@ impl<'a> RawReader for Iter<'a, KeyEvent> {
fn read_pasted_text(&mut self) -> Result<String> {
unimplemented!()
}

fn find_binding(&self, _: &KeyEvent) -> Option<Cmd> {
None
}
}

impl RawReader for IntoIter<KeyEvent> {
Expand All @@ -59,6 +64,10 @@ impl RawReader for IntoIter<KeyEvent> {
fn read_pasted_text(&mut self) -> Result<String> {
unimplemented!()
}

fn find_binding(&self, _: &KeyEvent) -> Option<Cmd> {
None
}
}

pub struct Sink {}
Expand Down Expand Up @@ -140,6 +149,7 @@ pub struct DummyTerminal {
}

impl Term for DummyTerminal {
type KeyMap = KeyMap;
type Mode = Mode;
type Reader = IntoIter<KeyEvent>;
type Writer = Sink;
Expand Down Expand Up @@ -181,11 +191,11 @@ impl Term for DummyTerminal {

// Interactive loop:

fn enable_raw_mode(&mut self) -> Result<Mode> {
Ok(())
fn enable_raw_mode(&mut self) -> Result<(Mode, KeyMap)> {
Ok(((), ()))
}

fn create_reader(&self, _: &Config) -> Result<IntoIter<KeyEvent>> {
fn create_reader(&self, _: &Config, _: KeyMap) -> Result<Self::Reader> {
Ok(self.keys.clone().into_iter())
}

Expand Down
60 changes: 46 additions & 14 deletions src/tty/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ use log::{debug, warn};
use nix::poll::{self, PollFlags};
use nix::sys::signal;
use nix::sys::termios;
use nix::sys::termios::SetArg;
use nix::sys::termios::{SetArg, SpecialCharacterIndices as SCI, Termios};
use unicode_segmentation::UnicodeSegmentation;
use utf8parse::{Parser, Receiver};

use super::{width, RawMode, RawReader, Renderer, Term};
use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
use crate::error;
use crate::highlight::Highlighter;
use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
use crate::layout::{Layout, Position};
use crate::line_buffer::LineBuffer;
use crate::Result;
use crate::{error, Cmd, Result};
use std::collections::HashMap;

const STDIN_FILENO: RawFd = libc::STDIN_FILENO;

Expand Down Expand Up @@ -95,6 +95,10 @@ fn is_a_tty(fd: RawFd) -> bool {
unsafe { libc::isatty(fd) != 0 }
}

pub type PosixKeyMap = HashMap<KeyEvent, Cmd>;
#[cfg(not(test))]
pub type KeyMap = PosixKeyMap;

#[must_use = "You must restore default mode (disable_raw_mode)"]
pub struct PosixMode {
termios: termios::Termios,
Expand Down Expand Up @@ -150,6 +154,7 @@ pub struct PosixRawReader {
buf: [u8; 1],
parser: Parser,
receiver: Utf8,
key_map: PosixKeyMap,
}

struct Utf8 {
Expand Down Expand Up @@ -184,7 +189,7 @@ const RXVT_CTRL: char = '\x1e';
const RXVT_CTRL_SHIFT: char = '@';

impl PosixRawReader {
fn new(config: &Config) -> Self {
fn new(config: &Config, key_map: PosixKeyMap) -> Self {
Self {
stdin: StdinRaw {},
timeout_ms: config.keyseq_timeout(),
Expand All @@ -194,6 +199,7 @@ impl PosixRawReader {
c: None,
valid: true,
},
key_map,
}
}

Expand Down Expand Up @@ -716,6 +722,14 @@ impl RawReader for PosixRawReader {
let buffer = buffer.replace("\r", "\n");
Ok(buffer)
}

fn find_binding(&self, key: &KeyEvent) -> Option<Cmd> {
let cmd = self.key_map.get(key).cloned();
if let Some(ref cmd) = cmd {
debug!(target: "rustyline", "terminal key binding: {:?} => {:?}", key, cmd);
}
cmd
}
}

impl Receiver for Utf8 {
Expand Down Expand Up @@ -1029,6 +1043,13 @@ extern "C" fn sigwinch_handler(_: libc::c_int) {
debug!(target: "rustyline", "SIGWINCH");
}

fn map_key(key_map: &mut HashMap<KeyEvent, Cmd>, raw: &Termios, index: SCI, name: &str, cmd: Cmd) {
let cc = char::from(raw.control_chars[index as usize]);
let key = KeyEvent::new(cc, M::NONE);
debug!(target: "rustyline", "{}: {:?}", name, key);
key_map.insert(key, cmd);
}

#[cfg(not(test))]
pub type Terminal = PosixTerminal;

Expand All @@ -1055,6 +1076,7 @@ impl PosixTerminal {
}

impl Term for PosixTerminal {
type KeyMap = PosixKeyMap;
type Mode = PosixMode;
type Reader = PosixRawReader;
type Writer = PosixRenderer;
Expand Down Expand Up @@ -1101,9 +1123,9 @@ impl Term for PosixTerminal {

// Interactive loop:

fn enable_raw_mode(&mut self) -> Result<Self::Mode> {
fn enable_raw_mode(&mut self) -> Result<(Self::Mode, PosixKeyMap)> {
use nix::errno::Errno::ENOTTY;
use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags, SpecialCharacterIndices};
use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags};
if !self.stdin_isatty {
return Err(nix::Error::from_errno(ENOTTY).into());
}
Expand All @@ -1125,8 +1147,15 @@ impl Term for PosixTerminal {
// disable echoing, canonical mode, extended input processing and signals
raw.local_flags &=
!(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 1; // One character-at-a-time input
raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 0; // with blocking read
raw.control_chars[SCI::VMIN as usize] = 1; // One character-at-a-time input
raw.control_chars[SCI::VTIME as usize] = 0; // with blocking read

let mut key_map: HashMap<KeyEvent, Cmd> = HashMap::with_capacity(3);
map_key(&mut key_map, &raw, SCI::VEOF, "VEOF", Cmd::EndOfFile);
map_key(&mut key_map, &raw, SCI::VINTR, "VINTR", Cmd::Interrupt);
map_key(&mut key_map, &raw, SCI::VQUIT, "VQUIT", Cmd::Interrupt);
map_key(&mut key_map, &raw, SCI::VSUSP, "VSUSP", Cmd::Suspend);

termios::tcsetattr(STDIN_FILENO, SetArg::TCSADRAIN, &raw)?;

// enable bracketed paste
Expand All @@ -1138,15 +1167,18 @@ impl Term for PosixTerminal {
} else {
Some(self.stream_type)
};
Ok(PosixMode {
termios: original_mode,
out,
})
Ok((
PosixMode {
termios: original_mode,
out,
},
key_map,
))
}

/// Create a RAW reader
fn create_reader(&self, config: &Config) -> Result<PosixRawReader> {
Ok(PosixRawReader::new(config))
fn create_reader(&self, config: &Config, key_map: PosixKeyMap) -> Result<PosixRawReader> {
Ok(PosixRawReader::new(config, key_map))
}

fn create_writer(&self) -> PosixRenderer {
Expand Down
Loading